首页
社区
课程
招聘
[原创]某美CS2反作弊驱动静态分析 - P3
发表于: 16小时前 200

[原创]某美CS2反作弊驱动静态分析 - P3

16小时前
200

在我往下挖的时候就发现了一个VT驱动检测点sub_1400EA90E

还原完成就是这样(自行放大看哈)

 

简单来说,这就是一个环境检测函数,他会检测你的环境是不是看着很可疑,例如:

 

疑似被驱动抢占虚拟化

 

并进行置位,返回值是bitmask.

 

如果异常的话,那么就会使用切到更细的 VAD/section 枚举路径对你的环境进行分析,也就是一个进程一个进程的扫你的VAD树(可能是出于性能考虑才在环境异常后干这个而不是常态干),那么到底test了一些什么呢?!

 

·CPUID.1:ECX[31] Hypervisor Present(如果这个位是1,那么就表示在hypervisor 下面跑)

·读Hyper-V / 虚拟化 synthetic MSR的这一段0x40000000 ~ 0x400000FF(正常裸机读这些 MSR 大概率返回 #GP,如果能读到东西说明大概率是虚拟机或者有虚拟化层)

 

·看CPU的APERF/MPERF支持和MSR实际读取是否一致(比如CPUID说我不支持,结果MSR 0xE7 / 0xE8读了又不#GP,那肯定有问题)

 

·尝试翻转CR0.NE的值,如果写入失败了那么就肯定是被拦了,返回true环境该位异常

 

·对 CR0 做一些很逆天的测试,比如尝试往 CR0 的高 32 位 reserved bit 里面写 1。正常 CPU 对这些保留位是很严格的,你乱写肯定要 #GP;如果写进去了,或者写完读回来还像真的一样,那说明这层 CPU 状态大概率是被 hypervisor 模拟出来的,而且模拟得还不够像

 

·测试 CR0 的低位保留位,比如构造一些正常系统根本不该接受的 CR0 组合。正常裸机会直接异常,如果你的 VMM/虚拟化层为了省事直接把 mov cr0, xxx 放过去了,那这个 bit 就会被打上异常分

 

·测 CR3,它会先用 CPUID 0x80000008 拿到 CPU 的物理地址宽度,比如 48 位,然后尝试往 CR3 物理地址宽度之外的 bit 里面塞东西。正常这些 bit 都是 reserved,不能随便写;如果写了不炸,那就是 CR3 虚拟化没处理干净

 

·测 CR3 bit63,也就是 PCID 下的 no-flush 位。这个位不是任何情况下都能用,只有 CR4.PCIDE 开的时候才有意义。所以它会分别测试“开 PCID”和“没开 PCID”两种情况。如果没开 PCID 的时候 CR3 bit63 还能被接受,那就很假

 

·还有 CR4.VMXE,也就是 VT-x 开关位。正常来说这个位能不能写、写了能不能读回来、清了以后 VMX 指令是什么异常,都有固定行为。如果你的驱动已经抢占 VT,然后为了隐藏自己对 CR4.VMXE 做了虚拟化,这里就非常容易露馅

 

·还有 CR4 的非法组合,比如乱改 PAE / LA57 / VMXE 这类关键位。正常 CPU 对 CR4 的组合限制很严格,不能你想怎么写就怎么写。如果 VMM 没按 Intel 手册模拟,就会出现“本来该 #GP 的没 #GP”或者“写完读回来不一致”的情况

 

·然后就是 CR4 高 32 位 reserved bit,这个和 CR0 高位测试类似,就是往不该写的高位写 1。正常应该直接 #GP,如果没有异常或者状态被错误保留,就说明环境可疑

 

·还有 XSETBV,它会故意用一个非法的 XCR index,比如 0x45,去执行 xsetbv。正常情况下这个 index 根本不存在,应该异常;如果居然执行成功,那就是 XSETBV 被虚拟化层错误放行了

 

·还会通过 CPUID 0xD 拿到当前 CPU 支持哪些 XCR0 bit,然后专门找“不支持的 bit”去 xsetbv。正常 CPU 不支持的 XCR0 bit 写进去肯定炸;如果没炸,说明 CPUID 和 XSETBV 行为对不上

 

·它还测 XCR0 的非法组合,比如:

x87 被关掉、只开 AVX 不开 SSE、AVX512 的几个状态位没一起开、MPX 的 BNDREGS/BNDCSR 不匹配。

这些组合正常都是非法的。如果 hypervisor 对 xsetbv 只是简单保存寄存器值,没有严格校验,就会被这个检测抓出来

 

·甚至还有 RDTSC 计时检测。它会连续执行两次 rdtsc,中间用 lfence 做序列化,然后看两次的 delta。正常裸机这个差值应该很小;如果因为 VM-exit、TSC offset、TSC scaling 或者调试器导致延迟很大,就会被认为环境异常

 

·它还会用 KeIpiGenericCall 把检测函数打到所有 CPU 核心上跑,让每个核心都测 rdtsc 是否单调递增。如果某个核心上出现第二次时间戳比第一次还小,那说明 TSC 同步/虚拟化有问题,直接给异常分

·然后测性能计数器,主要是 IA32_FIXED_CTR_CTRL 0x38D、IA32_PERF_GLOBAL_CTRL 0x38F 和 IA32_FIXED_CTR2 0x30B。它会打开 fixed counter,然后连续读计数器,看计数器有没有正常递增、读 MSR 的延迟是不是离谱。如果性能计数器被 VMM 模拟得不准,也会中招

 

·接着测 MPERF / APERF 的计时行为,也就是 MSR 0xE7 / 0xE8。前面已经测了“CPUID 说支不支持”,这里进一步测“读出来的值增长是否合理”。如果增长太慢、太快、不增长、倒退,或者读 MSR 延迟异常,就说明环境不像真机

 

·还有一个比较狠的是 cache / MTRR 行为检测。它会先正常测一段内存访问速度,然后设置 CR0.CD 关闭缓存,再改 IA32_MTRR_DEF_TYPE 0x2FF,配合 wbinvd 刷缓存,再测一次内存访问速度。正常关 cache 以后访问速度应该明显变慢;如果没慢多少,说明 cache/MTRR 行为可能被虚拟化层糊弄过去了

 

·主函数里还直接测了 DR7。它会保存原来的 DR7,然后写入 0x4FF,再读回来。如果读出来不是 0x4FF,说明调试寄存器被拦截、过滤或者虚拟化了。这个一般用来抓调试器、硬件断点隐藏、或者 hypervisor 对 DR 寄存器的处理不一致

 

·还有两个明确针对 VT 的。第一个是清掉 CR4.VMXE 后执行 VMXON。正常情况下 CR4.VMXE = 0 时执行 VMX 指令应该触发 #UD,也就是非法指令异常。如果它不是 #UD,或者居然执行成功,那基本就是 VMX 指令被你的 hypervisor 接管后没模拟对

 

·第二个更细,它会先读 IA32_FEATURE_CONTROL 0x3A,确认 VMX 是否被 BIOS/系统允许,然后根据 IA32_VMX_CR0_FIXED0/FIXED1 和 IA32_VMX_CR4_FIXED0/FIXED1 修正 CR0/CR4,最后拿一个故意构造坏的 VMXON region 去执行 VMXON。正常 CPU 应该返回固定的 VMfail 行为;如果你的 hypervisor 返回的 CF/ZF、异常类型、失败路径和真 CPU 不一致,这里就直接露馅

 

总而言之就是这个某美用了一堆方法,来测试你的机器到底是不是直接交互真实CPU,如果不是预期行为就会给那个位打一个可疑

 

前面环境检测部分提到,这个驱动会主动测试系统当前的虚拟化状态;但是“检测别人有没有抢 VT”和“它自己什么时候启动 VT”是两件事。本节继续从真正的通信入口往下追,看看用户态到底怎样把一个目标进程、一个虚拟地址和一组控制参数交给驱动,驱动又怎样把它们变成 EPT 权限控制,最后执行 VMLAUNCH。

在真正的消息处理函数 sub_14005B5DB 里可以看到,用户态不是直接把明文结构传入驱动,而是通过 \PacDomain 发送一段 XOR 加密的数据。驱动先解密输入包,再取出输入长度和输出长度,最终调用:

这就是第一层命令分发。

sub_140053703 一开始取的是:

也就是输入包的第二个 DWORD。这个函数本身被控制流混淆得比较厉害,间接调用目标也经过常量还原;不过和这条 VT 请求链相关的两个目标可以直接算出来:

所以完整的外层流程是:

sub_140065895 并不就直接是“VT 初始化函数”。它是一个通用二级 RPC 分发器,里面确实有两条会进入 SHV/VT 路径的请求,但另外一些分支只是查询进程、线程、模块、PE 导出或虚拟内存信息。所以必须把这两类行为分开看。

这个二级分发器并不会直接把最初用户态传递的ptr直接丢给handler,而是先调用sub_140064930 进行:

也就是说,后续代码用的 input / output 已经是锁页后的内核映射地址。请求结束后,sub_140064B73 会对应地解锁页面并释放 MDL,如果锁页失败则会直接ret。

映射成功后,第二层分发器才校验包头:

第一个 DWORD 是包内声明长度,input + 4 才是二级命令号。如果输入为空、长度不一致,或者非空输出包的长度不一致,这个func会直接返回 0xC000000D前面映射器失败则返回 0xC0000055

还有一个很关键的协议细节:这个函数的 C 返回值和 handler 的实际结果是两条不同通道

因此,只要映射和包头校验通过,sub_140065895 函数本身一般都返回 0;具体的业务操作成功还是失败,要看 output + 0x220。这个 64 位结果也不总是标准 NTSTATUS:部分分支返回小的内部码,部分分支把 NTSTATUS 放在高 32 位、把失败阶段放在低 32

switch 的完整分支如下:


[招生]科锐逆向工程师培训(2026年7月3日实地,远程教学同时开班, 第56期)!

最后于 16小时前 被Rw0terX编辑 ,原因:
收藏
免费 0
打赏
分享
最新回复 (6)
雪    币: 70
活跃值: (1722)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
1
11小时前
0
雪    币: 80
活跃值: (530)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
1
10小时前
0
雪    币: 3370
活跃值: (7418)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
4
111
10小时前
0
雪    币: 12998
活跃值: (9887)
能力值: ( LV9,RANK:280 )
在线值:
发帖
回帖
粉丝
5
66666666666666
8小时前
0
雪    币: 198
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
感谢你分享这么好的资源!
5小时前
0
雪    币: 0
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
7
1
3小时前
0
游客
登录 | 注册 方可回帖
返回