Try   HackMD

Task 3: Dynamic API Resolution

  • Chính xác kĩ thuật mà mình cần phải thực hiện trong task này là Resolve API, hay một số trang gọi là Dynamic API Resolution. Nói một cách ngắn gọn, API có thể hiểu là các hàm được viết sẵn, đặt trong các DLL của Windows, khi cần dùng thì user phải khai báo các hàm đó ra để load vào memory khi startup. Kĩ thuật này giúp chúng ta resolve và invoke WinAPI cùng lúc với run-time. Đây là một trong những kĩ thuật cơ bản trước khi nghiên cứu các kĩ thuật cao cấp hơn như shellcode injection hay (reflective) dll injection, và nó cũng khá hữu dụng để anti static analysis.

Code C

Idea

  • Trong task này, mình cần resolve tất cả các API cần dùng để gọi ra MessageBoxA. Thông thường, chúng ta sẽ làm như sau:
#include <windows.h> int main() { MessageBoxA(NULL, "Hello World!", "Hello", MB_OK); return 0; }

để hiện ra:

image

  • Trông nhỏ bé vậy nhưng thực ra chương trình đã trải qua các bước như sau:

Build Phase: - Compiler nhận thấy chương trình call MsgBox và nhận định nó là external function - Linker thêm entry vào IAT trong PE Header - Entry chỉ ra MsgBox imported từ user32.dll

Load Phase: - Windows loader duyệt IAT - Loader kiểm tra user32.dll trong memory - Loader tìm kiếm MsgBox trong user32.dll - Loader viết địa chỉ MsgBox vào IAT

Run Phase: - Call MsgBox thực chất là call qua IAT - Processor sẽ nhảy đến địa chỉ được viết bởi loader và user32.dll thực thi show MsgBox

  • Đó là các bước của một chương trình bình thường. Tuy nhiên, nếu malware sử dụng các hàm API rõ ràng như này thì rõ ràng đã mất đi tính stealth, khi đó cần thực hiện resolve API.
  • Về cơ bản, quy trình cũng sẽ giống như chương trình bình thường, chỉ khác là chúng ta cần thực hiện bằng tay các bước nói trên. Quy trình sẽ như sau:
  • Duyệt TEB, trỏ tới PEB
  • Duyệt PEB, trỏ tới Ldr (xem thêm ở đây) thực chất chính là danh sách liên kết đôi chứa thông tin về các modules đã load dưới dạng cấu trúc LDR_DATA_TABLE_ENTRY(xem thêm ở đây)
  • Duyệt LDR_DATA_TABLE_ENTRY và tìm kiếm targetModule mà ở đây chính là kernel32.dll
  • Duyệt tất cả các functions của kernel32.dll và lấy ra GetProcAddress, LoadLibraryA
  • Load user32.dll và sử dụng GetProcAddress để lấy địa chỉ của MessageBoxA để thực thi.
  • Tương đối thì mình có sơ đồ dưới đây để so sánh (gen bởi Claude dựa trên code của mình nên nó hơi đần):
    image
  • Ở đây, có hai bước khó chính là bước 1 và 2, dưới đây là cách mình deal với nó:

myGetModuleHandle

  • Trước tiên, nhận thấy PEB có lưu trữ nhiều thông tin quan trọng về Dll được load vào chương trình nên chúng ta sẽ tìm kiếm kernel32.dll trong đó. Do Ldr là danh sách liên kết nên mình có thể thực hiện PEB Traversal để duyệt và đọc các Dll bằng CONTAINING_RECORD, chi tiết như sau:
HMODULE myGetModuleHandle(LPCWSTR lModuleName) { PPEB peb = __readfsdword(0x30); //0x60 for x64 PLIST_ENTRY pListEntry = peb->Ldr->InMemoryOrderModuleList.Flink; PLIST_ENTRY pListEntryEnd = &peb->Ldr->InMemoryOrderModuleList; int moduleNameLength = wcslen(lModuleName); while (pListEntry != pListEntryEnd) { PLDR_DATA_TABLE_ENTRY module = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); WCHAR* fullName = module->FullDllName.Buffer; // retrieve the full path of the module int fullNameLength = module->FullDllName.Length / sizeof(WCHAR); // length of the full path WCHAR* fileNameStart = fullName + (fullNameLength - moduleNameLength); // start of the file name if (_wcsnicmp(fileNameStart, lModuleName, moduleNameLength) == 0) { // compare the file name return (HMODULE)module->DllBase; // return the base address of the module if the file name matches target } pListEntry = pListEntry->Flink; }; return NULL; }
  • Mình đã có comment bên cạnh để tiện theo dõi và hiểu luồng hơn. Sau hàm này, mình đã lấy được địa chỉ của con kernel32.dll trong process:
    image

myGetProcAddress

  • Trong kernel32.dll có hàm GetProcAddress rất quan trọng vì nó lấy được địa chỉ của hàm trong một con Dll:
    image
  • Bên cạnh đó còn có LoadLibraryA giúp load user32.dll vào process đang chạy để lấy được MessageBox:
    image
  • Vốn dĩ chỉ có kernel32.dll và một vài Dll cơ bản khác được nạp sẵn vào chương trình, các Dll khác thì phải khai báo mới sử dụng được. MessageBoxA nằm trong user32.dll là target của mình nên mình cần load Dll này.
  • Idea của hàm này mình viết rất đơn giản là duyệt toàn bộ các hàm có trong EAT của module, nếu trùng khớp với GetProcAddress thì sẽ trả về địa chỉ của hàm. Tuy nhiên, phần khó ở đây là làm sao để duyệt đúng thì bài này đã giúp mình đi chuẩn hướng.
  • Với mình, phần này đọc code asm dễ thở hơn hẳn. Đây là code C của mình:
FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule; PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew); DWORD exportDirRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + exportDirRVA); // Get tables DWORD* functionAddresses = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions); WORD* nameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals); DWORD* functionNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames); for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) { LPCSTR functionName = (LPCSTR)((BYTE*)hModule + functionNames[i]); if (strcmp(lpProcName, functionName) == 0) { DWORD functionIndex = nameOrdinals[i]; DWORD functionRVA = functionAddresses[functionIndex]; return (FARPROC)((BYTE*)hModule + functionRVA); } } return NULL; }
  • Ở đây chúng ta cần khai báo kiểu dữ liệu trả về đúng như trong syntax của MSDN.

API Resolution

  • Sau khi có được địa chỉ của GetProcAddress, mình sẽ call hàm để lấy địa chỉ của LoadLibraryAuser32.dll -> MessageBoxA:
typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR); typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR); typedef int(WINAPI* MESSAGEBOXA)(HWND, LPCSTR, LPCSTR, UINT); GETPROCADDRESS getProcAddress = myGetProcAddress(kernel32BaseAddr, getprocaddress); LOADLIBRARYA loadLibraryA = getProcAddress(kernel32BaseAddr, loadlibrarya); HMODULE user32BaseAddr = loadLibraryA(user32); MESSAGEBOXA messageBoxA = getProcAddress(user32BaseAddr, messageboxa);
  • Tại đây thì mình có thể call được MessageBoxA rồi, dưới đây là so sánh giữa hai địa chỉ của hai phương pháp:
HMODULE user32BaseAddr = loadLibraryA("user32.dll"); MESSAGEBOXA messageBoxA = getProcAddress(user32BaseAddr, "MessageBoxA"); printf("MessageBoxA from Resolution: %p\n", messageBoxA); HMODULE hModule = LoadLibraryA("user32.dll"); FARPROC pFunc = GetProcAddress(hModule, "MessageBoxA"); printf("MessageBoxA not from Resolution: %p\n", pFunc);

image

  • Vậy là phương pháp resolve API này đã thành công. Dưới đây là toàn bộ code C của mình (mình có nghịch nghịch thêm chút obfuscate):
#include <stdio.h> #include <string.h> #include <windows.h> #include <winternl.h> typedef FARPROC(WINAPI* GETPROCADDRESS)(HMODULE, LPCSTR); typedef HMODULE(WINAPI* LOADLIBRARYA)(LPCSTR); typedef int(WINAPI* MESSAGEBOXA)(HWND, LPCSTR, LPCSTR, UINT); char *decrypt(BYTE* data) { size_t length = strlen((char*)data); for (size_t i = 0; i < length; i++) { data[i] ^= (BYTE)length; } return (char*)data; } HMODULE myGetModuleHandle(LPCWSTR lModuleName) { PPEB peb = __readfsdword(0x30); //0x60 for x64 PLIST_ENTRY pListEntry = peb->Ldr->InMemoryOrderModuleList.Flink; PLIST_ENTRY pListEntryEnd = &peb->Ldr->InMemoryOrderModuleList; int moduleNameLength = wcslen(lModuleName); while (pListEntry != pListEntryEnd) { PLDR_DATA_TABLE_ENTRY module = CONTAINING_RECORD(pListEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks); WCHAR* fullName = module->FullDllName.Buffer; // retrieve the full path of the module int fullNameLength = module->FullDllName.Length / sizeof(WCHAR); // length of the full path WCHAR* fileNameStart = fullName + (fullNameLength - moduleNameLength); // start of the file name if (_wcsnicmp(fileNameStart, lModuleName, moduleNameLength) == 0) { // compare the file name return (HMODULE)module->DllBase; // return the base address of the module if the file name matches target } pListEntry = pListEntry->Flink; }; return NULL; } FARPROC myGetProcAddress(HMODULE hModule, LPCSTR lpProcName) { PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)hModule; PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((BYTE*)hModule + dosHeader->e_lfanew); DWORD exportDirRVA = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + exportDirRVA); // Get tables DWORD* functionAddresses = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfFunctions); WORD* nameOrdinals = (WORD*)((BYTE*)hModule + exportDirectory->AddressOfNameOrdinals); DWORD* functionNames = (DWORD*)((BYTE*)hModule + exportDirectory->AddressOfNames); for (DWORD i = 0; i < exportDirectory->NumberOfNames; ++i) { LPCSTR functionName = (LPCSTR)((BYTE*)hModule + functionNames[i]); if (strcmp(lpProcName, functionName) == 0) { DWORD functionIndex = nameOrdinals[i]; DWORD functionRVA = functionAddresses[functionIndex]; return (FARPROC)((BYTE*)hModule + functionRVA); } } return NULL; } int main() { BYTE user32[] = { 0x7f, 0x79, 0x6f, 0x78, 0x39, 0x38, 0x24, 0x6e, 0x66, 0x66, 0x00 }; BYTE getprocaddress[] = { 0x49, 0x6b, 0x7a, 0x5e, 0x7c, 0x61, 0x6d, 0x4f, 0x6a, 0x6a, 0x7c, 0x6b, 0x7d, 0x7d, 0x00 }; BYTE loadlibrarya[] = { 0x40, 0x63, 0x6d, 0x68, 0x40, 0x65, 0x6e, 0x7e, 0x6d, 0x7e, 0x75, 0x4d, 0x00 }; BYTE messageboxa[] = { 0x46, 0x6e, 0x78, 0x78, 0x6a, 0x6c, 0x6e, 0x49, 0x64, 0x73, 0x4a, 0x00 }; BYTE hello[] = { 0x5f, 0x72, 0x7b, 0x7b, 0x78, 0x37, 0x71, 0x65, 0x78, 0x7a, 0x37, 0x56, 0x47, 0x5e, 0x48, 0x45, 0x72, 0x64, 0x78, 0x7b, 0x61, 0x72, 0x36, 0x00 }; BYTE msg[] = { 0x4a, 0x5b, 0x42, 0x54, 0x59, 0x6e, 0x78, 0x64, 0x67, 0x7d, 0x6e, 0x00 }; HMODULE kernel32BaseAddr = myGetModuleHandle(L"kernel32.dll"); if (kernel32BaseAddr != NULL) { GETPROCADDRESS getProcAddress = myGetProcAddress(kernel32BaseAddr, decrypt(getprocaddress)); LOADLIBRARYA loadLibraryA = getProcAddress(kernel32BaseAddr, decrypt(loadlibrarya)); HMODULE user32BaseAddr = loadLibraryA((LPCSTR)decrypt(user32)); MESSAGEBOXA messageBoxA = getProcAddress(user32BaseAddr, decrypt(messageboxa)); messageBoxA(NULL, decrypt(msg), decrypt(hello), MB_OK); } return 0; }

