# Anti-Debugging
## Debug Flags
Debug Flags dựa trên trạng thái của những flag đặc biệt mà có thể chỉ ra rằng tiến trình đó đang bị debug
### 1. Using Win32 API
#### 1.1. IsDebuggerPresent()
[IsDebuggerPresent()](https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-isdebuggerpresent) trả về giá trị khác 0 thì process đang bị debug bởi một `user-mode debugger`
Assembly Code x86
```masm
call IsDebuggerPresent
test al, al
jne being_debugged
...
being_debugged:
push 1
call ExitProcess
```
#### 1.2. CheckRemoteDebuggerPresent()
Hàm [CheckRemoteDebuggerPresent()](https://learn.microsoft.com/en-us/windows/win32/api/debugapi/nf-debugapi-checkremotedebuggerpresent) sẽ đặt trường `pbDebuggerPresent` thành **True** hoặc **False** dựa trên trạng thái của process được chỉ tới bằng con trỏ `hProcess`. Nếu là True => Process bị debug
Assembly Code x86
```masm
lea eax, [bDebuggerPresent]
push eax
push -1 ; GetCurrentProcess()
call CheckRemoteDebuggerPresent
cmp [bDebuggerPresent], 1
jz being_debugged
...
being_debugged:
push -1
call ExitProcess
```
#### 1.3. NtQueryInformationProcess()
Hàm [NtQueryInformationProcess()](https://learn.microsoft.com/en-us/windows/win32/api/winternl/nf-winternl-ntqueryinformationprocess) lấy các thông tin cần thiết trong một process. Trường `ProcessInformationClass` sẽ chỉ ra thông tin chúng ta muốn lấy trong process và đưa thông tin đó vào trường `ProcessInformation`
##### 1.3.1. ProcessDebugPort
Để biết process có bị debug hay không. Chúng ta sẽ đặt trường `ProcessInformationClass` có giá trị là `ProcessDebugPort (7)`. Sau khi gọi hàm `NtQueryInformationProcess()` thì ta kiểm tra trường `ProcessInformation` để biết xem process có bị debug hay không. Nếu giá trị = -1 => Process bị debug
Assembly Code x86
```masm
lea eax, [dwReturned]
push eax ; ReturnLength
push 4 ; ProcessInformationLength
lea ecx, [dwProcessDebugPort]
push ecx ; ProcessInformation
push 7 ; ProcessInformationClass
push -1 ; ProcessHandle
call NtQueryInformationProcess
inc dword ptr [dwProcessDebugPort]
jz being_debugged
...
being_debugged:
push -1
call ExitProcess
```
##### 1.3.2. ProcessDebugFlags
Trường `ProcessDebugFlags (0x1f)` là một lớp không có trong tài liệu. Khi sử dụng trường này, nó sẽ lấy giá trị từ trường `NoDebugInherit` của structure [EPROCESS](https://www.nirsoft.net/kernel_struct/vista/EPROCESS.html#:~:text=ULONG%20NoDebugInherit%3A%201%3B). Nếu giá trị trả về = 0 => Process bị debug
Assembly Code x86
```masm
lea eax, [dwReturned]
push eax ; ReturnLength
push 4 ; ProcessInformationLength
lea ecx, [dwProcessDebugPort]
push ecx ; ProcessInformation
push 1Fh ; ProcessInformationClass
push -1 ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [dwProcessDebugPort], 0
jz being_debugged
...
being_debugged:
push -1
call ExitProcess
```
##### 1.3.3. ProcessDebugObjectHandle
Khi bắt đầu debug, kernel object `debug object` được khởi tạo. Điều đó giúp ta kiểm tra được process có bị debug hay không qua lớp `ProcessDebugObjectHandle (0x1e)`. Nếu giá trị trả về != 0 thì process bị debug
Assembly Code x86
```masm
lea eax, [dwReturned]
push eax ; ReturnLength
push 4 ; ProcessInformationLength
lea ecx, [hProcessDebugObject]
push ecx ; ProcessInformation
push 1Eh ; ProcessInformationClass
push -1 ; ProcessHandle
call NtQueryInformationProcess
cmp dword ptr [hProcessDebugObject], 0
jnz being_debugged
...
being_debugged:
push -1
call ExitProcess
```
#### 1.4. RtlQueryProcessHeapInformation()
Hàm `ntdll!RtlQueryProcessDebugInformation()` không phải hàm api chính thức. Khi gọi hàm, trong struct [RTL_DEBUG_INFORMATION](https://docs.rs/ntapi/latest/ntapi/ntrtl/struct.RTL_DEBUG_INFORMATION.html#structfield.ProcessHeap) chứa [RTL_PROCESS_HEAPS](https://docs.rs/ntapi/latest/ntapi/ntrtl/struct.RTL_PROCESS_HEAPS.html), rồi trong struct đó có [RTL_HEAP_INFORMATION](https://docs.rs/ntapi/latest/ntapi/ntrtl/struct.RTL_HEAP_INFORMATION.html#structfield.Flags) và ta tìm được trường `Flags`. Nếu Flags != 2 thì process bị debug
C/C++ Code
```cpp
bool Check()
{
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_GROWABLE; # HEAP_GROWABLE = 2. So if return != 0 => process being debugged
}
```
#### 1.5. RtlQueryProcessDebugInformation()
Hàm [RtlQueryProcessDebugInformation()](https://docs.rs/ntapi/latest/ntapi/ntrtl/fn.RtlQueryProcessDebugInformation.html) đọc các trường nhất định thậm chí có cả trường `flags` trong struct [RTL_DEBUG_INFORMATION](https://docs.rs/ntapi/latest/ntapi/ntrtl/struct.RTL_DEBUG_INFORMATION.html)
#### 1.6. NtQuerySystemInformation()
Chúng ta gọi hàm [NtQuerySystemInformation()](https://docs.rs/ntapi/latest/ntapi/ntexapi/fn.NtQuerySystemInformation.html) có tham số đầu vào là SYSTEM_INFORMATION_CLASS = lớp SystemKernelDebuggerInformation (0x23). Đây là lớp không có trong tài liệu. Khi đó trường `Systemlnformation` là con trỏ tới struct [SYSTEM_KERNEL_DEBUGGER_INFORMATION](https://docs.rs/ntapi/latest/ntapi/ntexapi/struct.SYSTEM_KERNEL_DEBUGGER_INFORMATION.html). Nếu trường `KernelDebuggerEnabled` = **True** và `KernelDebuggerNotPresent` = **False** , thì process bị debug
### 2. Manual checks
#### 2.1. PEB!BeingDebugged Flag
Kiểm tra cờ `BeingDebugged` của `PEB` (offset 0x02), nếu giá trị != 0 thì process bị debug
Assembly Code x86
```masm!
mov eax, fs:[30h]
cmp byte ptr [eax+2], 0
jne being_debugged
```
#### 2.2. NtGlobalFlag
Kiểm tra trường `NtGlobalFlag` của `PEB` (offset: x86: 0x68; x64: 0xBC). Nếu process được tạo bởi trình gỡ lỗi thì những cờ sau được đặt:
- FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
- FLG_HEAP_ENABLE_FREE_CHECK (0x20)
- FLG_HEAP_VALIDATE_PARAMETERS (0x40)
Nếu giá trị trả về = 0x70 thì process bị debug
Assembly Code x86
```masm
mov eax, fs:[30h]
mov al, [eax+68h]
and al, 70h
cmp al, 70h
jz being_debugged
```
#### 2.3. Heap Flags
Trong heap có 2 trường là `Flags` và `ForceFlag`. Giá trị của `Flags` thường = `HEAP_GROWABLE (2)`; `ForceFlags` = 0.
Vậy nên nếu 2 trường này có giá trị khác thì process bị debug
#### 2.4. Heap Protection
Nếu cờ `HEAP_TAIL_CHECKING_ENABLED` được đặt trong `NtGlobalFlag` thì 8 byte 0xAB (với Win 32bit) hoặc 16 byte 0xAB (với Win 64bit) sẽ được thêm vào cuối heap block. Vậy nên nếu thấy ở cuối heap block có thì chương trình bị debug
Ngoài ra, nếu chương trình bị debug,
thì nếu cần điềm thêm byte vào cuối heap block, chương trình sẽ thêm các byte 0xFEEEFEEE đến `memory block` tiếp theo
C/C++ Code
```cpp
bool Check()
{
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);
}
```
#### 2.5. Check KUSER_SHARED_DATA structure
Struct này nằm cố định ở 0x7ffe02d4 (Thực chất là 0x7ffe0000 + 0x2d4 ). Nếu giá trị ở đó là 0x01 hoặc 0x02 thì process bị debug
C/C++ Code
```cpp
bool check_kuser_shared_data_structure()
{
unsigned char b = *(unsigned char*)0x7ffe02d4;
return ((b & 0x01) || (b & 0x02));
}
```
## Object Handles
Đây là cách sử dụng `kernel objects handles` để phát hiện trình gỡ lỗi. Các tham số của `kernel objects` sẽ bị thay đổi khi debug hoặc có những `kernel objects` được tạo ra khi bắt đầu debug
### 1. OpenProcess()
Một số trình gỡ lỗi có thể bị phát hiện bằng cách sử dụng hàm `kernel32!OpenProcess()` trên process csrss.exe. Nếu gọi hàm thành công thì chương trình bị debug
C/C++ Code
```cpp
typedef DWORD (WINAPI *TCsrGetProcessId)(VOID);
bool Check()
{
HMODULE hNtdll = LoadLibraryA("ntdll.dll");
if (!hNtdll)
return false;
TCsrGetProcessId pfnCsrGetProcessId = (TCsrGetProcessId)GetProcAddress(hNtdll, "CsrGetProcessId");
if (!pfnCsrGetProcessId)
return false;
HANDLE hCsr = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pfnCsrGetProcessId());
if (hCsr != NULL)
{
CloseHandle(hCsr);
return true;
}
else
return false;
}
```
### 2. CreateFile()
Khi debug, handle của tệp được debug được lưu trong cấu trúc `CREATE_PROCESS_DEBUG_INFO`. Do trình gỡ lỗi đang đọc thông tin từ tệp này nên nếu trình gỡ lỗi không đóng handle thì ta không thể mở file được. Vậy nếu không mở được tệp của process hiện tại không được thì process bị debug
### 3. CloseHandle()
Khi process đang chạy trong trình gỡ lỗi thì khi ta gọi hàm `CloseHandle()`Thì exeption `EXCEPTION_INVALID_HANDLE (0xC0000008)` sẽ được return.
C/C++ Code
```cpp
bool Check()
{
__try
{
CloseHandle((HANDLE)0xDEADBEEF);
return false;
}
__except (EXCEPTION_INVALID_HANDLE == GetExceptionCode()
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH)
{
return true;
}
}
```
### 4. LoadLibrary()
KHi ta tải một tệp lên bằng hàm `kernel32!LoadLibraryW()`, Handle của file sẽ được lưu trữ trong cấu trúc `LOAD_DLL_DEBUG_INFO `. Nếu trình gỡ lỗi không đóng trình xử lý thì sẽ không mở được tệp. Vậy nên nếu lệnh gọi hàm kernel32!CreateFileA() không thành công tức là chương trình đang bị debug
C/C++ Code
```cpp
bool Check()
{
CHAR szBuffer[] = { "C:\\Windows\\System32\\calc.exe" };
LoadLibraryA(szBuffer);
return INVALID_HANDLE_VALUE == CreateFileA(szBuffer, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, NULL);
}
```
### 5. NtQueryObject()
Khi debug, một `kernel object` là "debug object" được tạo. Để check chương trình có bị debug hay không thì ta gọi hàm NtQueryObject() để liệt kê các object, nếu có `DebugObject` thì chương trình đang bị debug
## Exception
### 1. UnhandledExceptionFilter()
UnhandledExceptionFilter() được sử dụng để đặt hàm xử lý ngoại lệ. Nếu có ngoại lệ xảy ra, chương trình sẽ gọi hàm `UnhandledExceptionFilter() ` để xử lý. Nếu không thì chương trình đang bị debug
Assembly Code x86
```asm
include 'win32ax.inc'
.code
start:
jmp begin
not_debugged:
invoke MessageBox,HWND_DESKTOP,"Not Debugged","",MB_OK
invoke ExitProcess,0
begin:
invoke SetUnhandledExceptionFilter, not_debugged
int 3
jmp being_debugged
being_debugged:
invoke MessageBox,HWND_DESKTOP,"Debugged","",MB_OK
invoke ExitProcess,0
.end start
```
### 2. RaiseException()
Để phát hiện debug thì ta gọi hàm kernel32!RaiseException() để kiểm tra xem handle của các expeptions `DBC_CONTROL_C hoặc DBG_RIPEVENT` có được chuyển đến trình xử lý không. Nếu trình xử lý ngoại lệ không được gọi thì process bị debug
C/C++ Code
```cpp
bool Check()
{
__try
{
RaiseException(DBG_CONTROL_C, 0, 0, NULL);
return true;
}
__except(DBG_CONTROL_C == GetExceptionCode()
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH)
{
return false;
}
}
```
## Timing
Khi debug, sẽ có độ trễ rất lớn. Ta có thể đo và so sanh độ trễ gốc và độ trễ thực tế bằng những phương pháp sau:
### 1. RDPMC/RDTSC
Những instructions này yêu cầu cờ PCE đặt trong thanh ghi CR4
**LƯU Ý**: RDPMC chỉ sử dụng được ở trong Kernel Mode
C/C++ Code
```cpp
bool IsDebugged(DWORD64 qwNativeElapsed)
{
ULARGE_INTEGER Start, End;
__asm
{
xor ecx, ecx
rdpmc
mov Start.LowPart, eax
mov Start.HighPart, edx
}
// ... some work
__asm
{
xor ecx, ecx
rdpmc
mov End.LowPart, eax
mov End.HighPart, edx
}
return (End.QuadPart - Start.QuadPart) > qwNativeElapsed;
}
```
### 2. GetLocalTime()
C/C++ Code
```cpp
bool IsDebugged(DWORD64 qwNativeElapsed)
{
SYSTEMTIME stStart, stEnd;
FILETIME ftStart, ftEnd;
ULARGE_INTEGER uiStart, uiEnd;
GetLocalTime(&stStart);
// ... some work
GetLocalTime(&stEnd);
if (!SystemTimeToFileTime(&stStart, &ftStart))
return false;
if (!SystemTimeToFileTime(&stEnd, &ftEnd))
return false;
uiStart.LowPart = ftStart.dwLowDateTime;
uiStart.HighPart = ftStart.dwHighDateTime;
uiEnd.LowPart = ftEnd.dwLowDateTime;
uiEnd.HighPart = ftEnd.dwHighDateTime;
return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed;
}
```
### 3. GetSystemTime()
C/C++ Code
```cpp
bool IsDebugged(DWORD64 qwNativeElapsed)
{
SYSTEMTIME stStart, stEnd;
FILETIME ftStart, ftEnd;
ULARGE_INTEGER uiStart, uiEnd;
GetSystemTime(&stStart);
// ... some work
GetSystemTime(&stEnd);
if (!SystemTimeToFileTime(&stStart, &ftStart))
return false;
if (!SystemTimeToFileTime(&stEnd, &ftEnd))
return false;
uiStart.LowPart = ftStart.dwLowDateTime;
uiStart.HighPart = ftStart.dwHighDateTime;
uiEnd.LowPart = ftEnd.dwLowDateTime;
uiEnd.HighPart = ftEnd.dwHighDateTime;
return (uiEnd.QuadPart - uiStart.QuadPart) > qwNativeElapsed;
}
```
### 4. GetTickCount()
C/C++ Code
```cpp
bool IsDebugged(DWORD dwNativeElapsed)
{
DWORD dwStart = GetTickCount();
// ... some work
return (GetTickCount() - dwStart) > dwNativeElapsed;
}
```
### 5. ZwGetTickCount() / KiGetTickCount()
C/C++ Code
```cpp
bool IsDebugged(DWORD64 qwNativeElapsed)
{
ULARGE_INTEGER Start, End;
__asm
{
int 2ah
mov Start.LowPart, eax
mov Start.HighPart, edx
}
// ... some work
__asm
{
int 2ah
mov End.LowPart, eax
mov End.HighPart, edx
}
return (End.QuadPart - Start.QuadPart) > qwNativeElapsed;
}
```
### 6. QueryPerformanceCounter()
C/C++ Code
```cpp
bool IsDebugged(DWORD64 qwNativeElapsed)
{
LARGE_INTEGER liStart, liEnd;
QueryPerformanceCounter(&liStart);
// ... some work
QueryPerformanceCounter(&liEnd);
return (liEnd.QuadPart - liStart.QuadPart) > qwNativeElapsed;
}
```
### 7. timeGetTime()
C/C++ Code
```cpp
bool IsDebugged(DWORD dwNativeElapsed)
{
DWORD dwStart = timeGetTime();
// ... some work
return (timeGetTime() - dwStart) > dwNativeElapsed;
}
```
## Process Memory
### 1. Breakpoints
#### 1.1. Software Breakpoints (INT3)
Khi đặt breakpoint để debug, debugger sẽ chòn INT 3 (opcode 0xCC). Nên ta kiểm tra những byte `0xCC` . Nếu bất thường thì bị debug
C/C++ Code
```cpp
bool CheckForSpecificByte(BYTE cByte, PVOID pMemory, SIZE_T nMemorySize = 0)
{
PBYTE pBytes = (PBYTE)pMemory;
for (SIZE_T i = 0; ; i++)
{
// Break on RET (0xC3) if we don't know the function's size
if (((nMemorySize > 0) && (i >= nMemorySize)) ||
((nMemorySize == 0) && (pBytes[i] == 0xC3)))
break;
if (pBytes[i] == cByte)
return true;
}
return false;
}
bool IsDebugged()
{
PVOID functionsToCheck[] = {
&Function1,
&Function2,
&Function3,
};
for (auto funcAddr : functionsToCheck)
{
if (CheckForSpecificByte(0xCC, funcAddr))
return true;
}
return false;
}
```
#### 1.2. Anti-Step-Over
Khi ta trình gỡ lỗi Step Over qua lệnh gọi hàm. Thì ta kiểm tra xem software breakpoint có nằm ở địa chỉ trả về hay không. Nếu có thì chương trình bị debug
##### 1.2.1. Direct Memory Modification
Kiểm tra địa chỉ trả về có 0xCC không. Nếu có thì thay bằng 0x90(Nop)
C/C++ Code
```cpp
#include <intrin.h>
#pragma intrinsic(_ReturnAddress)
void foo()
{
// ...
PVOID pRetAddress = _ReturnAddress();
if (*(PBYTE)pRetAddress == 0xCC) // int 3
{
DWORD dwOldProtect;
if (VirtualProtect(pRetAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
*(PBYTE)pRetAddress = 0x90; // nop
VirtualProtect(pRetAddress, 1, dwOldProtect, &dwOldProtect);
}
}
// ...
}
```
##### 1.2.2. ReadFile()
Dùng hàm ReadFile() để patch code ở địa chỉ trả về
C/C++ Code
```cpp
#include <intrin.h>
#pragma intrinsic(_ReturnAddress)
void foo()
{
// ...
PVOID pRetAddress = _ReturnAddress();
if (*(PBYTE)pRetAddress == 0xCC) // int 3
{
DWORD dwOldProtect, dwRead;
CHAR szFilePath[MAX_PATH];
HANDLE hFile;
if (VirtualProtect(pRetAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
if (GetModuleFileNameA(NULL, szFilePath, MAX_PATH))
{
hFile = CreateFileA(szFilePath, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (INVALID_HANDLE_VALUE != hFile)
ReadFile(hFile, pRetAddress, 1, &dwRead, NULL);
}
VirtualProtect(pRetAddress, 1, dwOldProtect, &dwOldProtect);
}
}
// ...
}
```
##### 1.2.3. WriteProcessMemory()
Dùng hàm WriteProcessMemory() để patch code ở địa chỉ trả về
C/C++ Code
```cpp
#include <intrin.h>
#pragma intrinsic(_ReturnAddress)
void foo()
{
// ...
BYTE Patch = 0x90;
PVOID pRetAddress = _ReturnAddress();
if (*(PBYTE)pRetAddress == 0xCC)
{
DWORD dwOldProtect;
if (VirtualProtect(pRetAddress, 1, PAGE_EXECUTE_READWRITE, &dwOldProtect))
{
WriteProcessMemory(GetCurrentProcess(), pRetAddress, &Patch, 1, NULL);
VirtualProtect(pRetAddress, 1, dwOldProtect, &dwOldProtect);
}
}
// ...
}
```
##### 1.2.4. Toolhelp32ReadProcessMemory()
Hàm này cho phép ta chống Step-Over
C/C++ Code
```cpp
#include <TlHelp32.h>
bool foo()
{
// ..
PVOID pRetAddress = _ReturnAddress();
BYTE uByte;
if (FALSE != Toolhelp32ReadProcessMemory(GetCurrentProcessId(), _ReturnAddress(), &uByte, sizeof(BYTE), NULL))
{
if (uByte == 0xCC)
ExitProcess(0);
}
// ..
}
```
#### 1.3. Memory Breakpoints
Cấp phát một bộ đệm chứa lệnh RET, sau đó đánh dấu là guard page. Nếu bị debug, chương trình sẽ chạy lệnh RET và nhảy đến địa chỉ đã push vào stack
C/C++ Code
```cpp
bool IsDebugged()
{
DWORD dwOldProtect = 0;
SYSTEM_INFO SysInfo = { 0 };
GetSystemInfo(&SysInfo);
PVOID pPage = VirtualAlloc(NULL, SysInfo.dwPageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (NULL == pPage)
return false;
PBYTE pMem = (PBYTE)pPage;
*pMem = 0xC3;
// Make the page a guard page
if (!VirtualProtect(pPage, SysInfo.dwPageSize, PAGE_EXECUTE_READWRITE | PAGE_GUARD, &dwOldProtect))
return false;
__try
{
__asm
{
mov eax, pPage
push mem_bp_being_debugged
jmp eax
}
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
VirtualFree(pPage, NULL, MEM_RELEASE);
return false;
}
mem_bp_being_debugged:
VirtualFree(pPage, NULL, MEM_RELEASE);
return true;
}
```
#### 1.4. Hardware Breakpoints
Nếu các thanh ghi gỡ lỗi DR0, DR1, DR2 và DR3 có giá trị != 0 thì chương trình bị debug
C/C++ Code
```cpp
bool IsDebugged()
{
CONTEXT ctx;
ZeroMemory(&ctx, sizeof(CONTEXT));
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
if(!GetThreadContext(GetCurrentThread(), &ctx))
return false;
return ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3;
}
```
### 2. Other memory checks
#### 2.1. NtQueryVirtualMemory()
Kiểm tra các trường Shared và ShareCount của Khối Working Set để tìm trang có mã. Nếu debug thì software breakpoint sẽ được
đặt các trường này và trả về giá trị = 0
#### 2.2. Detecting a function patch
Chúng ta kiểm tra xem hàm kernel32!IsDebuggerPresent() có bị sửa đổi hay không. Nếu có thì chương trình bị debug
#### 2.3. Patch ntdll!DbgBreakPoint()
Nếu chúng ta xóa breakpoint bên trong ntdll!DbgBreakPoint() thì trình gỡ lỗ sẽ không xâm nhập vào luồng và sẽ thoát
#### 2.4. Patch ntdll!DbgUiRemoteBreakin()
Patch ntdll!DbgUiRemoteBreakin() hoặc kernel32!TerminateProcess() để chương trình sẽ tự động thoát nếu bị gắn debugger
#### 2.5 Performing Code Checksums
Kiểm tra code có bị sửa hay không. Nếu có thì chương trình bị debug
## Assembly instructions
### 1. INT 3
Khi gặp INT3 thì exception EXCEPTION_BREAKPOINT (0x80000003) sẽ được tạo ra và được gọi. Nếu chương trình bị debug thì expeption không được gọi
### 2. INT 2D
Tương tự như INT3. Chỉ khác là nếu có debbugger thì chương trình sẽ nhảy đến EIP + 1
### 3. DebugBreak
Nếu chương trình không debug thì sẽ được chuyển tới trình xử lý ngoại lê. Nếu không, việc thực thi bị debugger chặn lại
### 4. ICE
Opcode 0xF1 gây ra exception EXCEPTION_SINGLE_STEP (0x80000004). Nếu tiếp tục thực thi câu lệnh sau ICE thì chương trình bị debug
### 5. Stack Segment Register
Việc so sánh giá trị của Trap Flag sẽ cho ta biết chương trình có bị debug hay không. Nếu giá trị khác 1 thì chương trình bị debug
C/C++ Code
```cpp
bool IsDebugged()
{
bool bTraced = false;
__asm
{
push ss
pop ss
pushf
test byte ptr [esp+1], 1
jz movss_not_being_debugged
}
bTraced = true;
movss_not_being_debugged:
// restore stack
__asm popf;
return bTraced;
}
```
### 6. Instruction Counting
Đặt các hardware breakpoint tạo ra những EXCEPTION_SINGLE_STEP trong 1 chuỗi. Dùng thanh ghi để đếm số lần gọi Exception handler. Nếu khác với giá trị của chúng ta thì chương trình đang bị debug.
### 7. POPF and Trap Flag
Khi Trap Flag được đặt, exception SINGLE_STEP sẽ được tạo.Nếu Trap Flag bị debugger xóa thì chúng ta không thấy exception
### 8. Instruction Prefixes
Assembly Code x86
```masm
IsDebugged()
{
__try
{
// 0xF3 0x64 disassembles as PREFIX REP:
__asm __emit 0xF3
__asm __emit 0x64
// One byte INT 1
__asm __emit 0xF1
return true;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return false;
}
}
```
Trong trường hợp này. Nếu chúng ta thực thi trong OllyDbg thì sau byte F3, ta sẽ đi đến cuối khối lệnh này. Còn nếu chương trình không bị debug thì sẽ xuất hiện 1 exception và sẽ nhảy vào hàm xử lý
## Direct debugger interaction
Những kĩ thuật bên dưới cho phép tương tác với process gốc để phát hiện những điểm không nhất quán
### 1. Self-Debugging
Chúng ta sử dụng 3 hàm bên dưới để đính kèm dưới dạng debugger vào process đang chạy:
`kernel32!DebugActiveProcess()`
`ntdll!DbgUiDebugActiveProcess()`
`ntdll!NtDebugActiveProcess()`
Nếu không đính kèm được tức process bị debug
### 2. GenerateConsoleCtrlEvent()
Hàm này cho phép ta tạo ra 1 event liên quan đến việc nhấn phím Ctrl+C hoặc Ctrl+Break trong cửa sổ console, và gọi hàm `kernel32!ExitProcess()` để kết thúc process. Tuy nhiên ta có thể sử dụng hàm `SetConsoleCtrlHandler()` để thay vì kết thúc thì ta có thể điều khiển việc tiếp tục chương trình sau khi nhấn Ctrl+C hoặc Ctrl+Break. Khi ta ấn Ctrl+C thì hệ thống sẽ tạo ra exception `DBG_CONTROL_C`. Nếu exception có giá trị TRUE thì process bị debug
### 3. BlockInput()
Hàm này chặn tất cả các event từ chuột và bàn phím. Vậy nên nếu trả về giá trị TRUE
thì tức là process đang bị debug
### 4. NtSetInformationThread()
Ta có thể sử dụng hàm này để ẩn 1 thread khỏi debugger. Sau khi ẩn, nó sẽ tiếp tục chạy nhưng debugger không nhận được các event từ thread này khiến cho process gặp sự cố và debugger bị kẹt.
C/C++ Code
```cpp
#define NtCurrentThread ((HANDLE)-2)
bool AntiDebug()
{
NTSTATUS status = ntdll::NtSetInformationThread(
NtCurrentThread,
ntdll::THREAD_INFORMATION_CLASS::ThreadHideFromDebugger,
NULL,
0);
return status >= 0;
}
```
### 5. EnumWindows() and SuspendThread()
Gọi hàm `user32!EnumWindows()` hoặc `user32!EnumThreadWindows()` để liệt kê tất cả các cửa sổ đang chạy, tìm kiếm cửa sổ có ID là ID của process gốc và kiểm tra title. Nếu giống title của debugger thì ta sử dụng SuspendThread() hoặc ntdll!NtSuspendThread() để tạm dừng thread
### 6. SwitchDesktop()
Lợi dụng việc hỗ trợ nhiều destop trên windows, ta chuyển input sang desktop khác khiến cho không thể tương tác được với debugger
C/C++ Code
```cpp
BOOL Switch()
{
HDESK hNewDesktop = CreateDesktopA(
m_pcszNewDesktopName,
NULL,
NULL,
0,
DESKTOP_CREATEWINDOW | DESKTOP_WRITEOBJECTS | DESKTOP_SWITCHDESKTOP,
NULL);
if (!hNewDesktop)
return FALSE;
return SwitchDesktop(hNewDesktop);
}
```
### 7. OutputDebugString()
Kĩ thuật này chỉ hoạt động với các phiên bản Windows Vista trở xuống. Khi ta gọi hàm này mà không xảy ra lỗi thì chương trình bị debug
## Misc
### 1. FindWindow()
Kĩ thuật này liệt kê tất cả các lớp cửa sổ trong hệ thống và so sanh với các lớp debugger đã biết
C/C++ Code
```cpp
const std::vector<std::string> vWindowClasses = {
"antidbg",
"ID", // Immunity Debugger
"ntdll.dll", // peculiar name for a window class
"ObsidianGUI",
"OLLYDBG",
"Rock Debugger",
"SunAwtFrame",
"Qt5QWindowIcon"
"WinDbgFrameClass", // WinDbg
"Zeta Debugger",
};
bool IsDebugged()
{
for (auto &sWndClass : vWindowClasses)
{
if (NULL != FindWindowA(sWndClass.c_str(), NULL))
return true;
}
return false;
}
```
### 2. Parent Process Check
Do khi ta mở bằng cách nhấn đúp vào tệp thì có shell process là “explorer.exe”. Vậy nên để phát hiện debugger thì ta so sánh PID của process gốc với PID của “explorer.exe”.
#### 2.1. NtQueryInformationProcess()
Ta lấy handle của cửa sổ shell process bằng cách sử dụng user32!GetShellWindow() và lấy ID process của nó bằng cách gọi user32!GetWindowThreadProcessId().Sau đó, ID process gốc có thể được lấy từ cấu trúc PROCESS_BASIC_INFORMATION bằng cách gọi ntdll!NtQueryInformationProcess() với lớp ProcessBasicInformation.
C/C++ Code
```cpp
bool IsDebugged()
{
HWND hExplorerWnd = GetShellWindow();
if (!hExplorerWnd)
return false;
DWORD dwExplorerProcessId;
GetWindowThreadProcessId(hExplorerWnd, &dwExplorerProcessId);
ntdll::PROCESS_BASIC_INFORMATION ProcessInfo;
NTSTATUS status = ntdll::NtQueryInformationProcess(
GetCurrentProcess(),
ntdll::PROCESS_INFORMATION_CLASS::ProcessBasicInformation,
&ProcessInfo,
sizeof(ProcessInfo),
NULL);
if (!NT_SUCCESS(status))
return false;
return (DWORD)ProcessInfo.InheritedFromUniqueProcessId != dwExplorerProcessId;
}
```
#### 2.2. CreateToolhelp32Snapshot()
Ta lấy ID và tên process gốc bằng hàm này và hàm Process32Next()
### 3. Selectors
Bằng cách kiểm tra các selectors và sử dụng các exception để xác định liệu giá trị selectors có bị thay đổi hay không thì ta có thể xem chương trình có bị debug không. Nếu selectors này bị thay đổi, điều này có thể cho thấy rằng một debugger đang can thiệp vào bộ nhớ.
### 4. DbgPrint()
Khi ta gọi hàm này gây ra exception. Nếu hàm này không gây ra lỗi tức chương trình bị debug
C/C++ Code
```cpp
bool IsDebugged()
{
__try
{
RaiseException(DBG_PRINTEXCEPTION_C, 0, 0, 0);
}
__except(GetExceptionCode() == DBG_PRINTEXCEPTION_C)
{
return false;
}
return true;
}
```
### 5. DbgSetDebugFilterState()
Hàm này đặt 1 cờ sẽ được kiểm tra bởi kernel-mode debugger. Nếu gọi hàm thành công thì process bị debug
### 6. NtYieldExecution() / SwitchToThread()
Khi chương trình bị debug và single-step được thực hiện thì CPU không thể chuyển sang luồng khác và khiến hàm NtYieldExecution() trả về giá trị = 0.
C/C++ Code
```cpp
bool IsDebugged()
{
BYTE ucCounter = 1;
for (int i = 0; i < 8; i++)
{
Sleep(0x0F);
ucCounter <<= (1 - SwitchToThread());
}
return ucCounter == 0;
}
```
### 7. VirtualAlloc() / GetWriteWatch()
Ta sử dụng hàm GetWriteWatch() để truy xuất các địa chỉ của các trang bộ nhớ được ghi dữ liệu vào trong bộ nhớ đã được cấp phát bởi VirtualAlloc() với flag `MEM_WRITE_WATCH`. Nếu các trang bộ nhớ này được ghi vào mà không có lý do cụ thể thì nghĩa là chương trình bị debug
C/C++ Code
```cpp
bool Generic::CheckWrittenPages1() const {
const int SIZE_TO_CHECK = 4096;
PVOID* addresses = static_cast<PVOID*>(VirtualAlloc(NULL, SIZE_TO_CHECK * sizeof(PVOID), MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE));
if (addresses == NULL)
{
return true;
}
int* buffer = static_cast<int*>(VirtualAlloc(NULL, SIZE_TO_CHECK * SIZE_TO_CHECK, MEM_RESERVE | MEM_COMMIT | MEM_WRITE_WATCH, PAGE_READWRITE));
if (buffer == NULL)
{
VirtualFree(addresses, 0, MEM_RELEASE);
return true;
}
// Read the buffer once
buffer[0] = 1234;
ULONG_PTR hits = SIZE_TO_CHECK;
DWORD granularity;
if (GetWriteWatch(0, buffer, SIZE_TO_CHECK, addresses, &hits, &granularity) != 0)
{
return true;
}
else
{
VirtualFree(addresses, 0, MEM_RELEASE);
VirtualFree(buffer, 0, MEM_RELEASE);
return (hits == 1) ? false : true;
}
}
}
```