문제 정보
드림이가 FLAG를 가져왔어요!!.
어라… FLAG가 이상하네요…??.??
문제 파일을 다운받아보면, flag.txt와 ransom.exe 파일이 존재한다.
Exeinfo PE 프로그램으로 확인해보면, UPX로 패킹된 실행 파일인걸 알 수 있다.

upx -d 명령어로 언팩한다.
myMac@MacBook-Pro ransom % ls flag.txt ransom.exe myMac@MacBook-Pro ransom % upx -d ransom.exe Ultimate Packer for eXecutables Copyright (C) 1996 - 2023 UPX 4.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 30th 2023 File size Ratio Format Name -------------------- ------ ----------- ----------- 11776 <- 8192 69.57% win32/pe ransom.exe Unpacked 1 file.
분석
int __cdecl main(int argc, const char **argv, const char **envp) { sub_401080(); sub_401610(); sub_4011D0(); return 0; }
메인 함수에는 3개의 함수가 존재한다.
sub_401080
BOOL sub_401080() { HANDLE hSnapshot; // [esp+0h] [ebp-234h] PROCESSENTRY32W pe; // [esp+4h] [ebp-230h] BYREF memset(&pe, 0, sizeof(pe)); pe.dwSize = 556; hSnapshot = CreateToolhelp32Snapshot(0xFu, 0); if ( Process32FirstW(hSnapshot, &pe) ) { do { if ( !wcsicmp(pe.szExeFile, L"x32dbg.exe") ) { sub_4019C0("[+] Detect it!!", (char)hSnapshot); system("pause"); exit(0); } if ( !wcsicmp(pe.szExeFile, L"x64dbg.exe") ) { sub_4019C0("[+] Detect it!!", (char)hSnapshot); system("pause"); exit(0); } if ( !wcsicmp(pe.szExeFile, L"Ollydbg.exe") ) { sub_4019C0("[+] Detect it!!", (char)hSnapshot); system("pause"); exit(0); } } while ( Process32NextW(hSnapshot, &pe) ); } return CloseHandle(hSnapshot); }
CreateToolhelp32Snapshot – 프로세스 정보를 얻기 위해 핸들을 생성한다.
Process32First(), Process32Next() – 프로세스 정보를 나열한다.
따라서 실행중인 프로세스에서 x32dbg.exe, x64dbg.exe, Ollydbg.exe 위 이름 중 하나라도 있으면 종료시킨다.
sub_401610
int sub_401610() { ... if ( sub_401000() ) { sub_4019C0("[+] Detect it!!"); system("pause"); exit(0); } ... }
먼저 sub_401000 함수에서 참이면 프로그램을 종료시킨다.
sub_401000 함수를 살펴보자
sub_401000
int sub_401000() { HANDLE CurrentThread; // eax int v2; // [esp+0h] [ebp-2D4h] CONTEXT Context; // [esp+4h] [ebp-2D0h] BYREF v2 = 0; Context.ContextFlags = 0x10010; CurrentThread = GetCurrentThread(); GetThreadContext(CurrentThread, &Context); if ( Context.Dr0 || Context.Dr1 || Context.Dr2 || Context.Dr3 ) return 1; return v2; }
하드웨어 브레이크 포인트가 설정되었는지 확인하는 안티 디버깅이였다.
다시 sub_401610 함수로 돌아가보자
int sub_401610() { int v1; // [esp+0h] [ebp-450h] int Offset; // [esp+4h] [ebp-44Ch] size_t ElementCount; // [esp+Ch] [ebp-444h] size_t i; // [esp+10h] [ebp-440h] size_t j; // [esp+10h] [ebp-440h] FILE *Stream; // [esp+14h] [ebp-43Ch] BYREF char Buffer[2]; // [esp+18h] [ebp-438h] BYREF char v8; // [esp+1Ah] [ebp-436h] char v9; // [esp+1Bh] [ebp-435h] char v10; // [esp+1Fh] [ebp-431h] _WORD v11[8]; // [esp+418h] [ebp-38h] BYREF char v12[20]; // [esp+428h] [ebp-28h] BYREF char v13[16]; // [esp+43Ch] [ebp-14h] BYREF strcpy(v12, "abc!@#qwe012fgh456"); strcpy(v13, "1@!@#a134234"); *(_WORD *)&v13[13] = 0; v13[15] = 0; strcpy((char *)v11, "zBNdlwi102394"); v11[7] = 0; ... sub_4019C0("[+] Good!"); if ( fopen_s(&Stream, "flag.txt", "r+b") ) return -1; while ( !feof(Stream) ) { Offset = ftell(Stream); ElementCount = fread(Buffer, 1u, 0x400u, Stream); if ( !ElementCount ) break; for ( i = 0; i < ElementCount; ++i ) Buffer[i] ^= v12[i % 0x10]; for ( j = 0; j < ElementCount; ++j ) { if ( !(j % 2) ) ++Buffer[j]; if ( j % 2 == 1 ) Buffer[j] += 2; Buffer[0] ^= v9; // Buffer[0] ^= Buffer[3] v8 ^= v10; // Buffer[2] ^= Buffer[7] } v1 = ftell(Stream); fseek(Stream, Offset, 0); fwrite(Buffer, 1u, ElementCount, Stream); fseek(Stream, v1, 0); } fclose(Stream); return 1; }
flag.txt 파일을 암호화시키는 함수가 보이기 시작한다.
처음에는 flag.txt 파일에 있는 한 문자와 “abc!@#qwe012fgh456” 문자열에서 ‘반복 카운트 값에 0x10을 나눈 나머지’ 인덱스의 한문자와 XOR한다.
그리고 카운트값이 2로 나누어 떨어지면 Buffer[j] = Buffer[j] +1을 수행하고,
카운트값이 2로 나눴을때 나머지가 1이면 Buffer[j] = Buffer[j] +2를 수행한다.
(여기서 j는 카운트값)
하지만, 41~42줄에 있는 Buffer[0] ^= v9; v8 ^= v10; 연산에 대한 변수가 자세히 나와있지 않았다.
따라서 어셈블리로 한번 봐야할 필요가 있었다.
loc_401867: ; CODE XREF: sub_401610+237↑j mov edx, 1 ; edx = 1 imul eax, edx, 0 ; eax = edx * 0 = 0 mov ecx, 1 ; ecx = 1 imul edx, ecx, 3 ; edx = ecx * 3 = 3 movsx ecx, [ebp+edx+Buffer] ; ecx = Buffer[3] movsx edx, [ebp+eax+Buffer] ; edx = Buffer[0] xor edx, ecx ; edx = Buffer[0] ^ Buffer[3] mov eax, 1 ; eax = 1 imul ecx, eax, 0 ; ecx = eax * 0 = 0 mov [ebp+ecx+Buffer], dl ; Buffer[0] = dl (8bits edx) = Buffer[0] ^ Buffer[3] mov edx, 1 ; edx = 1 shl edx, 1 ; edx = edx << 1 = 1 << 1 = 2 mov eax, 1 ; eax = 1 imul ecx, eax, 7 ; ecx = eax * 7 = 7 movsx eax, [ebp+ecx+Buffer] ; eax = Buffer[7] movsx ecx, [ebp+edx+Buffer] ; ecx = Buffer[2] xor ecx, eax ; ecx = Buffer[2] ^ Buffer[7] mov edx, 1 ; edx = 1 shl edx, 1 ; edx = edx << 1 = 1 << 1 = 2 mov [ebp+edx+Buffer], cl ; Buffer[2] = cl (8bits ecx) = Buffer[2] ^ Buffer[7] jmp loc_4017E3
간단한 산수 계산으로, v9 = Buffer[3], v8 = Buffer[2], v10 = Buffer[7]이란걸 알 수 있었다.
sub_4011D0
int sub_4011D0() { int v1; // [esp+0h] [ebp-430h] int Offset; // [esp+4h] [ebp-42Ch] unsigned int ElementCount; // [esp+Ch] [ebp-424h] char v4[20]; // [esp+10h] [ebp-420h] BYREF unsigned int i; // [esp+24h] [ebp-40Ch] FILE *Stream; // [esp+28h] [ebp-408h] BYREF char Buffer[3]; // [esp+2Ch] [ebp-404h] BYREF char v8; // [esp+2Fh] [ebp-401h] char v9; // [esp+30h] [ebp-400h] char v10; // [esp+31h] [ebp-3FFh] char v11; // [esp+33h] [ebp-3FDh] char v12; // [esp+34h] [ebp-3FCh] char v13; // [esp+35h] [ebp-3FBh] char v14; // [esp+37h] [ebp-3F9h] if ( IsDebuggerPresent() ) { system("pause"); exit(0); } qmemcpy(v4, "Pp btXHE", 8); v4[8] = 0x90; v4[9] = 0x90; v4[10] = 0x70; v4[11] = 0x40; v4[12] = 0x36; v4[13] = 0x45; v4[14] = 0x55; v4[15] = 0x71; v4[16] = 0x18; v4[17] = 0x19; v4[18] = 0x70; if ( fopen_s(&Stream, "flag.txt", "r+b") ) return -1; while ( !feof(Stream) ) { Offset = ftell(Stream); ElementCount = fread(Buffer, 1u, 0x400u, Stream); if ( !ElementCount ) break; for ( i = 0; i < ElementCount; ++i ) { Buffer[i] ^= v4[i]; Buffer[0] -= 0x15; v8 ^= 0x18u; // Buffer[3] ^= 0x18 v12 -= 0x33; // Buffer[8] -= 0x33 v14 ^= 0x44u; // Buffer[11] ^= 0x44 v13 += 0x47; // Buffer[9] += 0x47 v9 ^= 0x88u; // Buffer[4] ^= 0x88 v11 ^= 0x68u; // Buffer[7] ^= 0x68 Buffer[0] += 0x15; v14 ^= 0x44u; // Buffer[11] ^= 0x44 v13 -= 0x47; // Buffer[9] -= 0x47 v12 += 0x33; // Buffer[8] += 0x33 v10 += 0x11; // Buffer[5] += 0x11 v8 ^= 0x18u; // Buffer[3] ^= 0x18 v9 ^= 0x88u; // Buffer[4] ^= 0x88 v11 ^= 0x68u; // Buffer[7] ^= 0x68 } v1 = ftell(Stream); fseek(Stream, Offset, 0); fwrite(Buffer, 1u, ElementCount, Stream); fseek(Stream, v1, 0); } return fclose(Stream); }
IsDebuggerPresent 함수로 디버깅 중인지 확인하고
한번더 flag.txt 파일을 열어서 암호화시킨다.
v4 문자열은 아래와 같고
\x50\x70\x20\x62\x74\x58\x48\x45\x90\x90\x70\x40\x36\x45\x55\x71\x18\x19\x70\x00
flag.txt 파일에 있는 한 문자와 v4 문자열에서 ‘반복 카운트 값인’ 인덱스의 한문자와 XOR한다.
그리고 여러 연산이 있는데 XOR을 2번해서 원래 값으로 돌리거나, 뺏다가 더해서 원래값을 돌리지만
57줄에 있는 v10 += 0x11;은 그대로였다.
mov ecx, [ebp+var_40C] movsx edx, [ebp+ecx+var_420] mov eax, [ebp+var_40C] movsx ecx, [ebp+eax+Buffer] xor ecx, edx mov edx, [ebp+var_40C] mov [ebp+edx-404h], cl mov eax, 1 imul ecx, eax, 0 movsx edx, [ebp+ecx+Buffer] sub edx, 15h mov eax, 1 imul ecx, eax, 0 mov [ebp+ecx+Buffer], dl ; Buffer[0] -= 0x15 mov edx, 1 imul eax, edx, 3 movsx ecx, [ebp+eax+Buffer] xor ecx, 18h mov edx, 1 imul eax, edx, 3 mov [ebp+eax+Buffer], cl ; Buffer[3] ^= 0x18 mov ecx, 1 shl ecx, 3 movsx edx, [ebp+ecx+Buffer] sub edx, 33h ; '3' mov eax, 1 shl eax, 3 mov [ebp+eax+Buffer], dl ; Buffer[8] -= 0x33 mov ecx, 1 imul edx, ecx, 0Bh movsx eax, [ebp+edx+Buffer] xor eax, 44h mov ecx, 1 imul edx, ecx, 0Bh mov [ebp+edx+Buffer], al ; Buffer[11] ^= 0x44 mov eax, 1 imul ecx, eax, 9 movsx edx, [ebp+ecx+Buffer] add edx, 47h ; 'G' mov eax, 1 imul ecx, eax, 9 mov [ebp+ecx+Buffer], dl ; Buffer[9] += 0x47 mov edx, 1 shl edx, 2 movsx eax, [ebp+edx+Buffer] xor eax, 88h mov ecx, 1 shl ecx, 2 mov [ebp+ecx+Buffer], al ; Buffer[4] ^= 0x88 mov edx, 1 imul eax, edx, 7 movsx ecx, [ebp+eax+Buffer] xor ecx, 68h mov edx, 1 imul eax, edx, 7 mov [ebp+eax+Buffer], cl ; Buffer[7] ^= 0x68 mov ecx, 1 imul edx, ecx, 0 movsx eax, [ebp+edx+Buffer] add eax, 15h mov ecx, 1 imul edx, ecx, 0 mov [ebp+edx+Buffer], al ; Buffer[0] += 0x15 mov eax, 1 imul ecx, eax, 0Bh movsx edx, [ebp+ecx+Buffer] xor edx, 44h mov eax, 1 imul ecx, eax, 0Bh mov [ebp+ecx+Buffer], dl ; Buffer[11] ^= 0x44 mov edx, 1 imul eax, edx, 9 movsx ecx, [ebp+eax+Buffer] sub ecx, 47h ; 'G' mov edx, 1 imul eax, edx, 9 mov [ebp+eax+Buffer], cl ; Buffer[9] -= 0x47 mov ecx, 1 shl ecx, 3 movsx edx, [ebp+ecx+Buffer] add edx, 33h ; '3' mov eax, 1 shl eax, 3 mov [ebp+eax+Buffer], dl ; Buffer[8] += 0x33 mov ecx, 1 imul edx, ecx, 5 movsx eax, [ebp+edx+Buffer] add eax, 11h mov ecx, 1 imul edx, ecx, 5 mov [ebp+edx+Buffer], al ; Buffer[5] += 0x11 mov eax, 1 imul ecx, eax, 3 movsx edx, [ebp+ecx+Buffer] xor edx, 18h mov eax, 1 imul ecx, eax, 3 mov [ebp+ecx+Buffer], dl ; Buffer[3] ^= 0x18 mov edx, 1 shl edx, 2 movsx eax, [ebp+edx+Buffer] xor eax, 88h mov ecx, 1 shl ecx, 2 mov [ebp+ecx+Buffer], al ; Buffer[4] ^= 0x88 mov edx, 1 imul eax, edx, 7 movsx ecx, [ebp+eax+Buffer] xor ecx, 68h mov edx, 1 imul eax, edx, 7 mov [ebp+eax+Buffer], cl ; Buffer[7] ^= 0x68 jmp loc_401325
어셈블리로 확인해보니 v10=Buffer[5]였다.
따라서, 문자열의 크기만큼 Buffer[5]는 계속 0x11을 더하는 것을 반복한다.
암호화
암호화시키는 코드를 파이썬 코드로 구현하면 아래와 같다.
0x11을 계속 더하다보면 0~255 char 범위를 넘어버리므로
24줄에 % 256 수식을 추가하였다.
#ransom.py def encrypt_stage1(data): data = list(data) v12 = "abc!@#qwe012fgh456" for i in range(len(data)): data[i] = data[i] ^ ord(v12[i%0x10]) for i in range(len(data)): if i % 2 == 0: data[i] = data[i] + 1 if i % 2 == 1: data[i] = data[i] + 2 data[0] = data[0] ^ data[3] data[2] = data[2] ^ data[7] return data def encrypt_stage2(data): result = "" v4 = b'\x50\x70\x20\x62\x74\x58\x48\x45\x90\x90\x70\x40\x36\x45\x55\x71\x18\x19\x70\x00' for i in range(len(data)): data[i] = data[i] ^ v4[i] data[5] = (data[5] + 0x11) % 256 #char 범위 0-255 for i in range(len(data)): result = result + chr(data[i]) return result data = bytes("DH{Do_y0u_Know_Oni?}", 'ascii') data = encrypt_stage1(data) # print(data) print(encrypt_stage2(data))
복호화
암호화된 문자열을 역순으로 하나씩 복호화해야 풀리더라
#ransom-dec.py def decrypt_stage1(data): data = list(data) v4 = b'\x50\x70\x20\x62\x74\x58\x48\x45\x90\x90\x70\x40\x36\x45\x55\x71\x18\x19\x70\x00' for i in range(len(data)-1, -1, -1): data[5] = (data[5] - 0x11) % 256 data[i] = data[i] ^ v4[i] return data def decrypt_stage2(data): v12 = "abc!@#qwe012fgh456" result = "" for i in range(len(data)-1 ,-1, -1): data[2] = data[2] ^ data[7] data[0] = data[0] ^ data[3] if i % 2 == 0: data[i] = data[i] - 1 if i % 2 == 1: data[i] = data[i] - 2 for i in range(len(data)-1, -1, -1): data[i] = data[i] ^ ord(v12[i%0x10]) for i in range(len(data)): result = result + chr(data[i]) return result data = b'\x74\x5C\x37\x05\x44\x8A\x41\x0C\x81\xE1\x0B\x1E\x3C\x57\x6D\x0C\x08\x14\x2D\x5E' data = decrypt_stage1(data) # print(data) print(decrypt_stage2(data))
myMac@MacBook-Pro Desktop % python3 ransom-dec.py DH{Do_y0u_Know_Oni?}