尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

WinVisor中断处理系统详解:单步、断点与INT 2E传统系统调用的VM Exit调度原理

WinVisor中断处理系统详解:单步、断点与INT 2E传统系统调用的VM Exit调度原理 WinVisor中断处理系统详解单步、断点与INT 2E传统系统调用的VM Exit调度原理【免费下载链接】WinVisorWinVisor - A hypervisor-based emulator for Windows x64 user-mode executables using Windows Hypervisor Platform API项目地址: https://gitcode.com/gh_mirrors/wi/WinVisorWinVisor 是一款基于 Windows Hypervisor PlatformWHPAPI 构建的 x64 用户态程序模拟器它把目标 EXE 跑在一颗虚拟 CPU里所有中断与系统调用都会触发VM Exit交给宿主端的调度逻辑处理。本文将拆解 WinVisor 中断处理系统Interrupts的三大核心单步中断、断点中断与 INT 2E 传统系统调用讲清它们如何被占位地址 缺页异常这套巧妙机制统一调度。一、先搞懂背景虚拟 CPU 为什么需要拦截中断WinVisor 利用 WHP API 创建虚拟 CPU把宿主进程的整个地址空间克隆进客户机guest执行。虚拟 CPU 绝大部分时间运行在CPL3用户态只有一段极小的CPL0 引导代码负责初始化寄存器、GDT、IDT、TSS 和四层页表后通过SYSRET切入用户态执行目标程序详见 WinVisorDLL/PrepareCPL0.cpp。关键设计在于WinVisor 并没有真正为每个中断实现处理函数。它在初始化时把整个 IDT256 个条目全部填成占位地址IDT[i] INTERRUPT_HANDLER_VIRTUAL_ADDRESS i这些占位地址位于内核空间且故意不映射物理页。于是当任何中断发生时CPU 跳转到占位地址执行触发缺页异常Page Fault缺页导致VM Exit控制权交还宿主端宿主端用RIP - 基地址就能精确算出中断号因为地址是唯一的调度到对应的中断处理函数处理完毕后把 RIP 指回IRETQ返回桩虚拟 CPU 干净地从中断处恢复执行。这套占位地址 → 缺页 → VM Exit的调度链路在 WinVisorDLL/PageFault.cpp 中完成它还能同时识别 SYSCALL 占位地址和引导完成占位地址是全部 VM Exit 的分流中枢。二、中断处理总览一张表看懂三大中断WinVisor 只注册了 3 个中断处理项定义在 WinVisorDLL/Interrupts.cpp 的gInterruptHandlerList中中断号类型处理函数当前行为0x01单步调试INT1InterruptHandler_SingleStep读取用户态 RIP 并记录日志0x03软件断点INT3InterruptHandler_Breakpoint捕获断点、记录日志后跳过继续执行0x2E传统系统调用INT 2EInterruptHandler_LegacySyscall代理转发给统一系统调用处理器这种白名单式注册让调度非常简单GetInterruptHandler()按中断号线性查找查不到就报错退出见 WinVisorDLL/Interrupts.cpp。三、单步中断0x01把执行轨迹逐条打印出来当目标程序开启单步调试或调试器插入INT1虚拟 CPU 每执行一条指令就触发一次中断。处理函数 WinVisorDLL/InterruptHandler_SingleStep.cpp 做的事很纯粹中断发生后CPU 已把用户态 RIP 压入内核态栈处理函数直接从RSP指向的位置取出该地址输出一条Single-step: 0x%p日志让开发者可以观察指令级执行轨迹返回后由IRETQ桩恢复原执行流。这就是 WinVisor 作为调试/分析工具的眼睛——单步日志能力。四、软件断点中断0x03让程序越障而不是崩溃INT3CC指令是 x86 上最经典的软件断点。如果放任不管目标程序会收到未处理异常而终止。WinVisor 的 WinVisorDLL/InterruptHandler_Breakpoint.cpp 选择捕获并跳过记录一条Caught breakpoint日志后直接返回RIP 在中断进入时已指向断点指令的下一条因此程序会无缝继续运行。这一设计对半虚拟化场景尤为重要GUI 类程序如 notepad.exe部分代码在宿主上原生执行原生侧插桩遗留的断点中断进入虚拟 CPU 后也会被优雅吸收而不会导致模拟崩溃。五、INT 2E 传统系统调用与 SYSCALL 殊途同归这是中断系统中最有含金量的一环。现代 Windows 使用SYSCALL指令发起系统调用但很多 16 位兼容代码、反调试技术或老旧组件仍使用INT 2E传统中断方式。WinVisor 两者通吃SYSCALL 路径把MSR_LSTAR设为保留占位地址执行SYSCALL时触发缺页 → VM ExitINT 2E 路径IDT 占位地址天然触发同样的缺页 → VM Exit。处理函数 WinVisorDLL/InterruptHandler_LegacySyscall.cpp 从内核栈RSP 0x18处取出用户态栈指针然后调用统一的HandleGuestSyscall()与 SYSCALL 路径共用同一套逻辑从RAX取系统调用号查表得到函数名与参数个数WinVisorDLL/SyscallNames.cpp、WinVisorDLL/SyscallParamCount.cpp按 Windows 约定从R10/RDX/R8/R9及用户栈提取最多 32 个参数查 WinVisorDLL/SyscallProxy.cpp 中的钩子表命中如NtTerminateThread、NtTerminateProcess走钩子否则通过代理线程ForwardSyscallToHost()转发给宿主 OS 并等待返回把返回值写回RAX并将 RIP 指向sysret/iretq返回桩干净地回到用户态。也就是说INT 2E 最终被代理成了与 SYSCALL 相同的中断出口这正是 README 中所述 proxy these calls to the same handler 的实现。六、中断返回的隐藏细节错误码处理不同中断进入时压栈的内容不同部分异常如通用保护异常会额外压入一个错误码。如果返回前不弹出它IRETQ就会错位弹出 RIP/RFLAGS 导致状态全乱。因此 WinVisorDLL/Interrupts.cpp 准备了两段返回桩字节码gbInterruptRet仅IRETQgbInterruptRet_ErrorCode先ADD RSP, 8弹掉错误码再IRETQ。GetInterruptReturn()根据处理项的dwHasErrorCode标志自动选择对应桩——这段只有十几个字节的机器码却是保证中断进出对称的关键。七、VM Exit 的完整分流逻辑所有异常最终汇入 WinVisorDLL/HandleVmExit.cppWHvRunVpExitReasonMemoryAccess非法物理内存访问直接报错WHvX64ExceptionTypePageFault交给HandlePageFault()先判断是否为占位地址系统调用 / 引导完成 / 中断否则按常规缺页把页面按需映射进客户机最多 256 页最老页被换出WHvX64ExceptionTypeGeneralProtectionFault及其他未知类型记录 RIP 后终止。配合 WinVisorDLL/PageTable.cpp 的按需分页机制WinVisor 用一套极简的占位地址体系同时优雅地解决了系统调用拦截、中断捕获与内存管理三大难题。八、相关文件速查模块说明WinVisorDLL/Interrupts.cpp中断处理表、调度与返回桩WinVisorDLL/InterruptHandler_SingleStep.cpp单步中断处理WinVisorDLL/InterruptHandler_Breakpoint.cpp断点中断处理WinVisorDLL/InterruptHandler_LegacySyscall.cppINT 2E 传统系统调用处理WinVisorDLL/HandleVmExit.cppVM Exit 原因分流WinVisorDLL/PageFault.cpp占位地址识别与按需分页WinVisorDLL/SyscallProxy.cpp系统调用代理转发与钩子小结WinVisor 的中断处理系统是少即是多的典范——不实现真中断、不写内核驱动仅靠IDT 占位地址 缺页触发 VM Exit 按偏移量反推中断号就把单步、断点、INT 2E 系统调用统统纳入了用户态宿主可控的调度轨道值得所有研究虚拟化与二进制安全的工程师借鉴。【免费下载链接】WinVisorWinVisor - A hypervisor-based emulator for Windows x64 user-mode executables using Windows Hypervisor Platform API项目地址: https://gitcode.com/gh_mirrors/wi/WinVisor创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表