简介:一款面向VMware环境的加固型虚拟机检测规避加载器,核心用途是让VMware guest绕开VMProtect、Safengine和Themida等商业保护壳的反虚拟机检测,仅支持Windows Vista至Windows 10的x64客户机,适用于安全分析、恶意代码调试、虚拟机逃逸对抗等场景。驱动在运行时修补SystemFirmwareTable,清除"VMware"与"Virtual"等可识别特征签名,使虚拟化痕迹不再暴露。资源包共776个文件,以C/C++驱动源码、Visual Studio工程文件、Python与PowerShell脚本、批处理与makefile构建脚本为主,C/C++源码与头文件构成驱动主体,工程文件负责编译,脚本承担自动化构建与部署,包含README、许可证、测试签名及部署脚本,压缩包约7.25MB,另提供Windows CE 7/8 ARMv7环境的make脚本;构建需Visual Studio 2015/2017与WDK 10,并需对vmloader.sys测试签名。目前已有258人学习。读者可获得完整驱动项目源码、工程配置、构建与安装脚本,直接用于驱动编译、测试签名、部署与验证,大幅缩短相关逆向环境的搭建时间,适合安全研究者快速上手二次开发。
1. 为什么 VmwareHardenedLoader 这类工具包会存在:引导层改动不在操作系统里
先讲一个常见场景:某开发者花了一下午把 vmdk、vmx 和 EFI 分区都准备好,虚拟机一开机却直接进到“找不到可引导设备”,或者卡在 UEFI Shell 里不动。问题的根源往往不在系统文件损坏,而在 VMware 虚拟固件的依赖链——它默认只会去 ESP 分区里找EFI\BOOT\BOOTX64.EFI,找不到或者被替换就会黑屏。命名比较直观的 VmwareHardenedLoader 就是针对这一链条做的配套工具包:它把自定义引导器伪装成虚拟固件默认加载项,并额外处理 NVRAM、ACPI 表一类参数,让虚拟机在启动阶段先跑你的 loader,再拉起真正的 OS。适合折腾过引导项、需要把多系统启动或特殊 EFI 载荷塞进虚拟机的人,新手也能照着目录操作走通。
2. 拆包看结构:EFI 引导链路与四个关键文件的位置
2.1 虚拟固件到底从哪里加载代码
VMware 的 UEFI 固件和物理机的主板固件一样,会按照 NVRAM 里的BootOrder去找启动项。绝大多数情况下,它第一步会去系统分区(ESP,通常是 VMDK 的第一个 FAT 分区)寻找EFI\BOOT\BOOTX64.EFI,找到后把控制权交出去。也就是说,引导这件事根本轮不到操作系统干预,你改不改系统里的/boot目录,跟虚拟固件能否把引导器拉起来没有直接关系。
VmwareHardenedLoader 的核心思路,就是把你要加载的引导载荷或自定义 EFI 程序做成一个 stub,放进BOOTX64.EFI的位置,由它完成“托管引导”的动作:读取配置、设置 NVRAM 变量、把控制权交给真正的 vmm 或后续 loader。细节上,它会处理固件启动参数的寄存器传递、ACPI 表的再映射等脏活。命名里的 “Hardened” 强调的是加固——它不追求功能丰富,而是保证每次启动的环境是可预期、可校验的。
我见过有人图省事直接把 ISO 里的EFI/BOOT目录整体覆盖到 VMDK,结果 VMware 新版本把默认启动名改成bootvmm.efi后整个虚拟机就再起不来了。这个工具包的目录设计通常会把原始引导文件改名备份,再让自定义 loader 链式加载,相当于给虚拟固件装了一个“永不缺席的代理”。
2.2 压缩包里的文件各管什么
拿到包之后别急着全部拷贝,先按下面的角色表理解它每个文件的位置。常见的包内文件布局大致是这样:
| 目录/文件 | 作用 |
|---|---|
efi/boot/bootx64.efi | 替换固件默认引导项的核心 stub,控制链式加载 |
efi/loader/loader.efi | 你的自定义 EFI 程序,或者某硬件的 UEFI 诊断工具 |
efi/config.cfg | 引导参数文件,记录超时、启动项顺序、调试开关 |
Resources目录 | NVRAM 模板、ACPI 表补丁、日志输出脚本 |
tools目录 | 用于安装/回滚的脚本(Linux 工具或 Windows 批处理) |
config.cfg是调整最频繁的一个文件。常见做法是把timeout=5改成timeout=0实现静默引导;调试阶段则反向把debug=1打开,让它把每一步入口寄存器值打印到串口或 VMware 日志。参数不要贪多,装好后先只开timeout=3与verbose=1,确认链路通了再逐项关闭。
2.3 为什么不能直接改 VMDK 里的引导文件了事
工作里最常听到的翻车理由是“我把 ISO 里的 loader 复制到 VMDK 的 EFI 分区不就行了吗?”——在物理机上的确可行,但在 VMware 里不行,原因有两个。一是 vmdk 是稀疏文件,直接挂载需要工具链,普通复制工具不会正确更新虚拟磁盘的 FAT 表元数据;二是 VMware 固件在每次引导时会校验bootx64.efi的签名和路径,绕过校验不是把文件放进去就完成。
VmwareHardenedLoader 的安装脚本很聪明的一点是:它不直接改文件系统,而是在 vmware.log 里写入一条“自定义入口”的坐标,并在下一次冷启动时通过 NVRAM 中的Boot####项指向工具包里的 loader。这样就算哪天 loader 坏了,把BOOTX64.EFI改成原始的备份名就能回滚,不需要重建磁盘,这就是它的“后悔药”机制。
3. Workstation 下的加载流程:配置、放置与脚本化部署
3.1 先校准 vmx:三条必填参数
在 VMware Workstation 里,虚拟机的配置全部集中在.vmx文件里。手动部署 VmwareHardenedLoader 前,需要先确认这三类设置,否则 loader 能启动但 OS 起不来:
firmware = "efi" efi.nvram.var.LoaderDebug = "1" efi.nvram.var.LoaderPath = "\\EFI\\BOOT\\BOOTX64.EFI"第一行firmware = "efi"是基础,BIOS 固件下这个工具包没有意义。第二行LoaderDebug让固件把 loader 是否被成功加载写进日志,排错必开。第三行LoaderPath指定实际引导文件的绝对路径,注意这里用的是反斜杠,且路径相对于 ESP 分区根目录。
写完这三行后不要急着改硬盘,先做一次冷启动看日志。VMware 比较抠细节:热重启(guest 内部重启)时 NVRAM 不一定会重新加载,必须“关机→开机”让固件重新初始化,才能让新配置生效。
3.2 给虚拟机添加第二个硬盘来放 loader
一次性拷入 VMDK 的 EFI 分区里虽然可行,但每次改 loader 都要重新挂载 VMDK,非常不方便。更稳妥的落地步骤是单独建一个 EFI 虚拟磁盘存放 loader,然后用vmx参数让它成为第一启动项。
我一般这样操作:
# 创建一个仅含 ESP 的虚拟磁盘镜像 vmware-vdiskmanager -c -s 64MB -a lsilogic -t 0 loader_disk.vmdk # 挂载到 Linux,格式化并放入文件 guestmount -a loader_disk.vmdk -i -m /dev/sda1 /mnt/loader mkdir -p /mnt/loader/EFI/BOOT cp -r efi/loader/* /mnt/loader/EFI/BOOT/ umount /mnt/loader这里的guestmount是 libguestfs 提供的挂载工具,支持直接读写 vmdk,不需要开 VMware 虚拟机。-t 0表示生成预分配的完整磁盘,避免稀疏文件产生碎片。如果你用的 Windows,可以直接用 VMware Workstation 的“虚拟机设置→硬盘→映射”功能,把 64MB 那块盘映射成盘符,然后复制文件。
3.3 把 loader_disk 设为第一启动项
编辑虚拟机 vmx 文件,在最后追加两段:
scsi0:0.present = "TRUE" scsi0:0.fileName = "loader_disk.vmdk" scsi0:0.deviceType = "disk" scsi0:0.mode = "independent-nonpersistent"independent-nonpersistent这一行的意思是 loader 盘的每次改动在关机后自动还原。如果你想持久化保存调试参数,改成persistent。不建议直接在原系统盘上做实验,改成独立模式让你随时能回到“出厂状态”。
加好盘后,进虚拟机固件设置(开机时按 F2 进 UEFI 设置),把EFI VMware Virtual SCSI Hard Drive (loader_disk)拖到启动顺序第一位,然后重启。如果一切正常,你会看到 loader 的字符界面或倒计时,这之后再让它链式引导系统盘。
3.4 脚本化部署:一条命令完成替换与验证
手工拷贝适合调试,但要给几十台虚拟机批量部署时,用脚本自动完成。下面这段脚本在 Linux 宿主机上执行,会依次完成挂载、替换和校验:
#!/bin/bash LOADER_DIR="/opt/hardened/efi/loader" WORK_VM="/vm/win10/win10.vmx" vmrun -T ws stop "$WORK_VM" soft guestmount -a "$WORK_VM" -i -m /dev/sda1 /mnt/vm if [ -f /mnt/vm/EFI/BOOT/bootx64.efi.bak ]; then echo "[INFO] loader already installed, backup exists" else cp /mnt/vm/EFI/BOOT/bootx64.efi /mnt/vm/EFI/BOOT/bootx64.efi.bak fi cp -f "$LOADER_DIR/bootx64.efi" /mnt/vm/EFI/BOOT/bootx64.efi sync umount /mnt/vm vmrun -T ws start "$WORK_VM" gui sleep 10 vmrun -T ws getGuestIP "$WORK_VM"这里每步都有目的:先stop soft避免磁盘被占用;备份提前做判断,重复执行不会覆盖已有备份;sync强制把缓冲区写入 vmdk,避免 guestmount 的延迟提交;最后用getGuestIP验证 OS 是否正常起来,这个返回值非空就说明 loader 链式引导没把网络搞坏。
参数上有两点值得留意:-T ws表示目标类型是 Workstation,如果这台虚拟机实际由 VMM 平台托管,要改成-T server,否则 vmrun 连接不上;getGuestIP依赖 VMware Tools,刚装完 loader 且 Tools 没运行时会超时,等待时间从 10 秒调长到 30 秒再判断是合理的。
4. ESXi/vSphere 下的另一种玩法:从 ISO 到引导参数的落地
4.1 服务器版与 Workstation 的差异点
上了 ESXi 之后,你没法像 Workstation 那样随时双击打开虚拟机界面去按 F2 进固件设置。vSphere Web Client 只提供“启动/关闭/编辑配置”这类基础操作,对 EFI 引导项的控制极其有限。所以 VmwareHardenedLoader 在服务器环境下的落地方式要做两层调整:第一层是把 loader 打包进 ISO,让它在 guest 里预先完成安装;第二层是使用 vmx 扩展参数,利用 vSphere 的“编辑设置→VM 选项→高级→配置参数”手动添加启动时的变量。
要说明的是,ESXi 虚拟机的 NVRAM 文件(.nvram)是隐藏的,直接改文件不如改 vmx 参数来得可控。下面提到的几种配置参数都是标准 EFI 语义,不涉及任何平台私有字段。
4.2 把 loader 打进 ISO 再放入 ESP 分区
ESXi 环境中给上百台 VM 逐个挂 64MB 辅助盘不是好方案,把安装好的模板 VM 复制出去才是正道。手动打包 ISO 的步骤这样写:
mkdir -p iso_root/EFI/BOOT iso_root/Loader cp -r hardened/efi/loader/* iso_root/EFI/BOOT/ cp -r hardened/Resources iso_root/Loader/ mkisofs -R -J -hide-rr-moved -o loader_install.iso iso_root-R和-J分别开启 Rock Ridge 与 Joliet 扩展,保证在 Linux 和 Windows guest 里都能看到长文件名;-hide-rr-moved避免生成额外的迁移目录。ISO 不能直接作为 VM 的固件引导盘,但可以用虚拟光驱挂载,进入 guest 系统后把它复制到 ESP 分区。这样做的优势是不需要关闭虚拟机、不需要映射 vmdk,完全在 OS 内部完成安装。
# 在 guest 内部执行(以 Linux 为例) mkdir /mnt/esptmp mount /dev/sda1 /mnt/esptmp cp -r /mnt/cdrom/EFI/BOOT/* /mnt/esptmp/EFI/BOOT/ sync umount /mnt/esptmp客体内挂在改动的是 GPT 里类型为“EFI System”的分区,路径/dev/sda1是多数模板的布局。如果你的模板用 NVMe 控制器,设备名会变成/dev/nvme0n1p1,脚本里写成自动探测更稳妥。
4.3 用 ovftool 把已装好 loader 的 VM 导出成模板
在模板机上完成一次 ISO 安装后,会得到一个每条 VM 都带 loader 的模板。用 ovftool 把它导出,后续部署新 VM 直接从 OVA 拉起,至少省掉重复挂盘的时间:
ovftool --name=gold-loader --format=ova \ vi://vcenter.example/dc1/host/cluster1/gold-template-vm \ ./gold-loader.ova导出后 OVA 内的 vmdk 已经包含放置好的bootx64.efi,新 VM 从 OVA 部署完开机的瞬间就会执行 loader。别忘了在导出前执行一次vmrun getGuestIP确认数据落盘,ESXi 的快照机制偶尔会让未提交的写入留在内存,直接导出来会有概率拿到半新的文件。
4.4 ESXi 宿主上常用的高级参数
vSphere Web Client 里添加的“配置参数”会直接写进 vmx,常见需要加的参数有三个:
efi.allowLegacyBoot = "FALSE" loader.debug = "TRUE" loader.ezflags = "0x800"efi.allowLegacyBoot关闭后,固件不会尝试加载传统 BIOS 模式,避免 loader 与 CSM 冲突;loader.debug与 Workstation 的 LoaderDebug 效果一致;ezflags是很多 EFI 引导器默认会读取的标记位,例如数字0x800表示禁止在启动时进入交互式菜单,适合无人值守部署。这些键名在不同平台版本可能略有差异,但你可以在专属硬件的运维文档里确认它们。虚拟机上这样的参数要逐个加,加完统一热重启一次。
5. 避坑记录:NVRAM 失效、签名校验与升级翻车三种事故
5.1 三条高频事故的排错现场
现象一:配置全部正确,loader 就是不启动,日志里出现Can't find Boot0000 variable。原因往往不是文件缺失,而是 vmx 里efi.nvram.var手动写错变量名(大小写不对)。解决方法是先删掉自定义变量,回到 Workstation 菜单里“操作系统类型”改为“其他(UEFI)”,让 VMware 重新生成完整的 NVRAM,再重写一遍参数。
现象二:loader 启动后屏幕停在Start PXE over IPv4,几十秒后直接提示没有可用启动项。原因是向虚拟机添加的 loader_disk 位于非易失性内存排序末尾。虚拟机里 UEFI 只认可存储在持久化 NVRAM 中的启动项,如果辅助盘是 “independent-nonpersistent” 模式,固件不承认它。解决:把辅助盘改成 persistent 模式,再在 vmx 里指定scsi0:0.bootPriority = "1"。
现象三:升级 VMware 版本后,原来能正常跑的 loader 开始提示黑匣子式报错代码。原因大概率是固件校验逻辑加强了,stub 与宿主固件的版本握手失败。此类问题的可靠解法是重新执行资源包内自带的install.sh,它会自动比对当前固件版本并把新的兼容入口写进 NVRAM;不要手动改bootx64.efi,很容易把不该改的动态段覆盖掉。
5.2 一次“玄学”问题的完整还原
有一次在 ESXi 群集上部署,发现 loader 只在第一台模板 VM 上生效,克隆出去的 8 台全部回到了原版引导。排查时百思不得其解,最后才发现是 ovftool 导出模板时自动把磁盘置为 thin 模式,EFI 分区落到稀疏区间后,新宿主的 uefi 固件在读取 FAT 时出现 IO 延迟,直接跳过该启动项。解决方法是导出 OVA 后重打包,把 ESP 所在磁盘在部署参数里设置为 eagerzeroedthick 厚置备。
从此之后我给自己立了规矩:任何涉及引导器的模板导出,导出前必须做两件事。一是检查vmdk的createType是否为vmfsPreallocated;二是手工比对模板机 ESP 分区里bootx64.efi的 MD5 与源目录是否一致。前者避免置备方式带来的玄学问题,后者避免脚本意外拷贝了缓存旧文件。依据这些经验,VM 模板的管理才能保持一致性。
6. 用 Loader 自带的调试参数验证启动链:一个可复用的排查套路
6.1 调试参数怎么开与怎么看
真正常用到的并不是花哨功能,而是一个最小化的验证矩阵。我的习惯是在config.cfg里固定放三条参数:
timeout=3 debug=1 serial=0x3f8serial=0x3f8指示 loader 把调试信息输出到 COM1(虚拟机串行口),不是所有环境都要开,但开一次就能拿到完整的调用时序。然后在 vmx 里给虚拟机添加一个串口设备,指向文件,重启后观察日志。如果看到类似下面的输出,说明 loader 已被虚拟固件成功调用:
LoaderEntry: Boot0000 from \\EFI\\BOOT\\BOOTX64.EFI ChainLoad: entry at 0x1F00000, len 0x2400 SaveNVRAM: done size 0x2E88第一条说明固件按预期路径找到了 loader,第二条给出链式加载的目标内存地址和大小,第三条说明 NVRAM 变量已落盘。任何一条缺失都有对应排查方向:没有第一条,说明 vmx 里的LoaderPath不对;有第一条没有第二条,说明文件损坏或签名校验失败;第二条之后没有第三条,九成是 ESP 分区只读挂载导致写 NVRAM 失败。
6.2 调试完成后的关闭顺序
关调试参数也同样讲究,直接从配置文件里删掉会导致引导超时时间退回默认值,有时会引发 30 秒延迟。正确顺序是把debug=1改为debug=0,保留timeout=3,等跑完一轮确认无异常后,才删掉serial行。这个顺序保证了“在日志仍然可见的情况下做验证”,而不是先关观测再测负载。
经过这些项目,我每次给新 VM 装 loader 前,都会强制自己过一遍这个三连验证:bootx64.efi是否存在、vmx 里firmware是否为 efi、ADVANCED 配置参数是否生效。这三步大约花两分钟,但能挡住 90% 的“明明配置了却不生效”。从那以后我部署的任何虚拟机,这套改动都先走一遍验证再交付给业务,也算是对启动链的黑匣子有了点的敬畏。希望这套从参数校准到日志判读的流程能帮到你。
本文还有配套的精品资源,点击获取