작년에 (전)동아리 회장님이 리버싱 연습하라고 주셨던 엑세스 파일을 주셨던 것이 이 글의 시작이며, 자료가 별로 없어서 리버싱을 하는 과정에서 조금이라도 편안하게 공부할 수 있는 자료가 될 거 같다.

 

 

IDA에 해당 엑세스(.exe) 확장자 파일을 열어주고 IDA-VIEW가 아닌 HEX-VIEW로 16진수에서 값을 찾는게 조금 더 수월하며 Function name에서 main/ sub_number과 같은 함수를 먼저 들어가서 확인하는 것이 워게임이나 리버싱 연습할 때 괜찮은 방법중 하나라 생각이라 느낀다. 

0000000140002260  FF FF FF FF FF FF FF FF  FF FF FF FF FF FF FF FF  ................
0000000140002270  74 68 69 6E 6B 32 30 32  32 00 00 00 00 00 00 00  think2022.......
0000000140002280  6B 65 79 B0 AA C0 BB 20  C0 D4 B7 C2 C7 CF BC BC  key.... .....ϼ .
0000000140002290  BF E4 3A 00 25 73 00 00  C0 CE C1 F5 20 BC BA B0  ....%s...... ...
00000001400022A0  F8 20 0A 00 00 00 00 00  54 48 49 4E 4B 5F 43 54  . ......THINK_CT
00000001400022B0  46 7B 71 69 77 32 38 31  6E 77 64 69 73 30 61 30  F{qiw281nwdis0a0
00000001400022C0  77 32 6A 77 64 5F 67 6F  6F 64 6A 6F 62 7D 0A 00  w2jwd_goodjob}..
00000001400022D0  C5 B0 20 B0 AA C0 CC 20  C0 CF C4 A1 C7 CF C1 F6  Ű  .... ..ġ ....
00000001400022E0  20 BE CA BD C0 B4 CF B4  D9 2E 20 0A 00 00 00 00   .ʽ ..ϴ .. ...

 

아무튼 위 코드를 보면 함수와 16진수, 16진수가 문자로 변환된 값이 있고 누가봐도 "저게 정답인 가?"  싶은게 정답이 맞다. 

 

: THINK_CTF{qiw281nwdis0a0 w2jwd_goodjob}

 


 

그리고 시간 날 때 마다 블로그 글을 작성해서 올려보겠습니다. 

오늘 풀어볼 문제는 드림핵 rev baisc #8이다.

 

문제의 main 화면을 보면 

 

 

입력을 하면 "Correct" , "wrong"을 출력하는 방식으로 조금 더 자세히 살펴보기로 해본다.

 

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

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

 

if문에 sub_140001000((__int64) v4 가 메인 함수이다.

 

클릭을 해보면 아래와 같이 나온다.


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

  for ( i = 0; (unsigned __int64)i < 0x15; ++i )
  {
    if ( (unsigned __int8)(-5 * *(_BYTE *)(a1 + i)) != byte_140003000[i] )
      return 0i64;
  }
  return 1i64;
}


.data:0000000140003000 ; unsigned __int8 byte_140003000[32]
.data:0000000140003000 byte_140003000  db 0ACh, 0F3h, 0Ch, 25h, 0A3h, 10h, 0B7h, 25h, 16h, 0C6h
.data:0000000140003000                                         ; DATA XREF: sub_140001000+40↑o
.data:000000014000300A                 db 0B7h, 0BCh, 7, 25h, 2, 0D5h, 0C6h, 11h, 7, 0C5h, 0Ch dup(0)
.data:0000000140003020 qword_140003020 dq 0FFFFD466D2205DCDh   ; DATA XREF: __report_gsfailure+B4↑r
.data:0000000140003020                                         ; __security_init_cookie+9F↑w


 

