这本书的反调试技术分成了静态反调试技术、动态反调试技术、高级反调试技术
我只是写下来作为一个笔记提示,其中可能有些写的略微简略,也应不乏谬误,欢迎提出意见,我再将其修缮
静态反调试技术:
A) PEB结构体可用来进行反调试的四项:
+0x002 BeingDebugged ;UChar
…
+0x00c Ldr ;Ptr32_RTL_USER_PROCESS_PARAMETERS
…
+0x018 ProcessHeap ;Ptr32 Void
…
+0x068 NtGlobalFlag ;Uint48
1. BeingDebugged 一个Flag
被调试:1
非调试:0
相关函数:IsDebuggerPresent() ;获取PEB.BeingDebugged值来判断
破解:将BeingDebugged值置1
2. Ldr
Ldr指向一个在堆中创建的_PEB_LDR_DATA结构体
而调试进程的时候,未使用的堆内存区域全部填充着0xEEFEEEFE
破解:将0xEEFEEEFE全部用NULL填充
注:XP系统之后不适用
3. Process Heap
指向HEAP结构体的指针
非调试状态:
此结构体Flags(+0xC)被设置为0x2
Force Flags(+0x10)被设置为0x0
相关函数:GetProcessHeap() 获取结构体地址
注:仅XP系统可用
4. NtGlobalFlag
调试状态:值为0x70
FLG_HEAP_ENABLE_TAIL_CHECK(0X10)|
FLG_HEAP_ENABLE_FREE_CHECK(0X20)|
FLG_HEAP_VALIDATE_PARAMETERS(0X40)
非调试状态:值为0
破解之法:该值设0
注:将运行中的进程附加到调试器时,NtGlobalFlag值不变
附:PEB位置查找方式
1.
FS:[0x30]=address of PEB
2.
FS:[0x18]=address of TEB -> EAX (FS:[0x0]为TEB偏移0x0处的值)
[EAX+0x30] = address of PEB
这种方法是上一种的扩展形式,看起来比较麻烦
B) NtQueryInformationProcess() API探测调试器技术
第二个参数——枚举类型,指定特定值,函数会将信息设置到第三个参数中
ProcessDebugPort ;+0x7
ProcessDebugObjectHandle ;+0x1E
ProcessDebugFlags ;+0x1F
以下针对第三个参数:
ProcessDebugPort获取调试端口——调试的时候系统分配进程一个调试端口FFFFFFFF,非调试状态为0
ProcessDebugObjectHandle获取调试对象句柄——如果进程处于调试状态,调试句柄值就存在,非调试状态为NULL
ProcessDebugFlags调试标志——调试状态,为0;非调试状态,为1。
附:
1.函数定义

2. 枚举类型

C) NtQuerySystemInformation()——基于调试环境检测的反调试技术(检测当前OS是否在调试模式下运行)
第一个参数 SYSTEM_INFORMATION_CLASS SystemInformationClass
SYSTEM_INFORMATION_CLASS是枚举类型
值为SystemKernelDebuggerInformation(0x23)时
第二个参数PVOID SystemInformation被填入
SYSTEM_KERNEL_DEBUGGER_INFORMATION结构体地址
系统处于调试状态:
SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerEnabled=1
破解:以正常模式启动windows
附:
1.SYSTEM_INFORMATION_CLASS枚举类型

2.函数定义

