# IHateDebugger Chall: IHateDebugger.exe ## TLS Callback <details> <summary>sub_7FF6591F4DC0 (NtQueryInformationProcess)</summary> ```cpp= bool sub_7FF6591F4DC0() { HANDLE CurrentProcess; // rax HANDLE v2; // rax HANDLE v3; // rax NTSTATUS (__stdcall *NtQueryInformationProcess)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); // [rsp+38h] [rbp-40h] HMODULE hModule; // [rsp+40h] [rbp-38h] __int64 v6; // [rsp+48h] [rbp-30h] BYREF int v7; // [rsp+50h] [rbp-28h] BYREF int v8; // [rsp+54h] [rbp-24h] BYREF __int64 v9; // [rsp+58h] [rbp-20h] BYREF hModule = GetModuleHandleW(L"ntdll.dll"); if ( !hModule ) return 0; NtQueryInformationProcess = (NTSTATUS (__stdcall *)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG))GetProcAddress(hModule, "NtQueryInformationProcess"); if ( !NtQueryInformationProcess ) return 0; v9 = 0LL; v7 = 0; CurrentProcess = GetCurrentProcess(); if ( !((unsigned int (__fastcall *)(HANDLE, __int64, __int64 *, __int64, int *))NtQueryInformationProcess)( CurrentProcess, 7LL, &v9, 8LL, &v7) && v9 ) { return 1; } v8 = 0; v2 = GetCurrentProcess(); if ( !((unsigned int (__fastcall *)(HANDLE, __int64, int *, __int64, int *))NtQueryInformationProcess)( v2, 0x1FLL, &v8, 4LL, &v7) && !v8 ) { return 1; } v6 = 0LL; v3 = GetCurrentProcess(); return !((unsigned int (__fastcall *)(HANDLE, __int64, __int64 *, __int64, int *))NtQueryInformationProcess)( v3, 0x1ELL, &v6, 8LL, &v7) && v6; } ``` </details> TlsCallback gọi hàm này Hàm này thực hiện 3 lần kiểm tra liên tiếp với 3 tham số khác nhau của NtQueryInformationProcess. Nếu bất kì lần nào phát hiện debugger nó sẽ trả về 1. ``` ProcessDebugPort(0x7) ProcessDebugObjectHandle(0x1E) ProcessDebugFlags(0x1F) ``` Mình tham khảo [tại đây (mục 1.2).](https://hackmd.io/@Zupp/Task2#12-NtQueryInformationProcess) SAu đấy TlsCallback 0 sẽ kiểm tra điều kiện và gọi ExitProcess. ```cpp= _BOOL8 TlsCallback_0() { _BOOL8 result; // rax result = sub_7FF739DA4DC0(); // = 1 (có debugger) if ( result ) ExitProcess(0xDEADu); return result; } ``` Mình bypass bằng cách chuyển thanh ghi `rax về 0` với 2 lần TlsCallback 0 và 1 (tlscallback 1 gọi lại tlscallback 0). Nhưng sau đấy thấy bất tiện nên mình sử dụng Scylla Hide để xử lí NtQueryInformationProcess. ![image](https://hackmd.io/_uploads/HJ4dlZAXZl.png) ## Main <details> <summary>main</summary> ```cpp= int __fastcall main(int argc, const char **argv, const char **envp) { __int64 v4; // rax _BYTE v5[32]; // [rsp+30h] [rbp-38h] BYREF sub_7FF739DA4000((struct std::_Container_base0 *)v5, (__int64)"DH{this_is_a_fake_flag}"); if ( NtCurrentPeb()->BeingDebugged ) { sub_7FF739DA11F0(std::cout, (__int64)"[!] Debugger detected!\n"); sub_7FF739DA4550((__int64)v5); return 1; } else { sub_7FF739DA4CA0(); v4 = sub_7FF739DA11A0(std::cout, (__int64)v5); std::ostream::operator<<(v4, sub_7FF739DA32C0); sub_7FF739DA11F0(std::cout, (__int64)"[+] Program finished.\n"); sub_7FF739DA4550((__int64)v5); return 0; } } ``` </details> Hàm main có điều kiện kiểm tra cấu trúc PEB để xem cờ BeingDebugged có được bật lên 1 hay không. Vì nãy mình sử dụng Scyllahide nên cũng có thể dễ dàng bypass cái này với lựa chọn Being Debugged. Hoặc mình có thể đặt thanh ghi rax thành 0 trước lệnh `test eax, eax` ![image](https://hackmd.io/_uploads/S1ylzZRXbl.png) Logic chính của main là nếu không phát hiện debugger thì nhảy sang nhánh else gọi hàm này. ![image](https://hackmd.io/_uploads/HysjzW0QZl.png) asm ![image](https://hackmd.io/_uploads/rkWtQWRmZg.png) Đây là kĩ thuật antidebug dựa trên exeption sử dụng seh. Chương trình đặt trạng thái mặc định là 1 (bị debug) sau đó gọi RaiseException với mã lỗi 0xE001DEAD. Nếu có debugger thì debugger sẽ dừng tại đây để cho mình xử lí, nếu không thì chương trình tự động tìm xem ai xử lí lỗi này và thấy khối except ở dưới, khối này đặt trạng thái về 0 (an toàn). Sau khi trace hàm trên trong x64dbg thì thấy ```asm= 00007FF739DA4CA0 | mov qword ptr ss:[rsp+8],rcx | 00007FF739DA4CA5 | sub rsp,58 | 00007FF739DA4CA9 | mov rax,qword ptr ds:[7FF739DAC040] | 00007FF739DA4CB0 | xor rax,rsp | 00007FF739DA4CB3 | mov qword ptr ss:[rsp+48],rax | 00007FF739DA4CB8 | mov byte ptr ss:[rsp+20],1 | 00007FF739DA4CBD | xor r9d,r9d | 00007FF739DA4CC0 | xor r8d,r8d | 00007FF739DA4CC3 | xor edx,edx | 00007FF739DA4CC5 | mov ecx,E001DEAD | 00007FF739DA4CCA | call qword ptr ds:[<RaiseException>] | 00007FF739DA4CD0 | nop | 00007FF739DA4CD1 | jmp ihatedebugger.7FF739DA4CD8 | 00007FF739DA4CD3 | mov byte ptr ss:[rsp+20],0 | 00007FF739DA4CD8 | movzx eax,byte ptr ss:[rsp+20] | 00007FF739DA4CDD | test eax,eax | 00007FF739DA4CDF | je ihatedebugger.7FF739DA4CFA | 00007FF739DA4CE1 | lea rdx,qword ptr ds:[7FF739DA94A0] | 00007FF739DA94A0:"[!] Debugger detected!\n" 00007FF739DA4CE8 | mov rcx,qword ptr ds:[<class std::basic_ostream | rcx:&"DH{this_is_a_fake_flag}" 00007FF739DA4CEF | call ihatedebugger.7FF739DA11F0 | 00007FF739DA4CF4 | nop | 00007FF739DA4CF5 | jmp ihatedebugger.7FF739DA4DA8 | 00007FF739DA4CFA | lea rax,qword ptr ds:[7FF739DAF000] | 00007FF739DA4D01 | mov qword ptr ss:[rsp+30],rax | 00007FF739DA4D06 | lea rax,qword ptr ds:[7FF739DAF02C] | 00007FF739DA4D0D | mov qword ptr ss:[rsp+38],rax | 00007FF739DA4D12 | mov rax,qword ptr ss:[rsp+30] | 00007FF739DA4D17 | mov rcx,qword ptr ss:[rsp+38] | 00007FF739DA4D1C | sub rcx,rax | rcx:&"DH{this_is_a_fake_flag}" 00007FF739DA4D1F | mov rax,rcx | rcx:&"DH{this_is_a_fake_flag}" 00007FF739DA4D22 | mov qword ptr ss:[rsp+28],rax | 00007FF739DA4D27 | cmp qword ptr ss:[rsp+28],0 | 00007FF739DA4D2D | jne ihatedebugger.7FF739DA4D31 | 00007FF739DA4D2F | jmp ihatedebugger.7FF739DA4DA8 | 00007FF739DA4D31 | mov dword ptr ss:[rsp+40],0 | 00007FF739DA4D39 | lea r9,qword ptr ss:[rsp+40] | 00007FF739DA4D3E | mov r8d,40 | 40:'@' 00007FF739DA4D44 | mov rdx,qword ptr ss:[rsp+28] | 00007FF739DA4D49 | mov rcx,qword ptr ss:[rsp+30] | 00007FF739DA4D4E | call qword ptr ds:[<VirtualProtect>] | 00007FF739DA4D54 | test eax,eax | 00007FF739DA4D56 | jne ihatedebugger.7FF739DA4D5A | 00007FF739DA4D58 | jmp ihatedebugger.7FF739DA4DA8 | 00007FF739DA4D5A | mov byte ptr ss:[rsp+21],AB | 00007FF739DA4D5F | mov r8b,AB | 00007FF739DA4D62 | mov rdx,qword ptr ss:[rsp+28] | 00007FF739DA4D67 | mov rcx,qword ptr ss:[rsp+30] | 00007FF739DA4D6C | call ihatedebugger.7FF739DA54A0 | 00007FF739DA4D71 | mov rcx,qword ptr ss:[rsp+60] | [rsp+60]:&"DH{this_is_a_fake_flag}" 00007FF739DA4D76 | call ihatedebugger.7FF739DAF010 | hàm gọi gen_flag 00007FF739DA4D7B | mov r8b,AB | 00007FF739DA4D7E | mov rdx,qword ptr ss:[rsp+28] | 00007FF739DA4D83 | mov rcx,qword ptr ss:[rsp+30] | 00007FF739DA4D88 | call ihatedebugger.7FF739DA54A0 | 00007FF739DA4D8D | lea r9,qword ptr ss:[rsp+40] | 00007FF739DA4D92 | mov r8d,dword ptr ss:[rsp+40] | 00007FF739DA4D97 | mov rdx,qword ptr ss:[rsp+28] | 00007FF739DA4D9C | mov rcx,qword ptr ss:[rsp+30] | 00007FF739DA4DA1 | call qword ptr ds:[<VirtualProtect>] | 00007FF739DA4DA7 | nop | 00007FF739DA4DA8 | mov rcx,qword ptr ss:[rsp+48] | 00007FF739DA4DAD | xor rcx,rsp | 00007FF739DA4DB0 | call ihatedebugger.7FF739DA7680 | 00007FF739DA4DB5 | add rsp,58 | 00007FF739DA4DB9 | ret | ``` Tại địa chỉ 00007FF739DA4D76 có thực hiện gọi hàm 7FF739DAF010 (hàm này bên ida là code rác, nhưng trong 4CA0 thì đã được chỉnh sửa (mình đoán thế tại so sánh ida với x64dbg thấy khác, và cả hàm này có gọi [VirtualProtect](https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualprotect) để chỉnh sửa quyền đọc ghi), hàm 7FF739DAF010 này lại tiếp tục gọi hàm 7FF739DA4F10. ![image](https://hackmd.io/_uploads/H1kxxfA7Wx.png) Qua ida để xem mã giả thì hàm này thực hiện tạo flag. Mình cũng đồng thời mò được hàm sha256 dựa trên các hàm của thư viện bcrypt. <details> <summary>sha256</summary> ```cpp= _BOOL8 __fastcall sha256(UCHAR *a1, ULONG a2, UCHAR *a3) { HANDLE ProcessHeap; // rax HANDLE v4; // rax bool v6; // [rsp+40h] [rbp-48h] UCHAR *pbHashObject; // [rsp+48h] [rbp-40h] SIZE_T dwBytes; // [rsp+50h] [rbp-38h] UCHAR pbOutput[4]; // [rsp+58h] [rbp-30h] BYREF BCRYPT_HASH_HANDLE phHash; // [rsp+60h] [rbp-28h] BYREF BCRYPT_ALG_HANDLE phAlgorithm; // [rsp+68h] [rbp-20h] BYREF ULONG pcbResult; // [rsp+70h] [rbp-18h] BYREF phAlgorithm = 0LL; phHash = 0LL; *(_DWORD *)pbOutput = 0; pcbResult = 0; pbHashObject = 0LL; v6 = 0; if ( BCryptOpenAlgorithmProvider(&phAlgorithm, L"SHA256", 0LL, 0) >= 0 && BCryptGetProperty(phAlgorithm, L"ObjectLength", pbOutput, 4u, &pcbResult, 0) >= 0 ) { dwBytes = *(unsigned int *)pbOutput; ProcessHeap = GetProcessHeap(); pbHashObject = (UCHAR *)HeapAlloc(ProcessHeap, 0, dwBytes); if ( pbHashObject ) { if ( BCryptCreateHash(phAlgorithm, &phHash, pbHashObject, *(ULONG *)pbOutput, 0LL, 0, 0) >= 0 && BCryptHashData(phHash, a1, a2, 0) >= 0 ) { v6 = BCryptFinishHash(phHash, a3, 0x20u, 0) >= 0; } } } if ( phHash ) BCryptDestroyHash(phHash); if ( pbHashObject ) { v4 = GetProcessHeap(); HeapFree(v4, 0, pbHashObject); } if ( phAlgorithm ) BCryptCloseAlgorithmProvider(phAlgorithm, 0); return v6; } ``` </details> Thấy có 2 hàm gọi đến sha256 ![image](https://hackmd.io/_uploads/HJMrO-Cm-g.png) Một hàm là kiểm tra tính toàn vẹn của file trên ổ đĩa (đọc nội dung file trên đĩa cứng để đảm bảo file không bị patch) và hàm còn lại là hàm tính key. ![image](https://hackmd.io/_uploads/B1R7d-R7Wg.png) Bởi hàm key này được hàm gen flag gọi (đây cũng là hàm 00007FF739DA4F10 hồi nãy mình tìm được) ![image](https://hackmd.io/_uploads/Sk0nezCmZg.png) <details> <summary>gen_flag</summary> ```cpp= int __fastcall gen_flag(__int64 a1) { const char *v2; // rax unsigned __int8 v3; // [rsp+20h] [rbp-27C8h] char v4; // [rsp+20h] [rbp-27C8h] int j; // [rsp+24h] [rbp-27C4h] int m; // [rsp+28h] [rbp-27C0h] char v7; // [rsp+2Ch] [rbp-27BCh] char v8; // [rsp+2Dh] [rbp-27BBh] char v9; // [rsp+2Eh] [rbp-27BAh] char v10; // [rsp+2Fh] [rbp-27B9h] int i; // [rsp+34h] [rbp-27B4h] __int64 v12; // [rsp+38h] [rbp-27B0h] int v13; // [rsp+40h] [rbp-27A8h] size_t k; // [rsp+48h] [rbp-27A0h] char v15; // [rsp+54h] [rbp-2794h] size_t v16; // [rsp+70h] [rbp-2778h] _BYTE v17[384]; // [rsp+80h] [rbp-2768h] _QWORD v18[2]; // [rsp+200h] [rbp-25E8h] BYREF UCHAR v19[5008]; // [rsp+210h] [rbp-25D8h] BYREF UCHAR MasterKey[32]; // [rsp+15A0h] [rbp-1248h] BYREF _BYTE SBox[256]; // [rsp+15C0h] [rbp-1228h] BYREF char v22[16]; // [rsp+16C0h] [rbp-1128h] BYREF char Input_String[256]; // [rsp+16D0h] [rbp-1118h] BYREF char Output_Flag[4096]; // [rsp+17D0h] [rbp-1018h] BYREF if ( !sub_7FF739DA63D0(MasterKey) ) return puts("Key derivation failed (tampered binary?)"); Key_expansion((__int64)v19, MasterKey); Sbox_init((__int64)SBox, (__int64)v22, 0); Sbox_scramble((__int64)SBox, (__int64)v22, (__int64)v19); unknown_libname_20(v18, (__int64)MasterKey); v7 = MasterKey[13] ^ MasterKey[0] ^ 0xA5; v8 = (unsigned int)get_random_byte(v18, 1) % 0xFD + 1; v9 = MasterKey[7] | 1; v10 = MasterKey[5] % 7 + 1; v17[0] = v7; for ( i = 1; i < 256; ++i ) v17[i] = ((int)(unsigned __int8)v17[i - 1] >> v10) ^ (v9 + v8 * v17[i - 1]); for ( j = 0; j < 128; ++j ) { v15 = v17[j]; v17[j + 256] = ((MasterKey[j & 0x1F] ^ j) + v17[(((unsigned int)get_random_byte(v18, 3) % 7 + 2) * j + 1) % 0x100]) ^ v15; } memset(Output_Flag, 0, sizeof(Output_Flag)); v2 = (const char *)sub_7FF739DA62B0(a1); strcpy_s(Input_String, 0x100uLL, v2); v16 = strlen(Input_String); v12 = sprintf(Output_Flag, (const char *const)0x1000, "DH{"); for ( k = 0LL; k < v16; ++k ) { v3 = Input_String[k]; for ( m = 0; m < 16; ++m ) { v4 = v17[(unsigned __int8)SBox[((unsigned __int8)m + (unsigned __int8)k + v3) % 256]] ^ v3; v3 = v17[(SBox[(unsigned __int8)(k ^ v4)] & 0x7F) + 256] ^ v4; v13 = m % 4; if ( m % 4 ) { switch ( v13 ) { case 1: v3 += MasterKey[(3 * (_BYTE)m) & 0x1F] | 1; break; case 2: v3 = ~v3; break; case 3: v3 -= MasterKey[((_BYTE)k + 5 * (_BYTE)m) & 0x1F] % 11; break; } } else { v3 = rotate_left8(v3, (unsigned __int8)(MasterKey[((_BYTE)k + (_BYTE)m) & 0x1F] % 7) + 1); } } v12 += sprintf(&Output_Flag[v12], (const char *const)(4096 - v12), "%02x", v3); } return sprintf(&Output_Flag[v12], (const char *const)(4096 - v12), "}"); } ``` </details> Hàm này là thuật toán mã hóa tùy chỉnh, có tạo và xáo trộn bảng sbox, biến v17 là mảng 384 byte được chạy qua vài vòng lặp với các phép toán bitwise. Input: `v2 = sub_7FF739DA62B0(a1);` trả về chuỗi DH{this_is_a_fake_flag} nên input là DH{this_is_a_fake_flag} ![image](https://hackmd.io/_uploads/rkjoi-Am-g.png) Sau khi chương trình tính toán xong flag thì nối dấu ngoặc đóng vào. sprintf(&Output_Flag[v12], (const char *const)(4096 - v12), "}"); Vậy mình sẽ thực hiện đặt bp tại cuối hàm để săn flag. ![image](https://hackmd.io/_uploads/rJhw6ZCQWe.png) Thiết lập bỏ qua exception để cho chương trình xử lí đi vào khối except kia. ![image](https://hackmd.io/_uploads/ryXzCZRX-l.png) Thực hiện dump vùng nhớ rsp+17D0 ra được flag ![image](https://hackmd.io/_uploads/r1LH0Z07be.png) Flag: DH{a7e5a49bd6035e09366036dc070026f25caa0b0f19e76d}