Voidgate三大核心机制拆解:VirtualAlloc、硬件断点与VEH如何协同工作
【免费下载链接】VoidgateA technique that can be used to bypass AV/EDR memory scanners. This can be used to hide well-known and detected shellcodes (such as msfvenom) by performing on-the-fly decryption of individual encrypted assembly instructions, thus rendering memory scanners useless for that specific memory page.项目地址: https://gitcode.com/gh_mirrors/vo/Voidgate
Voidgate 是一个围绕内存扫描绕过(bypass AV/EDR memory scanners)展开的教学型 C++ 项目,它演示如何让 shellcode 在内存中始终保持 XOR 加密,仅在 CPU 执行到某条指令的瞬间,用 VEH 单步解密这一条指令,执行完立即重新加密,使内存扫描器在任何时刻都抓不到完整的 shellcode 特征。本文带你拆解 VirtualAlloc、硬件断点与 VEH 三大核心机制是如何协同工作的。
🔍 先搞懂:Voidgate 要解决什么问题
传统内存扫描(如 AV/EDR 的内存扫描器)会遍历进程内存,匹配已知 shellcode 特征码(例如 msfvenom 生成的载荷)。常见的对抗思路是"先加密驻留、执行前解密",但一旦解密完成,完整明文就暴露在内存中,扫描器迟早能抓到。
Voidgate 的答案是:任何时刻,内存里最多只有一条解密后的汇编指令。
| 对比项 | 常规 shellcode 执行 | Voidgate 方式 |
|---|---|---|
| 内存中的 shellcode | 解密后完整驻留 | 始终加密,仅当前指令为明文 |
| 内存扫描器视角 | 可匹配完整特征码 | 只能看到密文 + 单条指令 |
| 执行速度 | 正常 | 每条指令触发一次异常处理,明显变慢 |
| 适用场景 | 通用 | 小型初始载荷(如 msfvenom 反弹 shell) |
一句话概括三大角色:
- ⚙️VirtualAlloc:准备一块"可执行 + 可读写"的内存舞台;
- 🚦硬件断点(HWBP):在 shellcode 入口处精准"踩刹车",把执行流交给异常机制;
- 🕵️VEH:扮演"贴身调试器",每条指令执行前后完成解密/加密,并用单步模式持续接力。
🧩 机制一:VirtualAlloc 开辟可执行内存区
入口代码在voidgate-master/Voidgate/main.cpp。程序启动后第一件事是调用VirtualAlloc,以PAGE_EXECUTE_READWRITE权限申请一段堆内存(第 17 行),大小 = 加密载荷长度 + 前后各 32 字节(0x20)填充,填充常量定义在voidgate-master/Voidgate/Voidgate.h第 6 行的SHELLCODE_PADDING。
关键细节有三个:
- 整块内存先填充 NOP(0x90),再把加密后的 shellcode 拷到中间(第 29-31 行)——这段"内存海绵"让载荷边界不那么生硬,也预留了容错空间;
- 程序记录三个全局地址:
payload_base(入口点)、payload_lower_bound、payload_upper_bound(第 25-33 行); - 这三个地址是后面 VEH 判断"当前指令是不是我们的 shellcode"的唯一依据。
为什么必须"可执行 + 可读写"?因为 VEH 要在这块内存上边执行、边原地改写(解密/加密),普通的只读代码段做不到。
🚦 机制二:硬件断点——在入口处精准踩刹车
SetHardwareBreakpoint函数(voidgate-master/Voidgate/Voidgate.cpp第 72-92 行)是第二个角色。它通过GetThreadContext/SetThreadContext操作当前线程的调试寄存器:
- Dr0写入 shellcode 入口地址(第 81 行)——"在哪个地址踩刹车";
- Dr7设置启用位(第 82 行)——"让刹车生效"。
硬件断点相比软件断点(如int 3)有一个关键优势:内存里不会多出任何可见字节。扫描器翻遍内存也找不到断点痕迹,只会看到纯密文。
当main把执行流跳转到 payload 入口(第 36-44 行)时,CPU 一碰到 Dr0 指定的地址就触发调试异常——相当于给 VEH 递上了第一根"接力棒"。
🕵️ 机制三:VEH 单步解密——永远只解密一条指令
main.cpp第 39 行通过AddVectoredExceptionHandler注册 VEH,处理函数VehDecryptHeapAsm位于voidgate-master/Voidgate/Voidgate.cpp(第 10-70 行)。它的工作循环可以概括为五步:
- 只认领单步异常:异常码不是
EXCEPTION_SINGLE_STEP就放行给其他处理器(第 65-68 行); - 范围校验:读取 RIP,若不在 payload 上下界内(比如跳进了 kernel32 的函数),直接恢复执行、不做任何加解密(第 24-27 行);
- 加密上一条:把全局变量
last_decrypted_asm记录的那条已执行指令重新 XOR 加密(第 32-46 行); - 解密当前条:以 RIP 为起点 XOR 解密当前指令(最多 15 字节,即 x64 单条指令最大长度,常量见
Voidgate.h第 7 行)(第 48-59 行); - 设置单步标志:在 EFlags 中置位 TRAP 标志(第 21 行),保证下一条指令再次触发异常,循环继续。
XOR 密钥的偏移并不是固定的:GetXorKeyIndexForAsm(第 94-100 行)用「当前指令地址 − 载荷基址」对密钥长度取模,保证同一条指令每次加解密都用同一组密钥字节。密钥同时硬编码在main.cpp(第 9 行)和加密工具voidgate-master/XorEncryptPayload/XorEncryptPayload.cpp(第 59 行)中,两端必须完全一致,否则解出来的就是乱码。
🔄 三者协同:一条指令的完整生命周期
把三个机制串起来,执行流是这样接力的:
VirtualAlloc铺好可执行内存,密文 shellcode 就位(main.cpp 第 17-33 行);- 硬件断点守在入口,主线程跳转后立刻被调试异常拦下;
- VEH 接管:解密第 1 条指令,置位 TRAP,放行执行,同时记下这条指令的地址;
- CPU 执行完这 1 条指令 → TRAP 触发
EXCEPTION_SINGLE_STEP; - VEH 回来:把第 1 条重新加密,解密第 2 条,再次置位 TRAP;
- 如此逐条接力……内存扫描器任意时刻采样,都只能看到密文 + 1 条明文指令;
- shellcode 执行完毕(回连成功),
VirtualFree释放内存(main.cpp 第 48 行)。
🚀 六步复现 PoC:从 msfvenom 到回连成功
想亲手验证这套内存扫描绕过机制,按下面顺序操作即可(仓库获取:git clone https://gitcode.com/gh_mirrors/vo/Voidgate):
- 用 msfvenom 生成目标载荷(README 给出的示例为 x64 反弹 TCP shell);
- 运行
XorEncryptPayload.exe <shellcode文件路径>完成 XOR 加密,工具会直接输出 C 数组; - 把输出的数组替换
voidgate-master/Voidgate/main.cpp中的payload[]; - 确认加密工具与
main.cpp中的 XOR 密钥完全一致; - 在监听机上启动
nc -nvlp 443; - 执行
Voidgate.exe,稍等片刻即可看到回连。
⏱️ 注意:由于每条指令都要走一遍异常处理(官方说明每次至少额外执行 300 条指令),执行速度会显著变慢——msfvenom 这类"求小而慢"的载荷甚至可能耗时 15 秒以上,这是机制的固有代价而非 bug。
⚠️ 已知限制:哪些场景不适合 Voidgate
README 明确列出了三条限制,研究或复现前务必了解:
| 限制 | 原因 | 影响 |
|---|---|---|
| 执行极慢 | 每条指令都触发 VEH 加解密 | 大型、循环密集型载荷基本不可用 |
载荷内NtCreateThread新建线程 | 新线程没有安装硬件断点 | 入口在新线程里的载荷会失败 |
| 载荷内部引用自身数据(内嵌字符串、常量等) | VEH 只解密 RIP 处的代码,不解密数据区 | 取到的是密文,行为异常 |
因此 Voidgate 最适合作为初始接入的小载荷,或 stage-1 引导载荷(先落地再拉取剩余 payload)。
✅ 总结:一张表看懂三大核心机制
| 机制 | 核心 API | 职责 | 源码位置 |
|---|---|---|---|
| 可执行内存区 | VirtualAlloc | 分配 RWX 内存,铺 NOP 与密文 | voidgate-master/Voidgate/main.cpp |
| 硬件断点 | SetThreadContext(Dr0/Dr7) | 在入口无痕拦截执行流 | voidgate-master/Voidgate/Voidgate.cpp |
| VEH 单步解密 | AddVectoredExceptionHandler | 逐条解密/加密,维持"仅一条明文" | voidgate-master/Voidgate/Voidgate.cpp |
Voidgate 的价值不在于某个单一 API,而在于三者协同形成的闭环:VirtualAlloc 提供"可原地改写的舞台",硬件断点提供"无痕的第一脚刹车",VEH 提供"持续单步的解密引擎"。理解了这条接力链,你就掌握了内存扫描绕过中最经典的"运行时逐指令解密"范式。(本项目为学术目的,实际使用请遵守当地法律法规。)
【免费下载链接】VoidgateA technique that can be used to bypass AV/EDR memory scanners. This can be used to hide well-known and detected shellcodes (such as msfvenom) by performing on-the-fly decryption of individual encrypted assembly instructions, thus rendering memory scanners useless for that specific memory page.项目地址: https://gitcode.com/gh_mirrors/vo/Voidgate
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考