@TOC
核心原理的最后一章是反调试,闲的无聊,在宿舍又读了一遍反调试,做了个总结。

静态反调试

PEB

PEB被广泛运用于反调试手段
这里一共有四个标志

1
2
3
4
0x2 BeingDebugged
0xc Ldr
0x1b ProcessHeap
0x68 NtGlobaFlag

首先说第一个BeingDebugged
这个标志对应的api就是我们最常见的IsDebuggerPresent,当其值为1的时候代表程序被调试,破解方法就是在内存中把1修改为0即可。

第二个Ldr标志,这个只适用于xp操作系统
PEB.Ldr指向PEB_LDR_DATA结构体,这个结构体实在堆内存创建的,而在调试的时候,未使用的堆内存会被填充为0xfeeefeee,只要我们把其修改为null,既可绕过反调试

第三个标志ProcessHeap,同样只使用于xp系统
ProcessHeap可以通过GetProcessHeap()这个api来获取
它指向HEAP结构体的指针
在这里插入图片描述第三个flags成员和第四个ForceFlags成员被用作反调试
正常情况下flags的值为2 ForceFlags的值为0

第四个标志就是NtGlobaFlag
这个在调试的时候,他的值会被设置为0x70,而在附加调试器的时候他的值不会改变
把其值调会0,即可绕过反调试

NtQueryInformationProcess()

NtQueryInformationProcess()是与进程相关联的api,通过调用这个api可以获取与进程相关的调试信息,当为这个api的第二个参数设置特定值,并调用这个函数的时候,相关信息会返回到第三个参数上。
在这里插入图片描述第二个参数是枚举类型
在这里插入图片描述与反调试相关联的就是注释的这三个参数
ProcesDebugPort
进程调试的时候,系统会为其分配一个端口,调用此api就可以返回端口
当第二个参数设置为7时,非调式时第三个参数为0,调试的时候,其值为 0xffffffff
ProcessDebugObjectHandle
当第二个参数设置为0x1e时,第三个参数可以获取调试对象的句柄
非调试 句柄值为0 调试时句柄的值存在
ProcessDebuggFlags
当第二个参数设置为0x1f的时候,第三个参数为调试的标志
被调试为1 不被调试为0

NtQuerysystemInformation()

NtQuerysystemInformation() 基于调试环境检测的反调试技术,利用这个api可以判断当前操作系统是否在调试模式下
在这里插入图片描述
在这里插入图片描述

这个api的第一个参数是枚举类型,当向第一个参数穿入0x23时,第二个参数在调试的时候会被设置为1

NtQueryObject()

系统中某个调试器调试进程时,会创建1个调试对象类型的内核对象。检测该对象是否存在即可判断是否有进程在被调试
和之前的类型,第二个参数时枚举类型,传入特定值后,相关信息会被返回到第三个参数
为了防止反调试,只需保证第二个参数的值不是3而是0即可
在这里插入图片描述

ZwSetInformationThread()

调用这个api可以把被调试的程序从调试的程序中分离出来
只要第二个参数设置为0x11即可
破解方法就是把第二个参数的值修改为0

这个api的工作原理是把线程隐藏起来,从而使调试器接受不到信息,就无法进行调试
还有一个和此类似的api是DebugActiveProcessStop(),它的原理是分离调试器和被调试的进程

TLS回调函数

这个就比较常见了,TLS回调函数会优先于EP代码执行,可以在前面加一些反调试,或者smc等手段,就可以达到反调试的效果,曾经被这个题目折磨过一次,记忆尤新

ETC

这方法就很鸡贼了
在这里插入图片描述

动态反调试

异常

关于异常的东西,我会在下篇博客里好好学习一下,因为最近遇到太多异常的题目了
关于异常的反调试,就是说,在正常的程序执行中,遇到一个异常(比如说除0),如果程序是没有被调试的,那么他就会交给特定的异常处理代码,而程序如果是在被调试,那么就会交给调试器来处理。

这里有一个api是SetUnhandledExtionFilter()
当程序发生异常时,SEH没有处理或者没有SEH的时候,就会调用这个api来执行程序的最后一个异常处理器(TOP Level Exception Filter),就是我们平常遇见终止程序的那个框框
而当程序在调试的时候,可以通过这个api来修改最后的异常处理器,新的异常处理器,修改修改eip
就可以到达反调试的效果

Timing Check

时间检测,这个还是可以很好理解的,调试的时候两条指令直接消耗很长的时间,而正常运行的时候,就一下子跑过去了。
x86中有一个叫TSC的64位寄存器,可以用来保存时间,
RDTSC这条汇编指令可以把TSC寄存器的值读到EDX:EAX

INT 2d

这条指令有两个特性
1忽略下一个字节
2可以一直运行到断点处

这两条是我在书上总结出来的,当然我还在网上发现了不一样的说法
第 0x2d(45) 号中断处理函数是 KiDebugService,执行Int 2dh指令时,进程如果没有处于被调试状态,将会抛出异常,如果在被调试状态则能够正常执行。我们可以利用这一点检测调试器的存在。

如果有调试器,调试器就会接管这个 int 2dh 产生的异常从而不走我们设置的异常回调处理函数,当然如果调试器选择不接管这个异常我们是无法检测出调试器的

0xcc

我们调试的时候,经常会在一些api上下断点,那么只要检查api的首位是不是0xcc就可以判断是否在调试,当然这种判断不是特别严谨,破解方法的话就是不在api的首位下断点,或者不下0xcc断点。
同时,这里还有一种校验和的判断手法,就是把这一些代码的汇编加起来,看看有没有改变等手段

高级反调试

PE保护器

就是加壳

垃圾代码

一句话用5句话来说,就要耗死逆,ollvm就是这么干的

扰乱代码对齐

花指令

加密/解密

在壳中或者smc字解码里常见

api重定向

把程序的api保护起来,放在其他的地方,这样使我们不能够通过下断点来寻找api

Debug Blocker

双进程保护,这种保护手段在最近的题目中经常遇见,
就是自己的一部分代码作为调试器调试自己的另一部分代码 那么在运行程序的时候不能附加调试器了
然后在里面设置一些smc或者修改一些eip的手段,达到混淆代码的目的
当时记录的一些笔记
在这里插入图片描述

总结

反调试的题目反正我遇见的不是很多,但是学习一下还是很有必要的
继续解决异常处理的问题