Try   HackMD

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. (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
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():

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 (CheckRemoteDebuggerPresent).

cũng hoạt động khi được spawn từ trình debugger.

Bool bDebuggerPresent; if(true == CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent) && bDebuggerPresent == true) ExitProcess(-1);
; 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():

  • Đượ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

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

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, 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).

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

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.

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

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

__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 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 ghiAH
- 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

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

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.

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:

tags: windows-internal anti-debug