해당 식을 for 문이 끝날 때까지 만족한다면 Correct를 출력한다. 여기서 -5는 정수이므로 4바이트 문자는 1바이트이고,  4바이트와 1바이트를 계산하면 1바이트 높은 주소부분을 0으로 채우고 4바이트로 만든 다음에 계산하면된다.

다음으로 unsigned __int8으로 형변환이 되므로 앞에 3바이트를 날려버린다. 그리고 140003000에 저장된 문자와 비교한다.

 

 

위에 메모리 값인  0ACh, 0F3h, 0Ch, 25h, 0A3h, 10h, 0B7h, 25h, 16h, 0C6h, 0B7h, 0BCh, 7, 25h, 2, 0D5h, 0C6h을 모아서 Python코드로 다시 만들어보기로 한다.

 

buf = [0xAC, 0xF3, 0x0C, 0x25, 0xA3, 0x10, 0xB7, 0x25, 0x16, 0xC6, 0xB7, 0xBC, 0x07, 0x25, 0x02, 0xD5, 0xC6, 0x11, 0x07, 0xC5, 0x00]

result = ""

for byte in buf:
    for i in range(256):
        a = byte + i * 256
        if a % 0xfb == 0:
            result += chr(a // 0xfb)

print(result)

 

출력값은 "Did_y0u_brute_force?" 로 나오게 된다. 

 

드림핵 baisc #6을 보면 입력값을 맞으면 "Correct" , 틀리면 "Wrong"으로 출력된다. 

baisc문제만 동일한 패턴으로 보이는 " __unwind { // __GSHandlerCheck" 해당 코드로 들어간다.

 

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

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

들어가 보면 해당 코드가 나온다.

 

if ( (unsigned int)sub_140001000((__int64)v4) )

sub_140001000가 답을 가지고 있기에 이를 집중적으로 파고 들어가야 한다.

 

 

클릭을 하고 나면  아래에 코드가 나타난다.

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

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

그럼 두 개의 바이트 값인 byte_140003000, byte_140003020이 나오며, 이 값을 다시 또 클릭해 보면 새로운 값이 나온다.

 

data:0000000140003000 byte_140003000  db 0, 4Dh, 51h, 50h, 0EFh, 0FBh, 0C3h, 0CFh, 92h, 45h
.data:0000000140003000                                         ; DATA XREF: sub_140001000+40↑o
.data:000000014000300A                 db 4Dh, 0CFh, 0F5h, 4, 40h, 50h, 43h, 63h, 0Eh dup(0)
.data:0000000140003020 ; unsigned __int8 byte_140003020[256]
.data:0000000140003020 byte_140003020  db 63h, 7Ch, 77h, 7Bh, 0F2h, 6Bh, 6Fh, 0C5h, 30h, 1, 67h
.data:0000000140003020                                         ; DATA XREF: sub_140001000+31↑o
.data:000000014000302B                 db 2Bh, 0FEh, 0D7h, 0ABh, 76h, 0CAh, 82h, 0C9h, 7Dh, 0FAh
.data:0000000140003035                 db 59h, 47h, 0F0h, 0ADh, 0D4h, 0A2h, 0AFh, 9Ch, 0A4h, 72h
.data:000000014000303F                 db 0C0h, 0B7h, 0FDh, 93h, 26h, 36h, 3Fh, 0F7h, 0CCh, 34h
.data:0000000140003049                 db 0A5h, 0E5h, 0F1h, 71h, 0D8h, 31h, 15h, 4, 0C7h, 23h
.data:0000000140003053                 db 0C3h, 18h, 96h, 5, 9Ah, 7, 12h, 80h, 0E2h, 0EBh, 27h
.data:000000014000305E                 db 0B2h, 75h, 9, 83h, 2Ch, 1Ah, 1Bh, 6Eh, 5Ah, 0A0h, 52h
.data:0000000140003069                 db 3Bh, 0D6h, 0B3h, 29h, 0E3h, 2Fh, 84h, 53h, 0D1h, 0
.data:0000000140003073                 db 0EDh, 20h, 0FCh, 0B1h, 5Bh, 6Ah, 0CBh, 0BEh, 39h, 4Ah
.data:000000014000307D                 db 4Ch, 58h, 0CFh, 0D0h, 0EFh, 0AAh, 0FBh, 43h, 4Dh, 33h
.data:0000000140003087                 db 85h, 45h, 0F9h, 2, 7Fh, 50h, 3Ch, 9Fh, 0A8h, 51h, 0A3h
.data:0000000140003092                 db 40h, 8Fh, 92h, 9Dh, 38h, 0F5h, 0BCh, 0B6h, 0DAh, 21h
.data:000000014000309C                 db 10h, 0FFh, 0F3h, 0D2h, 0CDh, 0Ch, 13h, 0ECh, 5Fh, 97h
.data:00000001400030A6                 db 44h, 17h, 0C4h, 0A7h, 7Eh, 3Dh, 64h, 5Dh, 19h, 73h
.data:00000001400030B0                 db 60h, 81h, 4Fh, 0DCh, 22h, 2Ah, 90h, 88h, 46h, 0EEh
.data:00000001400030BA                 db 0B8h, 14h, 0DEh, 5Eh, 0Bh, 0DBh, 0E0h, 32h, 3Ah, 0Ah
.data:00000001400030C4                 db 49h, 6, 24h, 5Ch, 0C2h, 0D3h, 0ACh, 62h, 91h, 95h, 0E4h
.data:00000001400030CF                 db 79h, 0E7h, 0C8h, 37h, 6Dh, 8Dh, 0D5h, 4Eh, 0A9h, 6Ch
.data:00000001400030D9                 db 56h, 0F4h, 0EAh, 65h, 7Ah, 0AEh, 8, 0BAh, 78h, 25h
.data:00000001400030E3                 db 2Eh, 1Ch, 0A6h, 0B4h, 0C6h, 0E8h, 0DDh, 74h, 1Fh, 4Bh
.data:00000001400030ED                 db 0BDh, 8Bh, 8Ah, 70h, 3Eh, 0B5h, 66h, 48h, 3, 0F6h, 0Eh
.data:00000001400030F8                 db 61h, 35h, 57h, 0B9h, 86h, 0C1h, 1Dh, 9Eh, 0E1h, 0F8h
.data:0000000140003102                 db 98h, 11h, 69h, 0D9h, 8Eh, 94h, 9Bh, 1Eh, 87h, 0E9h
.data:000000014000310C                 db 0CEh, 55h, 28h, 0DFh, 8Ch, 0A1h, 89h, 0Dh, 0BFh, 0E6h
.data:0000000140003116                 db 42h, 68h, 41h, 99h, 2Dh, 0Fh, 0B0h, 54h, 0BBh, 16h

이 값을 구하기 위해선 프로그래밍을 해야 한다.

 

또한 저 값을 다 구할 순 있으나 반으로 나눠서 만들어 계산한다.

 

 

1번째 블록

    db 2Bh, 0FEh, 0D7h, 0ABh, 76h, 0CAh, 82h, 0C9h, 7Dh, 0FAh
.data:0000000140003035                 db 59h, 47h, 0F0h, 0ADh, 0D4h, 0A2h, 0AFh, 9Ch, 0A4h, 72h
.data:000000014000303F                 db 0C0h, 0B7h, 0FDh, 93h, 26h, 36h, 3Fh, 0F7h, 0CCh, 34h
.data:0000000140003049                 db 0A5h, 0E5h, 0F1h, 71h, 0D8h, 31h, 15h, 4, 0C7h, 23h
.data:0000000140003053                 db 0C3h, 18h, 96h, 5, 9Ah, 7, 12h, 80h, 0E2h, 0EBh, 27h
.data:000000014000305E                 db 0B2h, 75h, 9, 83h, 2Ch, 1Ah, 1Bh, 6Eh, 5Ah, 0A0h, 52h
.data:0000000140003069                 db 3Bh, 0D6h, 0B3h, 29h, 0E3h, 2Fh, 84h, 53h, 0D1h, 0
.data:0000000140003073                 db 0EDh, 20h, 0FCh, 0B1h, 5Bh, 6Ah, 0CBh, 0BEh, 39h, 4Ah
.data:000000014000307D                 db 4Ch, 58h, 0CFh, 0D0h, 0EFh, 0AAh, 0FBh, 43h, 4Dh, 33h
.data:0000000140003087                 db 85h, 45h, 0F9h, 2, 7Fh, 50h, 3Ch, 9Fh, 0A8h, 51h, 0A3h
.data:0000000140003092                 db 40h, 8Fh, 92h, 9Dh, 38h, 0F5h, 0BCh, 0B6h, 0DAh, 21h
.data:000000014000309C                 db 10h, 0FFh, 0F3h, 0D2h

 

2번째 블록

0CDh, 0Ch, 13h, 0ECh, 5Fh, 97h
.data:00000001400030A6                 db 44h, 17h, 0C4h, 0A7h, 7Eh, 3Dh, 64h, 5Dh, 19h, 73h
.data:00000001400030B0                 db 60h, 81h, 4Fh, 0DCh, 22h, 2Ah, 90h, 88h, 46h, 0EEh
.data:00000001400030BA                 db 0B8h, 14h, 0DEh, 5Eh, 0Bh, 0DBh, 0E0h, 32h, 3Ah, 0Ah
.data:00000001400030C4                 db 49h, 6, 24h, 5Ch, 0C2h, 0D3h, 0ACh, 62h, 91h, 95h, 0E4h
.data:00000001400030CF                 db 79h, 0E7h, 0C8h, 37h, 6Dh, 8Dh, 0D5h, 4Eh, 0A9h, 6Ch
.data:00000001400030D9                 db 56h, 0F4h, 0EAh, 65h, 7Ah, 0AEh, 8, 0BAh, 78h, 25h
.data:00000001400030E3                 db 2Eh, 1Ch, 0A6h, 0B4h, 0C6h, 0E8h, 0DDh, 74h, 1Fh, 4Bh
.data:00000001400030ED                 db 0BDh, 8Bh, 8Ah, 70h, 3Eh, 0B5h, 66h, 48h, 3, 0F6h, 0Eh
.data:00000001400030F8                 db 61h, 35h, 57h, 0B9h, 86h, 0C1h, 1Dh, 9Eh, 0E1h, 0F8h
.data:0000000140003102                 db 98h, 11h, 69h, 0D9h, 8Eh, 94h, 9Bh, 1Eh, 87h, 0E9h
.data:000000014000310C                 db 0CEh, 55h, 28h, 0DFh, 8Ch, 0A1h, 89h, 0Dh, 0BFh, 0E6h
.data:0000000140003116                 db 42h, 68h, 41h, 99h, 2Dh, 0Fh, 0B0h, 54h, 0BBh, 16h

byte_140003000인 값은  4D, 51,  50,  EF,  FB,  C3,  CF,  92 , 45,  4D,  CF,  F5,  04,  40,  50, 43,  63

 

byte_140003020인 값은 63, 7C, 77, 7B, F2, 6B, 6F, C5,  30, 01, 67, 2B, FE, D7, AB, 76, CA, 82, C9, 7D, FA, 59, 47, F0,  AD, D4, A2, AF, 9C, A4, 72, C0, B7, FD, 93, 26, 36, 3F, F7, CC,  34, A5, E5, F1, 71, D8, 31, 15, 04, C7, 23,  C3,  18,  96, 05, 9A,  07, 12, 80, E2, EB, 27, B2, 75, 09, 83,  2C,  1A, 1B,  6E,  5A,  A0,  52, 3B,  D6,  B3,  29,  E3,  2F, 84, 53, D1, 00, ED, 20, FC, B1, 5B,  6A, CB, BE, 39, 4A, 4C, 58, CF, D0, EF, AA, FB, 43, 4D, 33, 85,  45, F9, 02, 7F, 50, 3C, 9F, A8, 51,  A3,  40,  8F,  92,  9D,  38,  F5,   BC,  B6,  DA,  21,  10,  FF,  F3,  D2 

 

 

앞 뒤에 0과 h를 빼고 프로그래밍 과정 중 "0x"를 넣어서 바이트를 계산해야 함

 

 

파이썬으로 개발하면 이러한 형태로 나온다.

str1 = [
    0x00, 0x4D, 0x51, 0x50, 0xEF, 0xFB, 0xC3, 0xCF, 0x92, 0x45,
    0x4D, 0xCF, 0xF5, 0x04, 0x40, 0x50, 0x43, 0x63
] 



str2 = [
    0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76,
    0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0,
    0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15,
    0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75,
    0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84,
    0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF,
    0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8,
    0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2
]



str3 = bytearray(19)  
for i in range(18):
    for j in range(128):  
        if str2[j] == str1[i]:
            str3[i] = j
            break

print(str3.decode("utf-8"))

 

 

위 코드를 컴파일 돌려보면 출력 값으로 "Replac3_the_w0rld"가 나온다.

 

해당 값을 input에 넣으면 Correct가 떠서 flag를 획득할 수 있다.

 

 

해당 문제를 제출하면 된다.

 

 

 

이 문제를 풀 때 시간이 상당히 많이 소요되서 힘들었다.. 

두번째로 풀어볼 문제는 드림핵 rev basic #2 이다.

문제 풀어보는 방식은 https://beru-123.tistory.com/10 와 동일한 방식으로 풀어보도록 하자.

 

 

 

문제도 어느 값을 입력하면 출력으로 "Correct"를 나오게 하면 된다.

Main 함수

 

; __unwind { // __GSHandlerCheck

해당 코드에 F5로 클릭해보면 다음 코드로 이동한다. 

 

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

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

 

해당 코드를 보고 puts들어가보면 아무 반응도 안일어난다. 

 

 

리버싱을 하다보면 자주 보이는 sub_140001000 함수는 중요한 역할을 하니 기억해두면 좋다

 

sub_140001000함수다음 코드로 들어갈 수 있다고 뜨고 있다. 

 

__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;
}

현재 코드에선 if ( *(_DWORD *)&aC[4 * i] != *(unsigned __int8 *)(a1 + i) ) 만 유일하게 반응한 코드이기에 더 들어가보면 

 

data:0000000140003000 aC              db 'C',0                ; DATA XREF: sub_140001000+28↑o
.data:0000000140003002                 align 4
.data:0000000140003004 aO              db 'o',0
.data:0000000140003006                 align 8
.data:0000000140003008 aM              db 'm',0
.data:000000014000300A                 align 4
.data:000000014000300C aP              db 'p',0
.data:000000014000300E                 align 10h
.data:0000000140003010 a4              db '4',0
.data:0000000140003012                 align 4
.data:0000000140003014 aR              db 'r',0
.data:0000000140003016                 align 8
.data:0000000140003018 aE              db 'e',0
.data:000000014000301A                 align 4
.data:000000014000301C                 db '_',0
.data:000000014000301E                 align 20h
.data:0000000140003020 aT              db 't',0
.data:0000000140003022                 align 4
.data:0000000140003024                 db 'h',0
.data:0000000140003026                 align 8
.data:0000000140003028 aE_0            db 'e',0
.data:000000014000302A                 align 4
.data:000000014000302C                 db '_',0
.data:000000014000302E                 align 10h
.data:0000000140003030 aA              db 'a',0
.data:0000000140003032                 align 4
.data:0000000140003034 aR_0            db 'r',0
.data:0000000140003036                 align 8
.data:0000000140003038 aR_1            db 'r',0
.data:000000014000303A                 align 4
.data:000000014000303C a4_0            db '4',0
.data:000000014000303E                 align 20h
.data:0000000140003040 aY              db 'y',0
.data:0000000140003042                 align 10h

세로로 익숙한 영단어와 숫자가 존재한다.  모두 종합해서 쓰자면 "Comp4re_the_arr4y"로 나온다. 

 

 

CMD에 답을 입력해보자

 정답이라 나왔다. 

 

국내에서 리버싱을 배울 수 있는 곳은 정말 한정적이다.

그나마 http://reversing.kr/ , https://dreamhack.io이며,  이외에는 영문으로 검색해야  찾아볼 수 있다.

 

Reversing.Kr

This site tests your ability to Cracking & Reverse Code Engineering. Now Challenge a problem for each environment. (Windows, Linux, .Net, Flash, Java, Python, Mobile..) Admin E-Mail: gogil@reversing.kr

reversing.kr

 

해커들의 놀이터, Dreamhack

해킹과 보안에 대한 공부를 하고 싶은 학생, 안전한 코드를 작성하고 싶은 개발자, 보안 지식과 실력을 업그레이드 시키고 싶은 보안 전문가까지 함께 공부하고 연습하며 지식을 나누고 실력 향

dreamhack.io

리버싱. kr에서는 기초적인 공부를 하고 난 뒤 직접 풀어봐야 알 수 있고 아무것도 모르고 시직 할 때는 드림핵에서 공부하는 걸 추천한다.  

 

 

아무튼 오늘 풀어볼 문제는 드림핵에 rev_basic #1 첫 번째 문제다.

 

 

문제를 다운로드하고 ida로 가지고 오면 아래와 같은 상태일 것이다.

어떠한 값을 입력하면 "Correct" , 아니라면 "Wrong"을 출력한다. 

 

IDA의 디버거를 이용해서 입력을 할 수 있지만 윈도우의 명령 프롬포트(CMD)chall.exe 파일을 드래그해서 가지고 오면 이 방식으로도 디버깅이 가능하다.

아직 문제의 답을 모르기 때문에 아무리 입력해도 "Wrong"으로 출력이 될 것이기에 한번 파해쳐 볼 것이다.

 

 

함수창main으로 쓰여있는 함수를 클릭하면 나오는 코드이자, 기본 코드 이기도 하다. 

; int __cdecl main(int argc, const char **argv, const char **envp)
main proc near

var_118= byte ptr -118h
var_18= qword ptr -18h

; __unwind { // __GSHandlerCheck
push    rdi
sub     rsp, 130h
mov     rax, cs:__security_cookie
xor     rax, rsp
mov     [rsp+138h+var_18], rax
lea     rax, [rsp+138h+var_118]
mov     rdi, rax
xor     eax, eax
mov     ecx, 100h
rep stosb
lea     rcx, aInput     ; "Input : "
call    sub_1400013E0
lea     rdx, [rsp+138h+var_118]
lea     rcx, a256s      ; "%256s"
call    sub_140001440
lea     rcx, [rsp+138h+var_118]
call    sub_140001000
test    eax, eax
jz      short loc_1400013B6

하지만 여기서 알아야 할 정보는 F5를 눌러야 더 자세히 나온다. 

그 정보 값은 어셈블리어 위에 있는 " ; __unwind { // __GSHandlerCheck " 코드다. 

 

 

눌러봤다면 화면창은 이럴 것이다.

int __cdecl 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;
}

 

코드가  아까보다 단순해졌고 한눈에 보기 쉬워졌다. 

자세히 보면 if문 sub_140001000이 같이 있다. 이 함수는 전에 올렸던 글에도 봤었던 중요 함수라 말한 적이 있다.

 

그렇다면 해당 함수로 더 자세히 들어가 보도록 하자

_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;
}