Code ASM

  • Code ASM thì có phần đơn giản hơn chút, mình code bằng MASM, sẽ thử bằng NASM sau. Các dòng code mình đều đã thêm comment để tiện theo dõi và debug:
.386 .model flat, stdcall .stack 4096 assume fs:nothing .data loadLibraryA BYTE "LoadLibraryA", 0 messageBoxA BYTE "MessageBoxA", 0 user32 BYTE "user32.dll", 0 caption BYTE "API_Resolve_ASM", 0 msg BYTE "Hello from API_Resolve_ASM", 0 MB_OK EQU 0 .code main PROC push ebp mov ebp, esp sub esp, 24h ; Reserve space for local variables xor eax, eax mov [ebp - 04h], eax ; Export functions mov [ebp - 08h], eax ; Export address table (EAT) mov [ebp - 0Ch], eax ; Export name table mov [ebp - 10h], eax ; Export ordinal table mov [ebp - 14h], eax ; Null terminated string "GetProcAddress" mov [ebp - 18h], eax ; Address to the function "GetProcAddress" mov [ebp - 1Ch], eax ; Reserved1 -> LoadLibraryA mov [ebp - 20h], eax ; Reserved2 -> user32.dll mov [ebp - 24h], eax ; Reserved3 -> MessageBoxA ; Push "GetProcAddress" to the stack push 00007373h ; "ss\0\0" push 65726464h ; "erdd" push 41636f72h ; "Acor" push 50746547h ; "PteG" mov [ebp - 14h], esp ; pointer to "GetProcAddress" ; Get the address of the kernel32.dll mov eax, [fs:30h] ; PEB mov eax, [eax + 0Ch] ; Ldr mov eax, [eax + 14h] ; InMemoryOrderModuleList mov eax, [eax] ; this program's module mov eax, [eax] ; ntdll module mov eax, [eax + 10h] ; kernel32.dll mov ebx, eax ; Save in ebx ; Get address of PE signature mov eax, [eax + 3Ch] ; e_lfanew add eax, ebx ; Get the address of the Export Table mov eax, [eax + 78h] add eax, ebx ; Get number of exported functions mov ecx, [eax + 14h] mov [ebp - 04h], ecx ; Get the address of the Export Address Table (EAT) mov ecx, [eax + 1Ch] ; RVA of EAT add ecx, ebx mov [ebp - 08h], ecx ; Get the address of the Export Name Table mov ecx, [eax + 20h] ; RVA of ENT add ecx, ebx mov [ebp - 0Ch], ecx ; Get the address of the Export Ordinal Table mov ecx, [eax + 24h] ; RVA of EOT add ecx, ebx mov [ebp - 10h], ecx ; Loop through the export name table xor eax, eax xor ecx, ecx findGetProcAddressPosition: mov esi, [ebp - 14h] mov edi, [ebp - 0Ch] cld mov edi, [edi + eax * 4] ; get RVA of next function name add edi, ebx ; get VA of next function name mov cx, 0Dh ; length of "GetProcAddress" repe cmpsb ; compare "GetProcAddress" with the current function name jz functionFound inc eax cmp eax, [ebp - 04h] ; check if reaching the end of the export name table jne findGetProcAddressPosition functionFound: mov ecx, [ebp - 10h] ; ecx = ordinal table mov edx, [ebp - 08h] ; edx = EAT ; Get address of GetProcAddress ordinal mov ax, [ecx + eax * 2] ; get ordinal mov eax, [edx + eax * 4] ; get RVA add eax, ebx ; get VA jmp invokeGetProcAddress invokeGetProcAddress: mov [ebp - 18h], eax ; save the address of GetProcAddress ; Get address of LoadLibraryA push offset loadLibraryA push ebx ; kernel32.dll base address call eax ; call GetProcAddress test eax, eax ; check if GetProcAddress succeeded jz error_exit mov [ebp - 1Ch], eax ; save the address of LoadLibraryA ; Load user32.dll push offset user32 call dword ptr [ebp - 1Ch] ; call LoadLibraryA test eax, eax ; check if LoadLibraryA succeeded jz error_exit mov [ebp - 20h], eax ; save the address of user32.dll ; Get address of MessageBoxA push offset messageBoxA push dword ptr [ebp - 20h] ; user32.dll base address call dword ptr [ebp - 18h] ; call GetProcAddress test eax, eax ; check if GetProcAddress succeeded jz error_exit mov [ebp - 24h], eax ; save the address of MessageBoxA ; Call MessageBoxA push MB_OK push offset caption push offset msg push 0 call dword ptr [ebp - 24h] ; call MessageBoxA ; Normal exit add esp, 24h + 4h ; 24h for local variables, 4h for the "GetProcAddress" mov esp, ebp pop ebp ret error_exit: main ENDP END main
  • Kiểm tra code:
    image
  • Trong trường hợp code chạy không thành công, mình đã để trống hàm error_exit để nó return giá trị lỗi:
    image

Subtask 3: VEH & WannaFlag

  • Mình được học hỏi kĩ thuật khá hay trong phần subtask này là cách điều hướng luồng bằng các lỗi ngoại lệ và resolve API dynamically. Với VEH thì là ngoại lệ INT_DIVIDE_BY_ZERO còn WannaFlag là ngoại lệ ACCESS_VIOLATION

VEH

  • Một bài khá hay mình được giao wu trong task này. Bài là sự kết hợp giữa Dynamic API Resolution và kĩ thuật điều hướng luồng thực thi bằng Exception mà cụ thể ở đây là EXCEPTION_INT_DIVIDE_BY_ZERO với mã lỗi 0xC0000094. Chương trình là một flag checker:
    image
  • Tuy nhiên nếu kiếm hai string Enter flag: [+] Wrong! thì sẽ không có. Bên cạnh đó chúng ta còn có một đống code rác trong chương trình:
    image
  • main cực xấu:
    image
  • Mình sẽ make code lại từ đầu, tuy nhiên gặp lỗi ở đây:
    image
  • Remake:
    image
  • Khả năng cao là author sẽ sử dụng các byte E9 này để làm rối chương trình, phân tích tiếp:
    image
  • Lại gặp tiếp nên mình sẽ make code lại main và NOP toàn bộ byte E9 này đi, được:
    image
  • Nhưng vẫn đỏ do E9 xuất hiện ở khá nhiều chỗ:
    image
  • Có thể nhận thấy một pattern được lặp lại là byte rác E9 sẽ xuất hiện sau mỗi lần div rax. Trông có vẻ không quan trọng nhưng đây là cách author làm khó reverser vì opcode E9 tương đương với jmp (khiến ida phân tích sai luồng thành jmp vào địa chỉ linh tinh):
    image
  • Cuối cùng sau khi loại bỏ rác, mình sẽ đi vào phân tích luồng của chương trình.

Phase 1: initterm and Handler Adding

  • Khi mình bật debugger lên thì chương trình nhảy tới lỗi ở phần này:
    image
  • Thực chất đây chính là hàm được define sẵn trong initterm:
    image
  • Code này raise lên exception đầu tiên, đây là mã giả của hàm:
