先说一下,对于加密与解密第四版中的反跟踪技术和逆核中的反调试相比,逆核更容易理解,加密与解密太底层了
静态
PEB
记录在PEB结构中的调试标志位置和解决方法,注意调试引起的标志位置改变基本上是连续性的,蝴蝶效应
BeingDebugged
位置:0x02
调试被改变的最基本位置,也有已经包装好的函数,通过查看该标志位判断是否调试中
IsDebuggerPresent函数,通过访问BeingDebugged,得到结果
非调试时值为0
Ldr
位置:0x0C
调试中,堆中的内存区域中就会出现一些特殊标识,其中最醒目的是很多的0xFEEEFEEE,本来这里是NULL,而Ldr是指向的内容刚好在堆中
非调试时0xFEEEFEEE是NULL
ProcessHeap
位置:0x18
通过BeingDebugged实现对PEB.ProcessHeap改变,也有对他访问判断调试的封装函数
GetProcessHeap函数,通过访问ProcessHeap,类似于IsDebuggedPresent
ProcessHeap指向的是一个HEAP,该结构中flags和ForceFlags是会被改变的标志,正常是2和0,所以只需要改回来即可
NtGlobalFlag
位置:0x68
调试时,该成员会被改变为0x70
将他改变为0即可
NtQueryInformationProcess()API
1 | NTSTATUS NtQueryInformationProcess( |
该函数可作为调试器探测器
该函数是通过传入不同的枚举类型,来检测是否有反应
1 | typedef enum _PROCESSINFOCLASS { |
枚举的成员如上,接下来要研究哪些成员对于判断调试有重要作用,所以接下来的位置是相对于枚举类型基础的
ProcessDebugPort
位置:0x07
进程处于调试的时候,会分配一个调试端口(Debug Port),当枚举参数为ProcessDebugPort,调用的NtQueryInformationProcess()函数就可以获取调试端口,若返回值显示,正在调试,就会将一个变量dwDebugPort设置为0xFFFFFFFF,非调试状态时是0
与前面的一样,一个类似于IsDebuggerPresent函数的CheckRemoteDebuggerPresent函数也用于检测调试,该函数不同的是,它的作用是调用NtQueryInformationProcess,嘿嘿无限套娃,然后获得是否调试
ProcessDebugObjectHandle
位置:0x1E
调试进程时会生成调试对象(Debug Object),如果调试对象存在,也就是NtQueryInformationProcess传入枚举是ProcessDebugObjectHandle时,会返回调试对象句柄,如果非调试状态,就是NULL
ProcessDebugFlags
位置:0x1F
看名字就知道,调试标志,调试是0,不调试是1
NtQueryInformationProcess()函数
1 | NTSTATUS ZwQuerySystemInformation( |
传入第二个参数是SystemKernelDebuggerInforation的时候,就代表该函数要查询是否有有关于调试的信息,通过返回值来确定是否在调试
可能发生的结果
调试器已启用且存在:
KernelDebuggerEnabled为TRUE。KernelDebuggerNotPresent为FALSE。
调试器未启用但存在:
KernelDebuggerEnabled为FALSE。KernelDebuggerNotPresent为FALSE。
调试器不存在:
KernelDebuggerEnabled为FALSE。KernelDebuggerNotPresent为TRUE。
调用失败:
- 如果
ZwQuerySystemInformation返回错误(例如,权限不足),会输出相应的错误代码。
NtQueryObject()函数
前面说过,调试的时候会有对象(哦吼,调试包分配吗) ,对于这个部分我们要主要研究它的函数实现, 不过因为我目前实力不够,所以还是研究破解之法
破解之法就是再调用这个函数的时候,第二个参数一般会传入ObjectAllTypesInformation(3)改成0就好了
ZwSetInformationThread()
函数作用是将程序分离出调试器,如果是正常运行就不会发生什么,调试的话就会退出
和前面那个一样,修改传入的参数即可,将第二个参数ThreadInformationClass改为0即可
TLS回调函数
这里待会去看回调函数然后可能单开一个记
ETC
怎么还etc上了,我又不上高速(bushi)
这里要跳出固定思维,前面总是在利用各种寄存器或者标志位来决定是否在调试中,但是其实还可以看看你是在哪运行的,也就是说你是windows直接双击点开的,还是用x64dbg,OllyDbg打开的
这里的实现通过一下函数可以实现因为过于多了,所以记一部分
检测OllyDbg窗口<-FindWindow()
检测OllyDbg进程<-CreateToolhelp32napshot()
……
解决办法比较单一,要么就是覆盖要检测的部分,要么就是覆盖返回值,总之就是骗过它
动态
异常
SEH
没错,和前面接上了,这里通过异常和注册处理函数的方式,普通进程可以直接将异常抛给已注册的函数,但是如果是调试,不知道大家看没看过那张图,异常的出现处理,要先判断是否在调试中,如果是调试中会将异常给调试器,所以程序就不可以接着进行了
——我去进修了异常,异常彻底看完了,我前面说的我重新解释补充
就是说如果我用到了SEH安装了回调函数,然后在程序中故意报错,来到回调函数,再进行操作,在调试的时候这个报错会优先抛给调试器,而调试器并没有回调函数来处理,就造成了反调试
解决的办法我认为两种,一让调试器忽略他,然后系统就会分发给下一级VEH,然后是SEH,然后就会让回调函数进行处理了,第二种就是他在安装push [SEH]的时候,去看看SEH回调函数里面是什么样子的,然后静态分析
SetUnhandledExceptionFilter()
如果没有安装过SEH,那么这个异常将被系统中的API处理(**kernel32!UnhandledExceptionFilter()**),这个函数会调用一个消息框提示你报错了,并且xxx已停止工作
需要注意的是这个函数内部调用了ntdll!NtQueryInformationProcessAPI,来判断程序是进程正常的还是调试器调用的,如果是正常的就会进入顶层异常处理,如果是调试器调用的,就会通过**SetUnhandledExceptionFilter()**来修改最后的顶层异常处理
所以基于异常的反调试中,可以在顶层异常处理里面注册一个处理函数,然后故意报异常,调试器就会报错,正常进程就还可以执行
破解的话参考前面对于NtQueryInformationProcess函数的处理(前面没记,通过hook技术什么的实现)
Timing Check
通过判断时间来确定是不是在调试中(调试的时候一步步走所以在程序中的时间长,运行的时候就会很短)
有两种方法来实现反调试不过都差不多,而这个反调试的绕过手段要么就是把要测得代码段运行跳过,要么就是改变标志位
陷阱标志TF
单步执行
陷阱标志位如果是1,就会进入单步执行模式,执行一条指令之后就会触发一种异常,异常过后会自动归0,如果在正常程序中就会被SEH处理,而在调试中,好像不会进入SEH,而是会直接单步继续,所以这里可以实现调试和运行的分离
破解法是让调试器直接报一个单步执行的异常,就可以了
INT 2D
是一个中断指令,它的第一个作用是可以让他后面紧跟着的一个字节码失去作用,可能原本是mov的意思,失去一个字节码变成了jmp
第二个特征是,当程序运行到INT 2D的时候,再次步过就会一直运行,知道遇到断点,书中说是在OllyDbg中的bug,相当于原本的字节码顺序被打乱了,系统就不能单步停下了,所以说这里用到的分离的主要原理是
普通程序遇到断点会进入异常处理SEH中,而调试器遇到INT 2D会直接不断地运行而不是异常
所以破解法主要用到了TF,调试的时候将TF改成1,下一步就会触发单步异常,然后就可以进入SEH了
0xCC探测
我终于懂了!!!!0xCC是断点指令,当你调试一个程序,下了一个断点,其实就是在这个代码中添加一个0xCC,所以有些程序就通过检测0xCC来判断,你是不是调试了下断点了
API断点
调试过程中为了方便经常会用到API断点,所以可以在API开头处检测0xCC断点
破解法就是不在第一条指令那下断点
比较检验和
取一部分区域的字节码值加起来,比如原本应该是0x12345678,如果下断点了就可能会改变了可能就是0xD71A5CA2
破解也简单,就是不在计算的范围下断点就好
加密与解密内容
这本书真的很难懂啊,太底层了
小奥秘
寻找特征码
可以根据调试器中的特征码,来判断是否处于调试中
父进程检测
一般的程序其父进程一般是Exploer.exe(资源管理器启动),cmd.exe(命令行启动),Services.exe(系统服务)中一种
如果父进程是其他进程,要么就是PEloader启动的,要么就是调试器
查看父进程方法是通过TEB或者函数来检测当前进程的PID