--- tags: [Beginner] --- # Subtask ## mixture.exe * Đối với bài này, sử dụng 1 kỹ thuật có thể nói là mới với mình, đó là **Process Hollowing**. * Một vài blog mình kiếm được để đọc về kỹ thuật này là https://sec.vnpt.vn/2019/01/process-hollowing https://viblo.asia/p/tim-hieu-ve-process-hollowing-V3m5WRmxlO7 Ngoài ra còn rất nhiều nguồn khác. ### Kỹ thuật Process Injection * Bài này tập trung ở kỹ thuật **Process hollowing**, nên đương nhiên phần này mình trình bày ở mức đơn giản thui nhe. 😶‍🌫️ #### Khái niệm kỹ thuật * **Process Injection** là tập hợp các kỹ thuật cho phép kẻ tấn công chèn mã độc vào không gian bộ nhớ của tiến trình khác và thực thi mã đó trong ngữ cảnh của tiến trình mục tiêu. #### Mục tiêu kỹ thuật * Che giấu hành vi tấn công (ẩn sau tiến trình hợp pháp). * Bypass AV/EDR. * Duy trì persistence hoặc nâng quyền. #### Một số kỹ thuật phổ biến thuộc nhóm này * DLL Injection * **Process Hollowing** * Thread Hijacking * Reflective DLL Injection #### Refs https://github.com/plackyhacker/Shellcode-Injection-Techniques/blob/master/README.md https://sec.vnpt.vn/2025/04/process-injection-ky-thuat-moi-su-dung-thread-name ### Kỹ thuật Process hollowing #### Khái niệm * **Process hollowing** là một kỹ thuật phổ biến trong lĩnh vực **malware** và **red team**, thuộc nhóm kỹ thuật **Process Injection**, nhằm che giấu mã độc bên trong một tiến trình hợp pháp của hệ điều hành. Đây là một tactic thường thấy trong fileless malware, giúp mã độc né tránh sự phát hiện của các phần mềm antivirus hay EDR. * Cụ thể hơn thì kẻ tấn công tạo một tiến trình hợp pháp ở trạng thái treo (`suspended`), rỗng hóa bộ nhớ chứa mã gốc của tiến trình đó, sau đó ghi mã độc vào và cho tiến trình tiếp tục chạy như bình thường. Mã độc lúc này sẽ chạy dưới vỏ bọc của một tiến trình hợp pháp. #### Mục đích * Ẩn mã độc dưới danh nghĩa tiến trình hệ thống hoặc tiến trình phổ biến (như `svchost.exe`, `explorer.exe`, `cmd.exe`, v.v.). * Tránh bị phát hiện bởi các công cụ bảo mật (`antivirus`, `EDR`). * Bypass `sandbox`, kiểm tra tĩnh, và `signature detection`. #### Cơ chế hoạt động * 1. Tạo tiến trình hợp pháp với trạng thái `CREATE_SUSPENDED`. * 2. Xóa (unmap) vùng bộ nhớ image gốc bằng `NtUnmapViewOfSection`. * 3. Cấp phát vùng nhớ mới, ghi payload (mã độc) vào. * 4. Chỉnh sửa **Entry Point** (`EIP/RIP`) để chuyển hướng thực thi về mã độc. * 5. Resume tiến trình, lúc này tiến trình sẽ chạy mã độc đã chèn vào. * Cơ bản thì chỉ có như thế, vậy thì vào bài xem có gì nào. ### Phân tích * Đây là 1 bài **flag checker**. * Load file vào **IDA**, hàm `main()` trông như sau: ![image](https://hackmd.io/_uploads/SkWN27tNxg.png) :::spoiler Code ```py int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int v3; // ebx unsigned int i; // esi int v5; // edi unsigned int j; // ecx int v7; // eax unsigned int k; // esi int v9; // edi unsigned int m; // ecx int v11; // eax unsigned int n; // ecx unsigned int ii; // ecx unsigned int jj; // esi void (__stdcall *v15)(int, int); // edi int v16; // esi char v18; // [esp+0h] [ebp-448h] _BYTE v19[4]; // [esp+Ch] [ebp-43Ch] BYREF void (__stdcall *v20)(int); // [esp+10h] [ebp-438h] int v21; // [esp+14h] [ebp-434h] BYREF void (__stdcall *v22)(int, char *, unsigned int, _BYTE *, _DWORD); // [esp+18h] [ebp-430h] int v23; // [esp+1Ch] [ebp-42Ch] char Format[4]; // [esp+20h] [ebp-428h] BYREF char v25; // [esp+24h] [ebp-424h] char v26; // [esp+25h] [ebp-423h] BYREF char v27; // [esp+26h] [ebp-422h] char v28; // [esp+27h] [ebp-421h] _BYTE v29[5]; // [esp+28h] [ebp-420h] BYREF char v30; // [esp+2Dh] [ebp-41Bh] char v31; // [esp+2Eh] [ebp-41Ah] char v32; // [esp+30h] [ebp-418h] char v33; // [esp+31h] [ebp-417h] BYREF _BYTE v34[18]; // [esp+32h] [ebp-416h] BYREF char Arglist[1024]; // [esp+44h] [ebp-404h] BYREF v3 = 0; v34[2] = 0; v34[3] = 7; v34[4] = 3; v34[5] = 54; v34[6] = 9; v34[7] = 3; v34[8] = 50; v34[9] = 118; v34[10] = 75; v34[11] = 30; qmemcpy(&v34[12], "W22T", 4); for ( i = 0; i < 0xD; ++i ) v34[i + 3] = (62 * (84 - (unsigned __int8)v34[i + 3]) % 127 + 127) % 127; v5 = sub_1F1123(&v34[3]); *(_DWORD *)Format = v5; v25 = 0; v26 = 9; v27 = 88; v28 = 104; v29[0] = 28; v29[1] = 97; v29[2] = 11; v29[3] = 104; v29[4] = 14; v30 = 97; v31 = 79; for ( j = 0; j < 0xA; ++j ) *(&v26 + j) = (55 * ((unsigned __int8)*(&v26 + j) - 79) % 127 + 127) % 127; v7 = sub_1F11B3(v5, &v26); v25 = 0; v26 = 110; v27 = 26; v28 = 37; v29[0] = 124; v29[1] = 16; v29[2] = 15; v29[3] = 102; v29[4] = 26; v30 = 18; v22 = (void (__stdcall *)(int, char *, unsigned int, _BYTE *, _DWORD))v7; for ( k = 0; k < 9; ++k ) *(&v26 + k) = (35 * (18 - (unsigned __int8)*(&v26 + k)) % 127 + 127) % 127; v9 = *(_DWORD *)Format; *(_DWORD *)v29 = sub_1F11B3(*(_DWORD *)Format, &v26); v34[2] = 0; v34[3] = 114; v34[4] = 115; v34[5] = 81; v34[6] = 78; v34[7] = 25; v34[8] = 15; v34[9] = 28; v34[10] = 50; v34[11] = 121; v34[12] = 115; v34[13] = 25; v34[14] = 69; for ( m = 0; m < 0xC; ++m ) v34[m + 3] = (41 * ((unsigned __int8)v34[m + 3] - 69) % 127 + 127) % 127; v11 = sub_1F11B3(v9, &v34[3]); v32 = 0; v33 = 29; qmemcpy(v34, ")_T~\nSJ)G_?>)", 13); v34[13] = 21; v34[14] = 21; v34[15] = 22; v20 = (void (__stdcall *)(int))v11; for ( n = 0; n < 0x11; ++n ) v34[n - 1] = (12 * ((unsigned __int8)v34[n - 1] - 22) % 127 + 127) % 127; v23 = sub_1F11B3(v9, &v33); memset(Arglist, 0, sizeof(Arglist)); v32 = 0; v33 = 68; v34[0] = 14; qmemcpy(&v34[1], "fQK\npoNQ\\g+", 11); v34[12] = 18; v34[13] = 15; qmemcpy(&v34[14], "Q-", 2); for ( ii = 0; ii < 0x11; ++ii ) v34[ii - 1] = (15 * ((unsigned __int8)v34[ii - 1] - 45) % 127 + 127) % 127; sub_1F32E6(&v33, v18); Format[0] = 0; Format[1] = 38; Format[2] = 18; Format[3] = 54; for ( jj = 0; jj < 3; ++jj ) Format[jj + 1] = (42 * (54 - (unsigned __int8)Format[jj + 1]) % 127 + 127) % 127; v15 = (void (__stdcall *)(int, int))v23; sub_1F32AE(&Format[1], (char)Arglist); v22(dword_1F6438, Arglist, strlen(Arglist), v19, 0); v16 = unknown_libname_1(100); (*(void (__stdcall **)(int, int, int, int *, _DWORD))v29)(dword_1F6438, v16, 100, &v21, 0); *(_BYTE *)(v21 + v16) = 0; v29[0] = 0; qmemcpy(&v29[1], "Tkx[", 4); do { v29[v3 + 1] = (31 * ((unsigned __int8)v29[v3 + 1] - 91) % 127 + 127) % 127; ++v3; } while ( v3 < 4 ); sub_1F32E6(&v29[1], v16); v20(dword_1F6438); v15(dword_1F643C, -1); return 0; } ``` ::: * Để detect kỹ thuật **Process Hollowing**, có thể sử dụng: * **[Process Explorer](https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer):** ![image](https://hackmd.io/_uploads/B1FhzEY4xg.png) * **[Process Hacker](https://systeminformer.sourceforge.io/downloads):** ![image](https://hackmd.io/_uploads/BkKTzEKNee.png) * Ta thấy trong `mixture.exe` đang có `cmd.exe`. * Tuy hơi rời rạc, nhưng mình sẽ thử phân tích tiếp `mixture.exe` xem có gì không. 😶 * Sau 1 hồi ~~vật vã~~ debug, thì mình thấy chương trình có tương đối nhiều hàm thực hiện **resolve API**, mình sẽ tóm tắt dưới đây. #### Hàm main() * `Kernel32.dll` ![image](https://hackmd.io/_uploads/HkLvw4FNll.png) * [kernel32_WriteFile](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-writefile) ![image](https://hackmd.io/_uploads/Byf9_NF4xe.png) * [kernel32_ReadFile](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile) ![image](https://hackmd.io/_uploads/BJgAdEtNge.png) * [kernel32_CloseHandle](https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle) ![image](https://hackmd.io/_uploads/r1_5tEt4ex.png) * Ngoài ra thì hàm `sub_4A32E6(&v33, v18);` để `printf([+] Enter Flag:)` và `(*(void (__stdcall **)(int, int, int, int *, _DWORD))v29)(dword_4A6438, v16, 100, &v21, 0);` để `scanf(input)`. #### Hàm sub_4A2082() :::spoiler Code ```py int sub_4A2082() { unsigned int i; // esi int v1; // eax int v2; // esi unsigned int j; // ecx void (__stdcall *v4)(int); // ebx unsigned int k; // ecx unsigned int m; // esi char *v7; // eax _BYTE v9[17]; // [esp+Dh] [ebp-47h] BYREF _BYTE v10[12]; // [esp+1Eh] [ebp-36h] BYREF _BYTE v11[13]; // [esp+2Ah] [ebp-2Ah] BYREF _BYTE v12[8]; // [esp+38h] [ebp-1Ch] BYREF _BYTE v13[8]; // [esp+40h] [ebp-14h] BYREF int v14; // [esp+48h] [ebp-Ch] char v15; // [esp+4Ch] [ebp-8h] char v16; // [esp+4Dh] [ebp-7h] v13[0] = 0; qmemcpy(&v13[1], "B6PH6D", 6); v13[7] = 18; v14 = 1144260624; v15 = 68; v16 = 43; for ( i = 0; i < 0xD; ++i ) v13[i + 1] = (63 * (43 - (unsigned __int8)v13[i + 1]) % 127 + 127) % 127; v1 = sub_4A1123(&v13[1]); v13[4] = 0; v2 = v1; strcpy(&v13[5], ">u"); LOBYTE(v14) = 0; *(_WORD *)((char *)&v14 + 1) = 27723; for ( j = 0; j < 6; ++j ) v13[j + 5] = (12 * ((unsigned __int8)v13[j + 5] - 108) % 127 + 127) % 127; v13[0] = 0; v4 = (void (__stdcall *)(int))sub_4A11B3(v1, (int)&v13[5]); v13[1] = 49; v13[2] = 28; v13[3] = 23; v13[4] = 41; v13[5] = 19; v13[6] = 23; v13[7] = 99; v14 = 1444361989; v15 = 33; for ( k = 0; k < 0xC; ++k ) v13[k + 1] = (28 * ((unsigned __int8)v13[k + 1] - 33) % 127 + 127) % 127; v14 = sub_4A11B3(v2, (int)&v13[1]); sub_4A154D(); v4(104); v9[0] = 14; v9[1] = 112; v9[2] = 14; v9[3] = 112; v9[4] = 63; v9[5] = 112; v9[6] = 14; v9[7] = 112; v9[8] = 70; v9[9] = 112; v9[10] = 25; v9[11] = 112; v9[12] = 70; v9[13] = 112; v9[14] = 90; v9[15] = 112; v9[16] = 14; qmemcpy(v10, "pDpGpApGpGp", 11); v10[11] = 17; qmemcpy(v11, "p6p}p^p}p}ppp", sizeof(v11)); for ( m = 0; m < 0x2A; ++m ) v9[m] = (45 * (112 - (unsigned __int8)v9[m]) % 127 + 127) % 127; dword_4A6438 = ((int (__stdcall *)(_BYTE *, int, _DWORD, _DWORD, int, _DWORD, _DWORD))v14)( v9, -536870912, 0, 0, 3, 0, 0); if ( dword_4A6438 == -1 ) { v12[0] = 0; v12[1] = 44; v12[2] = 111; v12[3] = 111; v12[4] = 115; v12[5] = 111; v12[6] = 51; v12[7] = 4; qmemcpy(v13, "oV1B{J)3", sizeof(v13)); v14 = 1444772637; v15 = 38; v16 = 9; v7 = (char *)sub_4A3273(v12); sub_4A32E6(v7); exit(0); } return 0; } ``` ::: * `Kernel32.dll` ![image](https://hackmd.io/_uploads/SJaopNYNex.png) * [kernel32_Sleep](https://learn.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-sleep) ![image](https://hackmd.io/_uploads/Syl-REK4ll.png) * [kernel32_CreateFileW](https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilea) ![image](https://hackmd.io/_uploads/HkI8CNt4le.png) * Còn 1,2 hàm nữa gì đấy, cũng **resolve API** như này, mình nghĩ thay vì liệt kê tiếp thì mình sẽ tóm tắt lại flow của chương trình, cũng như mục đích cụ thể của kỹ thuật **Process Hollowing** đối với bài này như sau: * **main()**: * Resolve API, hiển thị các prompt. * Gọi `WriteFile`, `ReadFile` để giao tiếp với một tiến trình khác (qua named pipe). * **sub_402082()**: * Resolve các API: `kernel32.dll`, `Sleep`, `CreateFileW`. * Gọi đến `sub_40154D()` → hàm thực hiện kỹ thuật **Process Hollowing**. * Gọi `Sleep` (mục đích: delay, tránh detection). * Tạo **Named Pipe** với tên `\\.\pipe\KCSCCTF2022`. * Hàm **sub_40154D()** — trọng tâm chính * Resolve các API phục vụ **Hollowing**: `CreateProcessW`,`VirtualAllocEx`,`WriteProcessMemory`,`NtUnmapViewOfSection`,`ResumeThread`. * Tạo tiến trình **cmd.exe** dưới dạng `CREATE_SUSPENDED`. Sau đó Unmap phân vùng code gốc trong tiến trình bằng `NtUnmapViewOfSection`. * Cấp phát lại vùng nhớ mới bằng **VirtualAllocEx** với quyền `PAGE_EXECUTE_READWRITE`. * Lấy **payload** từ resource (qua `sub_401233()`), dùng `FindResourceW`, `LoadResource`, `SizeofResource`. * **Payload** ban đầu bị mã hóa, cần giải mã trước khi inject (chắc để tránh detect). Sau đó inject **payload** vào tiến trình **cmd.exe** bằng `WriteProcessMemory`. * Resume thực thi tiến trình **cmd.exe** bằng `ResumeThread`. * Hiểu được ý đồ như vậy, mình nghĩ sẽ phải dump được con **payload** này - gọi là **malware** cũng được, phân tích thử xem nó có gì không. * Như ở trên mình có phân tích, là tiến trình `mixture.exe` đã tạo ra một tiến trình `cmd.exe`. Kết hợp với phần tóm tắt thì thứ mình cần dump chính là con `cmd.exe`. * Ở đây, sử dụng **[Process Explorer](https://learn.microsoft.com/en-us/sysinternals/downloads/process-explorer)** hoặc **[Process Hacker](https://systeminformer.sourceforge.io/downloads)** có lẽ được. Công nhân hơn dump tay :)). Nhưng mình thấy con **[Hollows_Hunter](https://github.com/hasherezade/hollows_hunter)** khá ngon khi nó tự scan và detect, đồng thời hỗ trợ dump. Hẹ hẹ hẹ 😂 * Nếu chỉ scan thì ` .\hollows_hunter64.exe /pname cmd.exe` ![image](https://hackmd.io/_uploads/ByqTG_FNll.png) * Nếu xuất file dump thì `.\hollows_hunter64.exe /pname cmd.exe /dir .` ![image](https://hackmd.io/_uploads/rkhlQdY4gl.png) * Như vậy thì file dump nằm cùng thư mục hiện tại, tiến hành phân tích bằng **IDA**. * Hàm `main()`: :::spoiler Code ```py int __cdecl main(int argc, const char **argv, const char **envp) { unsigned int i; // ecx int v4; // eax unsigned int v5; // ecx int (__stdcall *v6)(_BYTE *); // edi unsigned int j; // esi int v8; // eax unsigned int k; // esi int v10; // ebx int v11; // eax unsigned int m; // ecx int v13; // eax unsigned int n; // ecx int v15; // eax unsigned int ii; // ecx int v17; // eax unsigned int jj; // esi unsigned int kk; // esi int v20; // eax int v21; // edi unsigned int mm; // ecx int v23; // eax unsigned int nn; // esi int v25; // eax unsigned int i1; // ecx unsigned int i2; // esi int v28; // eax unsigned int i3; // esi int v30; // ebx int v31; // eax unsigned int i4; // ecx int v33; // eax unsigned int i5; // esi int v35; // eax unsigned int i6; // ecx int v37; // eax unsigned int i7; // esi int v39; // eax unsigned int i8; // ecx unsigned int i9; // ecx int v42; // eax unsigned int i10; // ecx int v44; // eax unsigned int i11; // ecx int v46; // eax unsigned int i12; // esi int v48; // eax unsigned int i13; // esi int v50; // ebx int v51; // eax unsigned int i14; // ecx int v53; // eax unsigned int i15; // esi unsigned int i16; // esi int v56; // eax int v57; // ebx unsigned int i17; // esi const char *v59; // edx unsigned int i18; // ecx char v62[96]; // [esp-60h] [ebp-520h] BYREF _DWORD v63[24]; // [esp+Ch] [ebp-4B4h] BYREF char v64; // [esp+6Ch] [ebp-454h] _BYTE v65[43]; // [esp+6Dh] [ebp-453h] BYREF int v66; // [esp+98h] [ebp-428h] char v67; // [esp+9Ch] [ebp-424h] _BYTE v68[23]; // [esp+9Dh] [ebp-423h] BYREF char v69[4]; // [esp+B4h] [ebp-40Ch] BYREF char v70[4]; // [esp+B8h] [ebp-408h] BYREF _BYTE v71[1024]; // [esp+BCh] [ebp-404h] BYREF memset(v71, 0, sizeof(v71)); v68[3] = 0; v68[4] = 86; v68[5] = 8; v68[6] = 50; v68[7] = 125; v68[8] = 8; v68[9] = 99; v68[10] = 28; v68[11] = 15; strcpy(&v68[12], "Zzcc"); for ( i = 0; i < 0xD; ++i ) v68[i + 4] = (-39 * (unsigned __int8)v68[i + 4] % 127 + 127) % 127; v4 = sub_B9102A(&v68[4]); v5 = 0; v68[3] = 0; v68[4] = 22; strcpy(&v68[5], "F"); v68[7] = 15; v68[8] = 22; v68[9] = 40; v68[10] = 5; strcpy(&v68[11], "U"); v68[13] = 85; v68[14] = 120; v68[15] = 94; v68[16] = 23; v66 = v4; do { v68[v5 + 4] = (51 * ((unsigned __int8)v68[v5 + 4] - 23) % 127 + 127) % 127; ++v5; } while ( v5 < 0xD ); v67 = 0; v6 = (int (__stdcall *)(_BYTE *))sub_B910BA(v4, &v68[4]); v68[0] = 118; v68[1] = 21; qmemcpy(&v68[2], "\nK4\nbK\a\n:", 9); v68[11] = 2; qmemcpy(&v68[12], "Hu\n/ ", 5); v63[0] = v6; for ( j = 0; j < 0x11; ++j ) v68[j] = (45 * (32 - (unsigned __int8)v68[j]) % 127 + 127) % 127; v8 = sub_B910BA(v66, v68); v67 = 0; qmemcpy(v68, "VR;;j<FU", 8); v68[8] = 14; v68[9] = 36; v68[10] = 106; v68[11] = 83; v68[12] = 4; qmemcpy(&v68[13], "GijE", 4); v63[2] = v8; for ( k = 0; k < 0x11; ++k ) v68[k] = (11 * (69 - (unsigned __int8)v68[k]) % 127 + 127) % 127; v10 = v66; v11 = sub_B910BA(v66, v68); v67 = 0; v68[0] = 7; v68[1] = 105; v68[2] = 80; v68[3] = 120; v68[4] = 90; v68[5] = 29; v68[6] = 29; v68[7] = 115; v68[8] = 120; v68[9] = 14; v68[10] = 109; v68[11] = 125; v68[12] = 95; v68[13] = 115; v68[14] = 54; v68[15] = 104; v68[16] = 105; v68[17] = 24; qmemcpy(&v68[18], "s2", 2); v63[3] = v11; for ( m = 0; m < 0x14; ++m ) v68[m] = (25 * ((unsigned __int8)v68[m] - 50) % 127 + 127) % 127; v13 = sub_B910BA(v10, v68); v68[7] = 0; v68[8] = 97; v68[9] = 35; v68[10] = 28; v68[11] = 65; v68[12] = 76; v68[13] = 42; v68[14] = 79; v68[15] = 35; v68[16] = 17; v63[4] = v13; for ( n = 0; n < 9; ++n ) v68[n + 8] = (55 * ((unsigned __int8)v68[n + 8] - 17) % 127 + 127) % 127; v15 = sub_B910BA(v10, &v68[8]); v68[7] = 0; v68[8] = 8; v68[9] = 125; v68[10] = 86; v68[11] = 49; v68[12] = 111; v68[13] = 19; v68[14] = 86; v68[15] = 99; v68[16] = 111; v68[17] = 12; v63[5] = v15; for ( ii = 0; ii < 0xA; ++ii ) v68[ii + 8] = (10 * ((unsigned __int8)v68[ii + 8] - 12) % 127 + 127) % 127; v17 = sub_B910BA(v10, &v68[8]); v68[7] = 0; v68[8] = 15; qmemcpy(&v68[9], "W4ZT~.j\vWTM", 11); v63[6] = v17; for ( jj = 0; jj < 0xC; ++jj ) v68[jj + 8] = (40 * (77 - (unsigned __int8)v68[jj + 8]) % 127 + 127) % 127; v68[3] = 0; qmemcpy(&v68[4], "mESm$WT7BE//", 12); v68[16] = 2; for ( kk = 0; kk < 0xD; ++kk ) v68[kk + 4] = (35 * (2 - (unsigned __int8)v68[kk + 4]) % 127 + 127) % 127; v20 = v6(&v68[4]); v68[3] = 0; v21 = v20; v68[4] = 18; v68[5] = 70; v68[6] = 22; v68[7] = 124; v68[8] = 12; qmemcpy(&v68[9], "F'[F;FbLz-Q", 11); for ( mm = 0; mm < 0x10; ++mm ) v68[mm + 4] = (37 * ((unsigned __int8)v68[mm + 4] - 81) % 127 + 127) % 127; v23 = sub_B910BA(v20, &v68[4]); v68[3] = 0; v68[4] = 27; v68[5] = 20; v68[6] = 46; v68[7] = 40; v68[8] = 20; v68[9] = 88; v68[10] = 79; v68[11] = 95; v68[12] = 111; v68[13] = 101; v68[14] = 20; v68[15] = 112; v68[16] = 13; qmemcpy(&v68[17], "<h", 2); v63[8] = v23; for ( nn = 0; nn < 0xF; ++nn ) v68[nn + 4] = (39 * (104 - (unsigned __int8)v68[nn + 4]) % 127 + 127) % 127; v25 = sub_B910BA(v21, &v68[4]); v68[7] = 0; v68[8] = 46; v68[9] = 63; v68[10] = 18; v68[11] = 66; v68[12] = 96; v68[13] = 92; v68[14] = 2; v68[15] = 63; v68[16] = 13; qmemcpy(&v68[17], "?yq", 3); v63[14] = v25; for ( i1 = 0; i1 < 0xC; ++i1 ) v68[i1 + 8] = (31 * ((unsigned __int8)v68[i1 + 8] - 113) % 127 + 127) % 127; v68[7] = 0; v68[8] = 90; v68[9] = 83; v68[10] = 67; v68[11] = 88; v68[12] = 78; v68[13] = 21; v68[14] = 57; v68[15] = 30; v68[16] = 41; v68[17] = 83; v68[18] = 78; v68[19] = 24; for ( i2 = 0; i2 < 0xC; ++i2 ) v68[i2 + 8] = (24 * (24 - (unsigned __int8)v68[i2 + 8]) % 127 + 127) % 127; v28 = sub_B910BA(v66, &v68[8]); v68[3] = 0; v68[4] = 22; v68[5] = 87; v68[6] = 42; v68[7] = 77; v68[8] = 6; v68[9] = 42; v68[10] = 32; v68[11] = 111; v68[12] = 87; v68[13] = 42; v68[14] = 77; v68[15] = 19; v68[16] = 5; v63[7] = v28; for ( i3 = 0; i3 < 0xD; ++i3 ) v68[i3 + 4] = (11 * (5 - (unsigned __int8)v68[i3 + 4]) % 127 + 127) % 127; v30 = v66; v31 = sub_B910BA(v66, &v68[4]); v67 = 0; qmemcpy(v68, "\t?", 2); v68[2] = 30; qmemcpy(&v68[3], "@_%hY", 5); v68[8] = 30; v68[9] = 57; v68[10] = 70; v68[11] = 97; v68[12] = 110; v68[13] = 42; v68[14] = 43; v68[15] = 10; v68[16] = 110; v68[17] = 23; qmemcpy(&v68[18], "@b", 2); v63[12] = v31; for ( i4 = 0; i4 < 0x14; ++i4 ) v68[i4] = (19 * ((unsigned __int8)v68[i4] - 98) % 127 + 127) % 127; v33 = sub_B910BA(v30, v68); v67 = 0; v68[0] = 1; v68[1] = 14; v68[2] = 84; v68[3] = 34; v68[4] = 18; v68[5] = 75; v68[6] = 84; v68[7] = 67; v68[8] = 103; v68[9] = 94; v68[10] = 14; v68[11] = 104; v68[12] = 28; v68[13] = 117; v68[14] = 14; qmemcpy(&v68[15], "P^]", 3); v63[13] = v33; for ( i5 = 0; i5 < 0x12; ++i5 ) v68[i5] = (27 * (93 - (unsigned __int8)v68[i5]) % 127 + 127) % 127; v35 = sub_B910BA(v66, v68); v68[3] = 0; v68[4] = 91; v68[5] = 4; v68[6] = 55; v68[7] = 78; v68[8] = 94; v68[9] = 4; v68[10] = 43; v68[11] = 103; v68[12] = 4; v68[13] = 6; qmemcpy(&v68[14], "M,f ", 4); v63[15] = v35; for ( i6 = 0; i6 < 0xE; ++i6 ) v68[i6 + 4] = (10 * ((unsigned __int8)v68[i6 + 4] - 32) % 127 + 127) % 127; v37 = sub_B910BA(v21, &v68[4]); v67 = 0; v68[0] = 120; v68[1] = 31; v68[2] = 35; v68[3] = 118; v68[4] = 63; v68[5] = 31; v68[6] = 57; v68[7] = 71; v68[8] = 1; v68[9] = 23; v68[10] = 45; v68[11] = 63; v68[12] = 31; qmemcpy(&v68[13], "^EVS", 4); v63[16] = v37; for ( i7 = 0; i7 < 0x11; ++i7 ) v68[i7] = (63 * (83 - (unsigned __int8)v68[i7]) % 127 + 127) % 127; v39 = sub_B910BA(v21, v68); v68[7] = 0; v68[8] = 31; v68[9] = 19; v68[10] = 118; v68[11] = 114; v68[12] = 48; v68[13] = 6; v68[14] = 77; v68[15] = 19; v68[16] = 2; v68[17] = 19; qmemcpy(&v68[18], "x$", 2); v63[17] = v39; for ( i8 = 0; i8 < 0xC; ++i8 ) v68[i8 + 8] = (9 * ((unsigned __int8)v68[i8 + 8] - 36) % 127 + 127) % 127; v65[19] = 0; v65[20] = 102; v65[21] = 117; v65[22] = 76; v65[23] = 38; v65[24] = 69; v65[25] = 23; v65[26] = 96; v65[27] = 14; qmemcpy(&v65[28], "-Ou0f>VE0dE", 11); v65[39] = 23; v65[40] = 59; v63[10] = sub_B910BA(v21, &v68[8]); for ( i9 = 0; i9 < 0x15; ++i9 ) v65[i9 + 20] = (37 * ((unsigned __int8)v65[i9 + 20] - 59) % 127 + 127) % 127; v42 = sub_B910BA(v21, &v65[20]); v68[3] = 0; v68[4] = 63; v68[5] = 14; v68[6] = 4; strcpy(&v68[7], "#x"); v68[10] = 3; v68[11] = 35; v68[12] = 109; v68[13] = 14; v68[14] = 120; v68[15] = 106; v68[16] = 87; v68[17] = 4; v68[18] = 68; v63[18] = v42; for ( i10 = 0; i10 < 0xF; ++i10 ) v68[i10 + 4] = (12 * ((unsigned __int8)v68[i10 + 4] - 68) % 127 + 127) % 127; v44 = sub_B910BA(v21, &v68[4]); v67 = 0; qmemcpy(v68, ";(K", 3); v68[3] = 30; v68[4] = 50; v68[5] = 12; v68[6] = 102; v68[7] = 50; v68[8] = 99; v68[9] = 102; v68[10] = 75; v68[11] = 124; v68[12] = 82; v68[13] = 40; v68[14] = 82; v68[15] = 15; v68[16] = 105; v63[19] = v44; for ( i11 = 0; i11 < 0x11; ++i11 ) v68[i11] = (51 * ((unsigned __int8)v68[i11] - 105) % 127 + 127) % 127; v46 = sub_B910BA(v21, v68); v68[3] = 0; v68[4] = 25; qmemcpy(&v68[5], "qv'<c\\Bqv'<V", 12); v63[20] = v46; for ( i12 = 0; i12 < 0xD; ++i12 ) v68[i12 + 4] = (24 * (86 - (unsigned __int8)v68[i12 + 4]) % 127 + 127) % 127; v48 = sub_B910BA(v21, &v68[4]); v68[3] = 0; qmemcpy(&v68[4], "aG @N%Y\vNG| }Y 5", 16); v63[21] = v48; for ( i13 = 0; i13 < 0x10; ++i13 ) v68[i13 + 4] = (36 * (53 - (unsigned __int8)v68[i13 + 4]) % 127 + 127) % 127; v50 = v66; v51 = sub_B910BA(v21, &v68[4]); v67 = 0; v68[0] = 71; v68[1] = 17; v68[2] = 63; v68[3] = 22; v68[4] = 12; v68[5] = 97; v68[6] = 113; v68[7] = 32; v68[8] = 113; v68[9] = 123; v68[10] = 78; v68[11] = 113; v68[12] = 71; v68[13] = 88; v68[14] = 27; v68[15] = 12; v68[16] = 113; v68[17] = 2; v68[18] = 12; v68[19] = 48; v63[22] = v51; for ( i14 = 0; i14 < 0x14; ++i14 ) v68[i14] = (25 * ((unsigned __int8)v68[i14] - 48) % 127 + 127) % 127; v53 = sub_B910BA(v21, v68); v68[7] = 0; v68[8] = 109; v68[9] = 57; v68[10] = 104; v68[11] = 10; v68[12] = 67; v68[13] = 29; qmemcpy(&v68[14], "+xq", 3); v63[23] = v53; for ( i15 = 0; i15 < 9; ++i15 ) v68[i15 + 8] = (27 * (113 - (unsigned __int8)v68[i15 + 8]) % 127 + 127) % 127; v63[11] = sub_B910BA(v50, &v68[8]); v64 = 0; qmemcpy(v65, "/&/&j&/&<&\v&<&n&/&7&~&o&~&~&v&", 30); v65[30] = 20; v65[31] = 38; v65[32] = 7; v65[33] = 38; v65[34] = 120; v65[35] = 38; v65[36] = 7; v65[37] = 38; v65[38] = 7; memset(&v65[39], 38, 3); for ( i16 = 0; i16 < 0x2A; ++i16 ) v65[i16] = (18 * (38 - (unsigned __int8)v65[i16]) % 127 + 127) % 127; v56 = ((int (__stdcall *)(_BYTE *, int, int, int, int, int, _DWORD, _DWORD))v63[2])( v65, 3, 6, 1, 0x4000, 0x4000, 0, 0); v57 = v56; v66 = v56; if ( v56 == -1 || !((int (__stdcall *)(int, _DWORD))v63[3])(v56, 0) ) return -1; ((void (__stdcall *)(int, _BYTE *, int, char *, _DWORD))v63[5])(v57, v71, 1024, v70, 0); qmemcpy(v62, v63, sizeof(v62)); if ( sub_B9155A(v62[0]) == 1 ) { v67 = 0; v68[0] = 20; v68[1] = 0; v68[2] = 86; v68[3] = 16; qmemcpy(&v68[4], "(#", 2); v68[6] = 3; qmemcpy(&v68[7], "(i\\99|iLquu", 11); for ( i17 = 0; i17 < 0x12; ++i17 ) v68[i17] = (40 * (117 - (unsigned __int8)v68[i17]) % 127 + 127) % 127; v57 = v66; v59 = v68; } else { v65[19] = 0; v65[20] = 50; v65[21] = 26; v65[22] = 53; v65[23] = 96; v65[24] = 74; v65[25] = 68; v65[26] = 55; v65[27] = 74; v65[28] = 68; v65[29] = 125; v65[30] = 25; v65[31] = 111; v65[32] = 69; v65[33] = 69; v65[34] = 124; v65[35] = 25; v65[36] = 41; v65[37] = 60; v65[38] = 14; v65[39] = 14; for ( i18 = 0; i18 < 0x14; ++i18 ) v65[i18 + 20] = (9 * ((unsigned __int8)v65[i18 + 20] - 14) % 127 + 127) % 127; v59 = &v65[20]; } ((void (__stdcall *)(int, const char *, unsigned int, char *, _DWORD))v63[6])(v57, v59, strlen(v59), v69, 0); ((void (__stdcall *)(int))v63[4])(v57); ((void (__stdcall *)(int))v63[7])(v57); return 0; } ``` ::: * Để tránh lan man, mình sẽ debug tới đoạn check flag: ![image](https://hackmd.io/_uploads/ByEgN_FVxe.png) * `main()` ----> `sub_B9155A()` ----> `sub_B9113A()`: :::spoiler Code ```py int __stdcall sub_B9113A(int a1) { const char *v1; // ecx unsigned int i; // esi unsigned int j; // ecx unsigned int k; // ecx _BYTE v6[8]; // [esp+Dh] [ebp-877h] BYREF _BYTE v7[14]; // [esp+15h] [ebp-86Fh] BYREF _BYTE v8[6]; // [esp+23h] [ebp-861h] BYREF _BYTE v9[4]; // [esp+29h] [ebp-85Bh] BYREF _BYTE v10[3]; // [esp+2Dh] [ebp-857h] BYREF int v11; // [esp+30h] [ebp-854h] BYREF unsigned int v12; // [esp+34h] [ebp-850h] BYREF _BYTE v13[4]; // [esp+38h] [ebp-84Ch] BYREF int v14; // [esp+3Ch] [ebp-848h] BYREF int v15; // [esp+40h] [ebp-844h] BYREF int v16; // [esp+44h] [ebp-840h] BYREF int v17; // [esp+48h] [ebp-83Ch] BYREF _BYTE v18[1024]; // [esp+4Ch] [ebp-838h] BYREF _DWORD v19[256]; // [esp+44Ch] [ebp-438h] BYREF _BYTE v20[32]; // [esp+84Ch] [ebp-38h] BYREF _BYTE v21[20]; // [esp+86Ch] [ebp-18h] BYREF v16 = 0; v17 = 0; memset(v20, 0, sizeof(v20)); v1 = *(const char **)(a1 + 4); memset(v21, 0, 17); v19[0] = 1214720153; v19[1] = 1707300528; v19[2] = -1726911327; v12 = strlen(v1) + 1; v19[3] = -1537570562; v19[4] = -1486946960; v19[5] = -870368618; v19[6] = 667672498; v19[7] = -1765573493; v19[8] = -1168597336; v19[9] = 325425054; v19[10] = 1783112709; v19[11] = 689536444; memset(&v19[12], 0, 0x3D0u); dword_B95434 = 520; dword_B95438 = 26128; dword_B9543C = 32; qmemcpy(v6, "qGz", 3); v6[3] = 20; v6[4] = 122; v6[5] = 42; v6[6] = 70; v6[7] = 29; qmemcpy(v7, "@CC* i@V*- \r\r", 13); v7[13] = 26; qmemcpy(v8, "Vv9", 3); for ( i = 0; i < 0x19; ++i ) v6[i] = (40 * (57 - (unsigned __int8)v6[i]) % 127 + 127) % 127; v9[3] = 0; qmemcpy(v10, "#jz", sizeof(v10)); for ( j = 0; j < 3; ++j ) v10[j] = (9 * ((unsigned __int8)v10[j] - 122) % 127 + 127) % 127; v8[5] = 0; v9[0] = 79; v9[1] = 12; v9[2] = 47; for ( k = 0; k < 3; ++k ) v9[k] = (15 * ((unsigned __int8)v9[k] - 47) % 127 + 127) % 127; v15 = 33; v14 = 16; if ( !(*(int (__stdcall **)(unsigned int, _BYTE *, _DWORD, int, int *))(a1 + 72))(0x80000000, v6, 0, 131097, &v11) ) { (*(void (__stdcall **)(int, _BYTE *, _DWORD, _BYTE *, _BYTE *, int *))(a1 + 76))(v11, v10, 0, v13, v20, &v15); (*(void (__stdcall **)(int, _BYTE *, _DWORD, _BYTE *, _BYTE *, int *))(a1 + 76))(v11, v9, 0, v13, v21, &v14); (*(void (__stdcall **)(int))(a1 + 48))(v11); } qmemcpy(&unk_B95440, v20, 0x20u); if ( (*(int (__stdcall **)(int *, _DWORD, _DWORD, int, int))(a1 + 80))(&v16, 0, 0, 24, -268435456) ) { if ( (*(int (__stdcall **)(int, int *, int, _DWORD, _DWORD, int *))(a1 + 84))(v16, &dword_B95434, 44, 0, 0, &v17) ) { if ( (*(int (__stdcall **)(int, int, _BYTE *, _DWORD))(a1 + 88))(v17, 1, v21, 0) ) { memset(v18, 0, sizeof(v18)); memcpy(v18, *(const void **)(a1 + 4), strlen(*(const char **)(a1 + 4))); if ( (*(int (__stdcall **)(int, _DWORD, int, _DWORD, _BYTE *, unsigned int *, int))(a1 + 92))( v17, 0, 1, 0, v18, &v12, 1024) ) { if ( !(*(int (__stdcall **)(_DWORD *, _BYTE *))(a1 + 52))(v19, v18) ) return 1; } } } } if ( v17 ) (*(void (__stdcall **)(int))(a1 + 96))(v17); if ( v16 ) (*(void (__stdcall **)(int, _DWORD))(a1 + 100))(v16, 0); return 0; } ``` ::: * Đoạn này: ![image](https://hackmd.io/_uploads/HJmvSuF4ex.png) * ``(a1 + 80)...`` là `advapi32_CryptAcquireContextA` * `(a1 + 84)...` là `advapi32_CryptImportKey` tạo `key` * `(a1 + 88)...` là `advapi32_CryptSetKeyParam` tạo `IV` * `(a1 + 92)...` là `advapi32_CryptEncrypt` * `(a1 + 52)...` là `kernel32_lstrcmp` copy **flag_encrypt**. * Tóm lại đoạn này thực hiện mã hóa bằng **AES-256-CBC**, sử dụng **CryptoAPI** (`CryptImportKey`, `CryptSetKeyParam`, `CryptDecrypt`) của **Windows API**. * Vậy nhặt data rồi decrypt thôi nào. Hẹ hẹ 😶‍🌫️ ### Script ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES import string from gmpy2 import * import string from Crypto.Cipher import AES enc = bytes([ 153, 40, 103, 72, 176, 86, 195, 101, 161, 108, 17, 153, 254, 136, 90, 164, 112, 253, 94, 167, 150, 58, 31, 204, 178, 223, 203, 39, 139, 124, 195, 150, 168, 158, 88, 186, 158, 151, 101, 19, 5, 36, 72, 106, 188, 125, 25, 41 ]) key = b"1st't_7l@g_u_2h0uldn't_s5bm1t_1t" iv = bytes([ 0x05, 0x17, 0x2B, 0x0C, 0x22, 0x41, 0x0C, 0x2D, 0x0C, 0x22, 0x20, 0x5E, 0x1C, 0x22, 0x2A, 0x1C ]) cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(enc) print(flag) ``` * Lúc đầu thì là `KCSC{C0n9r@tulT/10n2_0n_mak1ng_1t(^.^)}`, nhập thì đúng, nhưng mình thấy nó cứ cấn cấn sao á. `C0n9r@tulT/10n2` trông không giống `Congratulation`. Sau đó mình check lại và ~~thúc đít~~ **chatGPT** thì biết rằng hai số cuối của **IV** là lấy từ **serial number volume** của máy, mà của máy mỗi người thì giá trị này khác nhau. Tức là 2 bytes cuối của `flag` phải ~~bút phọt~~ rùi. * Mình đoán `flag` là `KCSC{C0n9r@tulat10n2_0n_mak1ng_1t(^.^)}` hoặc đại loại vậy. ## VEH.exe ### Phân tích * Đây là 1 bài **flag checker**. * Load file vào **IDA**, hàm `main()` trông như sau: ![image](https://hackmd.io/_uploads/BJ88tCzVgx.png) * Trông tởm quá, có lẽ chương trình đã obfucate ~~nhè nhẹ~~ 1 chút. * Mình lướt tiếp xuống xem như nào, thì thấy có vùng đỏ tiếp ![image](https://hackmd.io/_uploads/BJH3EkmVle.png) * Mình thử `Disassembly` của `48 89 C6` trên [defuse](https://defuse.ca/online-x86-assembler.htm#disassembly2) ![image](https://hackmd.io/_uploads/H19DS17Eee.png) * Sau 1 hồi ~~rất lâu~~ mình mò thì nhận ra 1 điều là có rất nhiều `div rax ~~ div 0` gây `exception` sau đó luôn là opcode `E9`. Có lẽ đây là bytes được dùng để obfucate khiến **IDA** không xác định được luồng để `jmp` chuẩn. * Vậy mình sẽ viết 1 đoạn code python ~~nho nhỏ~~ để replace bytes `E9` này đi. Tuy nhiên chỉ những bytes mà nằm sau opcode của `div rax` thôi nha, chứ những vị trí khác chưa gây ảnh hưởng. ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES import string from gmpy2 import * import string with open("VEH.exe", "rb") as f: data = f.read() data_tmp = data.replace(long_to_bytes(0x48F7F0E9),long_to_bytes(0x48F7F090)) with open("VEH.exe", "wb") as f: f.write(data_tmp) ``` ![image](https://hackmd.io/_uploads/Hk_rflmNex.png) * Đó trông đỡ khiếp ngay. 😶‍🌫️ * Mình biết được chương trình có khá nhiều `div 0` để raise exception. Vậy mình thử debug để tới từng lệnh `div` đó xem như nào. * `exception` đầu tiên: ![image](https://hackmd.io/_uploads/SJQf1F8Exg.png) * Hàm `sub_7FF7BD9F2540`: ```py __int64 sub_7FF7BD9F2540() { 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_7FF7BD9F11F0(); dword_7FF7BD9F5108 = 0; v0 = (void (__fastcall *)(__int64, __int64 (__fastcall *)()))sub_7FF7BD9F1000(3239761349LL, 341012667LL); v0(1LL, sub_7FF7BD9F11A0); dword_7FF7BD9F510C = 0; *(_QWORD *)&v2 = 0LL; *((_QWORD *)&v2 + 1) = v1; v3 = (__int64 (__fastcall *)(__int64, unsigned __int64, __int64, __int64))(v2 / 0); qword_7FF7BD9F50F8 = v3(4294967286LL, v2 % 0, -474380438LL, 701355107LL); result = ((__int64 (__fastcall *)(__int64))v3)(4294967285LL); qword_7FF7BD9F5100 = result; dword_7FF7BD9F5110 = 0; return result; } ``` * Tại hàm này thì mình thấy chỉ có hàm `sub_7FF7BD9F11F0()` là quan trọng: ```py __int64 sub_7FF7BD9F11F0() { void (__fastcall *v0)(_BYTE *); // 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] _BYTE v12[63]; // [rsp+29h] [rbp-3Fh] BYREF v0 = (void (__fastcall *)(_BYTE *))sub_7FF7BD9F1000(1102179001LL, 701355107LL); 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 = 1LL; i != 21; ++i ) { v2 = (unsigned __int16)(31 * (unsigned __int8)v12[i - 1] + ((unsigned int)(-32509 * (__int16)(31 * (unsigned __int8)v12[i - 1] - 31)) >> 16) - 31); v12[i - 1] = (unsigned __int8)(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 = 1LL; j != 23; ++j ) { v4 = (unsigned __int8)v12[j - 1]; v5 = 19 * v4; v6 = (unsigned __int16)(19 * v4 + ((unsigned int)(-32509 * (__int16)(19 * v4 - 2052)) >> 16) - 2052); v12[j - 1] = (unsigned __int8)(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 = 1LL; k != 25; ++k ) v12[k - 1] = (unsigned __int8)(7 * v12[k - 1] - 127 * (((unsigned __int16)(7 * (unsigned __int8)v12[k - 1] + ((unsigned int)(-32509 * (__int16)(7 * (unsigned __int8)v12[k - 1] - 525)) >> 16) - 525) >> 15) + ((unsigned __int16)(7 * (unsigned __int8)v12[k - 1] + ((unsigned int)(-32509 * (__int16)(7 * (unsigned __int8)v12[k - 1] - 525)) >> 16) - 525) >> 6)) - 13 + 127) % 0x7Fu; v0(v12); v11 = 0; v12[0] = 17; v12[1] = 70; v12[2] = 8; v12[3] = 70; v12[4] = 7; qmemcpy(&v12[5], "FrF\\F+F.F'F\vF\bF@F@FFF", 21); for ( m = 1LL; m != 27; ++m ) { v9 = 18 * (unsigned __int8)v12[m - 1]; v12[m - 1] = (unsigned __int8)(((unsigned __int16)((unsigned int)(33027 * (__int16)(1260 - v9)) >> 16) >> 15) + ((unsigned __int16)((unsigned int)(33027 * (__int16)(1260 - v9)) >> 16) >> 6) - (v9 + ((((unsigned __int16)((unsigned int)(33027 * (__int16)(1260 - v9)) >> 16) >> 15) + (unsigned __int8)((unsigned __int16)((unsigned int)(33027 * (__int16)(1260 - v9)) >> 16) >> 6)) << 7)) - 20 + 127) % 0x7Fu; } v0(v12); return 0LL; } ``` * Trông ~~tởm điên~~, mình thử debug thêm thì phát hiện hàm này có tác dụng resolve khá nhiều. Cụ thể hơn thì: * `ntdll_RtlAddVectoredExceptionHandler`: ![Screenshot 2025-06-23 143115](https://hackmd.io/_uploads/r1K-7tLNxe.png) * Theo [tài liệu](https://learn.microsoft.com/en-us/windows/win32/api/errhandlingapi/nf-errhandlingapi-addvectoredexceptionhandler) ![image](https://hackmd.io/_uploads/H1DYQt84ee.png) * `AddVectoredExceptionHandler` là **Windows API** cho phép đăng ký một hàm xử lý ngoại lệ (`exception handler`) ở cấp hệ thống, được gọi khi xảy ra lỗi như: * Lỗi truy cập bộ nhớ (Access Violation) * Lỗi chia cho 0 * Lỗi phần cứng (divide overflow, etc.) * Nó là một phần của cơ chế xử lý ngoại lệ **vectored** — khác với **SEH** (`__try/__except`), **vectored handler** có phạm vi toàn bộ quá trình. * Tham số: * `First`: Nếu là **TRUE (1)**, `handler` được thêm vào đầu danh sách (ưu tiên cao). Nếu **FALSE (0)**, thêm vào cuối. * `Handler`: Con trỏ tới hàm xử lý kiểu **LONG CALLBACK Handler**(`EXCEPTION_POINTERS *ExceptionInfo`) * `ntdll.dll`: ![Screenshot 2025-06-23 142445](https://hackmd.io/_uploads/rkbqbYU4ee.png) * `USER32.dll`: ![Screenshot 2025-06-23 142624](https://hackmd.io/_uploads/r1uAZF8Nlx.png) * `CRYPT32.DLL`: ![Screenshot 2025-06-23 142726](https://hackmd.io/_uploads/ryGGGtU4ll.png) * `Advapi32.dll`: ![Screenshot 2025-06-23 142814](https://hackmd.io/_uploads/ByPSzKU4le.png) * Tiếp sau đó, thì khi mà chương trình xảy ra `exception`, chương trình sẽ redirect sang 1 handle để xử lý, ở đây thì là hàm `sub_7FF7BD9F11A0()`: ![image](https://hackmd.io/_uploads/S1MlPKLNxl.png) * À còn hàm `sub_7FF7BD9F1000()` thì: ```py __int64 __fastcall sub_7FF7BD9F1000(int a1, int a2) { struct _PEB_LDR_DATA *Ldr; // r8 struct _LIST_ENTRY *Flink; // r9 struct _LIST_ENTRY *p_InMemoryOrderModuleList; // r8 __int64 result; // rax struct _LIST_ENTRY *v6; // r10 __int16 v7; // di unsigned __int64 v8; // rsi char v9; // bp unsigned __int64 v10; // rbp unsigned __int8 v12; // bl int v13; // ebp unsigned __int8 *v14; // rdi struct _LIST_ENTRY *v15; // r11 __int64 v16; // rbp unsigned __int64 v17; // r12 __int64 v18; // r14 __int64 v19; // r13 __int64 v20; // r15 bool v21; // bp __int64 v22; // rdi __int64 v23; // rsi unsigned __int8 v24; // bl unsigned __int8 *v25; // rsi int v26; // r10d unsigned __int8 v27; // [rsp+0h] [rbp-88h] char v28; // [rsp+1h] [rbp-87h] BYREF Ldr = NtCurrentTeb()->ProcessEnvironmentBlock->Ldr; Flink = Ldr->InMemoryOrderModuleList.Flink; p_InMemoryOrderModuleList = &Ldr->InMemoryOrderModuleList; result = 0LL; while ( 1 ) { if ( Flink == p_InMemoryOrderModuleList ) return result; if ( LOWORD(Flink[4].Blink) <= 0x3Eu ) { v6 = Flink[5].Flink; v7 = (__int16)v6->Flink; if ( LOWORD(v6->Flink) ) { v8 = 0LL; do { v9 = v7 - 32; if ( (unsigned __int8)(v7 - 97) >= 0x1Au ) v9 = v7; *(&v27 + v8) = v9; v10 = v8 + 1; v7 = *((_WORD *)&v6->Flink + v8 + 1); if ( !v7 ) break; } while ( v8++ < 0x3E ); } else { v10 = 0LL; } *(&v27 + v10) = 0; v12 = v27; v13 = -2128831035; if ( v27 ) { v14 = (unsigned __int8 *)&v28; do { v13 = 16777619 * (v13 ^ v12); v12 = *v14++; } while ( v12 ); } if ( v13 == a2 ) { v15 = Flink[2].Flink; v16 = *(int *)((char *)&v15[8].Blink + SHIDWORD(v15[3].Blink)); if ( *(_DWORD *)((char *)&v15[2].Flink + v16) ) { v17 = *(unsigned int *)((char *)&v15[1].Blink + v16); if ( *(_DWORD *)((char *)&v15[1].Blink + v16) ) break; } } } LABEL_2: Flink = Flink->Flink; } v18 = *(int *)((char *)&v15[2].Flink + v16 + 4); v19 = (__int64)v15 + *(int *)((char *)&v15[2].Flink + v16); v20 = *(int *)((char *)&v15[1].Blink + v16 + 4); v21 = 1; v22 = 0LL; while ( 1 ) { v23 = *(int *)(v19 + 4 * v22); v24 = *((_BYTE *)&v15->Flink + v23); if ( v24 ) break; if ( a1 == -2128831035 ) return (__int64)v15 + *(int *)((char *)&v15->Flink + 4 * *(unsigned __int16 *)((char *)&v15->Flink + 2 * (unsigned int)v22 + v18) + v20); LABEL_21: v21 = ++v22 < v17; if ( v22 == v17 ) goto LABEL_2; } v25 = (unsigned __int8 *)&v15->Flink + v23 + 1; v26 = -2128831035; do { v26 = 16777619 * (v26 ^ v24); v24 = *v25++; } while ( v24 ); if ( v26 != a1 ) goto LABEL_21; if ( !v21 ) goto LABEL_2; return (__int64)v15 + *(int *)((char *)&v15->Flink + 4 * *(unsigned __int16 *)((char *)&v15->Flink + 2 * (unsigned int)v22 + v18) + v20); } ``` * Nhìn thì cũng thấy rằng hàm này tác dụng là **resolve API**, kỹ hơn nữa thì nó là resolve function address bằng **FNV-1a** hash. Hay là kỹ thuật **resolve API** bằng **API hashing** sử dụng **FNV-1a** hash. * Vậy mình đổi tên hàm đó là `sub_7FF7BD9F1000()` ----> `ResolveAPI_FNV1a()`. * Quay lại với hàm `sub_7FF7BD9F11A0()`. Trông thì chưa rõ nó làm gì, mình stuck khá lâu ở đoạn này. * Nhớ ra là `AddVectoredExceptionHandler` khi xảy ra exception sẽ gọi `handler` trỏ tới hàm xử lý kiểu **LONG CALLBACK Handler**(`EXCEPTION_POINTERS *ExceptionInfo`). `Handler` đó chính là [PVECTORED_EXCEPTION_HANDLER](https://learn.microsoft.com/en-us/windows/win32/api/winnt/nc-winnt-pvectored_exception_handler) ![image](https://hackmd.io/_uploads/rk0opt8Ele.png) * Sau 1 hồi ~~thúc đít~~ **chatGPT** thì mình hiểu như này: **IDA** không biết hàm `sub_7FF7BD9F11A0()` là một **vectored exception handler** (`VEH`). Nên khi thấy truy cập kiểu ``*(_QWORD *)(a1 + 8)`` → **IDA** chỉ biết "à, offset $8$ khỏi `a1`, không biết là **struct** gì". Đại loại vậy. Nên các `field` khá mù mờ mà tối nghĩa. Và **chatGPT** recommend mình convert biến `a1` sang kiểu struct `_EXCEPTION_POINTERS`. 😶‍🌫️ * Cách convert xem ở [đây](https://hshrzd.wordpress.com/2022/02/09/ida-tips-how-to-use-a-custom-structure/#:~:text=Adding%20the%20structure%20into%20IDA&text=1%20%E2%80%93%20First%20we%20need%20to,paste%20there%20our%20custom%20structure.) nha. * Hoặc không thì như mình: `View` --> `Opensubviews` --> `Local Type`. Sau đó chuột phải chọn `Add Type`. ![image](https://hackmd.io/_uploads/rkLw5qI4le.png) ![image](https://hackmd.io/_uploads/ryTw598Elg.png) * Do kiến trúc file là 64 bit, nên 2 con trỏ 64-bit → mỗi con trỏ chiếm 8 bytes --> Tổng struct là 16 bytes hay là `0x10` nha. * Sau đó nhấn `Y` tại tên hàm `sub_7FF7BD9F11A0()`, ![image](https://hackmd.io/_uploads/Hy_0qq8Nle.png) * Thay prototype `__int64 __fastcall sub_7FF7BD9F11A0(struct _EXCEPTION_POINTERS* a1) ` * Còn nếu dùng `typedef`: `__int64 __fastcall sub_7FF7BD9F11A0(PEXCEPTION_POINTERS a1) ` ![image](https://hackmd.io/_uploads/BkAGjcLVlx.png) * Ú òa, đẹp chưa này: ![image](https://hackmd.io/_uploads/SJWEjqUVgl.png) * Như tại [PVECTORED_EXCEPTION_HANDLER](https://learn.microsoft.com/en-us/windows/win32/api/winnt/nc-winnt-pvectored_exception_handler) nói: ![image](https://hackmd.io/_uploads/BkPeTcUVxx.png) * Tại trường hợp của mình là `return 0xFFFFFFFFLL;`, là tiếp tục thực thi từ vị trí xảy ra `exception (RIP)`. * Thông thường, việc tăng `RIP/EIP` sau khi xử lý `exception` là điều bắt buộc nếu muốn tiếp tục thực thi, vì mặc định **OS** sẽ giữ nguyên `RIP` tại vị trí gây lỗi. Nếu không thay đổi nó, chương trình sẽ rơi vào `infinite exception loop`. * Và việc tăng bao nhiêu thì phụ thuộc vào độ dài lệnh máy (`instruction`) gây ra `exception`. Nếu tăng lung tung thì vẫn nhảy vào giữa opcode ----> crash tiếp. * Cụ thể với trường hợp của mình, thì là ~~div 0~~, ví dụ mình lấy tại hàm `sub_7FF7C8FA2540()` nhé: ![image](https://hackmd.io/_uploads/BJdIloLNxe.png) * Lệnh `nop` này thực chất chính là `E9` mà lúc đầu mình đã thực hiện patch nó. Tại `div 0` (opcode là `48 F7 F0`), thì sau khi raise exception, `rip + 4` sẽ làm cho chương trình nhảy vào lệnh ngay sau lệnh `nop`. Vậy rõ ràng mục đích ở đây là bypass/skip qua exception và lệnh `nop` - đúng hơn là lệnh rác cũ `E9`. * Được rồi, đến đây thì khá là clean và hiểu được chắc là gần hết ý đồ của chương trình, mình công nhân trace theo từng lệnh `div 0` thì `input` - có thể là `flag` luôn, mã hóa **AES** mode **CBC** và so sánh với 1 mảng trong chương trình. * Hàm `sub_7FF7BD9F1640()`: Tạo key. :::spoiler Code ```py __int64 __fastcall sub_7FF7BD9F1640(__int64 a1, __int64 a2) { unsigned __int128 v2; // rtt __int64 v3; // rdx unsigned __int64 v4; // rax __int64 i; // r8 __int64 v6; // rdx unsigned __int64 v7; // rdx unsigned __int128 v8; // rtt unsigned __int64 v9; // r14 unsigned __int64 v10; // rdx void (__fastcall *v11)(__int64); // rdi unsigned __int64 v12; // rax unsigned __int64 v13; // rdx __int64 v14; // rbx unsigned __int64 v15; // rax unsigned __int64 v16; // rdx unsigned __int128 v17; // rtt unsigned __int64 v18; // rax unsigned __int64 v19; // rdx unsigned __int128 v20; // rtt unsigned int v21; // ebx unsigned __int64 v22; // rax unsigned __int64 v23; // rdx unsigned __int128 v24; // rtt unsigned __int128 v25; // rtt unsigned __int64 v26; // rsi __int64 v27; // rax __int64 v28; // rcx char genkey[8]; // [rsp+30h] [rbp-C8h] BYREF char genkey_12; // [rsp+3Ch] [rbp-BCh] char genkey_13; // [rsp+3Dh] [rbp-BBh] char genkey_14; // [rsp+3Eh] [rbp-BAh] char genkey_15; // [rsp+3Fh] [rbp-B9h] char genkey_16; // [rsp+40h] [rbp-B8h] char genkey_17; // [rsp+41h] [rbp-B7h] char genkey_18; // [rsp+42h] [rbp-B6h] char genkey_19; // [rsp+43h] [rbp-B5h] char genkey_20; // [rsp+44h] [rbp-B4h] char genkey_21; // [rsp+45h] [rbp-B3h] char genkey_22; // [rsp+46h] [rbp-B2h] char genkey_23; // [rsp+47h] [rbp-B1h] char genkey_24; // [rsp+48h] [rbp-B0h] char genkey_25; // [rsp+49h] [rbp-AFh] char genkey_26; // [rsp+4Ah] [rbp-AEh] char genkey_27; // [rsp+4Bh] [rbp-ADh] char genkey_28; // [rsp+4Ch] [rbp-ACh] char genkey_29; // [rsp+4Dh] [rbp-ABh] char genkey_30; // [rsp+4Eh] [rbp-AAh] char genkey_31; // [rsp+4Fh] [rbp-A9h] _BYTE genkey_32[14]; // [rsp+50h] [rbp-A8h] BYREF __int64 v52; // [rsp+60h] [rbp-98h] BYREF int v53; // [rsp+6Ch] [rbp-8Ch] BYREF _BYTE v54[136]; // [rsp+70h] [rbp-88h] BYREF *(_QWORD *)&v2 = 0LL; *((_QWORD *)&v2 + 1) = a2; ((void (__fastcall *)(_BYTE *, _QWORD, __int64, __int64))(v2 / 0))(v54, 0LL, 100LL, 341012667LL); *(_QWORD *)&v2 = 0LL; *((_QWORD *)&v2 + 1) = v3; v4 = v2 / 0; genkey[0] = 0; qmemcpy(&genkey[1], "@UUNs/{{zzz", 11); genkey_12 = 26; genkey_13 = 62; genkey_14 = 108; genkey_15 = 55; genkey_16 = 85; genkey_17 = 55; genkey_18 = 117; genkey_19 = 27; genkey_20 = 26; genkey_21 = 87; genkey_22 = 108; genkey_23 = 41; genkey_24 = 123; genkey_25 = 122; genkey_26 = 20; genkey_27 = 85; genkey_28 = 87; genkey_29 = 64; genkey_30 = 24; genkey_31 = 25; qmemcpy(genkey_32, "T9wzdzMB^$Ww\t\t", sizeof(genkey_32)); for ( i = 1LL; i != 46; ++i ) genkey[i] = (unsigned __int8)(-127 * (((unsigned __int16)(55 * (unsigned __int8)genkey[i] + ((unsigned int)(-32509 * (__int16)(55 * (unsigned __int8)genkey[i] - 495)) >> 16) - 495) >> 15) + ((unsigned __int16)(55 * (unsigned __int8)genkey[i] + ((unsigned int)(-32509 * (__int16)(55 * (unsigned __int8)genkey[i] - 495)) >> 16) - 495) >> 6)) + 55 * genkey[i] - 112) % 0x7Fu; ((void (__fastcall *)(_BYTE *, char *, __int64, __int64))v4)(v54, &genkey[1], 44LL, 341012667LL); *(_QWORD *)genkey = 0LL; v52 = 0LL; *(_QWORD *)&v8 = 0LL; *((_QWORD *)&v8 + 1) = v6; v7 = v8 % 0; v9 = v8 / 0; *(_QWORD *)&v8 = 0LL; *((_QWORD *)&v8 + 1) = v7; v10 = v8 % 0; v11 = (void (__fastcall *)(__int64))(v8 / 0); *(_QWORD *)&v8 = 0LL; *((_QWORD *)&v8 + 1) = v10; v12 = v8 / 0; v13 = v8 % 0; if ( !v12 || (v14 = 0LL, ((unsigned int (__fastcall *)(char *, _QWORD, _QWORD, __int64, int))v12)(genkey, 0LL, 0LL, 24LL, -268435456)) ) { *(_QWORD *)&v17 = 0LL; *((_QWORD *)&v17 + 1) = v13; v15 = v17 / 0; v16 = v17 % 0; if ( !v15 || ((unsigned int (__fastcall *)(_QWORD, __int64, _QWORD, _QWORD, __int64 *))v15)( *(_QWORD *)genkey, 32780LL, 0LL, 0LL, &v52) ) { *(_QWORD *)&v20 = 0LL; *((_QWORD *)&v20 + 1) = v16; v18 = v20 / 0; v19 = v20 % 0; if ( v18 ) v21 = ((__int64 (__fastcall *)(_BYTE *, unsigned __int64, __int64, __int64))v18)( v54, v19, -1724985304LL, 701355107LL); else v21 = 0; *(_QWORD *)&v24 = 0LL; *((_QWORD *)&v24 + 1) = v19; v22 = v24 / 0; v23 = v24 % 0; if ( !v22 || ((unsigned int (__fastcall *)(__int64, _BYTE *, _QWORD, _QWORD))v22)(v52, v54, v21, 0LL) ) { *(_QWORD *)&v25 = 0LL; *((_QWORD *)&v25 + 1) = v23; v26 = v25 / 0; v27 = sub_7FF7BD9F25C8(0x21uLL); v14 = v27; v53 = 32; if ( v26 && !((unsigned int (__fastcall *)(__int64, __int64, __int64, int *, _DWORD))v26)(v52, 2LL, v27, &v53, 0) ) { v14 = 0LL; } v11(v52); v28 = *(_QWORD *)genkey; goto LABEL_18; } v11(v52); } v28 = *(_QWORD *)genkey; v14 = 0LL; LABEL_18: ((void (__fastcall *)(__int64, _QWORD))v9)(v28, 0LL); } return v14; } ``` ::: * Hàm sẽ sử dụng **CryptoAPI** của **Windows** tạo `key` nha. * Cụ thể hơn thì gán data = `https://www.youtube.com/watch?v=dQw4w9WgXcQ` qua hàm [RtlCopyMemory](https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/nf-wdm-rtlcopymemory), tạo context qua [CryptAcquireContext](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptacquirecontexta), với `dwProvType = 0x18` thì là `PROV_RSA_AES` từ [Cryptographic Provider Types](https://learn.microsoft.com/en-us/windows/win32/SecCrypto/cryptographic-provider-types). Sau đó khởi tạo đối tượng băm qua hàm [CryptCreateHash](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptcreatehash), với `ALG_ID = 0x800c` là `CALG_SHA_256` theo [ALG_ID](https://learn.microsoft.com/en-us/windows/win32/seccrypto/alg-id), hoàn thành xong thì gọi [CryptHashData](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-crypthashdata) để băm data. Lấy giá trị băm vừa rồi qua hàm [CryptGetHashParam](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgethashparam) và cuối cùng là giải phóng tài nguyên bằng [CryptDestroyHash](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptdestroyhash) và [CryptReleaseContext](https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptreleasecontext). * **IV**: ![image](https://hackmd.io/_uploads/SkUuG9DVge.png) * **Flag encrypted**: ![image](https://hackmd.io/_uploads/Hya7m5wNxx.png) * Có các tham số của **AES-CBC** rồi, tiến hành decrypt thôi. ### Script ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES import string from gmpy2 import * import string flag_enc = long_to_bytes(0xe560440942c4bbdef6a12d93d91d1372af8d4cf7a79f1fb999689cb8c24c4f85) iv = long_to_bytes(0x0102030405060708090a0b0c0d0e0f10) key = long_to_bytes(0x0424974c68530290458c8d58674e2637f65abc127057957d7b3acbd24c208f93) cipher = AES.new(key, AES.MODE_CBC, iv) flag = cipher.decrypt(flag_enc) print(flag) # b'KMACTF{3Xc3pTI0n_3v3rYwh3R3@_@}\x01' ``` * **Flag: ~~KMACTF{3Xc3pTI0n_3v3rYwh3R3@_@}~~** ![image](https://hackmd.io/_uploads/HJwJG4tVxg.png) ## FlagChecker.exe ### Phân tích * Cho vào **DIE**: ![image](https://hackmd.io/_uploads/S1o733zVlx.png) * Từ đây ta có thể thấy file đã bị pack bằng **UPX**. * Tải **UPX** trên **WSL**:`sudo apt install upx-ucl` * Chạy lệnh để unpack: `upx -d <file>.exe` * Unpack xong thì file trông như này: ![image](https://hackmd.io/_uploads/HJEN9FtEgx.png) * Trông bài khá lạ, mình chưa có ý tưởng gì, chính xác hơn thì mình chưa từng gặp thể loại bài như này bao giờ. Nên không còn cách nào khác, mình phải tìm xem có gì có thể hint được không. * Thực ra lúc đầu mình tính xem hết các function, tuy nhiên có tận 2500+ function :)). Cũng chả dại dột gì mà mất công vậy, do bài này có **GUI**: ![image](https://hackmd.io/_uploads/ByJoZ_jVeg.png) Nên mình sẽ thử tìm các **API** như `MessageBoxA()` rồi **xref** xem, thì thấy có 1 cái này lạ lạ: ![image](https://hackmd.io/_uploads/rkdGMuiNxe.png) * Kết hợp với thông tin mà **DIE** đưa ra ở phía trên, thì có lẽ bài này được code bằng **AutoIT**. #### AutoIT ##### Khái niệm * **AutoIt** là một ngôn ngữ scripting dùng để tự động hóa **GUI** và tác vụ trong **Windows**, tương tự như macro. * Tự động nhấn chuột, nhập phím. * Tự động thao tác GUI (như mở cửa sổ, bấm nút, v.v). * Dễ dàng tạo script giả lập hành vi người dùng. * Tập tin **AutoIt** script thường có đuôi `.au3` và có thể được biên dịch thành file thực thi (`.exe`). * Đặc điểm nhận biết file **AutoIt**: Trong file thực thi `.exe`, thường thấy chuỗi như `AU3!EA06`, `AUTOIT`, hay `This is a compiled AutoIt script`. Hoặc sử dụng công cụ như **Detect It Easy (DIE)** hoặc **PEiD**. ##### Tại sao AutoIt lại quan trọng trong reverse? * Nhiều phần mềm (hoặc malware) được viết bằng **AutoIt** để tạo **GUI** đơn giản, tự động hóa các hành động như nhập `key`, kiểm tra môi trường,... * **AutoIt** dễ bị dùng cho **obfuscation** hoặc **dropper** trong malware (tức là shell chính được viết bằng **AutoIt**, và tải `payload` thật khi chạy). * Khi reverse, bạn sẽ thấy file **PE** có kích thước nhỏ, nhưng thực chất là runtime interpreter cho mã **AutoIt** đã bị `packed/encrypted` bên trong. * Nhiều malware dùng **AutoIt** để: * Kiểm tra hệ thống ảo hóa. * Thực hiện `persistence`. * Decrypt payload `.NET` hoặc `shellcode`. ----> (`shellcode loader`) * Viết dropper nhỏ, dễ vượt `AV`. ----> (`malware dropper`) * Đó, sơ qua về **AutoIT** mình tìm hiểu thì có vậy, thế thì có lẽ phải đi kiếm tools để decompile nó. Mình có tìm kiếm 1 chút thì thấy [Exe2Aut](https://exe2aut.com/exe2aut-converter/) có vẻ tín. Nhưng có vẻ `Exe2Aut` chỉ decompile được file 32 bit, còn 64 bit thì mình thấy có **MyAutToExe**, à cái này cũng hỗ trợ 32 bit nữa.😶‍🌫️ * Sau khi decompile ra được file `.au3` thì mình có code 16380 dòng. * Đầu tiên ta có hàm `check()`, có thể dùng để check `input` với `flag`: ```py FUNC CHECKER() LOCAL $INPUT = GUICTRLREAD($IFLAG) LOCAL $LEN_INPUT = STRINGLEN($INPUT) LOCAL $OPCODE = "0x558bec83ec6c8d45d850e8aa05000083c4048d4d94518d55d8" & "52e8cb03000083c408e80c0000007573657233322e646c6c00" & "00ff55d88945f8837df8007505e9fb000000e80c0000004d65" & "7373616765426f7841008b45f850e8b306000083c4088945f0" & "8b4d0851ff55e883f81c740b8b550cc60200e9c4000000c645" & "bcf8c645bd50c645beccc645bfefc645c0e6c645c13cc645c2" & "35c645c396c645c41dc645c561c645c6aec645c7c0c645c8c5" & "c645c931c645cacec645cbb0c645cce7c645cd1dc645ceedc6" & "45cfbcc645d05dc645d181c645d269c645d38ac645d435c645" & "d574c645d657c645d7b68b4508508d4d94518d55d852e84700" & "000083c40c8945f4c745fc00000000eb098b45fc83c0018945" & "fc837dfc1c7d1f8b4df4034dfc0fb6118b45fc0fb64c05bc3b" & "d174088b550cc60200eb08ebd28b450cc600018be55dc3558b" & "ec83ec445657b90b000000e82c00000068747470733a2f2f77" & "77772e796f75747562652e636f6d2f77617463683f763d6451" & "773477395767586351005e8d7dbcf3a5c745f800000000c745" & "f400000000c745fc000000008b4510508b4d088b5110ffd289" & "45ec6a006a016a006a008d45f8508b4d0c8b11ffd285c07507" & "33c0e91b0200008d45f4506a006a0068048000008b4df8518b" & "550c8b420cffd085c075156a008b4df8518b550c8b4224ffd0" & "33c0e9e9010000837df40075156a008b4df8518b550c8b4224" & "ffd033c0e9ce0100006a008d4dbc518b55088b4210ffd0508d" & "4dbc518b55f4528b450c8b4808ffd185c075218b55f4528b45" & "0c8b481cffd16a008b55f8528b450c8b4824ffd133c0e98a01" & "00008d55fc526a008b45f45068016800008b4df8518b550c8b" & "4214ffd085c075218b4df4518b550c8b421cffd06a008b4df8" & "518b550c8b4224ffd033c0e94a0100008b4df4518b550c8b42" & "1cffd06a008d4dec516a006a006a016a008b55fc528b450c8b" & "4810ffd185c07527837dfc0074218b55fc528b450c8b4820ff" & "d16a008b55f8528b450c8b4824ffd133c0e9f90000006a0468" & "001000008b55ec83c201526a008b45088b4808ffd18945e883" & "7de8007527837dfc0074218b55fc528b450c8b4820ffd16a00" & "8b55f8528b450c8b4824ffd133c0e9b10000008b55ec83c201" & "528b45e850e8cc06000083c408c745f000000000eb098b4df0" & "83c101894df08b55f03b55ec73128b45e80345f08b4d10034d" & "f08a118810ebdd8b4510508b4d088b5110ffd2508d45ec508b" & "4de8516a006a016a008b55fc528b450c8b4810ffd185c07524" & "837dfc00741e8b55fc528b450c8b4820ffd16a008b55f8528b" & "450c8b4824ffd133c0eb23837dfc00741a8b55fc528b450c8b" & "4820ffd16a008b55f8528b450c8b4824ffd18b45e85f5e8be5" & "5dc3558bec51e81000000061647661706933322e646c6c0000" & "00008b45088b08ffd18945fc837dfc00750732c0e99b010000" & "e818000000437279707441637175697265436f6e7465787441" & "000000008b55fc52e8d102000083c4088b4d0c8901e8100000" & "00437279707443726561746548617368008b55fc52e8ab0200" & "0083c4088b4d0c89410ce8100000004372797074496d706f72" & "744b657900008b55fc52e88402000083c4088b4d0c894104e8" & "1000000043727970744465726976654b657900008b55fc52e8" & "5d02000083c4088b4d0c894114e81000000043727970744861" & "7368446174610000008b55fc52e83602000083c4088b4d0c89" & "4108e8100000004372797074456e6372797074000000008b55" & "fc52e80f02000083c4088b4d0c894110e81400000043727970" & "7447657448617368506172616d0000008b55fc52e8e4010000" & "83c4088b4d0c894118e814000000437279707444657374726f" & "7948617368000000008b55fc52e8b901000083c4088b4d0c89" & "411ce810000000437279707444657374726f794b6579008b55" & "fc52e89201000083c4088b4d0c894120e81400000043727970" & "7452656c65617365436f6e74657874008b55fc52e867010000" & "83c4088b4d0c894124b0018be55dc3558bec83ec18e81c0000" & "006b00650072006e0065006c00330032002e0064006c006c00" & "00000000e88602000083c4048945fc837dfc00750732c0e915" & "010000e8100000004c6f61644c69627261727941000000008b" & "45fc50e8fb00000083c4088945f8837df800750732c0e9e400" & "0000e81000000047657450726f634164647265737300008b4d" & "fc51e8ca00000083c4088945f4837df400750732c0e9b30000" & "00e8100000005669727475616c416c6c6f63000000008b55fc" & "52e89900000083c4088945f0837df000750732c0e982000000" & "e80c0000005669727475616c46726565008b45fc50e86c0000" & "0083c4088945ec837dec00750432c0eb58e80c0000006c7374" & "726c656e41000000008b4dfc51e84200000083c4088945e883" & "7de800750432c0eb2e8b55088b45f889028b4d088b55f48951" & "048b45088b4df08948088b55088b45ec89420c8b4d088b55e8" & "895110b0018be55dc3558bec83ec3c8b45088945ec8b4dec0f" & "b71181fa4d5a0000740733c0e9350100008b45ec8b4d080348" & "3c894de4ba080000006bc2008b4de48d5401788955e88b45e8" & "833800750733c0e9080100008b4de88b118955e08b45e00345" & "088945f48b4df48b51188955dc8b45f48b481c894dd08b55f4" & "8b42208945d88b4df48b51248955d4c745f800000000eb098b" & "45f883c0018945f88b4df83b4ddc0f83b30000008b55080355" & "d88b45f88d0c82894dc88b55080355d48b45f88d0c42894dcc" & "8b55080355d08b45cc0fb7088d148a8955c48b45c88b4d0803" & "08894df0c745fc00000000c745fc00000000eb098b55fc83c2" & "018955fc8b450c0345fc0fbe0885c974278b55f00355fc0fbe" & "0285c0741a8b4d0c034dfc0fbe118b45f00345fc0fbe083bd1" & "7402eb02ebc38b550c0355fc0fbe0285c075198b4df0034dfc" & "0fbe1185d2750c8b45c48b4d0803088bc1eb07e938ffffff33" & "c08be55dc3558bec83ec34c745e40000000064a13000000089" & "45e48b4de48b510c8955d88b45d88b480c8b5010894dcc8955" & "d08b45cc8945d48b4dd4894de8837de8000f845a0100008b55" & "e8837a18000f844d0100008b45e8837830007502ebde8b4de8" & "8b51308955ecc745f000000000c745f000000000eb098b45f0" & "83c0018945f08b4df08b55080fb7044a85c00f84dd0000008b" & "4df08b55ec0fb7044a85c00f84cb0000008b4df08b55080fb7" & "044a83f85a7f378b4df08b55080fb7044a83f8417c288b4df0" & "8b55080fb7044a83c0208945e08b4df08b5508668b45e06689" & "044a668b4de066894dfeeb0e8b55f08b4508668b0c5066894d" & "fe668b55fe668955f88b45f08b4dec0fb7144183fa5a7f378b" & "45f08b4dec0fb7144183fa417c288b45f08b4dec0fb7144183" & "c2208955dc8b45f08b4dec668b55dc66891441668b45dc6689" & "45fceb0e8b4df08b55ec668b044a668945fc668b4dfc66894d" & "f40fb755f80fb745f43bd07402eb05e908ffffff8b4df08b55" & "080fb7044a85c075168b4df08b55ec0fb7044a85c075088b4d" & "e88b4118eb0f8b55e88b028945e8e99cfeffff33c08be55dc3" & "558bec518b45088945fc837d0c00741a8b4dfcc601008b55fc" & "83c2018955fc8b450c83e80189450cebe08b45088be55dc300" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000" LOCAL $OPCODE_BUF = DLLSTRUCTCREATE("byte[" & BINARYLEN($OPCODE) & "]") DLLSTRUCTSETDATA($OPCODE_BUF, 1, BINARY($OPCODE)) LOCAL $INPUT_BUF = DLLSTRUCTCREATE("byte[" & BINARYLEN($INPUT) + 1 & "]") DLLSTRUCTSETDATA($INPUT_BUF, 1, BINARY($INPUT)) LOCAL $IS_FLAG = DLLSTRUCTCREATE("byte[1]") DLLSTRUCTSETDATA($IS_FLAG, 1, BINARY("0x00")) DLLCALL("user32.dll", "none", "CallWindowProcA", "ptr", DLLSTRUCTGETPTR($OPCODE_BUF), "ptr", DLLSTRUCTGETPTR($INPUT_BUF), "ptr", DLLSTRUCTGETPTR($IS_FLAG), "int", 0, "int", 0) IF DLLSTRUCTGETDATA($IS_FLAG, 1) == "0x01" THEN MSGBOX(0, "", "Correct!") ELSE MSGBOX($MB_ICONERROR, "", "Incorrect!") ENDIF ENDFUNC ``` * Đây có thể là một kỹ thuật thường gặp trong `obfuscation` hoặc `crackme`: * **Shellcode** chứa trong biến `$OPCODE` (gần 20.000 byte). * Nó được nạp vào bộ nhớ, rồi gọi thực thi bằng `CallWindowProcA` như một đoạn mã máy. * Dữ liệu truyền vào **shellcode** là: * `$INPUT_BUF`: chuỗi người dùng nhập * `$IS_FLAG`: 1 byte dùng làm kết quả đầu ra (`0x01` = đúng flag, `0x00` = sai) * Sau khi **shellcode** chạy xong, nếu `$IS_FLAG = 0x01` thì **"Correct!"** được hiện ra. * Do mình mới chỉ xem mỗi hàm này, và đoán rằng con **shellcode** này sẽ thực hiện việc chính là check `input`. Và `DllCall` gọi **API** `CallWindowProcA` để thực thi **shellcode** check flag. Ý mình là như mình giới thiệu thì code có tận `16380` dòng, chưa phân tích thêm xem còn gì liên quan nữa không, nhưng mà tác dụng của con **shellcode** này thì chắc chắn 99% là quan trọng. Vậy mình ưu tiên phân tích nó, nếu cụt đường thì quay lại thui 🤣 * Lúc đầu mình có lấy thử 1,2 giá trị của mảng **OPCODE**, đem lên [defuse](https://defuse.ca/online-x86-assembler.htm#disassembly2) thì nó là mã máy thật. À không cái này mình kiểm tra cho chắc thôi :)) Nhưng mà **defuse** thì nó không nhận được **opcode** khá dài, nên nhặt từng cái 1 thì công nhân quá. Tiếp theo thì mình ~~thúc đít~~ **chatGPT** nó viết code chuyển từ **OPCODE** sang **shellcode**, nhưng mà trông không khả khi vì cho vào **IDA** trông nó bùi nhùi hỗn độn với lỗi địa chỉ tùm lum. Stuck quá nhưng mình nhận ra là code gọi **API** `CallWindowProcA()` để thực thi **shell**, vậy debug lại file `.exe` biết đâu nhặt được **shell** đỡ tốn sức hơn. * Được rồi, vào debug thôi. Lỗi đầu tiên mình gặp: ![image](https://hackmd.io/_uploads/By7CPnsExe.png) * Tìm tới thì mình thấy có cơ chế anti debug: ![image](https://hackmd.io/_uploads/SJXVO2s4ge.png) * Vậy chắc phải bypass cái này rồi. Patch opcode `0F 85 61 F2 03 00` thành `E9 01 00 00 00 90` nha. * Sau đó tìm tới **API** `CallWindowProcA()`: ![image](https://hackmd.io/_uploads/ry7lJ124ee.png) * Phần này phải vào debug chứ không đọc tĩnh mà tìm được, vì có thể **AutoIT** compile khác bình thường. ~~Thực ra mình đọc tĩnh không tìm được `CallWindowProcA()` nên có khả năng là như vậy~~ * Nhìn phần **Modules**: ![image](https://hackmd.io/_uploads/rknelJ24ll.png) ![image](https://hackmd.io/_uploads/r1F-xyh4xe.png) * Đến đoạn này, mình không biết là do máy mình hay do **IDA**, nhưng việc load các **API** để phục vụ debug nó cứ dở chứng, mình loay hoay 1 lúc (~~1 tiếng~~) nhưng không ổn định được, nên mình quyết định viết code python dump còn **shellcode** này ra, chấp nhận rủi ro khó đọc hoặc không phân tích được. ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES import string from gmpy2 import * import string import re OPCODE = ( """0x558bec83ec6c8d45d850e8aa05000083c4048d4d94518d55d8" & "52e8cb03000083c408e80c0000007573657233322e646c6c00" & "00ff55d88945f8837df8007505e9fb000000e80c0000004d65" & "7373616765426f7841008b45f850e8b306000083c4088945f0" & "8b4d0851ff55e883f81c740b8b550cc60200e9c4000000c645" & "bcf8c645bd50c645beccc645bfefc645c0e6c645c13cc645c2" & "35c645c396c645c41dc645c561c645c6aec645c7c0c645c8c5" & "c645c931c645cacec645cbb0c645cce7c645cd1dc645ceedc6" & "45cfbcc645d05dc645d181c645d269c645d38ac645d435c645" & "d574c645d657c645d7b68b4508508d4d94518d55d852e84700" & "000083c40c8945f4c745fc00000000eb098b45fc83c0018945" & "fc837dfc1c7d1f8b4df4034dfc0fb6118b45fc0fb64c05bc3b" & "d174088b550cc60200eb08ebd28b450cc600018be55dc3558b" & "ec83ec445657b90b000000e82c00000068747470733a2f2f77" & "77772e796f75747562652e636f6d2f77617463683f763d6451" & "773477395767586351005e8d7dbcf3a5c745f800000000c745" & "f400000000c745fc000000008b4510508b4d088b5110ffd289" & "45ec6a006a016a006a008d45f8508b4d0c8b11ffd285c07507" & "33c0e91b0200008d45f4506a006a0068048000008b4df8518b" & "550c8b420cffd085c075156a008b4df8518b550c8b4224ffd0" & "33c0e9e9010000837df40075156a008b4df8518b550c8b4224" & "ffd033c0e9ce0100006a008d4dbc518b55088b4210ffd0508d" & "4dbc518b55f4528b450c8b4808ffd185c075218b55f4528b45" & "0c8b481cffd16a008b55f8528b450c8b4824ffd133c0e98a01" & "00008d55fc526a008b45f45068016800008b4df8518b550c8b" & "4214ffd085c075218b4df4518b550c8b421cffd06a008b4df8" & "518b550c8b4224ffd033c0e94a0100008b4df4518b550c8b42" & "1cffd06a008d4dec516a006a006a016a008b55fc528b450c8b" & "4810ffd185c07527837dfc0074218b55fc528b450c8b4820ff" & "d16a008b55f8528b450c8b4824ffd133c0e9f90000006a0468" & "001000008b55ec83c201526a008b45088b4808ffd18945e883" & "7de8007527837dfc0074218b55fc528b450c8b4820ffd16a00" & "8b55f8528b450c8b4824ffd133c0e9b10000008b55ec83c201" & "528b45e850e8cc06000083c408c745f000000000eb098b4df0" & "83c101894df08b55f03b55ec73128b45e80345f08b4d10034d" & "f08a118810ebdd8b4510508b4d088b5110ffd2508d45ec508b" & "4de8516a006a016a008b55fc528b450c8b4810ffd185c07524" & "837dfc00741e8b55fc528b450c8b4820ffd16a008b55f8528b" & "450c8b4824ffd133c0eb23837dfc00741a8b55fc528b450c8b" & "4820ffd16a008b55f8528b450c8b4824ffd18b45e85f5e8be5" & "5dc3558bec51e81000000061647661706933322e646c6c0000" & "00008b45088b08ffd18945fc837dfc00750732c0e99b010000" & "e818000000437279707441637175697265436f6e7465787441" & "000000008b55fc52e8d102000083c4088b4d0c8901e8100000" & "00437279707443726561746548617368008b55fc52e8ab0200" & "0083c4088b4d0c89410ce8100000004372797074496d706f72" & "744b657900008b55fc52e88402000083c4088b4d0c894104e8" & "1000000043727970744465726976654b657900008b55fc52e8" & "5d02000083c4088b4d0c894114e81000000043727970744861" & "7368446174610000008b55fc52e83602000083c4088b4d0c89" & "4108e8100000004372797074456e6372797074000000008b55" & "fc52e80f02000083c4088b4d0c894110e81400000043727970" & "7447657448617368506172616d0000008b55fc52e8e4010000" & "83c4088b4d0c894118e814000000437279707444657374726f" & "7948617368000000008b55fc52e8b901000083c4088b4d0c89" & "411ce810000000437279707444657374726f794b6579008b55" & "fc52e89201000083c4088b4d0c894120e81400000043727970" & "7452656c65617365436f6e74657874008b55fc52e867010000" & "83c4088b4d0c894124b0018be55dc3558bec83ec18e81c0000" & "006b00650072006e0065006c00330032002e0064006c006c00" & "00000000e88602000083c4048945fc837dfc00750732c0e915" & "010000e8100000004c6f61644c69627261727941000000008b" & "45fc50e8fb00000083c4088945f8837df800750732c0e9e400" & "0000e81000000047657450726f634164647265737300008b4d" & "fc51e8ca00000083c4088945f4837df400750732c0e9b30000" & "00e8100000005669727475616c416c6c6f63000000008b55fc" & "52e89900000083c4088945f0837df000750732c0e982000000" & "e80c0000005669727475616c46726565008b45fc50e86c0000" & "0083c4088945ec837dec00750432c0eb58e80c0000006c7374" & "726c656e41000000008b4dfc51e84200000083c4088945e883" & "7de800750432c0eb2e8b55088b45f889028b4d088b55f48951" & "048b45088b4df08948088b55088b45ec89420c8b4d088b55e8" & "895110b0018be55dc3558bec83ec3c8b45088945ec8b4dec0f" & "b71181fa4d5a0000740733c0e9350100008b45ec8b4d080348" & "3c894de4ba080000006bc2008b4de48d5401788955e88b45e8" & "833800750733c0e9080100008b4de88b118955e08b45e00345" & "088945f48b4df48b51188955dc8b45f48b481c894dd08b55f4" & "8b42208945d88b4df48b51248955d4c745f800000000eb098b" & "45f883c0018945f88b4df83b4ddc0f83b30000008b55080355" & "d88b45f88d0c82894dc88b55080355d48b45f88d0c42894dcc" & "8b55080355d08b45cc0fb7088d148a8955c48b45c88b4d0803" & "08894df0c745fc00000000c745fc00000000eb098b55fc83c2" & "018955fc8b450c0345fc0fbe0885c974278b55f00355fc0fbe" & "0285c0741a8b4d0c034dfc0fbe118b45f00345fc0fbe083bd1" & "7402eb02ebc38b550c0355fc0fbe0285c075198b4df0034dfc" & "0fbe1185d2750c8b45c48b4d0803088bc1eb07e938ffffff33" & "c08be55dc3558bec83ec34c745e40000000064a13000000089" & "45e48b4de48b510c8955d88b45d88b480c8b5010894dcc8955" & "d08b45cc8945d48b4dd4894de8837de8000f845a0100008b55" & "e8837a18000f844d0100008b45e8837830007502ebde8b4de8" & "8b51308955ecc745f000000000c745f000000000eb098b45f0" & "83c0018945f08b4df08b55080fb7044a85c00f84dd0000008b" & "4df08b55ec0fb7044a85c00f84cb0000008b4df08b55080fb7" & "044a83f85a7f378b4df08b55080fb7044a83f8417c288b4df0" & "8b55080fb7044a83c0208945e08b4df08b5508668b45e06689" & "044a668b4de066894dfeeb0e8b55f08b4508668b0c5066894d" & "fe668b55fe668955f88b45f08b4dec0fb7144183fa5a7f378b" & "45f08b4dec0fb7144183fa417c288b45f08b4dec0fb7144183" & "c2208955dc8b45f08b4dec668b55dc66891441668b45dc6689" & "45fceb0e8b4df08b55ec668b044a668945fc668b4dfc66894d" & "f40fb755f80fb745f43bd07402eb05e908ffffff8b4df08b55" & "080fb7044a85c075168b4df08b55ec0fb7044a85c075088b4d" & "e88b4118eb0f8b55e88b028945e8e99cfeffff33c08be55dc3" & "558bec518b45088945fc837d0c00741a8b4dfcc601008b55fc" & "83c2018955fc8b450c83e80189450cebe08b45088be55dc300" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000000000" & "00000000000000000000000000000000000000000000""" ) hex_string = OPCODE.replace("0x", "").replace("\"", "").replace("&", "").replace("\n", "").replace(" ", "") try: shellcode_bytes = bytes.fromhex(hex_string) except ValueError as e: print(f"[!] Lỗi khi chuyển hex sang bytes: {e}") exit(1) # Bước 3: Ghi ra file nhị phân output_file = "shellcode.bin" with open(output_file, "wb") as f: f.write(shellcode_bytes) print(f"[+] Đã ghi shellcode vào file: {output_file}") ``` * Đúng như mình nghĩ, địa chỉ lung tung, không có entrypoint, đỏ tùm lum, ... Thôi thì mình cố gắng ngồi đọc, xem có thể tìm ra được gì nữa không. ![image](https://hackmd.io/_uploads/rkpoOzhNxg.png) * Mình thấy hàm `sub_181()` đây ~~có khả năng cao~~ là hàm mã hóa chính rồi. Tóm tắt hành vi nó thì nó sử dụng cấu trúc giống **CryptoAPI**: * Tạo context (`CryptAcquireContext`) * Tạo hash (`CryptCreateHash`) * Derive key (`CryptDeriveKey`) * Giải mã (`CryptDecrypt`) * Vậy mình phân tích kỹ hơn hàm `sub_181()` nha. Flow như sau: * 1. **CryptAcquireContext** ![image](https://hackmd.io/_uploads/HynHcM24gl.png) Đây là dòng gọi hàm tại offset +0 trong bảng hàm tại `a2 + 12`, kết quả lưu vào `a2 - 8` → đây là nơi **CryptAcquireContext** lưu **HCRYPTPROV**. Dòng này tương đương với: `CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_AES, 0); ` * 2. **CryptCreateHash** ![image](https://hackmd.io/_uploads/rkaacGnExg.png) Hàm tại offset +12 của `vtable` (tức `a2 + 12 + 12`). Truyền vào `hProv`, `ALG_ID = 0x800C` (= `CALG_SHA1` = `32772`) → đây là `SHA-1`. Tương đương với: `CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash);` * 3. **CryptHashData** ![image](https://hackmd.io/_uploads/Syzjsz2Vex.png) Gọi hàm từ `vtable[a2 + 8 + 16]` để lấy độ dài dữ liệu hash (có thể là độ dài IV hoặc block dữ liệu). Sau đó gọi `vtable[a2 + 12 + 8]` với `hHash`, `buffer`, `length`. Tương ứng với `CryptHashData(hHash, pbData, dwDataLen, 0); ` * 4. **CryptDeriveKey** ![image](https://hackmd.io/_uploads/HyTojznExx.png) Gọi hàm từ offset +20 trong `vtable[a2 + 12]` `ALG_ID = 26625 = 0x6801 = CALG_RC4` Tương đương với `CryptDeriveKey(hProv, CALG_RC4, hHash, 0, &hKey);` * 5. **CryptDecrypt** ![image](https://hackmd.io/_uploads/BkuOhMh4lx.png) Gọi từ `a2 + 12 + 16` → offset +16 trong `vtable` Truyền `hKey, Final = TRUE, buffer, length`. Tương đương với `CryptDecrypt(hKey, 0, TRUE, 0, pbData, &dwDataLen);` * Các hàm khác, như `sub_86B()` là `GetModuleHandleW`, `sub_711()` là `GetProcAddress(hModule, functionName);`. ![image](https://hackmd.io/_uploads/S13_emnVex.png) * Kiểu kiểu đó, vậy mình hiểu cái **shellcode** làm gì rồi (~~nếu như không hiểu nhầm~~), đơn giản nó chỉ nhận `input`, mã hóa **RC4** `input` với `key` được đem đi `hash`, rồi so sánh với `data` nào đó có sẵn thui à. * **flag_encrypted** ở đây nha. ![image](https://hackmd.io/_uploads/r13xn8Wrge.png) * Đầy đủ data rồi thì giải mã kiếm lại flag thui. ### Script ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES import string from gmpy2 import * import string from Crypto.Cipher import ARC4 from Crypto.Hash import SHA1 cipher = bytes([ 0xF8, 0x50, 0xCC, 0xEF, 0xE6, 0x3C, 0x35, 0x96, 0x1D, 0x61, 0xAE, 0xC0, 0xC5, 0x31, 0xCE, 0xB0, 0xE7, 0x1D, 0xED, 0xBC, 0x5D, 0x81, 0x69, 0x8A, 0x35, 0x74, 0x57, 0xB6 ]) password = b"https://www.youtube.com/watch?v=dQw4w9WgXcQ" sha1 = SHA1.new() sha1.update(password) key = sha1.digest() rc4_key = key[:16] cipher_rc4 = ARC4.new(rc4_key) plaintext = cipher_rc4.decrypt(cipher) print(plaintext) # b'KCSC{rC4_8uT_1T_L00k2_W31Rd}' ``` * **Flag: ~~KCSC{rC4_8uT_1T_L00k2_W31Rd}~~** ![image](https://hackmd.io/_uploads/HJCxoIbSeg.png) ## 1 vài lưu ý/ chỉnh sửa * Khi làm xong 3 bài này, thì mình có 1 thắc mắc trong đầu. Có lẽ nếu các bạn để ý kỹ thì cũng thấy rằng 3 bài **resolve API** khá nhiều, chi tiết hơn thì là các **Crypt API**. Vậy câu hỏi đặt ra là như này. Ừ thì nếu chỉ **resolve** có ~~in ít~~ **API** thì không sao, nhưng trong trường hợp hàng trăm hàng nghìn cái được gọi ra, thì làm sao để biết nó gọi **API** nào? Debug chay tới từng chỗ đó để xem ư? * Đương nhiên là không ai công nhân như vậy cho khổ. Ở đây mình có tìm hiểu được 1 cái khá là ngon, đó là **Appcall**, đây chính là câu trả lời cho thắc mắc ở trên. Mình sẽ lấy bài **VEH.exe** để làm ví dụ thực hiện nha. ### Bài **VEH.exe** * Bây giờ ý tưởng là duyệt qua toàn bộ `.text section` của binary, tìm lệnh `div rax`, truy ngược 17 byte trước đó để phân tích giá trị `r8` và `r9` sau đó gọi `Appcall.ResolveAPI_FNV1a(r8, r9)` để tới luôn handler xử lý. Mình code 1 đoạn ~~tạm bợ~~ như sau: ```python import ida_bytes import ida_segment import idc import idautils seg = ida_segment.get_segm_by_name(".text") if not seg: print("Không tìm thấy đoạn .text") exit() start = seg.start_ea end = seg.end_ea for ea in range(start, end): b = ida_bytes.get_bytes(ea, 3) if b == b'\x48\xf7\xf0': # div rax resolveStart = ea - 17 data = ida_bytes.get_bytes(resolveStart, 17) if data.startswith(b'\x49\xc7') and data.endswith(b'\x31\xc0'): r8 = int.from_bytes(data[3:7], byteorder='little') r9 = int.from_bytes(data[10:14], byteorder='little') try: offset = Appcall.ResolveAPI_FNV1a(r8, r9) name = idc.get_name(offset) print(f"[+] DIV 0 at {hex(ea)} → resolveAPI({hex(r8)}, {hex(r9)}) = {hex(offset)}: {name}") except Exception as e: print(f"[!] Failed resolveAPI at {hex(ea)}: {e}") ``` * Kết quả: ![image](https://hackmd.io/_uploads/HkoctTGrll.png) * Dựa vào đây thì hoàn toàn khiến việc debug dễ dàng hơn rất nhiều thay vì debug từng lệnh, xong dính exception, đúng không nào? 😶‍🌫️ ### Bài mixture.exe * Đối với bài này thì không có gì lấn cấn cả, chỉ là mình thấy rằng dùng **tools** dump con **cmd.exe** rất tiện lợi. Nhưng nếu thử dump tay thì sao? Liệu có trường hợp tools nó sai hoặc thiếu sót gì không? * Khi thực hiện dump tay, mình lựa chọn sử dụng **Process Hacker**. * Các bước như sau: Chuột phải vào `cmd.exe` trong `mixture.exe` ----> Chọn **Create dump file** ----> **Full** ----> Chọn vị trí lưu file `.dmp`. * Tuy nhiên file `.dmp` này chưa phải thứ mình muốn, phải extract ra file `.exe`. Để chắc chắn file `.dmp` của mình là đúng, thì thử string grep xem có header PE không bằng lệnh `strings <ten_file>.dmp | grep -a MZ`. Còn nếu có nhiều `MZ` thì `grep -aob 'MZ' <ten_file>.dmp`. * Sau đó thì mình viết tạm bợ 1 đoạn code python, tạo 1 folder để lưu các file `exe` extract được, cũng là để phòng trường hợp có nhiều chuỗi `MZ`. ```py from Crypto.Util.number import * from pwn import * from tqdm import tqdm from hashlib import sha256 from Crypto.Cipher import AES import string from gmpy2 import * import string import os import struct def is_valid_pe(data): if data[0:2] != b'MZ': return False if len(data) < 0x40: return False e_lfanew = struct.unpack_from("<I", data, 0x3C)[0] if e_lfanew >= len(data): return False if data[e_lfanew:e_lfanew+4] != b'PE\x00\x00': return False return True def extract_pes(dmp_path, output_dir, max_pe_size=2*1024*1024): with open(dmp_path, 'rb') as f: dmp_data = f.read() mz_offsets = [] for i in range(len(dmp_data) - 2): if dmp_data[i:i+2] == b'MZ': mz_offsets.append(i) print(f"[+] Found {len(mz_offsets)} potential MZ headers") if not os.path.exists(output_dir): os.makedirs(output_dir) count = 0 for offset in mz_offsets: chunk = dmp_data[offset:offset + max_pe_size] if is_valid_pe(chunk): out_path = os.path.join(output_dir, f'pe_extracted_{count:03}.exe') with open(out_path, 'wb') as out: out.write(chunk) print(f"[+] Valid PE found at offset {offset}, saved to {out_path}") count += 1 if count == 0: print("[-] No valid PE headers found.") else: print(f"[✓] Extracted {count} PE file(s).") # ======== Customize this =============== DMP_PATH = "cmd.exe_2025-07-02_14-58-36.dmp" OUTPUT_DIR = "extracted_pe" # ====================================== if __name__ == "__main__": extract_pes(DMP_PATH, OUTPUT_DIR) ``` * Ví dụ kết quả: ![image](https://hackmd.io/_uploads/BJwhl_fSel.png) * Được rồi, việc cuối cùng là xem 12 file `.exe` con nào là `cmd.exe` được hollowing. Do mình đã có file đó từ bên trên rồi, nên compare cho nhanh. Và có 1 vài chỗ vẫn phải make code lại. * Ngoài ra, nếu công nhân hơn, tức là dump trực tiếp từ **IDA**, còn cách trên dump từ **Process Hacker**, thì mình làm như này. Nhưng đây là cách làm cụ thể ứng với bài này thôi, tức là với bài khác, hay nay mai gặp **malware** thật thì cách nó inject vào không biết đâu mà lần. ( ~~Thực ra lúc đầu mình tính viết 1 script dump trực tiếp trên RAM, nhưng mà nó cứ lệch offset liên tục khi trước và sau debug, không fix được nên mình bỏ lun~~ :)) * Ở đây thì mình dùng do biết payload nó inject từ section **resource**, nên mình dùng **CFF Explorer** tìm địa chỉ của nó. * ![Screenshot 2025-07-02 194310](https://hackmd.io/_uploads/rJXQqoGSgl.png) `VA = Base + VirtualAddress = 0xCF0000 + 0x7000 = 0xCF7000` * Cắm breakpoint tại `WriteProcessMemory()`: ![image](https://hackmd.io/_uploads/By3hcjfrgl.png) * Thông tin [WriteProcessMemory](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-writeprocessmemory): ```py BOOL WriteProcessMemory( HANDLE hProcess, // [esp+4] LPVOID lpBaseAddress, // [esp+8] LPCVOID lpBuffer, // [esp+C] SIZE_T nSize, // [esp+10] SIZE_T *lpNumberOfBytesWritten // [esp+14] ); ``` * Dựa vào đó thì mình viết code python tìm `address` của `payload`: ```py import idaapi, idc esp = idc.get_reg_value("esp") proc_handle = idaapi.get_wide_dword(esp + 4) dest_addr = idaapi.get_wide_dword(esp + 8) src_buffer = idaapi.get_wide_dword(esp + 12) size = idaapi.get_wide_dword(esp + 16) print(f"[+] hProcess = 0x{proc_handle:X}") print(f"[+] dest_addr = 0x{dest_addr:X}") print(f"[+] src_buffer = 0x{src_buffer:X}") print(f"[+] size = 0x{size:X}") ``` ![Screenshot 2025-07-02 195120](https://hackmd.io/_uploads/r1dqiozHeg.png) * **Jump** tới: ![Screenshot 2025-07-02 195249](https://hackmd.io/_uploads/SJ973jzHlx.png) * Ngon luôn, `MZ` chính là header của PE file, tiến hành dump con này ra nào. ```py import ida_bytes import struct import idaapi, idc esp = idc.get_reg_value("esp") proc_handle = idaapi.get_wide_dword(esp + 4) dest_addr = idaapi.get_wide_dword(esp + 8) src_buffer = idaapi.get_wide_dword(esp + 12) size = idaapi.get_wide_dword(esp + 16) print(f"[+] hProcess = 0x{proc_handle:X}") print(f"[+] dest_addr = 0x{dest_addr:X}") print(f"[+] src_buffer = 0x{src_buffer:X}") print(f"[+] size = 0x{size:X}") src = src_buffer header = ida_bytes.get_bytes(src, 0x1000) e_lfanew = struct.unpack_from("<I", header, 0x3C)[0] size_of_image = struct.unpack_from("<I", header, e_lfanew + 0x50)[0] buf = ida_bytes.get_bytes(src, size_of_image) with open("dump.exe", "wb") as f: f.write(buf) print(f"[✓] Dumped PE image ({size_of_image} bytes) from 0x{src:X} → dump.exe") ``` * Mút luôn, con `dump.exe` này cũng là con mà chúng ta cần dump từ nãy tới giờ nha.