__int64 sub_7FF6D4142540() { void (__fastcall *v0)(__int64, __int64 (__fastcall *)()); // rax __int64 v1; // rdx unsigned __int128 v2; // rtt __int64 (__fastcall *v3)(__int64, unsigned __int64, __int64, __int64); // rsi __int64 result; // rax sub_7FF6D41411F0(); dword_7FF6D4145108 = 0; v0 = sub_7FF6D4141000(3239761349i64, 341012667i64); v0(1i64, sub_7FF6D41411A0); dword_7FF6D414510C = 0; *&v2 = 0i64; *(&v2 + 1) = v1; v3 = (v2 / 0); qword_7FF6D41450F8 = v3(4294967286i64, v2 % 0, -474380438i64, 701355107i64); result = (v3)(4294967285i64); qword_7FF6D4145100 = result; dword_7FF6D4145110 = 0; return result; }
  • Tên của các hàm khá ngáo, trong đó có hàm sub_7FF6D41411F0 thực hiện khá nhiều việc resolve, cụ thể như sau:
__int64 sub_7FF6D41411F0() { void (__fastcall *v0)(char *); // r14 __int64 i; // rax unsigned int v2; // edx __int64 j; // rsi __int16 v4; // dx char v5; // di unsigned int v6; // edx __int64 k; // rsi __int64 m; // rsi __int16 v9; // dx char v11; // [rsp+28h] [rbp-40h] char v12[63]; // [rsp+29h] [rbp-3Fh] BYREF v0 = resolveAPI(1102179001, 701355107); v11 = 0; v12[0] = 24; v12[1] = 1; v12[2] = 16; v12[3] = 1; v12[4] = 122; v12[5] = 1; v12[6] = 69; v12[7] = 1; v12[8] = 69; v12[9] = 1; v12[10] = 109; v12[11] = 1; v12[12] = 37; v12[13] = 1; v12[14] = 111; v12[15] = 1; v12[16] = 111; v12[17] = 1; v12[18] = 1; v12[19] = 1; for ( i = 1i64; i != 21; ++i ) { v2 = (31 * v12[i - 1] + ((-32509 * (31 * v12[i - 1] - 31)) >> 16) - 31); v12[i - 1] = (31 * v12[i - 1] - 127 * ((v2 >> 15) + (v2 >> 6)) - 31 + 127) % 0x7Fu; } v0(v12); v11 = 0; qmemcpy(v12, ";lcl}lwlhl|lMl\rlklklll", 22); for ( j = 1i64; j != 23; ++j ) { v4 = v12[j - 1]; v5 = 19 * v4; v6 = (19 * v4 + ((-32509 * (19 * v4 - 2052)) >> 16) - 2052); v12[j - 1] = (v5 - 127 * ((v6 >> 15) + (v6 >> 6)) - 4 + 127) % 0x7Fu; } v0(v12); v11 = 0; v12[0] = 12; qmemcpy(&v12[1], "K{K|K KWK.K@K\tKyKhKhKKK", 23); for ( k = 1i64; k != 25; ++k ) v12[k - 1] = (7 * v12[k - 1] - 127 * (((7 * v12[k - 1] + ((-32509 * (7 * v12[k - 1] - 525)) >> 16) - 525) >> 15) + ((7 * v12[k - 1] + ((-32509 * (7 * v12[k - 1] - 525)) >> 16) - 525) >> 6)) - 13 + 127) % 0x7Fu; v0(v12); v11 = 0; v12[0] = 17; qmemcpy(&v12[1], "F\bF\aFrF\\F+F.F'F\vF\bF@F@FFF", 25); for ( m = 1i64; m != 27; ++m ) { v9 = 18 * v12[m - 1]; v12[m - 1] = ((((33027 * (1260 - v9)) >> 16) >> 15) + (((33027 * (1260 - v9)) >> 16) >> 6) - (v9 + (((((33027 * (1260 - v9)) >> 16) >> 15) + (((33027 * (1260 - v9)) >> 16) >> 6)) << 7)) - 20 + 127) % 0x7Fu; } v0(v12); return 0i64; }
  • Trong hàm, một số thư viện được resolved là:
    • ntdll.dll:
      image
    • user32.dll:
      image
    • crypt32.dll:
      image
    • Advapi32.dll:
      image
  • Bên cạnh đó, hàm còn thực hiện một lần resolve API tại đây:
    image
  • Thực chất chính là resolve lại ntdll_RtlAddVectoredExceptionHandler. Theo doc MSDN, hàm này giúp đăng kí một hàm xử lí ngoại lệ. Tham số truyền vào là (First, Handler), nếu First == 1 thì hàm đó được set ưu tiên hàng đầu. Ở đây, ngoại lệ chính của mình là lỗi chia 0 nên Handler sẽ được gọi mỗi lần chương trình thực thi div rax với rax == 0.

Phase 2: Handler and Obfuscation

  • Đây là hàm Handler mà chương trình sẽ call mỗi khi xảy ra ngoại lệ 0C0000094h:
    image
  • Theo tài liệu, đây sẽ là cấu trúc của một hàm Handler tiêu chuẩn được truyền vào trong ntdll_RtlAddVectoredExceptionHandler:
    image
  • Chính vì thế, mình sẽ set lại type của hàm và tham số:
    • Convert to struct _EXCEPTION_POINTERS:
      image
    • Set type return:
      image
  • Cuối cùng được mã giả đẹp keng như sau:
LONG __stdcall Handler(struct _EXCEPTION_POINTERS *ExceptionInfo) { ExceptionInfo->ContextRecord->Rax = resolveAPI(ExceptionInfo->ContextRecord->R8, ExceptionInfo->ContextRecord->R9); ExceptionInfo->ContextRecord->Rip += 4i64; return -1; }
  • Mình sẽ phân tích hàm này một chút. Được biết hàm này sẽ được call mỗi lần div 0 và lấy dữ liệu từ thanh ghi R8R9 để resolve API nên pattern của một lần resolve sẽ như sau:
    image
  • Instruction NOP kia chính là opcode mình đã patch vào các byte rác E9. Thực chất idea của author về resolve sẽ như sau:
mov r8, _hex1_ mov r9, _hex2_ xor rax, rax div rax `E9`
  • Điều này cũng giải thích lí do tại sao sau khi resolve API thì Handler tăng RIP thêm 4 đơn vị, cốt là để skip qua byte rác E9 <-> jmp kia. Sau đó trả về giá trị -1 chính là:
    image
    Tiếp tục thực thi tại EIP (RIP) để trả luồng về cho chương trình.

Note:

  • Thông thường mình thấy khi xây dựng hàm xử lí ngoại lệ, coder sẽ tăng EIP (RIP) lên 2 đơn vị để nhảy tới instruction tiếp theo nếu muốn chương trình tiếp tục thực thi. Bởi lẽ khi Handler trả về giá trị EXCEPTION_CONTINUE_EXECUTION thì EIP (RIP) lại trỏ vào đúng nơi xảy ra ngoại lệ, từ đó gây ra infinity loop.
  • Nếu EIP (RIP) += 2:
    image
  • Else:
    image
    (vẫn đang chạy như điên :man-facepalming:)

Phase 3: Analyze

  • Khi đã rõ ràng ý tưởng obfuscate và resolve API của chương trình, công việc của chúng ta bây giờ chỉ là men theo từng lần div 0 để phân tích.
  • Lúc đầu khi mình làm thì mình setIP tới từng lần div 0 để phân tích, nhưng sau khi biết tới Appcall thì mình đã có thể thực hiện phân tích nhanh hơn bằng code sau:
start = 0x00007FF636501000 #start of program end = start + 6000 #try to limit for ea in range(start, end): filter = bytes.hex(get_bytes(ea, 3)) if filter == "48f7f0": resolveStart = ea - 17 #try to set true offset where resolving starts data = get_bytes(resolveStart, 17) if bytes.hex(data).startswith("49c7") and bytes.hex(data).endswith("31c0"): r8, r9 = data[:7][-4:], data[7:14][-4:] #get registers value r8, r9 = bytes.hex(r8[::-1]), bytes.hex(r9[::-1]) #bytes to hex r8, r9 = int(r8, 16), int(r9, 16) #hex to int offset = Appcall.resolveAPI(r8, r9).value #to know what function had been resolved print(f"DIV at {hex(ea)}, function resolved at {hex(offset)}: {get_func_name(offset)}") #where div 0 and what function's name
  • Đây là output:
    image
  • Khá tiện để phân tích, sử dụng output này, mình có thể hiểu luồng chương trình, dưới đây là 3 hàm quan trọng trong bài, main, generateSHA256KeyencryptAES:
main
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) { unsigned __int128 v3; // rtt __int64 v4; // rdx unsigned __int64 kernel32_WriteFile; // r14 __int64 i; // rsi __int16 v7; // cx __int64 v8; // rdx unsigned __int128 v9; // rtt __int64 v10; // rdx __int64 v11; // rdx void *v12; // rax __int64 j; // rsi __int64 v14; // rdx __int64 k; // rsi unsigned __int128 v16; // rtt void (__fastcall *kernel32_CloseHandle)(__int64, unsigned __int64, unsigned __int64, __int64); // rsi char v19; // [rsp+30h] [rbp-138h] char v20[15]; // [rsp+31h] [rbp-137h] BYREF _BYTE v21[296]; // [rsp+40h] [rbp-128h] BYREF *&v3 = 0i64; // ntdll_memset *(&v3 + 1) = argv; ((v3 / 0))(v21, 0i64, 256i64, 341012667i64); *&v3 = 0i64; // kernel32_WriteFile *(&v3 + 1) = v4; kernel32_WriteFile = v3 / 0; v19 = 0; v20[0] = 10; v20[1] = 24; qmemcpy(&v20[2], "X7mQl-a\">Q$", 11); for ( i = 1i64; i != 14; ++i ) { v7 = 3 * v20[i - 1]; v20[i - 1] = (-127 * (((((-32509 * (4 * v7 - 432)) >> 16) + 4 * v7 - 432) >> 15) + ((((-32509 * (4 * v7 - 432)) >> 16) + 4 * v7 - 432) >> 6)) + 4 * v7 - 49) % 0x7Fu; } (kernel32_WriteFile)(qword_7FF636505100, v20, 12i64, 0i64, 0i64); *&v9 = 0i64; // kernel32_ReadFile *(&v9 + 1) = v8; ((v9 / 0))(qword_7FF6365050F8, v21, 256i64, 0i64, 0i64); *&v9 = 0i64; // ntdll_strcspn *(&v9 + 1) = v10; v21[((v9 / 0))(v21, &unk_7FF6365043B4, 0xFFFFFFFFD462345Cui64, 0x145370BBi64)] = 0; *&v9 = 0i64; // ntdll_memcmp *(&v9 + 1) = v11; v12 = encryptAES(v21, v9 % 0); if ( ((v9 / 0))(v12, flag_enc, 32i64) ) { v19 = 0; qmemcpy(v20, " 2?FaGX\t[", 9); v20[9] = 22; qmemcpy(&v20[10], "nR", 2); for ( j = 1i64; j != 13; ++j ) v20[j - 1] = (-127 * (((-45 * v20[j - 1] + ((-32509 * (-45 * v20[j - 1] + 3690)) >> 16) + 3690) >> 15) + ((-45 * v20[j - 1] + ((-32509 * (-45 * v20[j - 1] + 3690)) >> 16) + 3690) >> 6)) - 45 * v20[j - 1] - 23) % 0x7Fu; (kernel32_WriteFile)(qword_7FF636505100, v20, 12i64, 0i64, 0i64); } else { v19 = 0; qmemcpy(v20, "P*G", 3); v20[3] = 28; v20[4] = 61; v20[5] = 117; v20[6] = 40; v20[7] = 40; v20[8] = 35; v20[9] = 44; v20[10] = 31; strcpy(&v20[11], "W"); v20[13] = 45; for ( k = 1i64; k != 15; ++k ) v20[k - 1] = (-127 * (((28 * v20[k - 1] + ((-32509 * (28 * v20[k - 1] - 1260)) >> 16) - 1260) >> 15) + ((28 * v20[k - 1] + ((-32509 * (28 * v20[k - 1] - 1260)) >> 16) - 1260) >> 6)) + 28 * v20[k - 1] - 109) % 0x7Fu; (kernel32_WriteFile)(qword_7FF636505100, v20, 14i64, 0i64, 0i64); } *&v16 = 0i64; // kernel32_CloseHandle *(&v16 + 1) = v14; kernel32_CloseHandle = (v16 / 0); kernel32_CloseHandle(qword_7FF6365050F8, v16 % 0, 0xFFFFFFFFFABA0065ui64, 0x29CDD463i64); (kernel32_CloseHandle)(qword_7FF636505100); return 0; }
generateSHA256Key
void *__fastcall generateSHA256Key(__int64 a1, __int64 a2) { unsigned __int128 v2; // rtt __int64 v3; // rdx void (__fastcall *ntdll_RtlCopyMemory)(char *, BYTE *, __int64, __int64); // rax __int64 i; // r8 __int64 v6; // rdx unsigned __int64 v7; // rdx unsigned __int128 v8; // rtt unsigned __int64 advapi32_CryptReleaseContext; // r14 unsigned __int64 v10; // rdx void (__fastcall *advapi32_CryptDestroyHash)(HCRYPTHASH); // rdi unsigned __int64 advapi32_CryptAcquireContextW; // rax unsigned __int64 v13; // rdx void *v14; // rbx unsigned __int64 advapi32_CryptCreateHash; // rax unsigned __int64 v16; // rdx unsigned __int128 v17; // rtt __int64 (__fastcall *kernel32_lstrlen)(char *, unsigned __int64, unsigned __int64, __int64); // rax unsigned __int64 v19; // rdx unsigned __int128 v20; // rtt unsigned int dwDataLen; // ebx unsigned __int64 advapi32_CryptHashData; // rax unsigned __int64 v23; // rdx unsigned __int128 v24; // rtt unsigned __int128 v25; // rtt unsigned __int64 advapi32_CryptGetHashParam; // rsi void *v27; // rax __int64 v28; // rcx BYTE rickRoll[46]; // [rsp+30h] [rbp-C8h] BYREF HCRYPTHASH hHash; // [rsp+60h] [rbp-98h] BYREF int v32; // [rsp+6Ch] [rbp-8Ch] BYREF char pbData[136]; // [rsp+70h] [rbp-88h] BYREF *&v2 = 0i64; // ntdll_memset *(&v2 + 1) = a2; ((v2 / 0))(pbData, 0i64, 0x64i64, 0x145370BBi64); *&v2 = 0i64; // ntdll_RtlCopyMemory *(&v2 + 1) = v3; ntdll_RtlCopyMemory = (v2 / 0); rickRoll[0] = 0; rickRoll[1] = 64; rickRoll[2] = 85; rickRoll[3] = 85; rickRoll[4] = 78; rickRoll[5] = 115; rickRoll[6] = 47; rickRoll[7] = 123; rickRoll[8] = 123; rickRoll[9] = 122; rickRoll[10] = 122; rickRoll[11] = 122; rickRoll[12] = 26; rickRoll[13] = 62; rickRoll[14] = 108; rickRoll[15] = 55; rickRoll[16] = 85; rickRoll[17] = 55; rickRoll[18] = 117; rickRoll[19] = 27; rickRoll[20] = 26; rickRoll[21] = 87; rickRoll[22] = 108; rickRoll[23] = 41; rickRoll[24] = 123; rickRoll[25] = 122; rickRoll[26] = 20; rickRoll[27] = 85; rickRoll[28] = 87; rickRoll[29] = 64; rickRoll[30] = 24; rickRoll[31] = 25; rickRoll[32] = 84; rickRoll[33] = 57; rickRoll[34] = 119; rickRoll[35] = 122; rickRoll[36] = 100; rickRoll[37] = 122; rickRoll[38] = 77; rickRoll[39] = 66; rickRoll[40] = 94; rickRoll[41] = 36; rickRoll[42] = 87; rickRoll[43] = 119; rickRoll[44] = 9; rickRoll[45] = 9; for ( i = 1i64; i != 46; ++i ) rickRoll[i] = (-127 * (((55 * rickRoll[i] + ((-32509 * (55 * rickRoll[i] - 495)) >> 16) - 495) >> 15) + ((55 * rickRoll[i] + ((-32509 * (55 * rickRoll[i] - 495)) >> 16) - 495) >> 6)) + 55 * rickRoll[i] - 112) % 0x7Fu; ntdll_RtlCopyMemory(pbData, &rickRoll[1], 44i64, 341012667i64); *rickRoll = 0i64; hHash = 0i64; *&v8 = 0i64; // advapi32_CryptReleaseContext *(&v8 + 1) = v6; v7 = v8 % 0; advapi32_CryptReleaseContext = v8 / 0; *&v8 = 0i64; // advapi32_CryptDestroyHash *(&v8 + 1) = v7; v10 = v8 % 0; advapi32_CryptDestroyHash = (v8 / 0); *&v8 = 0i64; // advapi32_CryptAcquireContextW *(&v8 + 1) = v10; advapi32_CryptAcquireContextW = v8 / 0; v13 = v8 % 0; if ( !advapi32_CryptAcquireContextW || (v14 = 0i64, (advapi32_CryptAcquireContextW)(rickRoll, 0i64, 0i64, 24i64, -268435456)) ) { *&v17 = 0i64; // advapi32_CryptCreateHash *(&v17 + 1) = v13; advapi32_CryptCreateHash = v17 / 0; v16 = v17 % 0; if ( !advapi32_CryptCreateHash || (advapi32_CryptCreateHash)(*rickRoll, 0x800Ci64, 0i64, 0i64, &hHash) )// 0x800C means SHA256 { *&v20 = 0i64; // kernel32_lstrlen *(&v20 + 1) = v16; kernel32_lstrlen = (v20 / 0); v19 = v20 % 0; if ( kernel32_lstrlen ) dwDataLen = kernel32_lstrlen(pbData, v19, 0xFFFFFFFF992ED028ui64, 0x29CDD463i64); else dwDataLen = 0; *&v24 = 0i64; // advapi32_CryptHashData *(&v24 + 1) = v19; advapi32_CryptHashData = v24 / 0; v23 = v24 % 0; if ( !advapi32_CryptHashData || (advapi32_CryptHashData)(hHash, pbData, dwDataLen, 0i64) ) { *&v25 = 0i64; // advapi32_CryptGetHashParam *(&v25 + 1) = v23; advapi32_CryptGetHashParam = v25 / 0; v27 = operator new(0x21ui64); v14 = v27; v32 = 32; if ( advapi32_CryptGetHashParam && !(advapi32_CryptGetHashParam)(hHash, 2i64, v27, &v32, 0) ) v14 = 0i64; advapi32_CryptDestroyHash(hHash); v28 = *rickRoll; goto LABEL_18; } advapi32_CryptDestroyHash(hHash); } v28 = *rickRoll; v14 = 0i64; LABEL_18: (advapi32_CryptReleaseContext)(v28, 0i64); } return v14; }
encryptAES
void *__fastcall encryptAES(BYTE *pbData, __int64 a2) { void *SHA256Key; // rax __int64 v4; // rdx void *v5; // rbp unsigned __int64 v6; // rdx unsigned __int128 v7; // rtt unsigned int (*v8)(void); // r13 unsigned __int64 v9; // rdx unsigned __int64 advapi32_CryptReleaseContext; // r15 unsigned __int64 v11; // rdx void (__fastcall *advapi32_CryptDestroyKey)(__int64); // r14 unsigned __int64 v13; // rax unsigned int (__fastcall *advapi32_CryptAcquireContextA)(__int64 *, _QWORD, BYTE *, __int64, int); // rdi __int64 i; // rax __int16 v16; // cx char v17; // si unsigned int v18; // ecx __int64 v19; // rdx __int64 j; // rax __int16 v21; // bx unsigned int v22; // ecx void *v23; // rsi unsigned __int64 v24; // rdx unsigned __int128 v25; // rtt void (__fastcall *ntdll_RtlCopyMemory)(BYTE *, void *, __int64, __int64); // r13 DWORD bufferLen; // edi __int64 v28; // rdx unsigned __int64 advapi32_CryptImportKey; // rax unsigned __int64 v30; // rdx unsigned __int64 advapi32_CryptSetKeyParam; // rax unsigned __int128 v32; // rtt void (__fastcall *v33)(__int64, __int64, __int128 *, _QWORD); // rsi __int64 v34; // rdx unsigned __int64 advapi32_CryptEncrypt; // rax unsigned __int128 v36; // rtt __int64 v37; // rcx BYTE Lstring_Microsoft[109]; // [rsp+38h] [rbp-E0h] BYREF __int64 hKey; // [rsp+A8h] [rbp-70h] BYREF __int64 phProv; // [rsp+B0h] [rbp-68h] BYREF DWORD pdwDataLen; // [rsp+B8h] [rbp-60h] BYREF int mode; // [rsp+BCh] [rbp-5Ch] BYREF __int128 v44[5]; // [rsp+C0h] [rbp-58h] BYREF SHA256Key = generateSHA256Key(pbData, a2); if ( !SHA256Key ) return 0i64; v5 = SHA256Key; phProv = 0i64; hKey = 0i64; mode = 1; *&v7 = 0i64; // kernel32_GetLastError *(&v7 + 1) = v4; v6 = v7 % 0; v8 = (v7 / 0); *&v7 = 0i64; // advapi32_CryptReleaseContext *(&v7 + 1) = v6; v9 = v7 % 0; advapi32_CryptReleaseContext = v7 / 0; *&v7 = 0i64; // advapi32_CryptDestroyKey *(&v7 + 1) = v9; v11 = v7 % 0; advapi32_CryptDestroyKey = (v7 / 0); *&v7 = 0i64; // advapi32_CryptAcquireContextA *(&v7 + 1) = v11; v13 = v7 / 0; if ( !v13 ) return 0i64; advapi32_CryptAcquireContextA = v13; Lstring_Microsoft[0] = 0; Lstring_Microsoft[1] = 66; Lstring_Microsoft[2] = 22; Lstring_Microsoft[3] = 104; Lstring_Microsoft[4] = 26; Lstring_Microsoft[5] = 67; Lstring_Microsoft[6] = 97; Lstring_Microsoft[7] = 67; Lstring_Microsoft[8] = 63; Lstring_Microsoft[9] = 41; Lstring_Microsoft[10] = 46; Lstring_Microsoft[11] = 6; Lstring_Microsoft[12] = 123; Lstring_Microsoft[13] = 78; Lstring_Microsoft[14] = 89; Lstring_Microsoft[15] = 123; Lstring_Microsoft[16] = 104; Lstring_Microsoft[17] = 119; Lstring_Microsoft[18] = 48; Lstring_Microsoft[19] = 46; Lstring_Microsoft[20] = 40; Lstring_Microsoft[21] = 111; Lstring_Microsoft[22] = 103; Lstring_Microsoft[23] = 46; Lstring_Microsoft[24] = 89; Lstring_Microsoft[25] = 123; Lstring_Microsoft[26] = 48; Lstring_Microsoft[27] = 46; Lstring_Microsoft[28] = 0x67; Lstring_Microsoft[29] = 6; Lstring_Microsoft[30] = 111; Lstring_Microsoft[31] = 46; Lstring_Microsoft[32] = 118; Lstring_Microsoft[33] = 26; Lstring_Microsoft[34] = 15; Lstring_Microsoft[35] = 11; Lstring_Microsoft[36] = 41; Lstring_Microsoft[37] = 67; Lstring_Microsoft[38] = 7; Lstring_Microsoft[39] = 26; Lstring_Microsoft[40] = 89; Lstring_Microsoft[41] = 11; Lstring_Microsoft[42] = 78; Lstring_Microsoft[43] = 22; Lstring_Microsoft[44] = 104; Lstring_Microsoft[45] = 46; Lstring_Microsoft[46] = 25; Lstring_Microsoft[47] = 26; Lstring_Microsoft[48] = 67; Lstring_Microsoft[49] = 56; Lstring_Microsoft[50] = 22; Lstring_Microsoft[51] = 48; Lstring_Microsoft[52] = 119; Lstring_Microsoft[53] = 26; Lstring_Microsoft[54] = 60; for ( i = 1i64; i != 55; ++i ) { v16 = Lstring_Microsoft[i]; v17 = 34 * v16; v18 = (34 * v16 + ((-32509 * (34 * v16 - 2040)) >> 16) - 2040); Lstring_Microsoft[i] = (v17 - 127 * ((v18 >> 15) + (v18 >> 6)) - 121) % 0x7Fu; } if ( advapi32_CryptAcquireContextA(&phProv, 0i64, &Lstring_Microsoft[1], 24i64, 0) ) goto LABEL_10; if ( v8() != 0x80090016 ) goto LABEL_10; Lstring_Microsoft[0] = 0; Lstring_Microsoft[1] = 40; Lstring_Microsoft[2] = 13; Lstring_Microsoft[3] = 96; Lstring_Microsoft[4] = 13; Lstring_Microsoft[5] = 84; Lstring_Microsoft[6] = 13; Lstring_Microsoft[7] = 114; Lstring_Microsoft[8] = 13; Lstring_Microsoft[9] = 108; Lstring_Microsoft[10] = 13; Lstring_Microsoft[11] = 116; Lstring_Microsoft[12] = 13; Lstring_Microsoft[13] = 108; Lstring_Microsoft[14] = 13; Lstring_Microsoft[15] = 90; Lstring_Microsoft[16] = 13; Lstring_Microsoft[17] = 118; Lstring_Microsoft[18] = 13; Lstring_Microsoft[19] = 77; Lstring_Microsoft[20] = 13; Lstring_Microsoft[21] = 24; Lstring_Microsoft[22] = 13; Lstring_Microsoft[23] = 106; Lstring_Microsoft[24] = 13; Lstring_Microsoft[25] = 94; Lstring_Microsoft[26] = 13; Lstring_Microsoft[27] = 80; Lstring_Microsoft[28] = 13; Lstring_Microsoft[29] = 106; Lstring_Microsoft[30] = 13; Lstring_Microsoft[31] = 84; Lstring_Microsoft[32] = 13; Lstring_Microsoft[33] = 88; Lstring_Microsoft[34] = 13; Lstring_Microsoft[35] = 86; Lstring_Microsoft[36] = 13; Lstring_Microsoft[37] = 77; Lstring_Microsoft[38] = 13; Lstring_Microsoft[39] = 50; Lstring_Microsoft[40] = 13; Lstring_Microsoft[41] = 52; Lstring_Microsoft[42] = 13; Lstring_Microsoft[43] = 16; Lstring_Microsoft[44] = 13; Lstring_Microsoft[45] = 77; Lstring_Microsoft[46] = 13; Lstring_Microsoft[47] = 80; Lstring_Microsoft[48] = 13; Lstring_Microsoft[49] = 106; Lstring_Microsoft[50] = 13; Lstring_Microsoft[51] = 86; Lstring_Microsoft[52] = 13; Lstring_Microsoft[53] = 77; Lstring_Microsoft[54] = 13; Lstring_Microsoft[55] = 16; Lstring_Microsoft[56] = 13; Lstring_Microsoft[57] = 24; Lstring_Microsoft[58] = 13; Lstring_Microsoft[59] = 52; Lstring_Microsoft[60] = 13; Lstring_Microsoft[61] = 77; Lstring_Microsoft[62] = 13; Lstring_Microsoft[63] = 20; Lstring_Microsoft[64] = 13; Lstring_Microsoft[65] = 114; Lstring_Microsoft[66] = 13; Lstring_Microsoft[67] = 1; Lstring_Microsoft[68] = 13; Lstring_Microsoft[69] = 110; Lstring_Microsoft[70] = 13; Lstring_Microsoft[71] = 118; Lstring_Microsoft[72] = 13; Lstring_Microsoft[73] = 108; Lstring_Microsoft[74] = 13; Lstring_Microsoft[75] = 92; Lstring_Microsoft[76] = 13; Lstring_Microsoft[77] = 114; Lstring_Microsoft[78] = 13; Lstring_Microsoft[79] = 80; Lstring_Microsoft[80] = 13; Lstring_Microsoft[81] = 110; Lstring_Microsoft[82] = 13; Lstring_Microsoft[83] = 94; Lstring_Microsoft[84] = 13; Lstring_Microsoft[85] = 96; Lstring_Microsoft[86] = 13; Lstring_Microsoft[87] = 84; Lstring_Microsoft[88] = 13; Lstring_Microsoft[89] = 77; Lstring_Microsoft[90] = 13; Lstring_Microsoft[91] = 46; Lstring_Microsoft[92] = 13; Lstring_Microsoft[93] = 114; Lstring_Microsoft[94] = 13; Lstring_Microsoft[95] = 108; Lstring_Microsoft[96] = 13; Lstring_Microsoft[97] = 122; Lstring_Microsoft[98] = 13; Lstring_Microsoft[99] = 96; Lstring_Microsoft[100] = 13; Lstring_Microsoft[101] = 86; Lstring_Microsoft[102] = 13; Lstring_Microsoft[103] = 88; Lstring_Microsoft[104] = 13; Lstring_Microsoft[105] = 114; Lstring_Microsoft[106] = 13; Lstring_Microsoft[107] = 13; Lstring_Microsoft[108] = 13; for ( j = 1i64; j != 109; Lstring_Microsoft[j++] = (v21 - 127 * ((v22 >> 15) + (v22 >> 6)) - 78) % 0x7Fu ) { v21 = -63 * Lstring_Microsoft[j]; v22 = (v21 + ((-32509 * (v21 + 819)) >> 16) + 819); } v23 = 0i64; if ( advapi32_CryptAcquireContextA(&phProv, 0i64, &Lstring_Microsoft[1], 24i64, 8) ) { LABEL_10: memset(&Lstring_Microsoft[12], 0, 32); *Lstring_Microsoft = 520; *&Lstring_Microsoft[4] = 0x2000006610i64; *&v25 = 0i64; // ntdll_RtlCopyMemory *(&v25 + 1) = v19; v24 = v25 % 0; ntdll_RtlCopyMemory = (v25 / 0); *&v25 = 0i64; // kernel32_lstrlen *(&v25 + 1) = v24; ntdll_RtlCopyMemory(&Lstring_Microsoft[12], v5, 0x20i64, 0x29CDD463i64); bufferLen = ((v25 / 0))(pbData); pdwDataLen = bufferLen; *&v25 = 0i64; // advapi32_CryptImportKey *(&v25 + 1) = v28; advapi32_CryptImportKey = v25 / 0; v30 = v25 % 0; if ( !advapi32_CryptImportKey || (advapi32_CryptImportKey)(phProv, Lstring_Microsoft, 44i64, 0i64, 0, &hKey) ) { *&v32 = 0i64; // advapi32_CryptSetKeyParam *(&v32 + 1) = v30; advapi32_CryptSetKeyParam = v32 / 0; v33 = advapi32_CryptSetKeyParam; if ( !advapi32_CryptSetKeyParam || (advapi32_CryptSetKeyParam)(hKey, 4i64, &mode, 0i64) ) { v44[0] = IV; v33(hKey, 1i64, v44, 0i64); *&v36 = 0i64; // advapi32_CryptEncrypt *(&v36 + 1) = v34; advapi32_CryptEncrypt = v36 / 0; if ( advapi32_CryptEncrypt ) { v23 = 0i64; if ( !(advapi32_CryptEncrypt)(hKey, 0i64, 1i64, 0i64, pbData, &pdwDataLen, 1024) ) { LABEL_18: advapi32_CryptDestroyKey(hKey); v37 = phProv; LABEL_22: (advapi32_CryptReleaseContext)(v37, 0i64); return v23; } bufferLen = pdwDataLen; } v23 = operator new(bufferLen + 1); (ntdll_RtlCopyMemory)(v23, pbData, bufferLen); goto LABEL_18; } advapi32_CryptDestroyKey(hKey); } v37 = phProv; v23 = 0i64; goto LABEL_22; } return v23; }
  • Luồng chính sẽ là: chương trình nhận input, encrypt input đó với AES-CBC (mode = 1), trong đó:
key = generateSHA256Key(rickRoll) IV = "0102030405060708090A0B0C0D0E0F10"

image
image

  • Phần này đọc hàm mình để ở trên sẽ dễ hiểu hơn vì mình đã add comment giải thích rồi. Khi biết được key, IVmode rồi, mình cần tìm bản mã. Để ý trong main có gọi tới ntdll_memcmp, từ đó mình tìm được flag_enc, chuỗi mà chương trình sẽ sử dụng để so sánh với encryptAES(input):
    image
    image
  • Đem tất cả đi decrypt, mình được:
    image

KMACTF{3Xc3pTI0n_3v3rYwh3R3@_@}

image

WannaFlag

  • Đây cũng là một bài khá hay mà mình được thử sức trong task 3 này. Đề bài cho một file thực thi .exe và một file flag bị mã hóa, nếu chạy thử thì:
    image
  • Không có gì xảy ra, điều này là do chương trình không tìm thấy file flag chuẩn. Ném vào IDA, đây là hàm main:
int __cdecl main(int argc, const char **argv, const char **envp) { char *v3; // esi HANDLE FileA; // esi DWORD v5; // esi DWORD v6; // edi DWORD v7; // eax DWORD v8; // edx DWORD v9; // esi char *v10; // edi DWORD v11; // ecx int result; // eax DWORD NumberOfBytesRead; // [esp+10h] [ebp-808h] BYREF char v14[192]; // [esp+18h] [ebp-800h] BYREF int v15; // [esp+D8h] [ebp-740h] BYREF int v16; // [esp+DCh] [ebp-73Ch] int v17; // [esp+E0h] [ebp-738h] int v18; // [esp+E4h] [ebp-734h] int v19; // [esp+E8h] [ebp-730h] int v20; // [esp+ECh] [ebp-72Ch] int v21; // [esp+F0h] [ebp-728h] int i; // [esp+F4h] [ebp-724h] CHAR Filename[264]; // [esp+F8h] [ebp-720h] BYREF char Buffer[1024]; // [esp+308h] [ebp-510h] BYREF CHAR FileName[268]; // [esp+708h] [ebp-110h] BYREF memset(Buffer, 0, sizeof(Buffer)); GetModuleFileNameA(0, Filename, 0x104u); if ( IsDebuggerPresent() ) goto LABEL_24; v3 = strrchr(Filename, 92); if ( IsDebuggerPresent() ) goto LABEL_24; if ( v3 ) v3[1] = 0; sub_401360(FileName, 0x104u, "%s%s", Filename, "fl4g_f0r_y0u.txt"); FileA = CreateFileA(FileName, 0x80000000, 0, 0, 3u, 0, 0); if ( IsDebuggerPresent() ) goto LABEL_24; if ( FileA != (HANDLE)-1 && ReadFile(FileA, Buffer, 0x400u, &NumberOfBytesRead, 0) ) { CloseHandle(FileA); v5 = 16 - (NumberOfBytesRead & 0xF); if ( !IsDebuggerPresent() ) { v6 = 0; if ( v5 ) { v6 = v5; memset(&Buffer[NumberOfBytesRead], v5, v5); } v7 = v6 + NumberOfBytesRead; NumberOfBytesRead = v7; if ( v7 >= 0x400 ) { __report_rangecheckfailure(); __debugbreak(); } Buffer[v7] = 0; v15 = 0xA9A51F0D; v16 = 0x80CF669F; v17 = 0x63E7175C; v18 = 0xBDCD557A; v19 = 0xE696F133; v20 = 0x4FCC2511; v21 = 0xC21903BA; i = 0xD3FE4530; sub_401000(v14, &v15); if ( !IsDebuggerPresent() ) { v8 = NumberOfBytesRead; v9 = NumberOfBytesRead >> 4; if ( NumberOfBytesRead >> 4 ) { v10 = Buffer; do { sub_401140(v10, v14); v10 += 16; --v9; } while ( v9 ); v8 = NumberOfBytesRead; } v11 = 0; v15 = 0x3D5F678C; v16 = 0x31EF3EF1; v17 = 0xD752DEF0; v18 = 0x944DACC8; v19 = 0xE0B79816; v20 = 0xD2AE632; v21 = 0x9BCC5374; for ( i = 0x3A8A5B06; v11 < v8; ++v11 ) Buffer[v11] ^= *((_BYTE *)&v15 + (int)v11 % 32); if ( !IsDebuggerPresent() && !IsDebuggerPresent() && !IsDebuggerPresent() ) { result = (int)Buffer; MEMORY[0] = 0; return result; } } } LABEL_24: ExitProcess(0xFFFFFFFF); } return 1; }
  • Code khá đẹp nhưng bị anti-debug bởi IsDebuggerPresent khá nhiều. Các phần call IsDebuggerPresent đều có dạng:
    image
  • Tới đây mình cũng biết được flag sẽ nằm trong file fl4g_f0r_y0u.txt nên chương trình ban đầu không chạy cũng là phải. Mình sẽ patch để loại bỏ hết đống IsDebuggerPresent này, cuối cùng có main:
int __cdecl main(int argc, const char **argv, const char **envp) { char *v3; // esi HANDLE FileA; // esi DWORD v5; // edi DWORD input_len; // eax DWORD v7; // edx DWORD blockNum; // esi char *currentBlock; // edi DWORD idx; // ecx int result; // eax DWORD NumberOfBytesRead; // [esp+10h] [ebp-808h] BYREF char v13[192]; // [esp+18h] [ebp-800h] BYREF DWORD v14[8]; // [esp+D8h] [ebp-740h] BYREF CHAR Filename[264]; // [esp+F8h] [ebp-720h] BYREF char Buffer[1024]; // [esp+308h] [ebp-510h] BYREF CHAR FileName[268]; // [esp+708h] [ebp-110h] BYREF memset(Buffer, 0, sizeof(Buffer)); GetModuleFileNameA(0, Filename, 0x104u); v3 = strrchr(Filename, 92); if ( v3 ) v3[1] = 0; sub_401360(FileName, 0x104u, "%s%s", Filename, "fl4g_f0r_y0u.txt"); FileA = CreateFileA(FileName, 0x80000000, 0, 0, 3u, 0, 0); if ( FileA == (HANDLE)-1 || !ReadFile(FileA, Buffer, 0x400u, &NumberOfBytesRead, 0) ) return 1; CloseHandle(FileA); v5 = 0; if ( 16 != (NumberOfBytesRead & 0xF) ) { v5 = 16 - (NumberOfBytesRead & 0xF); memset(&Buffer[NumberOfBytesRead], v5, v5); // padding PKCS7 } input_len = v5 + NumberOfBytesRead; NumberOfBytesRead = input_len; if ( input_len >= 0x400 ) { __report_rangecheckfailure(); __debugbreak(); } Buffer[input_len] = 0; v14[0] = 0xA9A51F0D; v14[1] = 0x80CF669F; v14[2] = 0x63E7175C; v14[3] = 0xBDCD557A; v14[4] = 0xE696F133; v14[5] = 0x4FCC2511; v14[6] = 0xC21903BA; v14[7] = 0xD3FE4530; sub_401000(v13, v14); v7 = NumberOfBytesRead; blockNum = NumberOfBytesRead >> 4; if ( NumberOfBytesRead >> 4 ) { currentBlock = Buffer; do { sub_401140(currentBlock, v13); currentBlock += 16; --blockNum; } while ( blockNum ); v7 = NumberOfBytesRead; } idx = 0; v14[0] = 0x3D5F678C; v14[1] = 0x31EF3EF1; v14[2] = 0xD752DEF0; v14[3] = 0x944DACC8; v14[4] = 0xE0B79816; v14[5] = 0xD2AE632; v14[6] = 0x9BCC5374; for ( v14[7] = 0x3A8A5B06; idx < v7; ++idx ) Buffer[idx] ^= *((_BYTE *)v14 + (int)idx % 32); result = (int)Buffer; MEMORY[0] = 0; return result; }
  • Có thể thấy một đoạn khá sú:
    image
  • Chương trình đã cố tình truy cập vào address 0, điều này raise EXCEPTION_ACCESS_VIOLATION với mã lỗi 0x0C0000005.

Handler

  • Hàm Handler được khai báo khá rõ trong bảng function và được setup trong TlsCallBack:
    image
  • Đây là hàm xử lí ngoại lệ của chương trình:
LONG __stdcall Handler(struct _EXCEPTION_POINTERS *ExceptionInfo) { PCONTEXT ContextRecord; // ecx PCONTEXT *p_ContextRecord; // edi DWORD Edx; // eax unsigned int Ebx; // esi unsigned int v6; // eax DWORD Eax; // edx DWORD *p_Eax; // ebx unsigned int v9; // ecx DWORD v10; // ebx struct _EXCEPTION_POINTERS *ExceptionInfoa; // [esp+Ch] [ebp+8h] if ( ExceptionInfo->ExceptionRecord->ExceptionCode == -1073741819 ) { ContextRecord = ExceptionInfo->ContextRecord; p_ContextRecord = &ExceptionInfo->ContextRecord; Edx = ContextRecord->Edx; Ebx = ContextRecord->Ebx; switch ( Edx ) { case 0u: *(_BYTE *)(ContextRecord->Eax + Ebx) ^= 0xDu; (*p_ContextRecord)->Eip += 3; return -1; case 1u: *(_BYTE *)(ContextRecord->Eax + Ebx) += 37; (*p_ContextRecord)->Eip += 4; return -1; case 2u: *(_BYTE *)(ContextRecord->Eax + Ebx) -= 37; (*p_ContextRecord)->Eip += 5; return -1; case 3u: *(_BYTE *)(ContextRecord->Eax + Ebx) ^= 0x25u; (*p_ContextRecord)->Eip += 3; return -1; case 4u: *(_BYTE *)(ContextRecord->Eax + Ebx) += 13; (*p_ContextRecord)->Eip += 4; return -1; case 5u: *(_BYTE *)(ContextRecord->Eax + Ebx) -= 13; (*p_ContextRecord)->Eip += 5; return -1; } if ( Edx != 6 ) return -1; v6 = 0; if ( !Ebx ) goto LABEL_30; if ( Ebx < 8 ) goto LABEL_29; Eax = ContextRecord->Eax; ExceptionInfoa = (struct _EXCEPTION_POINTERS *)(Eax + Ebx - 1); p_Eax = &ContextRecord->Eax; if ( Eax <= (unsigned int)p_ContextRecord && ExceptionInfoa >= (struct _EXCEPTION_POINTERS *)p_ContextRecord ) goto LABEL_29; if ( Eax <= (unsigned int)p_Eax && ExceptionInfoa >= (struct _EXCEPTION_POINTERS *)p_Eax ) goto LABEL_29; if ( Ebx >= 0x40 ) { v9 = ContextRecord->Ebx & 0x3F; do { *(__m128i *)(Eax + v6) = _mm_andnot_si128(*(__m128i *)(Eax + v6), (__m128i)xmmword_407390); *(__m128i *)(Eax + v6 + 16) = _mm_andnot_si128(*(__m128i *)(Eax + v6 + 16), (__m128i)xmmword_407390); *(__m128i *)(Eax + v6 + 32) = _mm_andnot_si128(*(__m128i *)(Eax + v6 + 32), (__m128i)xmmword_407390); *(__m128i *)(Eax + v6 + 48) = _mm_andnot_si128(*(__m128i *)(Eax + v6 + 48), (__m128i)xmmword_407390); v6 += 64; } while ( v6 < Ebx - v9 ); if ( v9 < 8 ) { LABEL_28: while ( v6 < Ebx ) { LABEL_29: *(_BYTE *)((*p_ContextRecord)->Eax + v6) = ~*(_BYTE *)((*p_ContextRecord)->Eax + v6); ++v6; } LABEL_30: (*p_ContextRecord)->Eip += 4; return -1; } ContextRecord = *p_ContextRecord; } v10 = ContextRecord->Eax; do { *(_QWORD *)(v10 + v6) = _mm_andnot_si128(_mm_loadl_epi64((const __m128i *)(v10 + v6)), (__m128i)xmmword_407390).m128i_u64[0]; v6 += 8; } while ( v6 < Ebx - (Ebx & 7) ); goto LABEL_28; } return 0; }
  • Có thể thấy hàm thực hiện khá nhiều phép tính toán phức tạp, tuy nhiên sau khi debug và phân tích nhiều lần, mình đã mô phỏng lại được hành vi của hàm:
def Handler(plaintext_data, operations): data = bytearray(plaintext_data) for ebx, edx in operations: if edx == 0: # XOR with 0x0D data[ebx] ^= 0x0D elif edx == 1: # Add 0x25 data[ebx] = (data[ebx] + 0x25) & 0xFF elif edx == 2: # Subtract 0x25 data[ebx] = (data[ebx] - 0x25) & 0xFF elif edx == 3: # XOR with 0x25 data[ebx] ^= 0x25 elif edx == 4: # Add 0x0D data[ebx] = (data[ebx] + 0x0D) & 0xFF elif edx == 5: # Subtract 0x0D data[ebx] = (data[ebx] - 0x0D) & 0xFF elif edx == 6: # NOT operation for i in range(ebx): data[i] = ~data[i] & 0xFF return bytes(data)
  • Cụ thể, hàm sẽ lấy giá trị từ hai thanh ghi EBXEDX, trong đó, EBX được sử dụng làm chỉ số, EDX được sử dụng như điều kiện switch case để encrypt data tại EAX. Ví dụ một pattern:
    image
  • Trong chương trình có tổng cộng 1024 pattern như này, mình có hai cách để extract data, một là sử dụng idapython, hai là sử dụng capstone (đây là một tool mới lần đầu tiên mình dùng).
  • Với idapython, mình đơn giản làm như sau:
start = 0x0040184B # offset đầu tiên sau khi lea eax, [Buffer] end = start + 20000 cnt = 0 operations = [] for ea in range(start, end): check = get_wide_word(ea) if hex(check) == "0x989": # filter cnt += 1 data = get_bytes(ea-12, 12) ebx, edx = data[1], data[-1] print(f"EBX: {hex(ebx)}, EDX: {hex(edx)} at offset {hex(ea)}") operations.append((hex(ebx), hex(edx))) print(operations) print(f"Found {cnt}")
  • Với capstone, do mình chưa biết cách extract shellcode từ file .exe nên tạm thời copy paste đống shellcode lấy từ IDA:
from capstone import * # CODE taken from shellcode of patterns with open("CODE.bin", "r") as f: CODE = f.read().strip() CODE = bytes.fromhex(CODE) md = Cs(CS_ARCH_X86, CS_MODE_32) ebxs, edxs = [], [] for i in md.disasm(CODE, 0x0): if i.mnemonic == 'mov' and i.op_str.startswith("ebx"): ebx = i.op_str.split(",")[1].strip() ebxs.append(int(ebx, 16)) elif i.mnemonic == 'add' and i.op_str.startswith("edx"): edx = i.op_str.split(",")[1].strip() edxs.append(int(edx, 16)) assert len(ebxs) == len(edxs) operations = list(zip(ebxs, edxs)) print(operations)
  • Với file CODE.bin là file mình lưu shellcode. Về hành vi của chall, khi cố ý truy cập vào mem không được cấp quyền thì chương trình sẽ nhảy vào hàm Handler, từ đó biến đổi input của chúng ta theo từng trường hợp (tổng cộng trải qua 1024 lần biến đổi).
  • Nếu muốn đọc trực tiếp từ file .exe thì sử dụng thêm thư viện pefile:
import pefile from capstone import * from capstone.x86 import * exe = pefile.PE("WannaFlag.exe") shellcode = exe.sections[0].get_data() filter = "BB1C000000" filter = bytes.fromhex(filter) shellcode = shellcode[shellcode.find(filter):] md = Cs(CS_ARCH_X86, CS_MODE_32) md.detail = True ebxs, edxs = [], [] for i in md.disasm(shellcode, 0x0): if i.mnemonic == 'mov' and i.operands[0].reg == X86_REG_EBX and i.operands[1].type == X86_OP_IMM and i.operands[1].imm <= 0x40: ebx = i.op_str.split(",")[1].strip() ebxs.append(int(ebx, 16)) if i.mnemonic == 'add' and i.operands[0].reg == X86_REG_EDX and i.operands[1].type == X86_OP_IMM and i.operands[1].imm <= 6: edx = i.op_str.split(",")[1].strip() edxs.append(int(edx, 16)) assert len(ebxs) == len(edxs) operations = list(zip(ebxs, edxs))

sub_401000 - expandKey

  • Đây là một hàm giúp tạo round_keys trong thuật toán mã hóa AES. Mình đã sử dụng plugins Findcrypt nên tìm được bảng sboxinv_sbox trong code của sub_401000:
    image
void __fastcall expandKey(unsigned int a1, _BYTE *a2) { char v2; // al _BYTE *v3; // esi unsigned __int8 v4; // bl char v5; // cl char v6; // al char v7; // cl _BYTE *v8; // eax char v9; // dl unsigned __int8 v10; // bh unsigned int v11; // [esp+8h] [ebp-8h] char v12; // [esp+Eh] [ebp-2h] unsigned __int8 v13; // [esp+Fh] [ebp-1h] v2 = *a2 ^ 0x42; v3 = (_BYTE *)a1; v4 = a2[13] ^ 0x4D; v5 = a2[14] ^ 0x58; v3[13] = v4; *v3 = v2; v6 = a2[1] ^ 0x4D; v3[14] = v5; v3[1] = v6; v7 = a2[15]; v3[2] = a2[2] ^ 0x58; v3[3] = a2[3] ^ 0x63; v3[4] = a2[4] ^ 0x42; v3[5] = a2[5] ^ 0x4D; v3[6] = a2[6] ^ 0x58; v3[7] = a2[7] ^ 0x63; v3[8] = a2[8] ^ 0x42; v3[9] = a2[9] ^ 0x4D; v3[10] = a2[10] ^ 0x58; v3[11] = a2[11] ^ 0x63; v3[12] = a2[12] ^ 0x42; v8 = v3 + 13; v3[15] = v7 ^ 0x63; LOBYTE(a1) = 4; v11 = 4; do { v9 = v4; v10 = *(v8 - 1); v12 = v8[1]; v13 = v8[2]; if ( (a1 & 3) == 0 ) { v12 = sbox[v13]; v13 = sbox[v10]; v10 = sbox[v4] ^ rcon[v11 >> 2]; v9 = sbox[(unsigned __int8)v8[1]]; } v8[3] = v10 ^ *(v8 - 13); v4 = v9 ^ *(v8 - 12); v8[4] = v4; v8[5] = v12 ^ *(v8 - 11); v8[6] = v13 ^ *(v8 - 10); v8 += 4; a1 = v11 + 1; v11 = a1; } while ( a1 < 0x2C ); }
  • Đây là sbox:
    image
  • Còn đây là rcon:
    image
  • Chuẩn bảng Rcon:
    image
  • Vậy là bước expandKey này hoàn toàn khác so với tiêu chuẩn, vì vậy mình sẽ debug để lấy round_keys:
    image
  • Hay viết dưới dạng code là:
round_keys = [[[79, 82, 253, 202], [221, 43, 151, 227], [30, 90, 191, 0], [56, 24, 149, 222]], [[227, 120, 224, 205], [62, 83, 119, 46], [32, 9, 200, 46], [24, 17, 93, 240]], [[99, 52, 108, 96], [93, 103, 27, 78], [125, 110, 211, 96], [101, 127, 142, 144]], [[181, 45, 12, 45], [232, 74, 23, 99], [149, 36, 196, 3], [240, 91, 74, 147]], [[132, 251, 208, 161], [108, 177, 199, 194], [249, 149, 3, 193], [9, 206, 73, 82]], [[31, 192, 208, 160], [115, 113, 23, 98], [138, 228, 20, 163], [131, 42, 93, 241]], [[218, 140, 113, 76], [169, 253, 102, 46], [35, 25, 114, 141], [160, 51, 47, 124]], [[89, 153, 97, 172], [240, 100, 7, 130], [211, 125, 117, 15], [115, 78, 90, 115]], [[246, 39, 238, 35], [6, 67, 233, 161], [213, 62, 156, 174], [166, 112, 198, 221]], [[188, 147, 47, 7], [186, 208, 198, 166], [111, 238, 90, 8], [201, 158, 156, 213]], [[129, 77, 44, 218], [59, 157, 234, 124], [84, 115, 176, 116], [157, 237, 44, 161]]]

sub_301140 - encryptBlock

  • Đây là toàn bộ hàm:
int __fastcall encryptBlock(unsigned __int8 *a1, int a2) { unsigned __int8 *v2; // esi int v3; // edi int v4; // ebx int v5; // edx unsigned __int8 *v6; // edi int v7; // ebx unsigned __int8 *v8; // ecx int v9; // edx int v10; // eax unsigned __int8 v11; // cl unsigned __int8 v12; // al unsigned __int8 v13; // cl unsigned __int8 v14; // al unsigned __int8 v15; // cl unsigned __int8 v16; // al unsigned __int8 v17; // cl int result; // eax _BYTE *v19; // edi int v20; // esi char v21; // bh char v22; // ch char v23; // dl char v24; // dh int v25; // ebx unsigned __int8 *v26; // eax char *v27; // edx int v28; // edi char v29; // cl int v30; // edi _BYTE *v31; // ecx int v32; // edx unsigned __int8 *v33; // [esp+Ch] [ebp-Ch] char v35; // [esp+16h] [ebp-2h] unsigned __int8 i; // [esp+17h] [ebp-1h] v2 = a1; v33 = a1; v3 = a2 - a1; v4 = 4; do { v5 = 4; do { *a1 ^= a1[v3]; ++a1; --v5; } while ( v5 ); --v4; } while ( v4 ); for ( i = 1; ; ++i ) { v6 = v2; v7 = 4; do { v8 = v6; v9 = 4; do { v10 = *v8; v8 += 4; *(v8 - 4) = sbox[v10]; --v9; } while ( v9 ); ++v6; --v7; } while ( v7 ); v11 = v2[1]; v2[1] = v2[5]; v2[5] = v2[9]; v2[9] = v2[13]; v12 = v2[10]; v2[13] = v11; v13 = v2[2]; v2[2] = v12; v14 = v2[14]; v2[10] = v13; v15 = v2[6]; v2[6] = v14; v16 = v2[15]; v2[14] = v15; v17 = v2[3]; v2[3] = v16; v2[15] = v2[11]; result = v2[7]; v2[11] = result; v2[7] = v17; if ( i == 10 ) break; v19 = v2 + 2; v20 = 4; do { v21 = v19[1]; v19 += 4; v22 = *(v19 - 4); v23 = *(v19 - 5); v35 = *(v19 - 6); v24 = v23 ^ v35 ^ v22 ^ v21; *(v19 - 6) = v24 ^ v35 ^ (2 * (v23 ^ v35)) ^ (27 * ((v23 ^ v35) >> 7)); *(v19 - 5) = v24 ^ v23 ^ (2 * (v22 ^ v23)) ^ (27 * ((v22 ^ v23) >> 7)); *(v19 - 4) = v24 ^ v22 ^ (2 * (v22 ^ v21)) ^ (27 * ((v22 ^ v21) >> 7)); *(v19 - 3) = v24 ^ v21 ^ (2 * (v21 ^ v35)) ^ (27 * ((v21 ^ v35) >> 7)); --v20; } while ( v20 ); v25 = 4; v2 = v33; v26 = v33; v27 = (a2 + 16 * i); do { v28 = 4; do { v29 = *v27++; *v26++ ^= v29; --v28; } while ( v28 ); --v25; } while ( v25 ); } v30 = 4; v31 = (a2 + 160); do { v32 = 4; do { LOBYTE(result) = *v31++; *v2++ ^= result; --v32; } while ( v32 ); --v30; } while ( v30 ); return result; }
  • Với những hàm phức tạp như này thì cách tốt nhất sẽ là kiểm tra input và output. Thực ra lúc mình làm thì mình reverse cả hàm này và đúng thật nó là encrypt AES chuẩn, chỉ có hàm expandKey là bị customized mà thôi. Test input là KCSC{This_is_testing_flag_hihi!}:
    image
  • Và chall:
    image
  • Chứng tỏ đây là hàm encrypt chuẩn của AES.

xor something

  • Sau khi mã hóa AES, ciphertexts sẽ được đưa đi mã hóa với một số chuỗi như sau:
    image
  • Code mô phỏng:
from pwn import xor xorValues = ["8c675f3d", "f13eef31", "f0de52d7", "c8ac4d94", "1698b7e0", "32e62a0d", "7453cc9b", "065b8a3a"] xorValues = [bytes.fromhex(x) for x in xorValues] xorValues = b"".join(xorValues) ciphertexts = xor(ciphertexts, xorValues)

Decrypt

  • Luồng chương trình sẽ như sau: đọc file fl4g_f0r_y0u.txt và tiến hành encrypt theo các bước như sau:
key --[customed expandKey]--> round_keys
round_keys + encryptBlock --> ciphertexts
ciphertexts xor something --> ciphertexts
ciphertexts --[Handler]--> enc
  • Trước tiên mình sẽ test quá trình mã hóa đã khớp với chương trình chưa, vẫn xét input là KCSC{This_is_testing_flag_hihi!}. Đây là chuỗi enc của mình:
    image
  • Còn đây là chuỗi của bài:
    image
  • Check:
    image
  • Vậy là mình đã mô phỏng thành công thuật toán mã hóa của chương trình. Để giải mã ta chỉ cần làm ngược lại như trong sơ đồ, dưới đây là script:
from capstone import Cs, CS_ARCH_X86, CS_MODE_32 from pwn import xor from Crypto.Util.Padding import unpad N_ROUNDS = 10 S_BOX = ( 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, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16, ) INV_S_BOX = ( 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D, ) def bytes_to_matrix(text: bytes) -> list: return [list(text[i:i + 4]) for i in range(0, len(text), 4)] def matrix_to_bytes(matrix: list) -> bytes: return b''.join(bytes([byte]) for row in matrix for byte in row) # AES round functions def add_round_key(state: list, key: list) -> list: return [[state[i][j] ^ key[i][j] for j in range(4)] for i in range(4)] def inv_shift_rows(state: list) -> None: state[1][1], state[2][1], state[3][1], state[0][1] = state[0][1], state[1][1], state[2][1], state[3][1] state[2][2], state[3][2], state[0][2], state[1][2] = state[0][2], state[1][2], state[2][2], state[3][2] state[3][3], state[0][3], state[1][3], state[2][3] = state[0][3], state[1][3], state[2][3], state[3][3] # Galois Field multiplication helper xtime = lambda a: (((a << 1) ^ 0x1B) & 0xFF) if (a & 0x80) else (a << 1) def mix_single_column(column: list) -> None: t = column[0] ^ column[1] ^ column[2] ^ column[3] u = column[0] column[0] ^= t ^ xtime(column[0] ^ column[1]) column[1] ^= t ^ xtime(column[1] ^ column[2]) column[2] ^= t ^ xtime(column[2] ^ column[3]) column[3] ^= t ^ xtime(column[3] ^ u) def mix_columns(state: list) -> None: for i in range(4): mix_single_column(state[i]) def inv_mix_columns(state: list) -> None: for i in range(4): u = xtime(xtime(state[i][0] ^ state[i][2])) v = xtime(xtime(state[i][1] ^ state[i][3])) state[i][0] ^= u state[i][1] ^= v state[i][2] ^= u state[i][3] ^= v mix_columns(state) def sub_bytes(state: list, sbox: tuple = S_BOX) -> list: return [[sbox[byte] for byte in row] for row in state] def decrypt(round_keys: list, ciphertext: bytes) -> bytes: state = bytes_to_matrix(ciphertext) # Initial round: add the last round key state = add_round_key(state, round_keys.pop()) # Main rounds for _ in range(N_ROUNDS - 1): inv_shift_rows(state) state = sub_bytes(state, sbox=INV_S_BOX) state = add_round_key(state, round_keys.pop()) inv_mix_columns(state) # Final round (skipping InvMixColumns) inv_shift_rows(state) state = sub_bytes(state, sbox=INV_S_BOX) state = add_round_key(state, round_keys.pop()) return matrix_to_bytes(state) def inv_handler(encrypted_data: bytes, operations: list) -> bytes: data = bytearray(encrypted_data) for index, op_code in reversed(operations): if op_code == 0: data[index] ^= 0x0D elif op_code == 1: data[index] = (data[index] - 0x25) & 0xFF elif op_code == 2: data[index] = (data[index] + 0x25) & 0xFF elif op_code == 3: data[index] ^= 0x25 elif op_code == 4: data[index] = (data[index] - 0x0D) & 0xFF elif op_code == 5: data[index] = (data[index] + 0x0D) & 0xFF elif op_code == 6: for i in range(index): data[i] = ~data[i] & 0xFF return bytes(data) # Read and convert CODE.bin with open("CODE.bin", "r") as f: code_hex = f.read().strip() code_bytes = bytes.fromhex(code_hex) # Disassemble the code to extract operations md = Cs(CS_ARCH_X86, CS_MODE_32) ebx_values, edx_values = [], [] for instruction in md.disasm(code_bytes, 0x0): if instruction.mnemonic == 'mov' and instruction.op_str.startswith("ebx"): value = instruction.op_str.split(",")[1].strip() ebx_values.append(int(value, 16)) elif instruction.mnemonic == 'add' and instruction.op_str.startswith("edx"): value = instruction.op_str.split(",")[1].strip() edx_values.append(int(value, 16)) assert len(ebx_values) == len(edx_values), "Mismatch in operation operands." operations = list(zip(ebx_values, edx_values)) # Round keys (from debugging) round_keys = [ [[79, 82, 253, 202], [221, 43, 151, 227], [30, 90, 191, 0], [56, 24, 149, 222]], [[227, 120, 224, 205], [62, 83, 119, 46], [32, 9, 200, 46], [24, 17, 93, 240]], [[99, 52, 108, 96], [93, 103, 27, 78], [125, 110, 211, 96], [101, 127, 142, 144]], [[181, 45, 12, 45], [232, 74, 23, 99], [149, 36, 196, 3], [240, 91, 74, 147]], [[132, 251, 208, 161], [108, 177, 199, 194], [249, 149, 3, 193], [9, 206, 73, 82]], [[31, 192, 208, 160], [115, 113, 23, 98], [138, 228, 20, 163], [131, 42, 93, 241]], [[218, 140, 113, 76], [169, 253, 102, 46], [35, 25, 114, 141], [160, 51, 47, 124]], [[89, 153, 97, 172], [240, 100, 7, 130], [211, 125, 117, 15], [115, 78, 90, 115]], [[246, 39, 238, 35], [6, 67, 233, 161], [213, 62, 156, 174], [166, 112, 198, 221]], [[188, 147, 47, 7], [186, 208, 198, 166], [111, 238, 90, 8], [201, 158, 156, 213]], [[129, 77, 44, 218], [59, 157, 234, 124], [84, 115, 176, 116], [157, 237, 44, 161]] ] # Prepare XOR values xor_values_hex = [ "8c675f3d", "f13eef31", "f0de52d7", "c8ac4d94", "1698b7e0", "32e62a0d", "7453cc9b", "065b8a3a" ] xor_values = b"".join(bytes.fromhex(x) for x in xor_values_hex) with open("flag.tcp1p", "rb") as f: encrypted_data = f.read() # Process decryption steps modified_ciphertext = inv_handler(encrypted_data, operations) xored_ciphertext = xor(modified_ciphertext, xor_values) # Decrypt each 16-byte block - AES ECB mode flag = b"" for i in range(0, len(xored_ciphertext), 16): flag += decrypt(round_keys.copy(), xored_ciphertext[i:i + 16]) flag = unpad(flag, 16) print(flag)

image

TCP1P{wh4t_4_r3v3rs3_3ng1neEr!_76ad1fea}