Ikoct的饮冰室

你愿意和我学一辈子二进制吗?

0%

记录一次用头顶A〇E反作弊反作弊导致头破血流的分析经历

测试环境: Windows 10.0.18363

Ring-3 环境检测

内核代码完整性以及测试模式

测试模式和内核完整性校验是否开启:

image-20260509211137878

nixxeBase.dll使用动态解析出的 shellcode 来执行直接 syscall:

image-20260518223156432

分配点位于nixxeBase+0000000001111791:

image-20260519214024003

调试模式是否开启

image-20260509221948742

这个检测复用测试模式检测链路, 都是SystemCodeIntegrityInformation查询, 其中也包含了调试模式标志

A〇E_Base64.dll直接调用NtQuerySystemInformation查询内核调试器标志位:

image-20260519125110419

虚拟机环境

image-20260509222038384

A〇E_Base64.dll直接调用GetSystemFirmwareTableStub进行SMBIOSACPI检测

image-20260519125526418

进一步调试还可以发现直接 sycall 每次被写入可执行页的偏移都不一样, 通过强制修改分配给直接 syscall 的页属性为r-x可以拦截到这个写入过程发生在nixxeBase+00000000004c3a47, 使用rep movs, 目标缓冲区为rdi:

image-20260519211441313

Ring-0 环境检测

