
简介面向内核驱动开发者与系统安全研究人员的Hyperdbg ring0内核调试器源码包定位在于剖析与复现这款对标SoftIce/Windbg的调试器实现。资源内容覆盖VMX虚拟化扩展、内存管理、断点机制、寄存器监控等核心模块并以C与汇编源码为主共93个文件含38个C源文件、35个头文件、3个汇编文件及makefile/sources构建脚本等压缩包仅212KB适合对内核调试原理与驱动开发有基础、希望深入阅读实际工程代码的读者。已有551人学习。透过这份源码可了解Hyperdbg的宿主/客户机架构、VT-x事件处理与内核符号查询等设计思路也可以直接编译研究为后续构建轻量级调试工具或分析系统异常提供可参考的代码蓝本。1. ring0 内核调试器 HyperDbg它和 WinDbg 不是一类东西做内核调试的人多数是从 WinDbg 入的门。双机调试、kd 命令、breakpoint、dump 内存这套流程用了很多年直到你碰到 PatchGuard、碰到需要监控某条 syscall 被谁频繁调用、碰到想下断点却被系统保护机制拦住的场景才会意识到底层调试器的价值到底在哪。HyperDbg 就是一个跑在 ring0 甚至 VMX root 模式下的开源内核调试器它不依赖传统的软件断点而是拿 Intel VT-x 的 EPT 机制做指令级监控能在不改目标系统代码的前提下拦下内核行为。这篇文章写给两类人一是要分析内核驱动、做反 Rootkit 研究的二是被 WinDbg 断点局限性憋了很久、想换个思路看内核的。它解决的问题很具体syscall 级别的调用监控、内核内存无痕读取、隐藏进程排查。我会把原理、部署、命令、脚本和踩坑一起讲完照着复现即可。2. 为什么用 VT-x 做调试器从软断点聊到指令级监控2.1 软断点和硬件断点在内核态的天花板传统内核调试器最常用的手段是 int 3 软断点。调试器把目标地址的指令字节替换成 0xCCCPU 执行到这里触发 #BP 异常调试器接管。这套机制在用户态很好用但在 ring0 内核态有两个硬伤。第一个是 PatchGuard内核补丁保护会周期性校验关键结构你改指令字节本身就容易被查出来改完就蓝屏的案例不少。第二个是修改代码页这件事会破坏 Kernel Integrity 校验Win10/11 上开启 HVCI内存完整性之后连驱动自己写自己的代码段都会被拒。硬件断点Dr0-Dr7可以避免修改内存但只有 4 个寄存器断点而且不能按地址范围监控没法回答“这一段内存被谁读了”这类问题。对于内核调试来说你真正想要的是这样的能力系统正常运行不打断执行流但每次 CPU 访问某个物理页面时都能通知你。HyperDbg 的实现路径就是把调试器自己放进 VMX root 模式用 EPTExtended Page Tables搞出一套虚拟化层断点。2.2 EPT violationhypervisor 层断点原理先说 EPT 是什么。Intel VT-x 引入的 EPT 是 CPU 用来做“虚拟机物理地址 → 宿主机物理地址”转换的二级页表。任何 guest 内对内存的访问最终都要走 EPT 翻译。HyperDbg 的思路是它把自己做成一个类似 hypervisor 的东西在 VMCSVM control structure里配置 EPT然后对目标页表项设置只读或者无权限标记。CPU 一旦访问这些页面就会产生 EPT violationVM-exit 发生控制权回到 HyperDbg 手里这时调试器可以记录访问来源、读取寄存器、甚至修改执行流再返回。这套机制最大的优势是“无痕”。你不需要在目标系统内存里写任何字节不需要 hook IDT不需要改 SSDT目标系统认为自己在正常跑实际上每一步关键的内存访问都被上层拦下来过一次。相比软断点改字节的方式它对 PatchGuard 的感知能力要弱得多。用 HyperDbg 的术语来说这叫 invisible breakpoint对目标进程完全透明。// VMCS 中配置 EPT violation 的关键字段概念示意 vcpu-vmcs-exception_bitmap ~(1 14); // 不拦截一般异常 vcpu-vmcs-secondary_ctrl | SECONDARY_EXEC_ENABLE_EPT; vcpu-ept-pml4_page-write_protect 0; // 先放开写权限 ept_hook(va, hook_pfn, PROT_NONE); // 目标页设为无权限配置完成后目标页面每次被访问都会退到 hypervisor 层。常见的做法是只对你要监控的物理页设无权限而不是整块内存。因为 EPT violation 产生的 VM-exit 是有性能成本的监控粒度越粗系统整体拖延越明显。我一般会让 EPT hook 的页面控制在几十个以内这对内核调试场景基本够用。要监控对某段内核数据的访问时先把物理地址解析出来再设置 EPT 权限比逐个字节写 int 3 要高效很多。2.3 HyperDbg 的事件引擎命令和脚本之间的关系HyperDbg 的使用模式分成两层。第一层是交互式命令类似 WinDbg 的窗口你可以直接输入!syscall、!monitor、!dr这类命令查看当前内核状态。第二层是脚本引擎用一种接近 C 的脚本语法描述事件发生后要执行的逻辑。比如你监听 syscall可以指定“当进程名是 notepad.exe 时收集 rax 和返回地址”这些判断逻辑就写在 .script 文件里。脚本引擎和命令层的关系要搞清楚命令是动词脚本是过滤器加动作的组合。没有脚本层HyperDbg 只能做一次性快照式调试加上脚本层它才变成能持续监控运行时行为的工具。命令层的!syscall只是开启事件监听具体的过滤逻辑要写在 script 参数里。这个设计是从 eBPF 那类 tracer 借鉴来的事件源 过滤条件 动作三段式。3. 装好并用起来安装、签名与第一条脚本3.1 部署环境和签名要求HyperDbg 是 Windows 平台的工具实验目标建议用 Win10 x64 1809 以上版本Win11 也可以跑但必须先关掉 VBS/HVCI。CPU 要支持 VT-x 和 EPT这个基本 2010 年后的 Intel 和 AMD 都满足但要注意如果你是在虚拟机里跑 HyperDbg内层虚拟机也要能开 VT-x嵌套虚拟化VMware 默认是关的VirtualBox 支持但性能差一些。驱动签名是大坑。HyperDbg 的驱动需要加载到内核Win10 及以上强制驱动签名拿到的是 release 版编译好的 sys是测试签名或 EV 签名的。如果你机器的 Secure Boot 开着加载没签名的驱动会直接被拒。常见做法是关掉 Secure Boot然后以测试模式启动bcdedit /set testsigning on再加载。命令行敲完重启一次测试模式会显示在桌面右下角。bcdedit /set testsigning on bcdedit /set hypervisorlaunchtype off // 关闭 Windows 自家 hypervisor避免冲突 shutdown /r /t 0重启后确认!load能成功。如果加载报错先查签名状态再看内核隔离是否关闭。HyperDbg 要求以管理员身份运行命令行然后进入它的交互模式命令提示符会变成HyperDbg。加载驱动的命令是!load加载成功后再执行其它命令。3.2 第一条脚本监控进程创建安装完成后第一个值得跑的脚本是 syscall 监控。Windows 上进程创建最终会走 NtCreateUserProcess 这条 syscall我们监听它过滤出进程名并打印返回地址就能看到哪些进程在反复拉起新进程。脚本文件命名为 proc.script// proc.script !syscall script { if (rax 0xc8) { // 0xc8 是 NtCreateUserProcess 的 syscall number printf(Process create - PID: %d\n, rcx); dump(rsp, 16); } }脚本内容解释一下。rax在 syscall 入口处保存的是 syscall number0xc8在 Win10 x64 上是 NtCreateUserProcess 的编号不同 build 会有差异需要用!syscall命令先枚举确认。rcx是第一个参数对 NtCreateUserProcess 来说它是指向进程句柄的指针。dump(rsp, 16)是打印栈前 16 个字节用来观察调用来源。运行脚本的方式是!syscall script proc.script。脚本引擎执行过程是这样的每次 CPU 执行 syscall 指令进入内核时VM-exit 触发脚本引擎在 hypervisor 层判断当前 syscall number 是否匹配匹配才执行大括号里的动作。不匹配就直接返回不产生任何额外痕迹。这个脚本比 WinDbg 下断点的优势在于你不需要知道进程创建代码的具体地址也不需要处理模块加载顺序问题直接挂在 syscall 入口就可以。3.3 高频命令速查表命令作用典型使用场景!load加载 HyperDbg 驱动一切调试开始之前!syscall监控指定 syscall进程创建、文件操作、注册表访问!monitor监控内存访问/指令执行EPT 断点、检测隐藏行为!dr查看/修改通用寄存器断点命中时查看上下文!db/!dd/!dq按字节/双字/四字读物理内存内核池内存取证!traverse遍历页表解析虚拟地址对应的物理页!ps枚举当前进程对比 Activity 管理器找隐藏进程!sdt查看 SSDT 表检测 SSDT hook!db这类内存读取命令值得多说一句。它走的是 EPT 直接读物理内存不走目标系统的 API所以哪怕目标系统已经蓝屏或者内核堆被破坏只要 CPU 还能执行调试器代码内存就能读。这在分析内核池溢出时特别有用WinDbg 在系统崩溃后也有类似能力但 HyperDbg 不需要目标机提前开启内核转储现场感更强。4. 实战三例syscall 监控、隐藏进程与内核内存取证4.1 场景一抓频繁的 NtOpenProcess 调用恶意软件常做的事是遍历进程列表打开目标进程句柄尝试注入或读取内存。正常的杀软也会频繁 OpenProcess但频率和对象不一样。写一个针对 NtOpenProcess 的监控脚本按调用频次排序能很快找出反常行为。// openprocess.script !syscall script { if (rax 0x23 rdx 0x00100000) { // PROCESS_QUERY_INFORMATION printf(OpenProcess - Target PID: %x\n, r8); printf(Caller: %s\n, r9); // 调用者进程名指针 } }这段脚本里有几个细节要注意。rax是 syscall number在 Win10 19041 上 NtOpenProcess 是 0x23rdx是第二个参数 DesiredAccess0x00100000是 PROCESS_QUERY_INFORMATION 权限位恶意软件通常会带这个权限去查进程信息r8是第三个参数即目标进程 PID。r9是第四个参数指向 OBJECT_ATTRIBUTES 结构从中拿调用者名字比较绕我这里偷懒用了%s打印指针指向的字符串实际调的时候建议先用!dd看结构偏移。跑起来之后如果看到某个进程在几秒内连续 OpenProcess 上百次不同的 PID基本可以判定它在扫进程列表。另一个判断维度是目标 PID 的范围正常的杀软打开的是系统关键进程范围固定恶意样本会尝试全 PID 范围覆盖。配合脚本里的计数逻辑把同一调用者高频访问同一组 PID 的行为打出来就是一条清晰的证据链。4.2 场景二排查被隐藏的进程Rootkit 隐藏进程的经典手段是把自己从 EPROCESS 链上摘掉。任务管理器通过 ActiveProcessLinks 双向链表遍历进程摘链之后任务管理器看不到但进程作为线程的宿主容器依然存在CPU 也会正常调度它。找出这种隐藏进程的方法很简单不靠链表靠遍历。HyperDbg 的!ps命令底层就是遍历 EPROCESS 的链表如果发现某个 EPROCESS 不在链上但有有效的线程结构它会给标识。要是连!ps都看不到就直接走物理内存扫描。我常用的做法是先!traverse解析出内核数据段的物理内存范围然后用脚本按 PDPT 页表一级一级往下扫找 Signature 为 “Pro” 的 EPROCESS 头。// scanproc.script !monitor script { // 扫描物理内存中 EPROCESS 的 Signature0x50726F63 对应 Pro for (addr 0xfffff80000000000; addr 0xfffff80002000000; addr 0x1000) { if (read_paddr(va_to_pa(addr), 4) 0x50726F63) { printf(Potential EPROCESS at: %llx\n, addr); } } }这段脚本是示意写法实际跑之前要确认系统内核镜像的基址范围。扫描到 Signature 之后再手工解析ActiveProcessLinks的 Flink 和 Blink如果它们指向的不是自己所属的链表邻居那这个进程就是被手动摘链过的。用!ps对照一下凡是有 EPROCESS 却不在!ps输出里的就是隐藏进程。这个方法比依赖任何 API 都可靠因为它在 ring0 物理层直接读链表摘了能检测SSDT hook 了也没关系。4.3 场景三内核内存取证与恶意模块定位驱动级恶意软件加载时会分配内核非分页池往里面写 shellcode然后通过修改系统服务表或直接改写函数指针来劫持执行流。定位这类恶意模块的关键一步是找到不在已知模块列表里的内核内存区域。HyperDbg 的!sdt可以看 SSDT 表项如果某个表项的地址不在已加载模块范围内它就极可能是被 hook 了。找到可疑地址后用!db直接读那段物理内存看机器码特征。被 hook 的地址通常是一段 jmp 或者 push/ret 组合从里面解析出跳转目标再!traverse算出目标物理页最后在物理页头部看有没有 MZ 头或者明显的分配头标志。这套流程走下来恶意模块的基本情况就能掌握了。HyperDbg !sdt SSDT Table (Win10 x64) index0xC8 NtCreateUserProcess: fffff80012345678 - fffff80029abc000如果右侧地址和左侧不在同一模块区间就判定为可疑。接下来读内存确认。!db读物理地址需要先!traverse做一次虚拟地址到物理地址的转换这些 HyperDbg 都封装好了直接传虚拟地址给它就行。我一般会在抓到可疑地址后把前后 64 字节全部 dump 下来做字符串检索找模块名比挨个试快很多。5. 避坑清单五条让调试器翻车的经典操作5.1 现象!load加载失败或蓝屏原因多半是驱动签名问题或 Secure Boot 拦截。win10 开了 Secure Boot 后未签名驱动直接拒绝加载测试模式没开的话EV 签名的驱动也可能被拒。解决办法确认机器 BIOS 关闭 Secure Boot进系统后用bcdedit /set testsigning on开测试模式重启后再加载。如果还失败检查bcdedit /set hypervisorlaunchtype offWindows 自带的 Hyper-V 会抢占 VT-x导致 HyperDbg 拿不到 VMX root 权限。5.2 现象调试目标机无响应按键盘没反应调试器挂在 VMX root 层如果断点事件处理逻辑里死循环或者脚本逻辑写错会在 hypervisor 层直接卡死目标系统被冻住此时任何输入都无效。这种冻结不一定是蓝屏更像是整个机器被暂停了。解决方式先把脚本里的打印逻辑精简只输出关键信息不要用dump(rsp, 128)这种大面积抓取再把!monitor的监控范围调小默认是页粒度改成字节粒度之前确认目标页是否被频繁访问。此外要养成习惯跑复杂脚本之前先在虚拟机里试一遍物理机直接上脚本翻车了没有后悔药。5.3 现象syscall 监控在 Win11 上完全无效Win11 开启 VBS基于虚拟化的安全之后HyperDbg 的!syscall事件就捕获不到。原因是 VBS 的隔离进程跑在更底层的 VTL1HyperDbg 的 hypervisor 在 VTL0syscall 事件被隔离层消化掉了。解决办法关闭内存完整性内核隔离页面里关闭“内存完整性”再关闭 VBS组策略里关闭“基于虚拟化的安全”重启。Win11 上想用 HyperDbg 就要接受这个限制VBS 和第三方 hypervisor 调度器本来就互斥。5.4 现象!monitor设置 EPT 后系统随机蓝屏某个页面的 EPT 权限被设置后中断处理代码本身也在访问该页面触发 VM-exit 循环。HyperDbg 的 EPT 断点对“自引用”问题有处理但实际场景中经常踩到的是你监控了一个 ISR中断服务例程频繁访问的地址每个中断进来都触发一次退出性能急剧下降最后看门狗超时蓝屏。解决方式不要在中断密集的代码路径上开 EPT 监控。宁可把监控粒度做成采样式每 1000 次触发采样一次也不要全量捕获。脚本里可以用计数变量做采样逻辑每次触发加一到 1000 才执行动作。5.5 现象脚本语法报错或事件不执行HyperDbg 脚本引擎的语法和 C 接近但不完全一样最常见的错误是变量名用前缀搞混、漏了script关键字、大括号不闭合。事件不执行的原因是过滤条件不匹配syscall number 写错了。解决方式先跑一条最简单的脚本确认事件机制工作再往上加条件。syscall number 用!syscall命令查表不要背常量不同 Windows 版本的 syscall number 会变。脚本文件路径用绝对路径命令层不会自动补全相对路径。6. 把 HyperDbg 当引擎用脚本封装与和 WinDbg 的配合HyperDbg 的脚本引擎可以独立于交互窗口使用!script命令支持直接从文件加载这就给了封装的可能。我自己的习惯是把常用监控逻辑做成模板参数通过命令行传进去。比如上面的 openprocess.script抽成接受 PID 参数的版本// openprocess_template.script // 用法: !script openprocess_template.script PID if (rax 0x23) { if (r8 ARGUMENT_1) { printf(Target PID hit: %x\n, r8); dump(rsp, 32); } }ARGUMENT_1 是脚本引擎内置的参数占位符调用时传具体 PID 进去就行。这样同一套监控逻辑换 PID 就能复用不用每次改脚本内容。这比 WinDbg 的宏要方便因为它的事件获取层是虚拟化级的不是靠轮询寄存器。验证结果的方式也很重要。HyperDbg 给出的监控结论建议再叠加一次 WinDbg 的双机调试做交叉验证。比如 HyperDbg 说某个进程高频调用 NtOpenProcess你用 WinDbg 的bp nt!NtOpenProcess下个断点看命中频率和调用栈是否对得上。两套机制独立工作结论一致才可信。我一般是在 HyperDbg 跑监控脚本的同时另开一个 WinDbg 实例挂着这样能对比事件级监控和传统断点监控在丢事件率上的差异。在那以后我每次做内核行为监控都强制走一遍同样的流程先确认 VT-x 可用再关掉 Secure Boot 和 VBS测试模式加载驱动跑一条最小脚本确认事件链路最后才上完整脚本和 EPT 监控。这套流程能帮我隔离掉九成以上的环境问题剩下的一成就是脚本逻辑本身的坑。希望帮到你。本文还有配套的精品资源点击获取