反調試
文章目錄
幾乎任何技術在經過構造後都能變成反調試技術。
注意,測試時不要用vs,因爲vs調試並不能算調試器;也不要用帶插件的調試器(OD)。推薦windbg。
1. 反調試分類
1.1 靜態反調試
特點:調試時開始阻攔。一般找到原因就能突破。
PEB
- BeingDebugged
- Ldr
- Heap
- NtGlobalFlag
TEB
- StaticUnicodeString:靜態緩衝區
原始API
- NtQueryInformationProcess():後面整理的4種類型
- NtQuerySystemInfo():
- NtQueryObject():遍歷系統內核對象
其它
分離調試ZwSetInformatioThread()
利用TLS回調函數。
打開進程檢查SeDubugPrivilege()
還有普通API:
- 檢查父進程
- 檢查窗口名
- 遍歷進程名
- 檢查文件名/路徑
- 檢查註冊表
1.2 動態反調試
特點:一般在調試過程中阻攔。
使用SEH:
- 異常
- 斷點
SetUnhandledExceptionFilter()
時間檢查:RDTSC
,彙編指令,讀取時間戳計數器。
補丁檢查(常用CRC):
- 掃描0xcc
- 掃描關鍵代碼段hash
- 掃描API斷點,第一個字節是否爲0xcc
反反彙編(阻止opcode->指令):
- 指令截斷:截斷第一條指令,進而導致後續指令解析錯誤;
- 指令混淆
- 指令膨脹
- 代碼亂序:用jmp連接。
偷取代碼(類似殼):
- 移動OEP加密執行
- 移動API
分頁保護:
- 運行時保護分頁:修改代碼及數據段保護屬性干擾分析。
殼:
- 壓縮殼:配合OEP加密
- 加密殼:再添加各種反調試手段
虛擬機(類似解釋型語言):
- API虛擬機:針對常用API
- 指令級虛擬機:囊括任意指令
還有單步檢查、封鎖鍵盤鼠標、禁用窗口。
2. 部分方法的示例代碼
2.1 PEB
PEB地址存於fs:[0x30]
BeingDebugged
PEB偏移爲2的字段。
可直接用IsDebuggerPresent()
API,它的反彙編代碼如下。
IsDebbugerPresent()
BOOL PEB_BeingDubugged()
{
__asm {
mov eax, dword ptr fs : [0x30]
movzx eax, dword ptr[eax + 0x02];
}
}
ProcessHeap
PEB偏移0x18的字段,如果是64位,則位於
heap結構體的Flags(0x40)和ForceFlags(0x44)在正常運行時分別爲2和0。
bool CheckProcessHeap()
{
DWORD Flags = 0, ForceFlags = 0;
__asm
{
; 找到 PEB->FS:[0x30]
mov eax, dword ptr fs : [0x30]
; 找到 ProcessHeap->Peb + 0x18
mov eax, dword ptr[eax + 0x18]
; 找到 Flags->HEAP + 0x40
mov edx, dword ptr[eax + 0x40]
mov Flags, edx
; 找到 ForceFlags->HEAP + 0x44
mov ecx, dword ptr[eax + 0x44]
mov ForceFlags, ecx
}
printf("%08x %08x\n", Flags, ForceFlags);
return !(Flags == 2 && ForceFlags == 0);
}
NtGlobalFlag;
位於PEB偏移0x68處,正常值爲0x70。
bool CheckNtGlobalFlag()
{
__asm {
mov eax, dword ptr fs:[0x30];
mov eax, dword ptr [eax + 0x68]
}
}
2.2 NtQueryInformationProcess
NtQueryInformationProcess()
是一個可以在R0和R3運行的函數,用來查看進程信息。這個函數沒有官方文檔,也就是說未公開。
第二個參數是信息類型,我們有4種選擇:
- 0x00,ProcessBasicInformation
- 0x07,ProcessDebugPort
- 0x1e,ProcessDebugObjectHandle
- 0x1f,ProcessDebugFlag
注意要包含頭文件和導入庫。
ProcessDebugPort(0x07)
填ProcessDebugPort
可以獲取調試端口,如果正常運行,則端口爲0,否則爲-1。
#include <winternl.h>
#pragma comment(lib, "ntdll.lib")
BOOL NQIP_ProcessDebugPort()
{
int nDebugPort = 0;
NtQueryInformationProcess(
GetCurrentProcess(), //獲取僞句柄
ProcessDebugPort,
&nDebugPort,
sizeof(nDebugPort),
NULL);
return nDebugPort == -1 ? TRUE : FALSE;
}
ProcessDebugObjectHandle(0x1e)
也可以查詢目標進程的調試對象句柄,如果正常運行應該得到NULL。
BOOL NQIP_ProcessDebugPort()
{
HANDLE hProcessDebugObjectHandle = 0;
NtQueryInformationProcess(
GetCurrentProcess(),
(PROCESSINFOCLASS)0x1e,
&hProcessDebugObjectHandle,
sizeof(hProcessDebugObjectHandle),
NULL);
return hProcessDebugObjectHandle ? TRUE : FALSE;
}
ProcessDebugFlag(0x1f)
獲取目標調試標記,正常爲0,調試爲1。
BOOL NQIP_ProcessDebugFlag()
{
BOOL bProcessDebugFlag = 0;
NtQueryInformationProcess(
GetCurrentProcess(),
(PROCESSINFOCLASS)0x1f,
&bProcessDebugFlag,
sizeof(bProcessDebugFlag),
NULL);
return bProcessDebugFlag;
}
ProcessBasicInformation(0x00)
最後就是查詢父進程PID,與explorer.exe的PID對比,不匹配則證明本進程不是雙擊運行的。
bool NQIP_PPID()
{
struct PROCESS_BASIC_INFORMATION {
ULONG ExitStatus; // 進程返回碼
PPEB PebBaseAddress; // PEB地址
ULONG AffinityMask; // CPU親和性掩碼
LONG BasePriority; // 基本優先級
ULONG UniqueProcessId; // 本進程PID
ULONG InheritedFromUniqueProcessId; // 父進程PID
} stcProcInfo;
NtQueryInformationProcess(
GetCurrentProcess(),
ProcessBasicInformation,
&stcProcInfo,
sizeof(stcProcInfo),
NULL);
DWORD ExplorerPID = 0;
DWORD CurrentPID = stcProcInfo.InheritedFromUniqueProcessId;
GetWindowThreadProcessId(FindWindow(L"Progman", NULL), &ExplorerPID);
return ExplorerPID == CurrentPID ? false : true;
}
當然,在vs裏ctrl+f5也顯示是調試狀態。
2.2 檢查系統是否處於調試狀態
通過一個api獲取系統是否開啓調試模式。
/*
typedef enum _SYSTEM_INFORMATION_CLASS {
SystemBasicInformation = 0,
SystemPerformanceInformation = 2,
SystemTimeOfDayInformation = 3,
SystemProcessInformation = 5,
SystemProcessorPerformanceInformation = 8,
SystemInterruptInformation = 23,
SystemExceptionInformation = 33,
SystemRegistryQuotaInformation = 37,
SystemLookasideInformation = 45,
SystemPolicyInformation = 134,
} SYSTEM_INFORMATION_CLASS;
*/
BOOL CheckSystemKernelDebuggerInfomation()
{
struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION {
BOOLEAN KernelDebuggerEnabled;
BOOLEAN KernelDebuggerNotPresent;
} DebuggerInfo = { 0 };
NtQuerySystemInformation(
SystemInterruptInformation,
&DebuggerInfo,
sizeof(DebuggerInfo),
NULL);
return DebuggerInfo.KernelDebuggerEnabled;
}
2.3 NtQueryObject
BOOL NQO_NtQueryObject()
{
typedef struct _OBJECT_TYPE_INFORMATION{
UNICODE_STRING TypeName;
ULONG TotalNumberOfHandlers;
ULONG TotalNumberOfObjects;
}OBJECT_TYPE_INFORMATION, *POBJECT_TYPE_INFORMATION;
typedef struct _OBJECT_ALL_INFORMATION {
ULONG NumberOfObjectTypes;
OBJECT_TYPE_INFORMATION ObjectTypeInfo[1];
}OBJECT_ALL_INFORMATION, *POBJECT_ALL_INFORMATION;
// 1. 查詢信息大小
ULONG uSize = 0;
NtQueryObject(NULL, (OBJECT_INFORMATION_CLASS)0X03, &uSize, sizeof(uSize), &uSize);
// 2. 獲取信息
POBJECT_ALL_INFORMATION pObjectAllInfo = (POBJECT_ALL_INFORMATION)new BYTE[uSize]();
NtQueryObject(NULL, (OBJECT_INFORMATION_CLASS)0X03, pObjectAllInfo, uSize, &uSize);
// 3. 遍歷並處理
POBJECT_TYPE_INFORMATION pObjectTypeInfo = pObjectAllInfo->ObjectTypeInfo;
for (int i = 0; i < pObjectAllInfo->NumberOfObjectTypes; ++i)
{
// 3.1 查看類型是否爲DebugObject;
if (!wcscmp(L"DebugObject", pObjectTypeInfo->TypeName.Buffer))
{
return TRUE;
}
// 3.2 獲取對象名空間大小(考慮對齊)
ULONG uNameLength = pObjectTypeInfo->TypeName.Length;
ULONG uDataLength = uNameLength - uNameLength % sizeof(ULONG) + sizeof(ULONG);
// 3.3 指向下一個對象
pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)pObjectTypeInfo->TypeName.Buffer;
pObjectTypeInfo = (POBJECT_TYPE_INFORMATION)((PBYTE)pObjectTypeInfo + uDataLength);
}
delete[] pObjectAllInfo;
return FALSE;
}
2.4 脫離調試器
API名叫ZwSetInformationThread()
。
原理,當 DbgkForwardException 檢查到線程存在ThreadHideFromDebugger 的時候,就不會向調試器發送調試信息了。
#include <iostream>
#include <windows.h>
typedef enum THREAD_INFO_CLASS {
ThreadHideFromDebugger = 17
};
typedef NTSTATUS (NTAPI *ZW_SET_INFORMATION_THREAD)(
_In_ HANDLE ThreadHandle,
_In_ THREAD_INFO_CLASS ThreadInformationClass,
_In_reads_bytes_(ThreadInformationLength) PVOID ThreadInformation,
_In_ ULONG ThreadInformationLength
);
void ZSIT_DetachDebug()
{
ZW_SET_INFORMATION_THREAD Func;
Func = (ZW_SET_INFORMATION_THREAD)GetProcAddress(
LoadLibraryW(L"ntdll.dll"),
"ZwSetInformationThread");
Func(
GetCurrentThread(),
ThreadHideFromDebugger,
NULL, NULL);
}
int main()
{
ZSIT_DetachDebug();
printf("running...\n");
system("pause");
return 0;
}
複習
反調試的目的:
- 防止程序被調試
- 防止程序被暴力破解
如何破解常見反調試手段:
- 對於PEB手段,將值重寫即可;
- 對於無文檔的API,HOOK後根據參數判斷是否獲取調試信息,是的話替換稱錯誤參數即可。
NTSTATUS WINAPI MyNtQueryInformationProcess(
HANDLE ProcessHandle,
PROCESSINFOCLASS ProcessInformationClass,
PVOID ProcessInformation,
ULONG ProcessInformationLength,
PULONG ReturnLength){
// 查詢調試端口
if(ProcessInformationClass== ProcessDebugPort)
{
*(PDWORD*) ProcessInformation=0xFFFFFFFF;
if(ReturnLength) *ReturnLength = 4;
return 0;
}
else{
// 調用原版函數:
return pSrcNtQueryInformationProcess(ProcessHandle,
ProcessInformationClass,
ProcessInformation,
ProcessInformationLength,
ReturnLength);
}
}