[CrackMyApp] Fatmike's Crackme #1 (Remake 2024) 풀이

_2·2025년 2월 1일

Crackme

목록 보기
8/12

crackme

1. Unpacking


UPX로 packed 되어 있다.
UPX로 다시 unpack 할 수 있다.

2. Reversing


crackme를 실행하면 serial을 입력하는 칸과 입력을 확인하는 GO! 버튼으로 이루어져 있다.

DialogFunc을 보면 입력값을 검증하는 함수를 찾을 수 있다.

int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
  ...
  DialogBoxParamA(hInstance, 0x73, hWndParent, DialogFunc, 0);
  ...
}
  if ( message == WM_COMMAND )                        
  {
    switch ( wparam )
    {
      case 0x3EAu:
        input_length = GetDlgItemTextA(hDlg, 1001, input_data, 50);
        verify(&loc_406BE1);
        return 0;

verify에선 입력을 xor 한 뒤 hash를 만들어 hash와 글자 수를 확인한다.

  index = 0;
  for ( i = 0; i < 24; i += 6 )
  {
    if ( !input_data[index] )
      index = 0;
    v3 = input_data[index] ^ key[i];
    v4 = index + 1;
    xored_data[i] = v3;
    if ( !input_data[v4] )
      v4 = 0;
    v5 = input_data[v4] ^ key[i + 1];
    v6 = v4 + 1;
    xored_data[i + 1] = v5;
    if ( !input_data[v6] )
      v6 = 0;
    v7 = input_data[v6] ^ key[i + 2];
    v8 = v6 + 1;
    xored_data[i + 2] = v7;
    if ( !input_data[v8] )
      v8 = 0;
    v9 = input_data[v8] ^ key[i + 3];
    v10 = v8 + 1;
    xored_data[i + 3] = v9;
    if ( !input_data[v10] )
      v10 = 0;
    v11 = input_data[v10] ^ key[i + 4];
    v12 = v10 + 1;
    xored_data[i + 4] = v11;
    if ( !input_data[v12] )
      v12 = 0;
    v13 = input_data[v12] ^ key[i + 5];
    index = v12 + 1;
    xored_data[i + 5] = v13;
  }
  if ( hash_xored() == 0x5A6AA47D && input_length == 22 )
  {
    CurrentProcessId = GetCurrentProcessId();
    handle = OpenProcess(0x28u, 1, CurrentProcessId);
    WriteProcessMemory(handle, lpBaseAddress, xored_data, 0x18u, 0);
    return 1;
  }
  else
  {
    MessageBoxA(hWndParent, "Try again!", Caption, 0x40u);
    return 0;
  }
unsigned int hash_xored()
{
  unsigned int i; // esi
  unsigned int v1; // edx
  unsigned int v2; // ecx
  unsigned int v3; // edx
  unsigned int v4; // ecx
  unsigned int v5; // edx
  unsigned int v6; // ecx
  unsigned int v7; // edx
  unsigned int v8; // eax
  unsigned int v9; // edx
  unsigned int v10; // eax
  unsigned int v11; // eax
  unsigned int v12; // eax
  unsigned int v13; // eax
  unsigned int v14; // eax
  unsigned int v15; // eax
  char v16; // cl
  int magic_table[257]; // [esp+8h] [ebp-410h]
  int v19; // [esp+40Ch] [ebp-Ch]

  v19 = 0;
  for ( i = 0; i < 0x100; ++i )
  {
    v1 = (i >> 1) ^ 0xEDB88320;
    if ( (i & 1) == 0 )
      v1 = i >> 1;
    v2 = (v1 >> 1) ^ 0xEDB88320;
    if ( (v1 & 1) == 0 )
      v2 = v1 >> 1;
    v3 = (v2 >> 1) ^ 0xEDB88320;
    if ( (v2 & 1) == 0 )
      v3 = v2 >> 1;
    v4 = (v3 >> 1) ^ 0xEDB88320;
    if ( (v3 & 1) == 0 )
      v4 = v3 >> 1;
    v5 = (v4 >> 1) ^ 0xEDB88320;
    if ( (v4 & 1) == 0 )
      v5 = v4 >> 1;
    v6 = (v5 >> 1) ^ 0xEDB88320;
    if ( (v5 & 1) == 0 )
      v6 = v5 >> 1;
    v7 = (v6 >> 1) ^ 0xEDB88320;
    if ( (v6 & 1) == 0 )
      v7 = v6 >> 1;
    v8 = (v7 >> 1) ^ 0xEDB88320;
    if ( (v7 & 1) == 0 )
      v8 = v7 >> 1;
    magic_table[i] = v8;
  }
  v9 = 0;
  v10 = ~v19;
  do
  {
    v11 = magic_table[(v10 ^ xored_data[v9])] ^ (v10 >> 8);
    v12 = magic_table[(v11 ^ xored_data[v9 + 1])] ^ (v11 >> 8);
    v13 = magic_table[(v12 ^ xored_data[v9 + 2])] ^ (v12 >> 8);
    v14 = magic_table[(v13 ^ xored_data[v9 + 3])] ^ (v13 >> 8);
    v15 = magic_table[(v14 ^ xored_data[v9 + 4])] ^ (v14 >> 8);
    v16 = xored_data[v9 + 5];
    v9 += 6;
    v10 = magic_table[(v15 ^ v16)] ^ (v15 >> 8);
  }
  while ( v9 < 0x18 );
  return ~v10;
}

hash_xored의 역산은 할 수 없고 brute force 또한 입력의 크기가 커서 불가능하다.

그러나 비밀번호 비교 이후의 동작을 보면 문제를 풀 수 있다.

3. Solving

The goal is to find the valid serial. A window will pop up to let you know you found it :-)
There is only one valid serial.

readme를 보면 성공 message가 뜬다고 하는데 문제를 푸는 데 이 정보를 이용할 수 있다.

if ( hash_xored() == 0x5A6AA47D && input_length == 22 )
  {
    CurrentProcessId = GetCurrentProcessId();
    handle = OpenProcess(0x28u, 1, CurrentProcessId);
    WriteProcessMemory(handle, lpBaseAddress, xored_data, 0x18u, 0);
    return 1;
  }

코드를 보면 비교 후에 WriteProcessMemory로 특정 위치에 xor 한 데이터를 써주는데 확인해보면 verify 이 후 (eax==1) 실행되는 위치임을 알 수 있다.

.text:00406BCA                 mov     input_length, eax
.text:00406BCF                 push    offset loc_406BE1 ; lpBaseAddress
.text:00406BD4                 call    verify         
.text:00406BD9                 add     esp, 4
.text:00406BDC                 cmp     eax, 0
.text:00406BDF                 jz      short loc_406C10
.text:00406BE1
.text:00406BE1 loc_406BE1:                             ; DATA XREF: DialogFunc+38F↑o
.text:00406BE1                 nop
.text:00406BE2                 nop

해당 영역은 nop으로 채워져 있으며 검증 이후 xor 결과로 다시 쓰여진다.

if ( hash_xored() == 0x5A6AA47D && input_length == 22 )
  {
    CurrentProcessId = GetCurrentProcessId();
    handle = OpenProcess(0x28u, 1, CurrentProcessId);
    WriteProcessMemory(handle, lpBaseAddress, xored_data, 0x18u, 0);
    return 1;
  }
  else
  {
    MessageBoxA(hWndParent, "Try again!", Caption, 0x40u);
    return 0;
  }

실패시 Try again! 메시지 박스를 띄우는 걸로 보아 성공 시에도 메시지 박스를 띄운다는 걸 알 수 있다.

바이너리를 확인하면 성공시 출력되는 문자열을 찾을 수 있다.

6A 40                 - push 40 { 64 }
68 28B04000           - push crackme.exe+B028 { ("Information") }
68 F8924000           - push crackme.exe+92F8 { ("Try again!") }
FF 35 F0B44000        - push [crackme.exe+B4F0] { (24710324) }
FF 15 DC904000        - call dword ptr [crackme.exe+90DC] { ->USER32.MessageBoxA }

위 실패 메시지를 바꿔준다.

6A 40                 - push 40 { 64 }
68 28B04000           - push crackme.exe+B028 { ("Information") }
68 38B04000           - push crackme.exe+B038 { ("Well done!") }
FF 35 F0B44000        - push [crackme.exe+B4F0] { (24710324) }
FF 15 DC904000        - call dword ptr [crackme.exe+90DC] { ->USER32.MessageBoxA }

그러면 이렇게 되는데 nop으로 채워져있는 공간에 위 코드가 쓰여야 하므로
xored_data는 6A 40 68 ... FF 15 DC 90 40 00이 된다.

xored_datainput_datakey를 xor한 것이니 key와 xor 해서 input_data를 구할 수 있다.

input_length는 22여야 하므로 22글자만 출력한다.

key = [
      0x09, 0x32, 0x09, 0x4B, 0xDB, 0x2D, 0x65, 0x1B, 0x16, 0xDF, 
  0x2E, 0x65, 0xD2, 0x5E, 0x99, 0xD7, 0x2B, 0x73, 0xD2, 0x74, 
  0xAF, 0xE3, 0x23, 0x72
]


xored_data = [
    0x6A, 0X40, 0X68, 0X28, 0XB0, 0X40, 0X00, 0X68, 0X38, 0XB0, 0X40, 0X00, 0XFF, 0X35, 0XF0, 0XB4, 0X40, 0X00,
    0xFF, 0X15, 0XDC, 0X90, 0X40, 0X00
]

input_data = ''
for i in range(len(xored_data)):
    input_data = input_data + chr(xored_data[i] ^ key[i])


print(input_data[:22])

구한 serial을 입력하면 성공 메시지가 출력되는 것을 확인 할 수 있다.

0개의 댓글