D) NtQueryObject()——调试器调试进程时,系统会创建一个内核调试对象
第二个参数OBJECT_INFORMATION_CLASS ObjectInformationClass赋予特定值,相关信息会被填入第三个参数PVOID ObjectInformation
arg2:
OBJECT_INFORMATION_CLASS ObjectInformationClass是一个枚举类型,ObjectAllTypesInformation(0x3)获取系统所有对象信息,再从中检测是否存在调试对象。
arg3:(when arg2 == 0x3)
ObjectInformation
系统所有对象信息,先将其转换为POBJECT_ALL_INFORMATION类型
OBJECT_ALL_INFORMATION是一个结构体,前面加上一个大写字母P代表指向这个结构体的指针。结构体的第二个成员
OBJECT_TYPE_INFORMATION ObjectTypeInformation结构体的第一个成员TypeName,当此项为”DebugObject”时,说明正在调试
循环直到结束,如果没有,则不在调试中
亦即判断
ObjectInformation->ObjectTypeInformation->TypeName == “DebugObject”
与否
破解:
将函数第二个参数从0x3改为0x0,或者钩取对应api函数
注意:书中举的例子是ZwQueryObject()而非NtQueryObject()
书中并没有对其作解释,下面是微软给的说法

关于Nt前缀与Zw前缀的区别:
附函数定义以及一些结构体:
1.NtQueryObject()函数定义

2. SYSTEM_INFORMATION_CLASS枚举类型

3. OBJECT_ALL_INFORMATION结构体

4. OBJECT_TYPE_INFORMATION结构体
E) ZwSetInformationThread()——强制分离调试器
第一个参数ThreadHandle(枚举类型)用来接收当前线程的句柄
第二个参数ThreadInformationClass表示线程信息,当其设置为ThreadHideFromDebugger(0x11)后,调试进程会被分离出来。
非调试状态:该函数没有任何影响
调试状态:调试器终止,停止自身进程
破解:将ThreadInformationClass参数改为0x0
或者钩取此api函数
附:
1.ThreadHandle(枚举类型)

2.ZwSetInformationThread()函数定义

F) TLS回调函数
回调函数会先于EP代码执行,在这个时候可以使用其他的静态反调试技术进行判断。
破解之法:将OD断点设置为System breakpoint即可。
G) 其他:
检测OllyDbg窗口 <-FindWindow()
检测OllyDbg进程 <-CreateToolhemp32Snapshot()
检查计算机名称是否为”TEST”、”ANALYSIS”等 <-GetComputerName()
检查程序运行路径中是否存在”TEST”、”ANALYSIS”等 <-GetCommandLine()
检查虚拟机是否处于运行状态 <-查看虚拟机特有的进程名称VMWareService.exe等
动态反调试技术:
1. SEH:
首先安装SEH,然后触发INT3异常。用户模式中调试器什么也不做。
正常运行:
转到安装的SEH处理函数中,该处理函数通过设置CONTEXT.Eip进行跳转到正常运行的代码段。
调试运行:
调试器因为什么也不做走入程序终止/垃圾代码段(极大消耗逆向人员精力)。
破解方法:忽略INT3 break异常选项
2. SetUnhandledExceptionFilter()
如果程序发生异常,SEH未处理或者不存在,那么系统就会运行最后一个异常处理器Top Level Exception Filter,弹出错误消息框,程序停止运行。
反调试技术的程序会:先特意触发异常;进入UnhandledExceptionFilter()内部;再进入使用SetUnhandledException()注册的顶级异常函数,即TopLevelExceptionFilter()API;判断程序正常还是调试运行,以设置eip的值。
注意:UnhandledExceptionFilter()函数内部有ZwQueryInformationProcess()函数,见静态反调试B)
破解:先解除UnhandledExceptionEilter()中的静态反调试,然后看看程序打算注册的新TopLevelExceptionFilter()函数打算返回到哪个地址。
附:
a. 注册顶级异常的方式:
将新的TopLevelExceptionFilter()地址传递给SetUnhandledException()的参数即可。
注意:TopLevelExceptionFilter()函数内部是自己实现的。
在UnhandledExceptionFilter()函数内,其将参数传递给TopLevelExceptionFilter()函数
b. 相关函数api


3. Timing Check
程序逐行跟踪程序代码比正常运行耗费时间多得多,因此比对时间差异就可以判断是否在调试。
x86CPU中有一个TSC(Time Stamp Counter)的64位寄存器,CPU对每个时钟周期计数,填入TSC。
RDTSC汇编指令用来将TSC值读入EDX:EAX寄存器。
将EDX和EAX的数值和相应的数值对比即可。
破解方法:
不跟踪,直接run过
修改第二次RDTSC结果
就该条件分支指令
附:
a) 时间间隔测量法

