[CTF]

[DREAMHACK] rev-basic 1~3

준제 2023. 9. 11. 23:48

DREAMHACK에서 제공하는 리버스 엔지니어링 기초 문제 3개를 풀어보자. 세 문제 모두 비슷한 구조를 갖고 있어 하나를 해결하면 다음 문제는 쉽게 접근할 수 있다. 따라서 1번 문제를 자세하게 설명하고, 나머지 문제는 차이점 위주로 설명한다.

 


 

https://dreamhack.io/wargame/challenges/15

 

rev-basic-1

Reversing Basic Challenge #1 이 문제는 사용자에게 문자열 입력을 받아 정해진 방법으로 입력값을 검증하여 correct 또는 wrong을 출력하는 프로그램이 주어집니다. 해당 바이너리를 분석하여 correct를 출

dreamhack.io

 

rev-basic-1의 exe파일을 IDA64에서 실행했을 때의 모습

 

주어진 파일을 IDA64에서 실행시켰다. C언어 코드를 해석할 것이기 때문에 Tab을 눌러 Decompiling 시켰다. Decompile된 코드는 아래와 같다.

 

int __fastcall main(int argc, const char **argv, const char **envp)
{
  char v4[256]; // [rsp+20h] [rbp-118h] BYREF

  memset(v4, 0, sizeof(v4));
  sub_1400013E0("Input : ", argv, envp);
  sub_140001440("%256s", v4);
  if ( (unsigned int)sub_140001000(v4) )
    puts("Correct");
  else
    puts("Wrong");
  return 0;
}

 

여러 함수가 등장하는데, 함수 각각의 이름은 디컴파일 과정에서 임의로 생성된 것이다. 함수를 더블클릭하면 내부 구조를 확인할 수 있고, 이를 통해 어떤 함수인지 추론할 수 있다. 추론 결과는 아래와 같다.

 

함수 memset()은 변수를 선언한다.

함수 sub_1400013E0()은 출력한다.

함수 sub_140001440()은 입력을 받는다.
함수 sub_140001000()은 이 코드의 핵심으로, 입력값의 참 거짓을 판단한다.

 

sub_140001000()을 더블클릭해서 구조를 자세히 확인 해 보자. (나머지 문제들은 main 함수의 같고, 함수 sub_140001000()의 내부 구조만 다르다. 2번 문제부터는 이것만 살펴보자.)

 

_BOOL8 __fastcall sub_140001000(_BYTE *a1)
{
  if ( *a1 != 67 )
    return 0i64;
  if ( a1[1] != 111 )
    return 0i64;
  if ( a1[2] != 109 )
    return 0i64;
  if ( a1[3] != 112 )
    return 0i64;
  if ( a1[4] != 97 )
    return 0i64;
  if ( a1[5] != 114 )
    return 0i64;
  if ( a1[6] != 51 )
    return 0i64;
  if ( a1[7] != 95 )
    return 0i64;
  if ( a1[8] != 116 )
    return 0i64;
  if ( a1[9] != 104 )
    return 0i64;
  if ( a1[10] != 101 )
    return 0i64;
  if ( a1[11] != 95 )
    return 0i64;
  if ( a1[12] != 99 )
    return 0i64;
  if ( a1[13] != 104 )
    return 0i64;
  if ( a1[14] != 52 )
    return 0i64;
  if ( a1[15] != 114 )
    return 0i64;
  if ( a1[16] != 97 )
    return 0i64;
  if ( a1[17] != 99 )
    return 0i64;
  if ( a1[18] != 116 )
    return 0i64;
  if ( a1[19] != 51 )
    return 0i64;
  if ( a1[20] == 114 )
    return a1[21] == 0;
  return 0i64;
}

 

a1[i]가 원하는 값이 아니면 중단된다. 입력받은 배열 a1의 원소 각각이 해당하는 아스키코드(10진수)값과 같은지 확인하는 코드이다. 따라서 flag는 위의 아스키코드값을 합친 문자열이다.

 

# ANSWER rev-basic-1

password = [ 67, 111, 109, 112,  97, 114,  51,  95, 116, 104,
            101,  95,  99, 104,  52, 114,  97,  99, 116,  51, 114]

for i in password:
    print(chr(i), end="")

 

Flag : DH{Compar3_the_ch4ract3r}

 


 

https://dreamhack.io/wargame/challenges/16

 

rev-basic-2

Reversing Basic Challenge #2 이 문제는 사용자에게 문자열 입력을 받아 정해진 방법으로 입력값을 검증하여 correct 또는 wrong을 출력하는 프로그램이 주어집니다. 해당 바이너리를 분석하여 correct를 출

dreamhack.io

 

__int64 __fastcall sub_140001000(__int64 a1)
{
  int i; // [rsp+0h] [rbp-18h]

  for ( i = 0; (unsigned __int64)i < 0x12; ++i )
  {
    if ( *(_DWORD *)&aC[4 * i] != *(unsigned __int8 *)(a1 + i) )
      return 0i64;
  }
  return 1i64;
}

 

a1[i]와 aC[4 * i]를 18번 비교한다. 따라서 flag는 aC 배열을 4개 단위로 18개 읽은 값이다. aC를 Hex-View에서 확인하자.

 

aC를 Hex-View에서 확인한 모습

 

Flag : DH{Comp4re_the_arr4y}

 

 


 

https://dreamhack.io/wargame/challenges/17

 

rev-basic-3

Reversing Basic Challenge #3 이 문제는 사용자에게 문자열 입력을 받아 정해진 방법으로 입력값을 검증하여 correct 또는 wrong을 출력하는 프로그램이 주어집니다. 해당 바이너리를 분석하여 correct를 출

dreamhack.io

 

__int64 __fastcall sub_140001000(__int64 a1)
{
  int i; // [rsp+0h] [rbp-18h]

  for ( i = 0; (unsigned __int64)i < 0x18; ++i )
  {
    if ( byte_140003000[i] != (i ^ *(unsigned __int8 *)(a1 + i)) + 2 * i )
      return 0i64;
  }
  return 1i64;
}

 

i ^ a[i] + 2 * i 와 byte_140003000[i]가 같은지 0x18번(24번) 비교한다. byte_140003000[i]가 무엇인지 확인하고, 간단하게 코드로 구현하면 문제를 해결할 수 있다.

 

byte_140003000을 IDA-View에서 확인한 모습

 

db는 바이트를 뜻하고, n dup(k)는 k가 n번 입력되었음을 뜻한다. 복사해서 Python으로 복호화하자.

 

arr = [0x49, 0x60, 0x67, 0x74, 0x63, 0x67, 0x42, 0x66,
       0x80, 0x78, 0x69, 0x69, 0x7B, 0x99, 0x6D, 0x88,
       0x68, 0x94, 0x9F, 0x8D, 0x4D, 0xA5, 0x9D, 0x45]

for i in range(0x18):
    print(chr((arr[i] - 2 * i) ^ i), end="")

 

Flag : DH{I_am_X0_xo_Xor_eXcit1ng}