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级别的调试中,一个进程只有一个调试器,因此可以使用该方法进行双进程保护。