b) 测量精度

4. 陷阱标志
陷阱标志指的是EFLAGS第九个比特位TF,当TF为1时,CPU进入单步执行模式。在该模式下,CPU执行一条指令就会触发EXCEPTION_SINGLE_STEP异常,之后TF会自动清零。
程序不能直接修改EFLAGS,因此需要结合PUSHFD/POPFD修改TF的值(or0x100)
如果程序正常运行,经过TF=1下的一条指令之后,直接进入异常
如果程序调试运行,则继续执行下面的代码(可能程序终止运行,可能进入垃圾代码)
破解:调试器忽略EXCEPTION_SINGLE_STEP异常,在注册SHE地址设置断点。
附:
EFLAGS

5. INT 2D
原用于内核模式触发断点异常(也可以用户模式下触发异常),但是调试状态下不会触发异常,只会忽略。
在调试状态下的特性:
1. 忽略下一条指令的第一个字节(可以形成代码混淆)
2. 使用F7或者F8时,会一直运行到断点处(即不能单步暂停,会一直运行,像F9),属于OD的一个Bug
破解:首先设置OD忽略EXCEPTION_SINGLE_STEP异常,在INT 2D语句设置TF标志,进入异常(INT 2D为内核指令,因此调试器在下一条指令处暂停,TF不清零,再次F7/8会触发异常,就会进入指定SHE继续调试了)
6. API断点
探测代码逆向分析人员常用的API开头是否为0xCC
破解:设置断点在API代码的中间部分之下,或者设置硬件断点也可以。
附:
1. 背景知识:像OD这种调试器,设置断点或者单步调试,都是设置0xCC做到的,程序只不过没有显示出来而已
2. 逆向分析常用API



7. 比较校验和
特定代码区域计算校验和,对比失败说明程序可能设置了断点。
破解:修改校验和比较语句,或者调试器强制修改JMP
注:这种方法会巧妙隐藏在程序各处,可能数十个、数百个,可以大大增加破解的难度。
高级反调试技术:
1. 垃圾代码
垃圾代码中引入很多可以相互抵消的指令,减缓逆向人员的分析速度。
2. 扰乱代码对齐
加入额外代码,令分析程序错误解析。
比如415115-415119被调试器解析为一条指令,而前面程序有jmp到415116的,其实软件本身是415116-415119为一条指令的。
3. 加密/解密
程序一部分代码用于将其余部分代码解密,以隐藏程序与数据。
注:反转储技术中,加密代码被解码为正常代码后,有时会被再次加密,所以转储的代码可能还是处于加密状态。
特殊情况:代码重组——指令修改程序的代码。
4. Stolen Bytes(Remove OEP)
顾名思义:
将源代码OEP(一部分代码)转移到压缩器/保护器创建的内存区域运行。
注:有些保护器先保存Stolen Bytes再运行,还有些保护器将它们直接从内存中删除。
5. API重定向
防止在API处被设下断点
类似于API钩取,将本来call API的地址CALL到另一个地址,另一个地址预先创建好和原先API同一个功能的代码。
该书中第二个示例实现方式较为复杂,可以详细看看
6. Debug Blocker(Self Debugging)
调试模式下运行自身进程。
这种技术是自我创建技术(以子进程形式运行自身进程)的演进形式。
有如下优点:
1.防止代码调试,因为子进程运行实际的源代码处于调试之中,原则上无法再使用其他调试器附加。(57章会讲到一种方法破解)
2.能够控制子进程,因为对于调试进程发生的异常,调试器拥有优先处理权,所以可以把异常处理器的代码放到调试进程中。——因此,想要调试子进程,要先断开已有调试器的连接,而这样子进程又无法执行。

上一篇帖子:https://bbs.pediy.com/thread-264679.htm
下一篇帖子: NULL