测试环境: Windows 10.0.18363
Ring-3 环境检测
内核代码完整性以及测试模式
测试模式和内核完整性校验是否开启:

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

分配点位于nixxeBase+0000000001111791:

调试模式是否开启

这个检测复用测试模式检测链路, 都是SystemCodeIntegrityInformation查询, 其中也包含了调试模式标志
A〇E_Base64.dll直接调用NtQuerySystemInformation查询内核调试器标志位:

虚拟机环境

A〇E_Base64.dll直接调用GetSystemFirmwareTableStub进行SMBIOS和ACPI检测

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

Ring-0 环境检测
过完上面的检测之后大概就进入了驱动的部分, 因为直接蓝屏了(
首要怀疑它检测了内核调试环境, 换了一个纯净环境再启动, 果然就弹出了另一个版本的虚拟机环境警告
内核调试环境
那么大概可以确定它确实检测了内核调试器, 直接 trace 找到标志位读取点位不太现实, 估计得跑一万年, 想用KA〇E模拟执行但是这个驱动显然不可能独立加载, 于是只能找可能的上游点位, 第一个想到的就是它可能要读取内核导出的KdDebuggerEnabled或KdDebuggerNotPresent, 绕不过的一点就是通过符号名找到这两个标志位的地址(我就不信它编码了所有系统的版本号和对应偏移直接定位到), 并且A〇E_BASE.sys中确实导入了MmGetSystemRoutineAddress, 不得不 hook 看看了, 于是得到了一个污点:

得到两个标志位的地址后立马就触发了蓝屏, 很难不深究了
交叉引用它得到的调试标志位可以找到它们被用于对比的点位分别在A〇E_BASE+0000000000442F4F和A〇E_BASE+0000000000442FD8, 先浅浅地通过 hook 交换它得到的两个标志位地址, 实际上应该可以直接返回两个指向自己模块的恒真和恒假的指针, 但是我怕它还检测解析符号得到的地址是否在 nt 模块范围内就直接交换了一下得到的两个标志位地址
但是依旧蓝屏, 蓝屏代码依旧1030002, 这时候可以想到还有一份镜像内存放在KUSER_SHARED_DATA里, 直接在A〇E_BASE.sys里搜立即数0xFFFFF78000000000和0xFFFFF780000002d4果然找到了00000000004252A3处读取了该字节校验镜像内存中的KdDebuggerEnabled, 先在这里下个断点手动改一下结果, 但是依旧蓝屏, 依旧1030002
这个时候敏锐的观察到每次从我修改内存放行到蓝屏中间 Windbg 都会输出 KDTARGET: Refreshing KD connection
虽然A〇E_BASE导入表里没有KdRefreshDebuggerNotPresent, 但是 hook MmGetSystemRoutineAddress 的结果告诉我它很有可能就是使用这个 API 的返回值来检测调试环境的(之前的腾游就有这样检测的先例), 下个在这个函数上断点果然触发了, 而且来源就是A〇E_BASE, 修改 API 返回值后看到了熟悉的虚拟机环境检测不通过的警告:

虚拟机环境
直接搜索立即数'VX'可以搜到 3 个 VMware 后门的I/O端口测试:
1 | .text:0000000000072A20 sub_72A20 proc near ; CODE XREF: sub_430CD6+134A↓p |
全部 patch 掉之后仍然报警告
接下来就是常规抹除 VMware 的进程和驱动痕迹, 但是依然报警告, 安装注册表访问回调后找到了以下可疑键值访问:
1 | [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
