## **ANTI-DEBUG**
---
### **Anti-debug: Debug flags**
**1. Dùng Win32 API:**
Dùng các hàm API (Win API hoặc NativeAPI) để kiểm tra xem cấu trúc hệ thống trong bộ nhớ từ các cờ trạng thái cụ thể có biểu thị quá trình có đang được gỡ lỗi ngay bây giờ không.
* **IsDebuggerPresent()**
* Chức năng kernel32!IsDebuggerPresent() xác định chương trình có đang được debug bởi một trình gỡ lỗi nào đó hay không. Chức năng chỉ kiểm tra xem chương trình có đang được debug hay không.
* Code:
* Assembly code:
```=
call IsDebuggerPresent
test al, al
jne being_debugged
...
being_debugged:
push 1
call ExitProcess
```
* C/C++ code:
```cpp=
if (IsDebuggerPresent())
ExitProcess(-1);
```
* **CheckremoteDebuggerPresent()**
* Chức năng kernel32!CheckremotrDebuggerPresent() là kiểm tra chương trình có đang được gỡ lỗi từ xa hoặc trong một chương trình khác trên cùng một thiết bị hay không.
* Code:
* C/C++ code:
```cpp=
BOOL bDebuggerPresent;
if (TRUE == CheckRemoteDebuggerPresent(GetCurrentProcess(), &bDebuggerPresent) &&
TRUE == bDebuggerPresent)
ExitProcess(-1);
```
* x86 Assembly code:
```=
lea eax, [bDebuggerPresent]
push eax
push -1 ; GetCurrentProcess()
call CheckRemoteDebuggerPresent
cmp [bDebuggerPresent], 1
jz being_debugged
...
being_debugged:
push -1
call ExitProcess
```
* x86-64 Assembly code:
```=
lea rdx, [bDebuggerPresent]
mov rcx, -1 ; GetCurrentProcess()
call CheckRemoteDebuggerPresent
cmp [bDebuggerPresent], 1
jz being_debugged
...
being_debugged:
mov ecx, -1
call ExitProcess
```
**2. Manual check
2.1. PEB!BeingDebugged Flag**
* Kiểm tra cờ BeingDebugged nếu giá trị khác 0 thì đang được gỡ lỗi.
**2.2. NtGlobalFlag**
* Là một trường của PEB (mặc định bằng 0):
* offset =0x68 trong 32-bit
* offset =0xBC trong 64-bit
* Các cờ:
* FLG_HEAP_ENABLE_TAIL_CHECK (0x10)
* FLG_HEAP_ENABLE_FREE_CHECK (0x20)
* FLG_HEAP_VALIDATE_PARAMETERS (0x40)
* Khi các cờ này được bật thì có nghĩa là chương trình đang chạy trong trình gỡ lỗi. Giá trị trường NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS)=0x70
* Code
```cpp=
#define FLG_HEAP_ENABLE_TAIL_CHECK 0x10
#define FLG_HEAP_ENABLE_FREE_CHECK 0x20
#define FLG_HEAP_VALIDATE_PARAMETERS 0x40
#define NT_GLOBAL_FLAG_DEBUGGED (FLG_HEAP_ENABLE_TAIL_CHECK | FLG_HEAP_ENABLE_FREE_CHECK | FLG_HEAP_VALIDATE_PARAMETERS)
#ifndef _WIN64
PPEB pPeb = (PPEB)__readfsdword(0x30);
DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0x68);
#else
PPEB pPeb = (PPEB)__readgsqword(0x60);
DWORD dwNtGlobalFlag = *(PDWORD)((PBYTE)pPeb + 0xBC);
#endif // _WIN64
if (dwNtGlobalFlag & NT_GLOBAL_FLAG_DEBUGGED)
goto being_debugged;
```
**2.3 Heap Flags**
* Kiểm tra giá trị của hai trường `Flags và ForceFlags` thông thường nó có giá trị là 0 hoặc HEAP_GROWABLE (2)
* Nếu khác hai giá trị đó có thể chương đã bị attach vào một debugger nào đó.
**2.4 Heap Protection**
* Cờ HEAP_TAIL_CHECKING_ENABLED được đặt trong NtGlobalFlag, 0xABABABAB (32bit)/ 0xBABABABBABABAB (64 bit) sẽ được set tại cuối khối heap.
* Cờ HEAP_FREE_CHECKING_ENABLED được đặt trong NtGlobalFlag, 0xFEEEFEEE sẽ được thêm vào nếu bộ nhớ heap còn trống.
* Nếu 0xABABABAB không được tìm thì có nghĩa là đã bị debug.
```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);
}
```
-------
### **Anti-debug: Object handles**
**1. OpenProcess()**
* Các debugger sẽ bị phát hiện khi dùng kernel32!OpenProcess() trong tiến trình csrss.exe
* Quyền "SeDebugPrivilege" là quyền cho phép người dùng thực hiện các hoạt động gỡ lỗi trên các quá trình khác. Để kiểm tra quá trình này có quyền đó hay không chúng ta sử dụng hàm CsrGetProcessId để lấy PID của csrss.exe.Sau đó, chúng ta sử dụng hàm OpenProcess() để thử mở quá trình này với quyền truy cập thông thường. Nếu mở thành công cũng tức là có quyền "SeDebugPrivilege", nếu thất bại thì không có quyền đó nên sẽ phát hiện việc chương trình đang bị gỡ lỗi.
* 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 mở file bằng trình gỡ lỗi thì thông tin trong file thực thi sẽ được lưu ở CREATE_PROCESS_DEBUG_INFO để đọc dữ liệu. Và sẽ không mở file để đọc với quyền truy cập độc quyền được.
* Sử dụng hàm kernel32!CreateFileW() hoặc kernel32!CreateFileA() để tệp tin với quyền truy cập độc quyền. Nếu thất bại kết quả trả về là:`INVALID_HANDLE_VALUE` thì có nghĩa là chương trình đang được gỡ lỗi.
* Code
```cpp=
bool Check()
{
CHAR szFileName[MAX_PATH];
if (0 == GetModuleFileNameA(NULL, szFileName, sizeof(szFileName)))
return false;
return INVALID_HANDLE_VALUE == CreateFileA(szFileName, GENERIC_READ, 0, NULL, OPEN_EXISTING, 0, 0);
}
```
**3. CloseHandle()**
* Dùng hàm CloseHandle() với một handle không hợp lệ để tạo ngoại lệ EXCEPTION_INVALID_HANDLE. Nếu có ngoại lệ EXCEPTION_INVALID_HANDLE thì chương trình bị debug.
* Code:
```cpp=
bool Check()
{
__try
{
CloseHandle((HANDLE)0xDEADBEEF);
return false;
}
__except (EXCEPTION_INVALID_HANDLE == GetExceptionCode()
? EXCEPTION_EXECUTE_HANDLER
: EXCEPTION_CONTINUE_SEARCH)
{
return true;
}
}
```
---
### **Anti-debug: Exception**
**1. UnhandledExceptionFilter():**
* Khi exception xảy ra và không có handle exception, hàm kernel32!UnhandledExceptionFilter() sẽ được gọi bởi hệ điều hành. Có thể sử dụng hàm kernel32!SetUnhandledExceptionFilter() để tùy chỉnh đăng ký một handle exception. Nếu chương trình không chạy môi trường debug thì SetUnhandledExceptionFilter() sẽ được gọi, mặt khác nếu chạy trong môi trường debug thì hàm sẽ không được gọi và exception này sẽ chuyển qua debugger ->xác định được chương trình đang được debug.
* Code:
```cpp=
LONG UnhandledExceptionFilter(PEXCEPTION_POINTERS pExceptionInfo)
{
PCONTEXT ctx = pExceptionInfo->ContextRecord;
ctx->Eip += 3; // Skip \xCC\xEB\x??
return EXCEPTION_CONTINUE_EXECUTION;
}
bool Check()
{
bool bDebugged = true;
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)UnhandledExceptionFilter);
__asm
{
int 3 // CC
jmp near being_debugged // EB ??
}
bDebugged = false;
being_debugged:
return bDebugged;
}
```
**2. RaiseException()**
* Các exception như DBC_CONTROL_C hay DBG_RIPEVENT không được chuyển tiếp đến exception handlers của tiến trình hiện tại mà được xử lí bởi debugger.
* Hàm RaiseException() tạo một ngoại lệ cụ thể nếu nó không được gọi thì có thể nó đang được debugging
* 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;
}
}
```
---------------
### **Anti-debug: Timing**
Khi process được trace trong khi debug, thời gian delay giữa các câu lệnh rất lớn so với thời gian thực thi bình thương.
**1. RDPMC/RDTSC**
* Là hai câu lệnh sử dụng cờ PCE trong thanh ghi CR4
* RDPMC: chỉ được dùng trong chế độ kernel
* RDTSC: chỉ được dùng trong chế độ user
* Hai biến Start và End được sử dụng để lưu trữ giá trị bộ đếm hiệu năng trước và sau khi thực thi một phần mã `some work`.Sau đó so sánh sự chênh lệch giữa End.QuadPart và Start.QuadPart với giá trị qwNativeElapsed. Nếu sự chênh lệch lớn hơn qwNativeElapsed, cho thấy thời gian đã trôi qua của phần mã "some work" lớn hơn ngưỡng đã cho, hàm sẽ trả về true ngược lại trả về false
* Nếu đặt breakpoint hay step trace trong đoạn mã ở phần `some work` sẽ gây ra độ trễ thời gian lớn và sẽ phát hiện debug.
* 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. ZwGetTickCount() / KiGetTickCount():**
* Cả hai hàm đều sử dụng trong chế độ kernel
* Tương tự như trên thì sẽ so sánh độ trễ của hai biến start và end.
* 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;
}
```
------------
### **Anti-debug: Process Memory**
**1. Breakpoints
1.1. Software Breakpoints (INT3)**
* INT 3 : lệnh trong assembly dùng để chèn breakpoint vào mã máy.
* Tìm có byte 0xCC (INT 3). Nếu không tìm thấy byte 0xCC trong bất kỳ hàm nào thì chương trình không được gỡ lỗi,ngược lại nếu tìm thấy thì cho biết chương trình đang được gỡ lỗi.
* 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. Memory Breakpoints**
* `quard page` được tạo bằng cách thiết lập công cụ sửa đổi PAGE_GUARD trong các hàm kernel32!VirtualAlloc(), kernel32!VirtualAllocEx(), kernel32!VirtualProtect() và kernel32!VirtualProtectEx().
* Nếu phát hiện trang `quard page` đã được truy cập, nó sẽ đưa ra ngoại lệ STATUS_GUARD_PAGE_VIOLATION -> chương trình không chạy trong trình gỡ lỗi. Mặt khác nếu không phát hiện trang `quard page` -> chạy trong trình gỡ lỗi.
* 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.3 Hardware Breakpoints**
* DR0, DR1, DR2 và DR3 là các thanh ghi phần cứng được sử dụng để đặt các hardware breakpoints. Giá trị của các thanh ghi được lấy từ cấu trúc CONTEXT trên Windows. Nếu bất kì thanh ghi nào khác 0 thì điều đó cho biết chương trình đang được gỡ lỗi.
* 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;
}
```
---
### **Anti-debug: Assembly instructions**
**1. INT 3**
* INT 3 tạo ngoại lệ EXCEPTION_BREAKPOINT (0x80000003) và exception handler sẽ được gọi. Nếu không được gọi thì chương trình đang được debug.
* Code:
```cpp=
bool IsDebugged()
{
__try
{
__asm int 3;
return true;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return false;
}
}
```
**2. INT 2D**
* Tương tự như INT 3 sẽ tạo ngoại lệ EXCEPTION_BREAKPOINT và exception handler sẽ được gọi. Nếu không được gọi thì chương trình đang được debug.
* Code:
```cpp=
bool IsDebugged()
{
__try
{
__asm xor eax, eax;
__asm int 0x2d;
__asm nop;
return true;
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
return false;
}
}
```
**3. DebugBreak**
* DebugBreak(): hàm này được sử dụng để tạo một ngoại lệ gỡ lỗi được gửi tới trình gỡ lỗi hiện tại. Nếu chương trình không được chạy trong chế độ gỡ lỗi, việc gọi hàm này sẽ không gây ra ngoại lệ. Ngược lại, nếu chương trình chạy trong chế độ gỡ lỗi thì IsDebugged sẽ trả về giá trị false : chương trình đang được gỡ lỗi.
* Code:
```cpp=
bool IsDebugged()
{
__try
{
DebugBreak();
}
__except(EXCEPTION_BREAKPOINT)
{
return false;
}
return true;
}
```
----
### **Anti-debug: Direct debugger interaction**
**1. Self-Debugging**
* Gắn một trong ba hàm kernel32!DebugActiveProcess(), ntdll!DbgUiDebugActiveProcess(), ntdll!NtDebugActiveProcess(), vào để kiểm tra sự hiện diện của một trình gỡ lỗi khác. Vì chỉ một trình gỡ lỗi kết nối với chương trình tại một thời điểm
* Nếu gọi thành công thì khồn có trình gỡ lỗi khác, ngược lại nếu thất bại thì có một trình gỡ lỗi khác.
**2. BlockInput()**
* Hàm BlockInput() là chặn đầu vào từ chuột hay bàn phím (nó chỉ chặn được một lần)
* Nếu BlockInput() thứ hai trả về True tức là đã có sự can thiệp của một công cụ khác làm thay đổi giá trị trả về -> phát hiện debug
* Code:
```cpp=
bool IsHooked ()
{
BOOL bFirstResult = FALSE, bSecondResult = FALSE;
__try
{
bFirstResult = BlockInput(TRUE);
bSecondResult = BlockInput(TRUE);
}
__finally
{
BlockInput(FALSE);
}
return bFirstResult && bSecondResult;
}
```
**3. NtSetInformationThread()**
* Đây là hàm dùng để ẩn luồng khỏi trình gỡ lỗi. Khi debug thì luồng vẫn sẽ chạy nhưng trình gỡ lỗi sẽ không phát hiện bất cứ thông tin gi về luồng đó.
* Code
```cpp=
#define NtCurrentThread ((HANDLE)-2)
bool AntiDebug()
{
NTSTATUS status = ntdll::NtSetInformationThread(
NtCurrentThread,
ntdll::THREAD_INFORMATION_CLASS::ThreadHideFromDebugger,
NULL,
0);
return status >= 0; //sẽ trả về true nếu ẩn luồng thành công
}
```
------
### **Anti-debug: Misc**
**1. FindWindow()**
* So sánh các cửa sổ của một trình gỡ lỗi đã biết với các cửa sổ của tiến trình đang chạy nếu phát hiện một cửa sổ nào đó-> đang được debug
* 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
2.1. NtQueryInformationProcess()**
* user32!GetWindowThreadProcessId() : lấy ID của tiến trình
* NtQueryInformationProcess() :lấy ID của tiến trình gốc từ PROCESS_BASIC_INFORMATION ((GetCurrentProcess())
* So sánh với nhau nếu giống -> đang được debug
* 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;
}
```