ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

VMProtect 3.6虚拟机检测对抗:驱动源码原理与工程实践

VMProtect 3.6虚拟机检测对抗:驱动源码原理与工程实践 简介一份面向驱动开发、逆向工程与安全测试人员的反虚拟机检测驱动源码专用于绕过VMware Workstation Pro 3.6的VMP虚拟化特征识别。源码围绕硬件ID模拟、系统调用钩子、内存扫描规避、时间戳与性能计数器处理、异常处理等关键机制设计使目标软件难以察觉自身运行在虚拟机中适合在安全分析、恶意代码行为调试及虚拟环境兼容性改造等场景中集成使用。资源包共58个文件以50个h头文件为主体包含完整的内核结构声明另有1个cpp入口、sln与vcxproj工程文件、filters筛选器及README说明整体体积仅190KB工程组织清晰便于二次修改。已有586人学习下载对于希望理解虚拟机检测原理或需要临时隐藏虚拟化状态的技术人员是一份可运行的代码级参考。1. 先把这个话题拆开VMP 3.6、虚拟机、驱动源码三者是什么关系1.1 一句话理解 VMProtect 3.6VMProtect圈内都叫 VMP是一款商业软件保护工具虽然日常被归类到“壳”这个品类里但它和常见的压缩壳完全是两码事。压缩壳只是把程序体积变小、加个简单的加密入口而 VMP 的 3.x 系列做的是指令级虚拟化它会把原始程序的机器码按照自己的规则转换成一整套自定义字节码然后在一个内置的虚拟 CPU 上解释执行相当于把原本讲普通话的程序硬生生变成了一门“只有壳自己听得懂的方言”。逆向分析的人要还原就得先把方言逐条翻译回普通话再重新组织成能看懂的汇编逻辑工作量直接上了一个数量级。3.6 这个版本在 3.x 基础上做了不少细节强化比如指令拆分粒度更细、变异引擎生成的花指令更多、对调试器附加的检测更敏感。正因如此很多做安全研究、恶意软件分析、以及做软件兼容性验证的人都喜欢拿 VMP 3.6 作为目标样本来研究。1.2 为什么“虚拟机”在 VMP 话题里如此关键经常有人问VMP 保护的软件放进虚拟机里跑为什么会闪退、报错、或者干脆没反应这里有很大的概率是程序主动检测到了虚拟机环境。很多商业软件在加 VMP 保护之后会额外加一层“反虚拟机”逻辑。原因很现实如果程序检测到自己正跑在虚拟机里说明这个运行环境非常可疑——可能是恶意样本分析、可能是反外挂调试、可能是自动化脚本在批量操作——于是程序会选择拒绝运行。这其实是一种防御策略用来对抗安全研究者和外挂作者。所以在虚拟机里跑 VMP 保护的进程本质上是在跟“环境特征”较劲。虚拟机和物理机在 CPU 指令、设备列表、ACPI 表、I/O 端口等多个层面都有可被感知的差异这些差异就是检测的依据。1.3 “驱动源码”解决的是哪个层面问题用户态程序做任何事都在明面上操作注册表、读写文件、查询硬件信息每一环都可能被观察。而内核驱动直接跑在 Ring 0能访问到更多的系统底层数据也能在更早的阶段介入操作空间和隐蔽性都完全不同。所以像“可以过虚拟机 vmp3.6 的驱动源码”这类东西核心思路就是用驱动完成底层环境层面的处理让 VMP 保护的进程在虚拟机里运行得更像在真机上。要真正看懂这类源码需要的知识栈其实很明确Windows 内核架构、虚拟化原理、设备模型、驱动签名、以及内核调试。这篇博文就把这条技术线完整梳理一遍给做底层研究和安全方向的读者一个能落地的参考。2. 虚拟机藏在哪常见的环境指纹梳理2.1 CPU 指令层指纹虚拟机检测最经典、最高效的手段就是 CPUID 指令。CPUID 本身是 x86 架构里的标准指令程序可以通过它查询 CPU 的厂商、型号、特性集。但在虚拟化环境中hypervisor 会接管 CPUID并在特定叶子节点上暴露自己的存在。比较经典的做法是执行 CPUID 的 0x40000000 叶子如果返回值里有“VMwareVMware”“VBoxVBoxVBox”“KVMKVM”这类字符串基本就能确定当前处于哪家虚拟机里。另外CPUID 1 号叶子里的 hypervisor present bitECX 第 31 位为 1也说明有虚拟机监控程序存在。这些都是公开汇编资料里反复出现的判断逻辑做底层研究的同学应该都不陌生。除了 CPUID时间戳检测也是常见招数。RDTSC 指令读取 CPU 的时间戳计数器在虚拟机里执行这条指令的开销通常远大于物理机程序可以连续读取两次取差值如果发现“时间异常”就判定当前不是真机。这类检测不依赖厂商字符串所以更难对付。2.2 设备与 ACPI 层指纹设备层特征更直观。打开设备管理器如果看到 VMware SVGA II、VMware Virtual disk、VMMouse 之类的硬件不用多说环境暴露了。更底层一点的玩法是直接扫描 ACPI 表虚拟机厂商会在表头 OEM ID 字段里写入“VMWARE”“VBOX”之类的标识用 RWEverything 或者 ACPIViewer 这类工具一眼就能扫出来。还有一个非常经典的 VMware 后门端口0x5658也就是“VX”两个 ASCII 字符对应的端口号。这个 I/O 端口是 VMware 提供虚拟硬件服务的通道向它发送特定指令就能得到 VMware 版本信息。很多反虚拟机样本会直接检测这个端口是否存在存在就说明自己跑在 VMware 里。2.3 内核数据层特征到了内核层特征就更多了。比如中断描述符表 IDT 的地址范围、某些 MSR 寄存器的取值、CPU 虚拟化扩展相关的 CR4 位段在虚拟机和物理机上都会出现差异。这些特征是驱动源码发挥作用的地方因为驱动可以在内核态读取这些数据也能在更底层的位置处理问题。说实话把这些指纹列出来不是为了教你写检测而是为了理解一个道理虚拟机和物理机在底层上的差异是全方位的想靠某个单点方案“一步到位”基本不可能。这也是为什么“过虚拟机”的驱动源码通常需要组合拳的原因——CPUID、设备、ACPI、时间戳这几个方向都得照顾到。3. 驱动侧的技术栈开发、加载、调试一条线3.1 开发环境与工具链写内核驱动首选工具链是 Visual Studio WDKWindows Driver Kit。VS 版本和 WDK 版本需要匹配目前主流的做法是用 VS 2019/2022 配对应版本的 WDK创建项目时直接选“Kernel Mode Driver, Empty”模板就行。目标测试系统建议准备两台环境Win7 x86 和 Win10 x64。Win7 x86 的驱动签名门槛低适合功能调试Win10 x64 更接近真实目标环境但必须先处理签名问题和 Secure Boot 问题。调试环境需要 WinDbg。现在 WinDbg 已经从独立的调试器变成了 Microsoft Store 里的现代化应用界面友好很多支持网络调试。调试虚拟机里的驱动最常用的拓扑是宿主机跑 WinDbg虚拟机装调试版驱动通过网络vmxnet3 网卡建立内核调试会话。3.2 最小驱动骨架不能缺什么一份能跑的驱动源码无论功能多复杂基础骨架都是固定的DriverEntry 入口函数负责初始化DriverUnload 负责卸载再创建一个设备对象来接收应用层的 IOCTL 请求。下面这个模板是最精简的 WDM 驱动框架不含任何具体业务逻辑适合拿来做开发起点。#include ntddk.h #define DEVICE_NAME L\\Device\\MyDemo #define SYMBOL_NAME L\\DosDevices\\MyDemo NTSTATUS DriverEntry(PDRIVER_OBJECT pDriver, PUNICODE_STRING pRegistry) { NTSTATUS status; PDEVICE_OBJECT pDevice NULL; UNICODE_STRING devName, symName; RtlInitUnicodeString(devName, DEVICE_NAME); RtlInitUnicodeString(symName, SYMBOL_NAME); status IoCreateDevice(pDriver, 0, devName, FILE_DEVICE_UNKNOWN, 0, FALSE, pDevice); if (!NT_SUCCESS(status)) return status; pDriver-DriverUnload DriverUnload; pDriver-MajorFunction[IRP_MJ_CREATE] DispatchCreate; pDriver-MajorFunction[IRP_MJ_CLOSE] DispatchClose; pDriver-MajorFunction[IRP_MJ_DEVICE_CONTROL] DispatchIoctl; status IoCreateSymbolicLink(symName, devName); if (!NT_SUCCESS(status)) { IoDeleteDevice(pDevice); return status; } return STATUS_SUCCESS; } NTSTATUS DispatchCreate(PDEVICE_OBJECT pDevice, PIRP pIrp) { IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchClose(PDEVICE_OBJECT pDevice, PIRP pIrp) { IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } NTSTATUS DispatchIoctl(PDEVICE_OBJECT pDevice, PIRP pIrp) { PIO_STACK_LOCATION stack IoGetCurrentIrpStackLocation(pIrp); ULONG code stack-Parameters.DeviceIoControl.IoControlCode; // 根据 code 分发处理即可 IoCompleteRequest(pIrp, IO_NO_INCREMENT); return STATUS_SUCCESS; } VOID DriverUnload(PDRIVER_OBJECT pDriver) { UNICODE_STRING symName; RtlInitUnicodeString(symName, SYMBOL_NAME); IoDeleteSymbolicLink(symName); IoDeleteDevice(pDriver-DeviceObject); }这段代码里最关键的是 IRP_MJ_DEVICE_CONTROL 分发例程应用层程序通过 DeviceIoControl 向驱动下发指令时最终都会汇聚到这个函数里。做底层研究工作的时候驱动相当于一个“传输层”应用层把请求发下来驱动去内核底层执行再把结果返回。3.3 签名的现实问题在 Win10 x64 上加载未签名驱动系统会直接拒绝这是 PIL内核模式代码签名策略的规定。实际测试阶段主流做法是开启测试签名模式bcdedit /set testsigning on执行后重启系统桌面右下角会出现“测试模式”水印之后就可以加载带测试签名的驱动了。需要注意的是Secure Boot 开启时 testsigning 会失效所以测试机器上要把 Secure Boot 关掉。如果目标环境不允许关 Secure Boot那就要走 EV 证书签名或者申请微软 WHQL 签名这对个人研究者来说成本比较高。我自己调试的经验是先在一台 Win7 x86 虚拟机里验证驱动逻辑逻辑稳定后再搬到 Win10 测试签名环境。别一上来就在高版本系统折腾签名问题和驱动蓝屏问题混在一起排查起来非常痛苦。4. 虚拟机“更像真机”的配置优化能落地的方案4.1 VMware 官方可用的反检测参数既然要研究“过虚拟机”那就绕不开 VMware Workstation 本身。其实 VMware 官方提供了一批配置参数可以用来降低虚拟化特征它们在 .vmx 文件里设置后重启虚拟机即可生效。参数作用代价hypervisor.cpuid.v0 FALSE隐藏 CPUID 中的 hypervisor 位段让 CPUID 输出更像物理机虚拟机内的 CPU 特性集显示会受限monitor_control.restrict_backdoor TRUE关闭 VMware 后门 I/O 端口 0x5658依赖 VMtools 的部分功能会失效monitor_control.disable_directexec TRUE使虚拟机内的代码尽量走二进制翻译而非硬件虚拟化直接执行性能明显下降vhv.enable FALSE关闭虚拟机内的嵌套虚拟化支持影响 VBS/Hyper-V 相关功能这些配置不是“破解”什么而是 VMware 提供给企业用户做软件兼容性测试用的正规功能。研究恶意软件或测试特定软件时隐藏虚拟化特征能有效避开样本的“发现自己在虚拟机里就跑偏”的对抗逻辑。4.2 配置调整的取舍经验把 hypervisor.cpuid.v0 设为 FALSE 之后CPUID 输出确实会变干净但虚拟机的整体用户态体验也会有变化VMware Tools 的某些功能会对 CPUID 有依赖。disable_directexec 这个参数要慎用它虽然降低了一些虚拟化特征但会导致虚拟机运行速度暴跌开个大型软件可能要等半天。设备层面的调整更折腾把 VMware SVGA II 显卡换成标准 VGA 显卡、移除 VMCI 设备、删除虚拟打印机这些都能减少设备管理器里的暴露面。但代价也很明显——屏幕分辨率受限制、剪贴板共享失效、文件拖拽功能不可用开发和调试效率大幅下降。所以我的建议是如果只是做技术验证先用 CPUID 和 I/O 端口这两个方向的配置设备层面能不动就不动。4.3 从“配置优化”到“驱动源码”的边界需要特别说明的是虚拟机配置优化和驱动源码是两码事。配置优化解决的是“虚拟机自身的特征暴露”问题而驱动源码侧重的是在系统内核层对检测行为进行观察和处理。这两者可以配合使用但绝对不能混为一谈。我个人对这类技术研究的理解是合法的研究方向包括恶意软件样本分析、驱动兼容性测试、以及安全防护机制的验证但如果把“过检测”用于破解商业软件授权、绕过软件保护那就超出了技术讨论的边界。做底层研究的人都应该有自己的底线技术无罪但使用技术的目的必须合规。5. 实弹中会遇到的问题和排查思路5.1 驱动加载失败、签名报错怎么破驱动加载失败是入门阶段最常遇到的事。在服务方式加载时如果系统提示“错误 577Windows 无法验证此文件的数字签名”基本就是签名问题先确认测试签名模式是否真的开启、Secure Boot 是否关闭。如果加载后系统直接蓝屏大概率是 DriverEntry 里某个初始化操作出错比如 IoCreateDevice 参数错误、设备名格式不对。排查蓝屏最有效的工具是 WinDbg连接上调试会话后执行!analyze -v这条命令会自动分析崩溃原因指出问题线程和调用堆栈八成以上的驱动问题能直接定位到具体行。另外建议在虚拟机里开启完整内存转储蓝屏后生成的 dump 文件可以拿到宿主机上离线分析不必每次都在线调试。5.2 虚拟机里调试驱动的特殊麻烦在 VMware 里做内核调试网络调试是最方便的路径。给虚拟机加一块 vmxnet3 网卡设置好调试端口和 Key宿主机 WinDbg 用 net 连接即可。需要注意的一点是虚拟机默认开了时间同步每次宿主机和虚拟机的时钟偏差超过阈值VMware Tools 会强制校准时间这会让依赖 RDTSC 的代码产生异常行为。调试时建议在 .vmx 里加一句tools.syncTime FALSE关闭时间同步能让时间相关的代码行为更稳定。另外快照功能要善用每次改完驱动代码重新加载之前先恢复到一个干净的快照状态能极大缩短失败后的修复周期。5.3 VMP 程序在虚拟机中的表现观察最后说回 VMP 保护程序在虚拟机里的运行问题。如果驱动和配置都处理完了程序还是退出或报错要先区分原因是 VMP 主动检测到了环境特征还是驱动不稳定导致了连锁崩溃。观察手段一般用 Process Monitor 看程序退出前的注册表和文件操作再用 WinDbg 在驱动关键位置下断点。独立排查每一步比对着结果瞎猜有效得多。回头看我折腾这套技术路线的过程踩得最深的坑就是“想一步到位”。虚拟机、VMP、驱动源码每一块都是完整的知识体系指望看几行代码就全搞定根本不现实。我的建议是先踏踏实实在虚拟机里跑通一个最小驱动再研究环境特征最后才谈得上跟 VMP 保护逻辑做对抗。这个顺序反过来只会浪费大量时间在环境排查上。技术研究没有捷径把基础打牢后面的路自然就顺了。本文还有配套的精品资源点击获取
返回列表