note20210514
note20210514
Ivoripuion反跟踪技术
BeingDebugged
BeingDebugged标志位
Win32 API IsDebuggedPresent可以检测当前进程是否处于调试状态:
1 | BOOL APIENTRY IsDebuggerPresent(VOID){ |
该函数通过读取PEB的BeingDebugged标志位来检测当前进程是否处于调试态,BeingDebugged标志位在PEB的0x002偏移处。
而PEB在TEB的偏移0x30处,又因为TEB被寄存器fs:[18]指向,所以PEB的地址获取方式如下:
1 | mov eax,fs:[18h] |
又因为fs:[18h]其实指向自身,所以上面两句可以用一句mov eax,fs:[0x30]替代,结合BeingDebugged偏移,可以得到BeingDebugged获取方式:
1 | mov eax,fs:[0x30h] |
具体实现如下:
1 | BOOL is_beingdebugged() { |
NtGlobalFlag标志位
Windows在设置了BeingDebugged标志位后还会更改NtGlobalFlag标志位,因为没设置BeingDebugged之前该标志位的值为0x70,该标志位相对于PEB结构偏移为0x68。根据该标志位得到另一种检测进程是否处于调试态的方法:
1 | BOOL is_beingdebugged_1() { |
HeapMagic
调试态下创建的chunk会包含一些Magic标记,包括0xABABABAB、0xBAADF00D、0xFEEEFEEE,所以可以创建一个chunk然后查找这些Magic标记来判断当前进程是否处于调试态。
代码如下:
1 | LPVOID GetHeap(SIZE_T nSize) { |
chunk创建完毕可以看到Magic标志:
除了上述的这几个标志,还有一些标志也因为BeingDebugged的设置而被“污染”,如进程堆的Flags、ForceFlags等,还有一些PEB里的结构。
源头消灭BeingDebugged
方法是在编写调试器时,在第二次LOAD_DLL_DEBUG_EVENT时,将BeingDebugged设置为TRUE,然后停在系统断点处,继而消除BeingDebugged。
回归Native
CheckRemoteDebuggerPresent
测试代码:
1 | void ckremotedebugger_test() { |
使用该函数进行反调试,攻击者将BeingDebugged标志位设置为0仍然会检测到进程处于调试态:
该函数关键代码:
1 | 76722C84 6A 00 push 0x0 |
关键函数:NtQueryInformationProcess(dword ptr ss:[ebp+0x8],0x7,0x4,0x0),根据该函数的返回值1或者0来判断是否被调试,其中0x7代表查询ProcessDebugPort,该参数可以通过使用NtSetInformationProcess函数来设置为0,从而使调试器无法与被调试进程通信以达到反调试的目的,但是这个参数只能在为0情况下被设置。
ThreadHideFromDebugger
测试代码:
1 | typedef DWORD(WINAPI* ZW_SET_INFORMATION_THREAD)(HANDLE, DWORD, PVOID, ULONG); |
ZwSetInformationThread函数通过将HideFromDebugger结构设置为True, 来达到类似于设置ProcessDebugPort为0x0从而使调试器和被调试进程断开的目的。
DebugObject
调试器与被调试进程建立关系有两种途径:
- 创建进程时设置
DEBUG_PROCESS。 - 调用
DebugActiveProcess附加到某个已经运行的进程上。
DebugActiveProcess函数是对DbgUiConnectToDbg函数的Wrapper,DbgUiConnectToDbg函数创建一个DebugObject,并存储到NtCurrentTeb()->DbgSsReserved[1]。因此可以通过检测NtCurrentTeb()->DbgSsReserved[1]来判断是否存在调试器,若NtCurrentTeb()->DbgSsReserved[1]不为空就说明当前进程是一个用户态调试器的进程。
SystemKernelDebuggerInformation
函数ZwQuerySystemInformation的参数SystemInformation被设置为SystemKernelDebuggerInformation时,可以判断是否有系统调试去存在。
测试代码:
1 | typedef struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION |
用于检测以Debug方式启动的系统。
Hook & AntiHook
主要是Windows2000及Win9.X的情形,许多调用规则已经不一样了。。。
小节
文章最后给了一种通用调用ThreadHideFromDebugger的方法:
1 | push 0 |
小技巧
检测OD
- 特征码
OD 1.1的几个特征码:
1 | 0x401126->83 3D 1B 01 |
- 检测DBGHELP模块
检测一个进程有无加载DBGHELP.DLL。
- 查找窗口
FindWindow、EnumWindow、GetForeGroundWindow这几个方法可以查找窗口。
直接查找进程Ollydbg.exe
SeDebugPrivilege方法
打开CSRSS.EXE进程间接使用SeDebugPrivilegel来判断进程是否被调试了。
SetUnhandledExceptionFilter方法
EnableWindow方法
使用EnabelWindow方法暂时锁定前台的窗口,此时调试器无法工作。
- BlockInput方法
调用BlockInput锁住键盘。
防止调试器附加
RING3调试器附加使用的是DebugActiveProcess函数,附加相关进程是先致谢ZwContinue函数,最后停在DbgBreakPoint函数。因此只需要对ZwContinue函数以及DbgBreakPoint函数进行检测就可以了。
父进程检测
正常程序启动时父进程应该是“Exploer.exe”、“cmd.exe”、“Services.exe”中的一个,因此只需要对一个进程的父进程进行检测,若不是上面的三个之一就可以认为被调试了。
绕过方法比较简单,跳过检测函数即可。
时间差
若程序开启的时间比较长,就可以认为一个程序被追踪了,当然,误报率比较高,所以可以作为其他检测的一个开启条件。
Trap Flag 检测
1 | pushfd; |
双进程保护
RING3级别的调试中,一个进程只有一个调试器,因此可以使用该方法进行双进程保护。







