反调试技术整理
反调试技术整理
Anti-Debug: Exceptions (checkpoint.com)
IsDebuggerPresent()
检查peb
中的BeingDebugged
参数,当返回值为1时表明正在被调试
检查段寄存器中的fs:[0x30] (32位)
和 GS:[0x60] (64位)
也是一样的效果
IsDebuggerPresent - CTF Wiki (ctf-wiki.org)
CheckRemoteDebuggerPresent()
1 |
|
如果调试器存在,pbDebuggerPresent
的值会被设置为0xffffffff
CheckRemoteDebuggerPresent
通过调用NtQueryInformationProcess
来实现对调试器的检测,当参数设置为7时,NtQueryInformationProcess
会返回远程调试器的端口
CheckRemoteDebuggerPresent - CTF Wiki (ctf-wiki.org)
OutputDebugString
在有调试器存在和没有调试器存在时,OutputDebugString函数表现会有所不同。最明显的不同是, 如果有调试器存在,
其后的GetLastError()的返回值为零
异常捕获与处理
程序通过引发或捕获异常,将异常丢给调试器进行处理,从而完成反调试,在没有调试器的时候程序自身需要拥有处理异常的能力
Windows异常
Windows异常处理机制 - 知乎 (zhihu.com)
VEH和SEH
VEH
1.CPU捕获异常
2.通过KiDispatchException
进行分发(3环异常将EIP修改为KiUserExceptionDispatcher)
3.KiUserExceptionDispatcher
调用RtlDispatchException
4.RtlDispatchException
查找VEH
处理函数链表,并调用相关处理函数
5.代码返回到ZwContinue
再次进入0环
6.线程再次返回3环后,从修正的位置开始执行
SEH
1.FS:[0]指向SEH链表的第一个成员
2.SEH的异常处理函数必须在当前线程的堆栈中
3.只有当VEH中的异常处理函数不存在或者不处理才会到SEH链表中查找
异常捕获
硬件异常
1.检测到异常
2.查找IDT表,执行中断处理函数_KiTrap00
_KiTrap00函数
:1)保存现场,将寄存器堆栈信息等保存到_trap_frame
中
2)调用CommonDispatchException
函数(分析参数EAX,EBX中的值)
3.CommonDispatchException
函数:该函数构造一个_EXCEPTION_RECORD
结构体并赋值
1 |
|
4.调用KiDispatchException
软件异常
1.检测到throw
关键字
2.转入CxxThrowException
3.调用KERNEL32.DLL RaiseException
,填充_EXCEPTION_RECORD
结构体
4.转入NTDLL.DLL RtIRaiseException()
5.转入NT NtRaiseException
6.转入NT KiRaiseException
,ExceptionCode
最高位清零
7.调用KiDispatchException
异常处理
内核异常
1.尝试传递给内核调试器
2.失败,调用RtIDispatchException
传递给SEH
RtIDispatchException
函数:遍历异常链表,调用异常处理函数,如果成功处理返回1,失败则调用下一个,直到最后也没成功处理即返回0
3.传递给内核调试器
4.终止Windows运行(BSOD)
用户异常
1.尝试传递给内核调试器
2.失败,则传入KiUserExceptionDispatcher
3.调用RtIDispatchException
查找并执行异常处理函数
4.由VEH链表查找至SEH链表
5.如果未处理,则传递给内核调试器
6.失败,终止进程
try与__try
C++ 异常捕获 try 和 __try的区别-CSDN博客
CloseHandle()
反调试 - CloseHanlde,NtClose_closehandle 检测异常-CSDN博客
调用CloseHandle
释放一个无效句柄,这个异常会被一个异常处理程序缓存,即被调试器缓存,如果没有被调试,那么函数返回FALSE,GetLastError = ERROR_INVALID_HANDLE:0xC0000008
;但如果被调试,那么系统将抛出异常 C0000008H
断点植入(int 2dh, int 3h)
提前植入一个断点,再植入VEH异常处理例程,如果正在被调试的话会抛出中断异常
2d断点会跳过下一个字节
Interrupt 3 - CTF Wiki (ctf-wiki.org)
Page-Guard
分配被保护的内存区,用PAGE_GUARD
属性修饰,填入ret指令,如果程序尝试访问保护页中的地址,系统会引发 STATUS_GUARD_PAGE_VIOLATION (0x80000001)
异常
创建 Guard 页面 - Win32 apps | Microsoft Learn
引发EXCEPTION_EXECUTE_HANDLER错误
EFLAGS
使用EFLAGS写入异常标志,然后监视异常
将EFLAGS与0X100进行或运算,利用VEH检测调试器
EFLAGS寄存器(标志寄存器) - Reverse-xiaoyu - 博客园 (cnblogs.com)
UnhandledExceptionFilter()
引发并不处理异常,诱导调试器去处理
反调试 - SetUnhandledExceptionFilter-CSDN博客
内存扫描与监视
硬件断点寄存器监视
利用GetThreadContext
获取上下文,检查硬件断点寄存器的值是否为0
软断点、硬件断点和内存断点(逆向基础知识) - 知乎 (zhihu.com)
MEM_WRITE_WATCH
利用MEM_WRITE_WATCH
检查申请的内存块中的写入,访问情况
VirtualAlloc 函数 (memoryapi.h) - Win32 apps | Microsoft Learn
LFH低碎片堆
堆碎片是一种状态,其中可用内存被分解为较小的非连续块。 当堆被碎片化时,即使堆中的可用内存总量足以满足请求,内存分配也可能会失败,因为没有一个内存块足够大。 低碎片堆 (LFH) 有助于减少堆碎片
调试工具无法启用LFH,会导致空间开辟错误,因此可以判断是否正在调试
低碎片堆 - Win32 apps | Microsoft Learn
反HOOK
通过对比DLL中函数的地址判断是否被hook
NtGlobalFlag
在 32 位机器上, NtGlobalFlag
字段位于PEB
(进程环境块)0x68
的偏移处, 64 位机器则是在偏移0xBC
位置. 该字段的默认值为 0. 当调试器正在运行时, 该字段会被设置为一个特定的值. 尽管该值并不能十分可信地表明某个调试器真的有在运行
有调试器时会有以下标志位
1 |
|
NtGlobalFlag - CTF Wiki (ctf-wiki.org)
NtQueryInformationProcess
将ProcessInformationClass
设置为7,可以检查调试器
NtQueryInformationProcess 函数 (winternl.h) - Win32 apps | Microsoft Learn
NtQuerySystemInformation
当第一个参数传入 0x23 (SystemInterruptInformation) 时,会返回一个 SYSTEM_KERNEL_DEBUGGER_INFORMATION
结构,里面的成员KdKdDebuggerEnable
和 KdDebuggerNotPresent
标志系统是否启用内核调试
查询调试对象
利用NtQueryObject检查所有调试对象,从而禁止调试
Windows软件调试学习笔记(一)—— 调试对象_createdebugobject-CSDN博客
ZwSetInformationThread
将第二个参数设为0x11,可以将进程隐藏
ZwSetInformationThread - CTF Wiki (ctf-wiki.org)
NtYeildExecution
这个函数可以让任何就绪的线程暂停执行,等待下一个线程调度。
当前线程放弃剩余时间,让给其他线程执行。如果没有其他准备好的线程,该函数返回false,否则返回true。
当前线程如果被调试,那么调试器线程若处于单步状态,随时等待继续运行,则被调试线程执行NtYieldExecution时,调试器线程会恢复执行。
此时NtYieldExecution返回true,该线程则认为自身被调试了
父进程检查
如果启动进程不是cmd.exe或explorer.exe,则返回警告
Heap Flags
Heap Flags - CTF Wiki (ctf-wiki.org)
HeapFlag
大于2说明正在被调试
HeapForceFlag
大于0说明正在被调试
Job Object
为了共享权限,调试器和被调试进程会放在同一job object中,通过检查进程同一object中的所有进程,可以枚举出调试器
SeDebugPrivileges
一般进程是默认禁用SeDebugPrivilege权限的,但是调试器会开启此权限,通过调试器开启的进程也会获得该权限,通过检查进程是否启用SeDebugPrivilege权限可以间接检测调试器
在 C++ 中启用和禁用特权 - Win32 apps | Microsoft Learn
KUSER_SHARD_DATA
用户空间和内核空间有一块共享空间KUSER_SHARD_DATA
,可以通过检查其中的内核调试检查位KdDebuggerEnabled
来获取内核调试状态
KUSER_SHARED_DATA (ntddk.h) - Windows drivers | Microsoft Learn
0xCC
Debug版本下未被初始化的区域会被设置为0xCC,字符为“烫”,通过检查关键区域的字节码,确认其中是否有0xCC,可以检测到是否处于调试状态
TLS_Callback
TLS在main函数之前执行
[原创]TLS回调函数(Note)-软件逆向-看雪-安全社区|安全招聘|kanxue.com
WUDF
WUDFPlatfrom.dll
中有一个函数类似于IsDebuggerPresent
,可以用于检测调试器