# 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.

## 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`

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.

asm

Đâ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.

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

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.

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)

<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}

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.

Thiết lập bỏ qua exception để cho chương trình xử lí đi vào khối except kia.

Thực hiện dump vùng nhớ rsp+17D0 ra được flag

Flag: DH{a7e5a49bd6035e09366036dc070026f25caa0b0f19e76d}