过完上面的检测之后大概就进入了驱动的部分, 因为直接蓝屏了(

首要怀疑它检测了内核调试环境, 换了一个纯净环境再启动, 果然就弹出了另一个版本的虚拟机环境警告

内核调试环境

那么大概可以确定它确实检测了内核调试器, 直接 trace 找到标志位读取点位不太现实, 估计得跑一万年, 想用KA〇E模拟执行但是这个驱动显然不可能独立加载, 于是只能找可能的上游点位, 第一个想到的就是它可能要读取内核导出的KdDebuggerEnabledKdDebuggerNotPresent, 绕不过的一点就是通过符号名找到这两个标志位的地址(我就不信它编码了所有系统的版本号和对应偏移直接定位到), 并且A〇E_BASE.sys中确实导入了MmGetSystemRoutineAddress, 不得不 hook 看看了, 于是得到了一个污点:

image-20260520184034447

得到两个标志位的地址后立马就触发了蓝屏, 很难不深究了

交叉引用它得到的调试标志位可以找到它们被用于对比的点位分别在A〇E_BASE+0000000000442F4FA〇E_BASE+0000000000442FD8, 先浅浅地通过 hook 交换它得到的两个标志位地址, 实际上应该可以直接返回两个指向自己模块的恒真和恒假的指针, 但是我怕它还检测解析符号得到的地址是否在 nt 模块范围内就直接交换了一下得到的两个标志位地址

但是依旧蓝屏, 蓝屏代码依旧1030002, 这时候可以想到还有一份镜像内存放在KUSER_SHARED_DATA里, 直接在A〇E_BASE.sys里搜立即数0xFFFFF780000000000xFFFFF780000002d4果然找到了00000000004252A3处读取了该字节校验镜像内存中的KdDebuggerEnabled, 先在这里下个断点手动改一下结果, 但是依旧蓝屏, 依旧1030002

这个时候敏锐的观察到每次从我修改内存放行到蓝屏中间 Windbg 都会输出 KDTARGET: Refreshing KD connection

虽然A〇E_BASE导入表里没有KdRefreshDebuggerNotPresent, 但是 hook MmGetSystemRoutineAddress 的结果告诉我它很有可能就是使用这个 API 的返回值来检测调试环境的(之前的腾游就有这样检测的先例), 下个在这个函数上断点果然触发了, 而且来源就是A〇E_BASE, 修改 API 返回值后看到了熟悉的虚拟机环境检测不通过的警告:

img

虚拟机环境

直接搜索立即数'VX'可以搜到 3 个 VMware 后门的I/O端口测试:

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
.text:0000000000072A20 sub_72A20       proc near               ; CODE XREF: sub_430CD6+134A↓p
.text:0000000000072A20 push rdx
.text:0000000000072A21 push rcx
.text:0000000000072A22 push rbx
.text:0000000000072A23 mov eax, 564D5868h
.text:0000000000072A28 mov ebx, 0
.text:0000000000072A2D mov ecx, 0Ah
.text:0000000000072A32 mov edx, 'VX'
.text:0000000000072A37 in eax, dx
.text:0000000000072A38 mov eax, ebx
.text:0000000000072A3A pop rbx
.text:0000000000072A3B pop rcx
.text:0000000000072A3C pop rdx
.text:0000000000072A3D retn
.text:0000000000072A3D sub_72A20 endp
.text:0000000000072A3E sub_72A3E proc near ; CODE XREF: sub_430CD6+112A↓p
.text:0000000000072A3E push rdx
.text:0000000000072A3F push rcx
.text:0000000000072A40 push rbx
.text:0000000000072A41 mov eax, 'VMXh'
.text:0000000000072A46 mov ebx, 0
.text:0000000000072A4B mov ecx, 17h
.text:0000000000072A50 mov edx, 'VX'
.text:0000000000072A55 in eax, dx
.text:0000000000072A56 pop rbx
.text:0000000000072A57 pop rcx
.text:0000000000072A58 pop rdx
.text:0000000000072A59 retn
.text:0000000000072A59 sub_72A3E endp
.text:0000000000072A5A sub_72A5A proc near ; CODE XREF: sub_430CD6+95↓p
.text:0000000000072A5A ; sub_430CD6+28D↓p ...
.text:0000000000072A5A push rdx
.text:0000000000072A5B push rbx
.text:0000000000072A5C mov eax, 'VMXh'
.text:0000000000072A61 mov ebx, 0
.text:0000000000072A66 mov edx, 'VX'
.text:0000000000072A6B in eax, dx
.text:0000000000072A6C pop rbx
.text:0000000000072A6D pop rdx
.text:0000000000072A6E retn
.text:0000000000072A6E sub_72A5A endp

全部 patch 掉之后仍然报警告

接下来就是常规抹除 VMware 的进程和驱动痕迹, 但是依然报警告, 安装注册表访问回调后找到了以下可疑键值访问:

1
2
3
4
5
6
7
[Registry] PreOpenKey: SCSI\DISK&VEN_NVME&PROD_VMWARE_VIRTUAL_N\5&25A13950&0&000000
[Registry] QueryValue: HardwareID
[Registry] PreOpenKey: SCSI\DISK&VEN_NVME&PROD_VMWARE_VIRTUAL_N\5&25A13950&0&000000
[Registry] QueryValue: HardwareID
[Registry] PreOpenKey: SCSI\DISK&VEN_NVME&PROD_VMWARE_VIRTUAL_N\5&25A13950&0&000000
[Registry] QueryValue: HardwareID
[Registry] PreOpenKey: SCSI\DISK&VEN_NVME&PROD_VMWARE_VIRTUAL_N\5&25A13950&0&000000

经典检测 VMware 虚拟磁盘标识, 这里可以直接修改.vmx来绕过, 顺手把cpuid的结果也给抹除掉, 我们来到了下一个蓝屏, 蓝屏代码为 0x50, 后续按照VMAware项目的检测方法绕过了除了时间差检测的所有检测, 可惜都没绕过, 也没有人可以问, 遂放弃(

nixxeBase.dll 导入表修复

导入表长得非常抽象, 槽位存放的是一个分配出的 rwx 内存的地址, 这个地址存放了跳到目标API的 jmp 跳板, 跳板用的是相对地址解引用跳转, 这个相对地址也在这块 rwx 内存之内, 最后得到的甚至还不是这个 API, 而是这个 API 的 Stub 槽位, 它本身也是一个跳板, 跳到对应的 API

要恢复比较好的方法就是将这块原始IAT和分配出的 rwx 内存全都在运行时恢复后 dump 下来, 然后进行两次映射恢复IAT