이제 뭔가 알듯 말 듯 한 코드의 형태가 나왔다.

 

한번 클릭해 보니 해당 값은 int,정수값인데 ida에서는 값을 변환을 시킬 수 있기에 한번 char인 문자값으로 바꿔보면 어떠한 결과가 나올까?

 

단축키 혹은 우클릭으로 해당 값을 변환시킬 수 있으니 참고하길 바란다.

 

변환한 코드

_BOOL8 __fastcall sub_140001000(_BYTE *a1)
{
  if ( *a1 != 'C' )
    return 0i64;
  if ( a1[1] != 'o' )
    return 0i64;
  if ( a1[2] != 'm' )
    return 0i64;
  if ( a1[3] != 'p' )
    return 0i64;
  if ( a1[4] != 'a' )
    return 0i64;
  if ( a1[5] != 'r' )
    return 0i64;
  if ( a1[6] != '3' )
    return 0i64;
  if ( a1[7] != '_' )
    return 0i64;
  if ( a1[8] != 't' )
    return 0i64;
  if ( a1[9] != 'h' )
    return 0i64;
  if ( a1[10] != 'e' )
    return 0i64;
  if ( a1[11] != '_' )
    return 0i64;
  if ( a1[12] != 'c' )
    return 0i64;
  if ( a1[13] != 'h' )
    return 0i64;
  if ( a1[14] != '4' )
    return 0i64;
  if ( a1[15] != 'r' )
    return 0i64;
  if ( a1[16] != 'a' )
    return 0i64;
  if ( a1[17] != 'c' )
    return 0i64;
  if ( a1[18] != 't' )
    return 0i64;
  if ( a1[19] != '3' )
    return 0i64;
  if ( a1[20] == 'r' )
    return a1[21] == 0;
  return 0i64;

해당 문자값을 모아보자면 "Compar3_the_ch4ract3r" 이고 이 값을 input에 넣어보면

 

Correct정답을 맞출 수 있다. 

리버스 엔지니어링을 독학으로 얼마나 가능할진 모르겠으나, 최대한 책과 유튜브 등을 보면서 공부를 해보겠다.

 

시작에 앞서 리버스 엔지니어링에 대해 잠깐 설명하자면 C, C++ 등 고급언어(high level language) 즉 사람이 알아보기 쉽게 만들어진 언어로 개발한 프로그램, 하드웨어등을 IDA, Windbg, Ghidra 등의 도구를 사용하여 분석 후 보다 더 안전하게 만드는 목적이며, 이를 악용하면 인터넷상 존재하는 벽돌판 게임, 인게임 핵등이 만들어진다.   

 

이제 본론으로 들어가면 제목, 제목 하단 내용과 동일하게 정말 어디까지 목표가 될지 모르겠지만 해볼 수 있는 한 계속해서 진행할 스터디다. 

 

exe의 파일의 예상 소스코드 

#include <stdio.h>

void main(){
	printf("Hello, World\n");
    
    return 0;
}

 

 

해당 코드를 리버싱 프로그램으로 돌려보면 

 

 

해당 창과 같이 나오게 된다. 

  • 1번 창은 실행파일의 함수를 모아놓는다.
  • 2번 창은 함수에 있는 코드를 가지고 와서 보여주는 메인 화면이며 위에 String, Hex view, Structures, Enums, imports, Export 등 프로그램을 쉽고 빠르게 분석할 수 있게 존재하는 것이다.
  • 3번 창은 이름 그대로 출력을 하는 동시에 IDC에 입력을 하는 공간이기도 하다. 

 

해당 창에서 

text:00000001400010F6  lea     rax, aHelloWorld ; "Hello, world!\n"

이 코드 부분에 함수를 들어가 보면

rdata:000000014001A140 aHelloWorld     db 'Hello, world!',0Ah,0

로 들어가진다.

문자열(String)로 보면 더 자세히 알 수 있다.

 

 

함수 sub_140001000로 들어가서 

.text:00000001400010F6  lea  rax, aHelloWorld ; "Hello, world!\n"

해당 코드를 조금 더 알아보기 쉽게 단축키 F5를 누르면 

int __cdecl main(int argc, const char **argv, const char **envp)
{
  Sleep(0x3E8u);
  qword_14001DBE0 = (__int64)"Hello, world!\n";
  sub_140001060((__int64)"Hello, world!\n");
  return 0;
}

위와 같이 나온다. 

 

 

위 매개변수에서에선 argc, argv, envp로 3개의 인자를 받는다.

  • Sleep함수를 호출하여 1초 대기한다.
  • qword_14001DBE0에 “Hello, world!\n” 문자열의 주소를 넣은 후
  • sub_140001060에 “Hello, world!\n” 를 인자로 전달하여 호출한다. 
  • 그리고 0을 반환하는 코드의 형태이다.

 

 

아래 코드에서  sub_140001060로 클릭을 해보면 

 

 

아래의 코드로 나온다.

__int64 sub_140001060(__int64 a1, ...)
{
  FILE *v1; // rax
  va_list va; // [rsp+58h] [rbp+10h] BYREF

  va_start(va, a1);
  v1 = _acrt_iob_func(1u);
  return (unsigned int)sub_140001010(v1, a1, 0i64, (__int64 *)va);
}

 

 

va_start는 인자값을 가지고 있기에 void main을 뜻하는거 였고 v1의 부분은 printf 로 생각할 수 있다.

금요일에 시작한게 엊그제가 맞지만, 아무튼 시간은 참 빠르게 지나가서 벌써 마지막 날이다.

 

3일차는 현직에서 하는 방법으로 시큐어 코딩을 시작하였다.

 

VMware

가상 머신인 VMware의 windows로 실습하는데 처음 사용해보는 " Jenkins" 라는 툴이 있었다.

젠킨스 툴에는 이클립스에서 나왔던 툴인 PMD, Findbug가 기본으로 탑제되어 있어 이를 통해 해당 코드에 뭐가 문제인지 더 자세히 나오는 느낌도 있었다. 확실히 툴이 커서 그런지 눈에 확실하게 띄어서 풀이할 수 있었다.

 

 

모든 과정이 끝난 후 문자로 설문하고 며칠 후에 메일로 "수료증"이 날라왔다.

 

 

3일동안 공부한게 수료증으로 나온게 뭔가 여러가지 감정이 들었다. 

그래도 공부하면서 보안이랑 잘 맞는지, 안맞는지 일종의 테스트를 한 느낌인데 

확실한건

 

적성에 맞아서 정말 다행이란 느낌도 들었다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

10.14 (토) 그렇다. 주말이다. 

 

주말은 직장인, 학생 모두가 쉬고 싶어하는 날이지만 1일차에 너무 만족스러웠고 재미있어서 알람을 맞추고 

빠르게 출발했다. 

 

정신상태는 반쯤 졸려있었... 

 

 

 

아무튼 2일차에서 1일차와 다르게 KISA에서 준비한듯한 워게임 사이트 2곳에서 진행을 하였다.

 

실습에 도움될 수 있게 교재 보면서 공부하면서 살펴 봤지만 대부분 1일차에 나온 가이드를 보면서 실습이 더 많았다

 

취약한 코드를 찾고 그 코드를 안전한 코드를 바꾸는 객관식 문제였는데 생각보다 어려웠다.

겉으로 봤을 때는 문제가 없지만 한문제 풀려고 10분이상 붙잡고 고민고민해서 선택했더니 답은 안맞고 ㅎ...

 

 

머리가 띵한 순간이 중간중간 나왔긴 했지만 문제가 점차 익숙해져서 조금씩 점수는 올라서 기분은 좋았다.

 

점심 먹고 다시 문제 풀고 반복하니 시간은 빠르게 지나가니 2일차가 끝났다.

기숙사로 돌아가는 길

빠르게 기숙사로 간다.

 

오늘의 마무리는 기숙사에 사는 고양이로~~ 

+ Recent posts