先说一下,对于加密与解密第四版中的反跟踪技术和逆核中的反调试相比,逆核更容易理解,加密与解密太底层了

静态

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
2
3
4
5
6
7
NTSTATUS NtQueryInformationProcess(
HANDLE ProcessHandle, // 进程句柄
PROCESSINFOCLASS ProcessInformationClass, // 要查询的信息类别
PVOID ProcessInformation, // 指向存储结果的缓冲区
ULONG ProcessInformationLength, // 缓冲区的大小
PULONG ReturnLength // 实际写入缓冲区的字节数
);

该函数可作为调试器探测器

该函数是通过传入不同的枚举类型,来检测是否有反应

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
typedef enum _PROCESSINFOCLASS {
ProcessBasicInformation = 0, // 基本信息
ProcessQuotaLimits, // 配额限制
ProcessIoCounters, // I/O 计数器
ProcessVmCounters, // 虚拟内存计数器
ProcessTimes, // 进程时间信息
ProcessBasePriority, // 基本优先级
ProcessRaisePriority, // 提高优先级
ProcessDebugPort, // 调试端口
ProcessExceptionPort, // 异常端口
ProcessAccessToken, // 访问令牌
ProcessLdtInformation, // 局部描述符表信息
ProcessLdtSize, // 局部描述符表大小
ProcessDefaultHardErrorMode, // 默认硬错误模式
ProcessIoPortHandlers, // I/O 端口处理程序
ProcessPooledUsageAndLimits, // 池使用情况和限制
ProcessWorkingSetWatch, // 工作集监视
ProcessUserModeIOPL, // 用户模式 IOPL
ProcessEnableAlignmentFaultFixup, // 对齐错误修复
ProcessPriorityClass, // 优先级类别
ProcessWx86Information, // Wx86 信息
ProcessHandleCount, // 句柄数量
ProcessAffinityMask, // 关联掩码
ProcessPriorityBoost, // 优先级提升
ProcessDeviceMap, // 设备映射
ProcessSessionInformation, // 会话信息
ProcessForegroundInformation, // 前台信息
ProcessWow64Information, // WOW64 信息
ProcessImageFileName, // 映像文件名
ProcessLUIDDeviceMapsEnabled, // LUID 设备映射已启用
ProcessBreakOnTermination, // 终止时中断
ProcessDebugObjectHandle, // 调试对象句柄
ProcessDebugFlags, // 调试标志
ProcessHandleTracing, // 句柄跟踪
ProcessIoPriority, // I/O 优先级
ProcessExecuteFlags, // 执行标志
ProcessResourceManagement, // 资源管理
ProcessCookie, // Cookie 值
ProcessImageInformation, // 映像信息
ProcessCycleTime, // 周期时间
ProcessPagePriority, // 页面优先级
ProcessInstrumentationCallback, // 仪器化回调
ProcessThreadStackAllocation, // 线程堆栈分配
ProcessWorkingSetWatchEx, // 扩展工作集监视
ProcessImageFileNameWin32, // Win32 映像文件名
ProcessImageFileMapping, // 映像文件映射
ProcessAffinityUpdateMode, // 关联更新模式
ProcessMemoryAllocationMode, // 内存分配模式
ProcessGroupInformation, // 组信息
ProcessTokenVirtualizationEnabled, // 令牌虚拟化已启用
ProcessConsoleHostProcess, // 控制台主机进程
ProcessWindowInformation, // 窗口信息
ProcessHandleInformation, // 句柄信息
ProcessMitigationPolicy, // 缓解策略
ProcessDynamicFunctionTableInformation, // 动态函数表信息
ProcessHandleCheckingMode, // 句柄检查模式
ProcessKeepAliveCount, // 保活计数
ProcessRevokeFileHandles, // 撤销文件句柄
ProcessWorkingSetControl, // 工作集控制
ProcessHandleTable, // 句柄表
ProcessCheckStackExtentsMode, // 堆栈扩展检查模式
ProcessCommandLineInformation, // 命令行信息
ProcessProtectionInformation, // 保护信息
ProcessMemoryExhaustion, // 内存耗尽
ProcessFaultInformation, // 故障信息
ProcessTelemetryIdInformation, // 遥测 ID 信息
ProcessCommitReleaseInformation, // 提交释放信息
ProcessDefaultCpuSetsInformation, // 默认 CPU 集信息
ProcessAllowedCpuSetsInformation, // 允许的 CPU 集信息
ProcessSubsystemProcess, // 子系统进程
ProcessJobMemoryInformation, // 任务内存信息
ProcessInPrivate, // 私有模式
ProcessRaiseUMExceptionOnInvalidHandleClose, // 无效句柄关闭时引发用户模式异常
ProcessIumChallengeResponse, // IUM 挑战响应
ProcessChildProcessInformation, // 子进程信息
ProcessHighGraphicsPriorityInformation, // 高图形优先级信息
ProcessSubsystemInformation, // 子系统信息
ProcessEnergyValues, // 能量值
ProcessActivityThrottleState, // 活动限制状态
ProcessActivityThrottlePolicy, // 活动限制策略
ProcessWin32kSyscallFilterInformation, // Win32k 系统调用过滤信息
ProcessDisableSystemAllowedCpuSets, // 禁用系统允许的 CPU 集
MaxProcessInfoClass // 最大枚举值(占位符)
} 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
2
3
4
5
6
NTSTATUS ZwQuerySystemInformation(
SYSTEM_INFORMATION_CLASS SystemInformationClass,
PVOID SystemInformation,
ULONG SystemInformationLength,
PULONG ReturnLength
);

传入第二个参数是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