静态反调试技术
@author: dlive
0x01 PEB
PEB中与反调试技术密切相关的成员有如下几个
+0x002 BeingDebugged; UChar
+0x00c Ldr ; Ptr32 _PEB_LDR_DATA
+0X018 ProcessHeap ; Ptr32 Void
+0x068 NtGlobalFlag ; Uint4B
BeingDebugged成员是一个标志,用来表示进程是否处于被调试状态
Ldr, ProcessHeap, NtGloabFlag成员与被调试进程的堆内存特性相关
获取PEB结构体的地址
# 第一种方法
MOV EAX, DWORD PTR FS:[0x30]
# 第二种方法
MOV EAX, DWORD PTR FS:[0x18]
MOV EAX, DWORD PTR DS:[EAX+0x30]
BeingDebugged(+0x2)
通过IsDebuggerPresent() API可以获取PEB.BeingDebugged的值,若为1则表示进程处于被调试状态
破解方法:只要借助OD,将PEB.BeingDebugged的值修改为0即可
Ldr(+0xC)
调试进程时其堆内存就会出现一些特殊标识,表示它正处于被调试状态
其中最醒目的是,未使用的堆内存区域全部填充着0xFEEEFEEE,这证明正在调试进程
PEB.Ldr成员是一个指向_PEB_LDR_DATA
结构体的指针,而_PEB_LDR_DATA
结构体恰好是在堆内存区域中创建的,所以扫描该区域即可轻松查找是否存在0xFEEEFEEE区域
破解方法:只要将填充着0xFEEEFEEE的区域全部覆写为NULL即可
注意:该方法只适用于WindowsXP,而在Windows Vista以后的系统中则无法使用。另外,利用附加功能将运行中的进程附加到调试器时,堆内存中并不出现上述标识。
ProcessHeap(+0x18)
PEB.ProcessHeap成员是指向HEAP结构体的指针
GetProcessHeap() API可以获取PEB.ProcessHeap结构体的地址
HEAP结构体中Flags(+0xC)和ForceFlags(+0x10)和反调试相关,进程正常运行时,Heap.Flags的值为0x2,Heap.ForceFlags的值为0x0,进程处于调试状态时这些值会改变。
破解方法:修改HEAP.Flags =2, HEAP.ForceFlags=0
注意:该方法只适用于WindowsXP,而在Windows Vista以后的系统中则无法使用。另外,利用附加功能将运行中的进程附加到调试器时,也不会有上述特征。
NtGlobalFlag(0x68)
调试进程时,PEB.NtGlobalFlag会被设置为0x70
注意:利用附加功能将运行中的进程附加到调试器时,不会有上述特征。
0x02 NtQueryInformationProcess API
https://msdn.microsoft.com/en-us/library/ms684280(VS.85).aspx
NTSTATUS WINAPI NtQueryInformationProcess(
_In_ HANDLE ProcessHandle,
_In_ PROCESSINFOCLASS ProcessInformationClass,
_Out_ PVOID ProcessInformation,
_In_ ULONG ProcessInformationLength,
_Out_opt_ PULONG ReturnLength
);
为该函数第二个参数ProcessInformationClass设定特定的值,函数执行结果会保存在第三个参数ProcessInformation中
第二个参数为枚举类型,其中与反调试相关的参数有ProcessDebugPort(0x07),ProcessDebugObject-Handle(0x1E),ProcessDebugFlags(0x1F)
ProcessDebugPort(0x7)
非调试状态下debugport == 0
// ProcessDebugPort (0x7)
DWORD dwDebugPort = 0;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugPort,
&dwDebugPort,
sizeof(dwDebugPort),
NULL);
printf("NtQueryInformationProcess(ProcessDebugPort) = 0x%X\n", dwDebugPort);
if( dwDebugPort != 0x0 ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
checkRemoteDebuggerPresent()API可以用来检测进程是否处于被调试状态,与IsDebuggerPresent API不同的是,它不仅可以检测当前进程是否处于被调试状态,也可检测其他进程。该函数的实现中使用了NtQueryInformationProcess(ProcessDebugPort) API
ProcessDebugObjectHandle(0x1E)
// ProcessDebugObjectHandle (0x1E)
HANDLE hDebugObject = NULL;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugObjectHandle,
&hDebugObject,
sizeof(hDebugObject),
NULL);
printf("NtQueryInformationProcess(ProcessDebugObjectHandle) = 0x%X\n", hDebugObject);
if( hDebugObject != 0x0 ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
第二个参数为ProcessDebugObjectHandle时,第三个参数返回为被调试对象句柄,当返回NULL时说明进程处于非调试状态
ProcessDebugFlags(0x1F)
// ProcessDebugFlags (0x1F)
BOOL bDebugFlag = TRUE;
pNtQueryInformationProcess(GetCurrentProcess(),
ProcessDebugFlags,
&bDebugFlag,
sizeof(bDebugFlag),
NULL);
printf("NtQueryInformationProcess(ProcessDebugFlags) = 0x%X\n", bDebugFlag);
if( bDebugFlag == 0x0 ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
ProcessDebugFlags参数用于获取调试标识,DebugFlag的值若为0则处于调试状态,若为1则处于非调试状态
破解方法
patch函数/修改函数返回值/API Hook
OD中advanced olly提供了对该API Hook的功能
0x03 NtQuerySytemInformation API
检测系统是否以调试模式运行(WinDbg调试需要)
之前进程隐藏的章节中使用过ZwQuerySytemInformation API来获取进程列表,用户层Zw和Nt没什么区别,所以这里使用Zw也是可以的
NTSTATUS WINAPI NtQuerySystemInformation(
_In_ SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_ PVOID SystemInformation,
_In_ ULONG SystemInformationLength,
_Out_opt_ PULONG ReturnLength
);
SystemInformationClass参数中指定需要的系统信息类型,将某结构体的地址传递给SystemInformation参数,API结束时,该结构体中就填充着相关的信息
SystemKernelDebuggerInformation(0x23)
NtQuerySystemInformation = (NTQUERYSYSTEMINFORMATION)
GetProcAddress(GetModuleHandle(L"ntdll"),
"NtQuerySystemInformation");
ULONG SystemKernelDebuggerInformation = 0x23;
ULONG ulReturnedLength = 0;
SYSTEM_KERNEL_DEBUGGER_INFORMATION DebuggerInfo = {0,};
NtQuerySystemInformation(SystemKernelDebuggerInformation,
(PVOID) &DebuggerInfo,
sizeof(DebuggerInfo), // 2 bytes
&ulReturnedLength);
printf("NtQuerySystemInformation(SystemKernelDebuggerInformation) = 0x%X 0x%X\n",
DebuggerInfo.DebuggerEnabled, DebuggerInfo.DebuggerNotPresent);
if( DebuggerInfo.DebuggerEnabled ) printf(" => Debugging!!!\n\n");
else printf(" => Not debugging...\n\n");
DebuggerInfo.DebuggerEnabled==1时为调试状态
破解方法:Win XP: 编辑boot.ini 删除/debugport=com1 /baudrate=115200 /Debug
Win7: cmd执行 bcdedit /debug off
0x04 NtQueryObject API
系统中某个调试器调试进程时,会创建一个调试对象类型的内核对象。检测该对象是否存在即可判断是否有进程正在被调试
ntdll!NtQueryObject可获得系统各种内核对象信息
NTSTATUS NtQueryObject(
_In_opt_ HANDLE Handle,
_In_ OBJECT_INFORMATION_CLASS ObjectInformationClass,
_Out_opt_ PVOID ObjectInformation,
_In_ ULONG ObjectInformationLength,
_Out_opt_ PULONG ReturnLength
);
该API与上面讲过的API使用方法类似
typedef enum _OBJECT_INFORMATION_CLASS {
ObjectBasicInformation,
ObjectTypeInformation,
ObjectNameInformation,
ObjectAllInformation, //3
ObjectHandleInformation
} OBJECT_INFORMATION_CLASS;
ObjectAllInformation(0x3)
使用ObjectAllInformation可获得系统所有对象信息,然后从中检测是否存在调试对象
0x05 ZwSetInformationThread API
强制分离被调试者与调试者的技术,使用该API被调试者可将自身从调试器中分离出来(使调试进程终止运行,同时终止自身进程),该API不会对正常运行的程序(非调试运行)产生任何影响。
NTSTATUS ZwSetInformationThread(
HANDLE ThreadHandle,
THREAD_INFORMATION_CLASS ThreadInformationClass,
PVOID ThreadInformation,
ULONG ThreadInformationLength
);
ThreadHideFromDebugger(0x11)
API第二个参数设置为ThreadHideFromDebugger即可达到分离调试进程的目的
void DetachDebugger()
{
typedef enum _THREAD_INFORMATION_CLASS {
ThreadBasicInformation,
ThreadTimes,
ThreadPriority,
ThreadBasePriority,
ThreadAffinityMask,
ThreadImpersonationToken,
ThreadDescriptorTableEntry,
ThreadEnableAlignmentFaultFixup,
ThreadEventPair,
ThreadQuerySetWin32StartAddress,
ThreadZeroTlsCell,
ThreadPerformanceCount,
ThreadAmILastThread,
ThreadIdealProcessor,
ThreadPriorityBoost,
ThreadSetTlsArrayAddress,
ThreadIsIoPending,
ThreadHideFromDebugger // 17 (0x11)
} THREAD_INFORMATION_CLASS, *PTHREAD_INFORMATION_CLASS;
typedef NTSTATUS (WINAPI* ZWSETINFORMATIONTHREAD)(
HANDLE ThreadHandle,
THREAD_INFORMATION_CLASS ThreadInformationClass,
PVOID ThreadInformation,
ULONG ThreadInformationLength
);
ZWSETINFORMATIONTHREAD pZwSetInformationThread = NULL;
pZwSetInformationThread = (ZWSETINFORMATIONTHREAD)
GetProcAddress(GetModuleHandle(L"ntdll.dll"),
"ZwSetInformationThread");
pZwSetInformationThread(GetCurrentThread(), ThreadHideFromDebugger, NULL, 0);
printf("ZwSetInformationThread() -> Debugger detached!!!\n\n");
}
0x06 其他
检测当前运行环境是否处于一个逆向分析专用环境/系统
- 检测OD窗口 => FindWindow()
- 检测OD进程 => CreateToolhelp32Snapshot()
- 检查计算机名是否为TEST, ANALYSIS => GetComputerName()
- 检测程序运行路径是否存在TEST, SAMPLE => GetCommandLine()
- 检测是否为虚拟机环境 => VMWareService.exe , VMWareTray.exe, VMWareUser.exe