# Anti-debug.Debug Flags (p1) # 1. Using Win32 API: ## 1.1 kernel32!IsDebuggerPresent(): Xác định xem tiến trình hiện tại có bị debugging bởi trình debugger (ollyDbg, x64dbg) không? Bằng cách kiểm tra cờ `BeingDebugged` trong [PEB](https://www.nirsoft.net/kernel_struct/vista/PEB.html). (Tiến trình bị debug sẽ được spawn từ tiến trình debugger). > API cũng hoạt động khi process đang bị attached > | isDebuggerPresent()| 32bit | 64bit | | -------| -------- | -------- | | Get **PEB** struct| mov eax, dword ptr fs:[30h] | mov rax, qword ptr gs:[60h] | | Get Being Debuged flag | movzx eax, byte ptr [eax+2h] | movzx eax, byte ptr [rax+2h] | |Return | ret | ret | ```cpp! BOOL IsDebuggerPresentPEB () { char IsDbgPresent = 0; __asm { mov eax, fs:[30h] // PEB struct, gs:[60h] in x64 mov al, [eax + 2h] // BeingDebugged flag mov IsDbgPresent, al } return IsDbgPresent; } ``` > Bypass: set BeingDebugged flag trong PEB thành 0 ## 1.2. [kernel32!CheckRemoteDebuggerPresent()](https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-checkremotedebuggerpresent): Kiểm tra xem có trình debugger nào (tiến trình khác trên cùng máy) attach tới tiến trình hiện tại (Check**Remote**DebuggerPresent). > cũng hoạt động khi được spawn từ trình debugger. ```c++= Bool bDebuggerPresent; if(true == CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent) && bDebuggerPresent == true) ExitProcess(-1); ``` ```asm= ; x64 lea rdx, [bDebuggerPresent] mov rcx, -1 ; current PID call CheckRemoteDebuggerPresent cmp [bDebuggerPresent], 1 jz _being_debugged ; ... _being_debugged: mov ecx, -1 call ExitProcess ``` > bypass: bypass được NtQueryInformationProcess ## 1.3. [ntdll!NtQueryInformationProcess()](https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess): - Được sử dụng để lấy nhiều loại thông tin từ một tiến trình tùy thuộc vào tham số `ProcessInformationClass` > Hàm này được gọi bên trong của `CheckRemoteDebuggerPresent` ```cpp= NTSTATUS WINAPI NtQueryInformationProcess( [in] HANDLE ProcessHandle, [in] PROCESSINFOCLASS ProcessInformationClass, [out] PVOID ProcessInformation, [in] ULONG ProcessInformationLength, [out, optional] PULONG ReturnLength ); ``` ### A. ProcessDebugPort (0x7h): - Object được sử dụng để giao tiếp giữa debugger và debuggee. - Khi tham số thứ 2 là cấu trúc enum [`ProcessDebugPort`](https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess#PROCESSDEBUGPORT) bằng **7**. Giá trị trả về sẽ được lưu vào tham số thứ 3. - Do thông tin được lấy từ kernel, nên khó sử dụng user-mode code để ngăn hàm này. > Retrieves a DWORD_PTR value that is the port number of the debugger for the process. A nonzero value indicates that the process is being run under the control of a ring 3 debugger. > hoạt động cả khi spawn từ debbuger hoặc bị attached > **NOTE: chỉ hoạt động với build x86** ```cpp= DWORD dwProcDbgPort = 0; NTSTATUS Status = NtQueryInformationProcess( GetCurrentProcess(), 7, // ProcessDbgPort &dwProcDbgPort, sizeof(DWORD), NULL); if (dwProcDbgPort == -1) { // 0xffffffff exit(-1); ``` > bypass: hook NtQueryInformationProcess và set giá trị trả về khác -1 ### B. ProcessDebugFlags: Tương tự với undocumented **ProcessDebugFlags** (= **0x1F**) class sẽ trả về giá trị trường **NoDebugInherit** trong cấu trúc **EPROCESS**. Cấu trúc kernel [EPROCESS](https://www.nirsoft.net/kernel_struct/vista/EPROCESS.html), bao gồm trường **NoDebugInherit**. Nếu giá trị trả về là **0** -> có sự tồn tại của trình debugger. (= **1** Nếu không bị attached/debugging). ```c++= DWORD dwProcDbgFlags = 0; NTSTATUS Status = NtQueryInformationProcess( GetCurrentProcess(), 0x1f, // ProcessDebugFlags &dwProcDbgFlags, sizeof(DWORD), NULL); if (Status == 0 && dwProcDbgFlags == 0) exit(-1); ``` > bypass: hook NtQueryInformationProcess và set giá trị trả về khác 0. ### C. ProcessDebugObjectHandle: Khi quá trình debug được bắt đầu, một đối tượng kernel được gọi là `debug object` được tạo. Có thể truy vấn tới **handle** của đối tượng này bởi undocument class **ProcessDebugObjectHandle** = **0x1E**. > **NOTE: chỉ hoạt động với build x86** ```cpp= DWORD dwReturnLength; HANDLE hProcessDebugObject = 0; const DWORD ProcessDebugObjectHandle = 0x1e; NTSTATUS status = NtQueryInformationProcess( GetCurrentProcess(), ProcessDebugObjectHandle, &hProcessDebugObject, sizeof(HANDLE), &dwReturnLength) if(status==0 && (hProcessDebugObjectHandle != 0)) ExitProcess(-1); ``` > bypass: hook NtQueryInformationProcess và set giá trị trả về = 0. ## 1.4. ntdll!RtlQueryProcessHeapInformation(): Có thể sử dụng để đọc các cờ heap của bộ nhớ tiến trình hiện tại. > build x86: > spawn từ VisualStudio -> HeapFlags = 2 -> not detect. > spawn từ debugger (IDA) -> HeapFlags = 0x40000062 -> detect. > attach từ debugger (IDA) -> HeapFlags = 2 -> not detect. ```cpp= #define HEAP_FLAGS 0x40000062 bool isDebug(){ ntdll:PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); if (!SUCCEEDED(ntdll::RtlQueryProcessHeapInformation((ntdll::PRTL_DEBUG_INFORMATION)pDebugBuffer))) return false; ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; return dwFlags & ~HEAP_FLAGS; } ``` ```asm= ; x32 push 0 push 0 ; FALSE call RtlCreateQueryDebugBuffer mov [ebp+pDebugBuffer], eax mov eax, [ebp+pDebugBuffer] push eax call RtlQueryProcessHeapInformation mov eax, [ebp+pDebugBuffer] mov ecx, [eax+38h] ; HeapInformation 70h in x64 mov edx, 40h imul eax, edx, 0 ; Heaps[0] mov ecx, [ecx+eax+8] ; Flags ; Flags field in window 64 bit: ; #define HEAP_GROWABLE 0x2 ; #define HEAP_TAIL_CHECKING_ENABLED 0x20 ; #define HEAP_FREE_CHECKING_ENABLED 0x40 ; #define HEAP_VALIDATE_PARAMETERS_ENABLED 0x40000000 cmp ecx, 0x40000062h je _being_debugged ``` > bypass: hook RtlQueryProcessHeapInformation và set giá trị trả về thành HEAP_GROWABLE (0x2h). ## 1.5. ntdll!RtlQueryProcessDebugInformation Đọc một số trường từ bộ nhớ tiến trình, bao gồm cả HeapFlags. > build x86: > spawn từ VisualStudio -> HeapFlags = 2 -> not detect. > spawn từ debugger (IDA) -> HeapFlags = 0x40000062 -> detect > attach từ debugger (IDA) -> HeapFlags = 2 -> not detect. ```cpp= bool isDebug(){ ntdll::PDEBUG_BUFFER pDebugBuffer = ntdll::RtlCreateQueryDebugBuffer(0, FALSE); if(!SUCCESS(ntdll::RtlQueryProcessDebugInformation(GetCurrentProcessId(), ntdll::PDI_HEAPS | ntdll::PDI_HEAP_BLOCKS, pDebugBuffer))) return false; ULONG dwFlags = ((ntdll::PRTL_PROCESS_HEAPS)pDebugBuffer->HeapInformation)->Heaps[0].Flags; return dwFlags & ~HEAP_GROWABLE; } ``` ```asm= ; x86 push 0 push 0 call RtlCreateQueryDebugBuffer mov [ebp+pDebugBuffer], eax push eax push 14h; PDI_HEAPS + PDI_HEAP_BLOCKS call GetCurrentProcessId push eax call RtlQueryProcessDebugInformation mov eax, [ebp+pDebugBuffer] mov ecx, [eax+38h] ; HeapInformation, 0x70 in x64 mov edx, 40h imul eax, edx, 0 mov ecx, [ecx+eax+8] ; Flags, 10h in x64 ; Flags ; HEAP_TAIL_CHECKING_ENABLED ; HEAP_FREE_CHECKING_ENABLED ; HEAP_VALIDATE_PARAMETERS_ENABLE cmp ecx, 40000062h je _being_debugged ``` > bypass: hook RtlQueryProcessHeapInformation và set giá trị trả về thành HEAP_GROWABLE (0x2h). ## 1.6 ntdll! [NtQuerySystemInformation](https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntquerysysteminformation): ```cpp __kernel_entry NTSTATUS NtQuerySystemInformation( [in] SYSTEM_INFORMATION_CLASS SystemInformationClass, [in, out] PVOID SystemInformation, [in] ULONG SystemInformationLength, [out, optional] PULONG ReturnLength ); ``` Tham số đầu tiên của hàm là `SYSTEM_INFORMATION_CLASS`, từ enumerate class [`SYSTEM_INFORMATION_CLASS`](https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi/system_information_class.htm) sẽ quyết định thông tin được truy vấn. Nếu thành công, kết quả sẽ được lưu tại biến được khai báo thủ công `SystemInformation`. Cái ta sử dụng sẽ là `0x23` (SystemKernelDebuggerInformation class). Class này sẽ trả về 2 flags: - `KdDebuggerEnabled` là thanh ghi`AH` - `KdDebuggerNotPresennt` là thanh ghi `AL`. -> Do đó, nếu thanh ghi `AH` = 0 -> có sự tồn tại của trình debugger. > Phương pháp này chỉ hoạt động trên phiên bản WinXP > ```cpp! enum {SystemKernelDebuggerInformation = 0x23}; typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION{ BOOLEAN DebuggerEnabled; BOOLEAN DebuggerNotPresent; } SYSTEM_KERNEL_DEBUGGER_INFORMATION, *PSYSTEM_KERNEL_DEBUGGER_INFORMATION; bool Check(){ NTSTATUS status; SYSTEM_KERNEL_DEBUGGER_INFORMATION SystemInfo; status = NtQuerySystemInformation( (SYSTEM_INFORMATION_CLASS) SystemKernelDebuggerInformation, &SystemInfo, sizeof(SystemInfo), NULL); return SUCCEEDED(status)?(SystemInfo.DebuggerEnabled && ! SystemInfo.DebuggerNotPresent): false; } ``` > bypass: hook RtlQueryProcessHeapInformation và set giá trị trả về DebuggerEnabled = 0 và DebuggerNotPresent = 1. # 2. Manual checks: ## 2.1. NtGlobalFlag - là một trường trong PEB (giá trị mặc định = 0) - offset = 0x68 on 32 bit - offset = 0xBC on 64 bit - Attach vào process không làm giá trị này thay đổi, nhưng process spawn từ debugger, các flags sẽ được set: - FLG_HEAP_ENABLE_TAIL_CHECK (0x10) - FLG_HEAP_ENABLE_FREE_CHECK (0x20) - FLG_HEAP_VALIDATE_PARAMETERS (0x40) > bypass: set flag này thành 0, bằng cách DLL injection. Hay sử dụng ScyllaHide plugin. ## 2.3. Heap Flags - 2 trường **Flags** và **ForceFlags** bị thay đổi phụ thuộc vào phiên bản Windows. Hai trường này có giá trị mặc định là 0 hoặc `HEAP_GROWABLE` (0x2). > Không hoạt động khi attach process. ```cpp= bool heap_flags() { BOOL bIsWow64; if (ERROR_SUCCESS != IsWow64Process(GetCurrentProcess(), &bIsWow64)) bIsWow64 = FALSE; #ifndef _WIN64 PPEB pPeb = (PPEB)__readfsdword(0x30); PVOID pHeapBase = !bIsWow64 ? (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x18)) : (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x1030)); DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() ? 0x40 : 0x0C; DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() ? 0x44 : 0x10; #else PPEB pPeb = (PPEB)__readgsqword(0x60); PVOID pHeapBase = (PVOID)(*(PDWORD_PTR)((PBYTE)pPeb + 0x30)); DWORD dwHeapFlagsOffset = IsWindowsVistaOrGreater() ? 0x70 : 0x14; DWORD dwHeapForceFlagsOffset = IsWindowsVistaOrGreater() ? 0x74 : 0x18; #endif // _WIN64 PDWORD pdwHeapFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapFlagsOffset); PDWORD pdwHeapForceFlags = (PDWORD)((PBYTE)pHeapBase + dwHeapForceFlagsOffset); return (*pdwHeapFlags & ~HEAP_GROWABLE) || (*pdwHeapForceFlags != 0); } ``` > bypass: set Flags thành HEAP_GROWABLE (0x2) và ForgeFlags thành 0. Sử dụng DLL Injection hay ScyllaHide plugin. ## 2.4 Heap Protection Khi vùng nhớ heap được khởi tạo, Nếu cờ `HEAP_TAIL_CHECKING_ENABLED`(0x20) được set trong **NtGlobalFlag**, **0xABABABAB** (32bit)/ **0xBABABABBABABAB** (64 bit) sẽ được set tại cuối khối heap được khởi tạo. Nếu cờ `HEAP_FREE_CHECKING_ENABLED` được set trong **NtGlobalFlag**, 0xFEEEFEEE sẽ được thêm nếu chuỗi bytes bổ sung được yêu cầu để fill vào không gian trống đến khối nhớ tiếp theo. > Không hoạt động khi attach process. ```cpp= PROCESS_HEAP_ENTRY HeapEntry = { 0 }; do { if (!HeapWalk(GetProcessHeap(), &HeapEntry)) return false; } while (HeapEntry.wFlags != PROCESS_HEAP_ENTRY_BUSY); PVOID pOverlapped = (PBYTE)HeapEntry.lpData + HeapEntry.cbData; return (DWORD)(*(PDWORD)pOverlapped) == 0xABABABAB; ``` # refs: - https://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software - http://www.cs.utah.edu/~aburtsev/malw-sem/slides/02-anti-debugging.pdf - https://anti-reversing.com/Downloads/Anti-Reversing/The_Ultimate_Anti-Reversing_Reference.pdf - https://anti-debug.checkpoint.com ###### tags: `windows-internal` `anti-debug`