SVM根模式下调用内核函数触发BSOD的解决方案咨询
当初始化SVM内核虚拟机监控程序,退出vmrun进入SvmExitHandler(管理退出码的调度器)后,尝试调用任何IRQL Passive级API时,会触发BSOD,错误码为KERNEL_SECURITY_CHECK_FAILURE;但在启动SVM前调用相同参数的同一API却能正常运行。需要在SVM中实现与内核函数的交互(例如在指令追踪过程中处理每条指令),但直接调用ZwWriteFile等API会导致崩溃,因为SVM模式下所有中断均已禁用。
相关代码
extern "C" VMM_STATUS SvmVmexitHandler(PRIVATE_VM_DATA* Private, GUEST_CONTEXT* Context, GUEST_SSE_CONTEXT* SSeContext, GUEST_AVX_CONTEXT* AvxContext) { // 加载宿主状态: __svm_vmload(reinterpret_cast<size_t>(Private->VmmStack.Layout.InitialStack.HostVmcbPa)); // 恢复被宿主RAX覆盖的客户机RAX(#VMEXIT时发生): Context->Rax = Private->Guest.StateSaveArea.Rax; VMM_STATUS Status = VMM_STATUS::VMM_CONTINUE; switch (Private->Guest.ControlArea.ExitCode) { case VMEXIT_CPUID: { CPUID_REGS Regs = {}; int Function = static_cast<int>(Context->Rax); int SubLeaf = static_cast<int>(Context->Rcx); __cpuidex(Regs.Raw, Function, SubLeaf); switch (Function) { case CPUID_VMM_SHUTDOWN: { // 触发关闭操作: Status = VMM_STATUS::VMM_SHUTDOWN; break; } case CPUID::Generic::CPUID_MAXIMUM_FUNCTION_NUMBER_AND_VENDOR_ID: { // 厂商名称 = 'Hyper-Bridge',通过RBX + RDX + RCX组合: Context->Rax = Regs.Regs.Eax; GetHvCpuName(Context->Rbx, Context->Rcx, Context->Rdx); break; } default: { Context->Rax = Regs.Regs.Eax; Context->Rbx = Regs.Regs.Ebx; Context->Rcx = Regs.Regs.Ecx; Context->Rdx = Regs.Regs.Edx; break; } } break; } case VMEXIT_MSR: { if ((Context->Rcx & MAXUINT32) == static_cast<unsigned int>(AMD_MSR::MSR_EFER) && Private->Guest.ControlArea.ExitInfo1) { EFER Efer = {}; Efer.Value = ((Context->Rdx & MAXUINT32) << 32) | (Context->Rax & MAXUINT32); if (!Efer.Bitmap.SecureVirtualMachineEnable) { InjectEvent(&Private->Guest, INTERRUPT_VECTOR::GeneralProtection, EXCEPTION_VECTOR::FaultTrapException, 0); // #GP(向量=13,类型=异常) break; } Private->Guest.StateSaveArea.Efer = Efer.Value; } break; } case VMEXIT_VMRUN: { InjectEvent(&Private->Guest, INTERRUPT_VECTOR::GeneralProtection, EXCEPTION_VECTOR::FaultTrapException, 0); // #GP(向量=13,类型=异常) break; } case VMEXIT_EXCP_DB:// 通过TF标志追踪异常 { KdPrint(("RIP %p\n", Private->Guest.StateSaveArea.Rip)); Private->Guest.StateSaveArea.Rax = Context->Rax; return Status; } } if (Status == VMM_STATUS::VMM_SHUTDOWN) { // 解除该处理器的虚拟化: Context->Rax = reinterpret_cast<UINT64>(Private) & MAXUINT32; // 低32位 Context->Rbx = Private->Guest.ControlArea.NextRip; Context->Rcx = Private->Guest.StateSaveArea.Rsp; Context->Rdx = reinterpret_cast<UINT64>(Private) >> 32; // 高32位 // 加载客户机状态: __svm_vmload(reinterpret_cast<size_t>(Private->VmmStack.Layout.InitialStack.GuestVmcbPa)); // 保存全局中断标志(GIF): _disable(); __svm_stgi(); // 重置EFER.SVME位以禁用SVM: EFER Efer = {}; Efer.Value = __readmsr(static_cast<unsigned long>(AMD_MSR::MSR_EFER)); Efer.Bitmap.SecureVirtualMachineEnable = FALSE; __writemsr(static_cast<unsigned long>(AMD_MSR::MSR_EFER), Efer.Value); // 恢复EFlags: __writeeflags(Private->Guest.StateSaveArea.Rflags); } Private->Guest.StateSaveArea.Rax = Context->Rax; // 跳转到下一条指令: Private->Guest.StateSaveArea.Rip = Private->Guest.ControlArea.NextRip; return Status; }
核心原因
SVM退出(#VMEXIT)后,处理器处于DIRQL(调度级中断请求级别),此时全局中断被禁用(GIF=0)。而Passive级API要求运行在IRQL ≤ APC_LEVEL,且依赖中断启用才能正常工作(比如线程调度、事件等待),直接调用会触发内核安全检查,导致KERNEL_SECURITY_CHECK_FAILURE崩溃。
具体解决方法
异步调度到Passive级执行
这是最安全的方案:将需要调用Passive级API的逻辑(比如写入文件、复杂数据处理)封装成回调函数,通过IoQueueWorkItem将任务放到系统工作线程中执行;若仅需处理轻量操作,也可用KeInsertQueueDpc放到DPC队列(DPC运行在DISPATCH_LEVEL,若仍需调用Passive级API,可在DPC中再调度工作线程)。
示例思路:- 定义全局工作项或每个CPU的工作队列,在VMExit Handler中收集需要处理的数据(比如指令RIP)并放入队列。
- 触发工作线程,在回调中安全调用
ZwWriteFile等Passive级API。
临时启用中断(谨慎使用)
若必须在VMExit Handler中快速处理,可临时保存当前GIF状态,启用中断后调用API,再恢复状态。但此方法风险极高,可能触发竞态条件(比如中断破坏VM状态),仅适合简单、无阻塞的低级操作:// 保存当前GIF状态 UINT64 gif = __svm_stgi(); // 启用全局中断 __svm_clgi(); // 调用仅允许在DIRQL下执行的低级API(注意:大部分Passive级API仍不可用) // ... // 恢复GIF状态 __svm_ldgi(gif);此方法禁止用于
ZwWriteFile这类需要阻塞或依赖线程调度的API。调试阶段替代方案
若只是调试指令追踪需求,用KdPrint/DbgPrint替代ZwWriteFile,这些调试输出API可在DIRQL级别安全执行,避免崩溃。
代码修改示例(针对指令追踪)
在VMEXIT_EXCP_DB分支中,将RIP信息放入工作队列,由工作线程异步写入文件:
// 定义工作项回调函数 VOID TraceWorkItemCallback(PDEVICE_OBJECT DeviceObject, PVOID Context) { UINT64* ripPtr = static_cast<UINT64*>(Context); // 此处可安全调用ZwWriteFile等Passive级API KdPrint(("Trace RIP: %p\n", *ripPtr)); ExFreePool(ripPtr); } // 在VMEXIT_EXCP_DB分支中修改: case VMEXIT_EXCP_DB:// 通过TF标志追踪异常 { UINT64* ripPtr = static_cast<UINT64*>(ExAllocatePoolWithTag(NonPagedPoolNx, sizeof(UINT64), 'TRCE')); if (ripPtr) { *ripPtr = Private->Guest.StateSaveArea.Rip; IoQueueWorkItem(g_DeviceObject, TraceWorkItemCallback, DelayedWorkQueue, ripPtr); } Private->Guest.StateSaveArea.Rax = Context->Rax; return Status; }
内容的提问来源于stack exchange,提问作者Barbosso

