【硬件虛擬化】遠(yuǎn)離kernel的理想鄉(xiāng)
簡介
這個故事描述了如何使用硬件虛擬化(HVM)使得自己的一些hook代碼遠(yuǎn)離內(nèi)核不容易被其他內(nèi)核hook所影響并且較難被檢測。本文的思路來源于某學(xué)校的動態(tài)linux內(nèi)核更新的玩意,代碼大量抄自bluepill。
第一章 (Avalon) 阿瓦隆的黎明
由于驅(qū)動牛人越來越多系統(tǒng)控制權(quán)的爭奪愈演愈烈,內(nèi)核之中幾乎無一塊凈土。inline hook、ssdt hook等等各種hook充斥著我們幼小的內(nèi)核。他們有些結(jié)構(gòu)復(fù)雜,有些相互關(guān)聯(lián),有些檢測監(jiān)視,想要重新獲得那些控制點的控制權(quán)必須花些力氣研究那些hook,使得原來很簡單的hook變得牽一發(fā)而動全身。
現(xiàn)在有硬件虛擬化技術(shù),我們可以換個思路來解決那些問題了…
一、(Avalon)阿瓦隆的構(gòu)成
(Avalon) 阿瓦隆的本體為以下幾個部分:
1、Avlboot.sys(用于保存剛初始化后還沒被hook污染的系統(tǒng)內(nèi)核方便之后使用)
2、Avalon.sys(用于讀取偽內(nèi)核信息,開啟硬件虛擬化加載偽內(nèi)核)
3、XXX.sys(用戶利用Avlboot.sys中所提供的信息進行具體hook操作的程序)
二、(Avalon)阿瓦隆的真相
本文所介紹的(Avalon) 阿瓦隆是一個基于虛擬化的內(nèi)核加載框架。其利用硬件虛擬化(HVM)和一塊沒有被hook污染的內(nèi)核內(nèi)存,使得PC中同時運行著兩個內(nèi)核。并且Avalon通過控制sysenter_eip和idt中指向偽內(nèi)核的相應(yīng)地址來獲得控制權(quán)。
簡而言之,(Avalon)阿瓦隆就是利用硬件虛擬化(HVM)加載自己的內(nèi)核架空原內(nèi)核,使得自己能在不被檢測的環(huán)境下獲得內(nèi)核的控制權(quán)。
實現(xiàn)原理如下圖所示:
自己的hook程序在獲得偽內(nèi)核的基地址后,可以通過hookport來處理ssdt,shadow ssdt(strongod需要備份其所hook的ssdt稍微麻煩點,agp什么的只要把地址偏移一下就能用了)。
三、(Avalon)阿瓦隆的應(yīng)用
其實我們可以把(Avalon)阿瓦隆看成是一種變相的sysenter hook+idt hook。
由于(Avalon)阿瓦隆基于硬件虛擬化(HVM)使得我們可以架空整個內(nèi)核,使得內(nèi)核可以在運行時可以實時的替換。
因為偽內(nèi)核的所在內(nèi)存范圍不受內(nèi)存監(jiān)控,別的程序也無法直接訪問,使得我們的偽內(nèi)核就成了不用考慮其他干擾問題,可以隨意hook的安全的理想鄉(xiāng)。
這種方式的hook和傳統(tǒng)的一些hook相比有著以下優(yōu)點:
1、不在傳統(tǒng)的內(nèi)存監(jiān)控的范圍,較難檢測。
2、不干擾系統(tǒng)中原有的hook代碼,兼容性較高。
但是同樣有一些缺點:
1、無法直接干涉object hook,irp hook等不依賴于內(nèi)核內(nèi)存的hook代碼
2、整個系統(tǒng)依賴于硬件虛擬化(HVM),對硬件設(shè)備有一定要求。
第二章 空想具現(xiàn)化
一、純潔的初始化
首先我們來實現(xiàn)(Avalon)阿瓦隆的初始化,初始化由4個部分組成:內(nèi)核信息獲取、偽內(nèi)核構(gòu)造、IDT信息保存、原始SYSENTER_EIP獲取。
實現(xiàn)代碼如下:
- NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
- {
- ...
- KrnlCopy();
- IDTCopy();
- ReadMsrSysenter();
- ...
- }
- /*
- 內(nèi)核拷貝
- */
- VOID KrnlCopy()
- {
- PVOID Buffer;
- ULONG Size;
- ULONG RetSize;
- PSYSTEM_MODULE_INFORMATION InfoBuffer;
- NTSTATUS Status;
- PVOID ModuleBase;
- ULONG ModuleSize;
- Size=0x1000;
- do {
- Buffer=ExAllocatePool(NonPagedPool,Size);
- Status=ZwQuerySystemInformation(SystemModuleInformation,Buffer,Size,&RetSize);
- if (Status == STATUS_INFO_LENGTH_MISMATCH)
- {
- ExFreePool(Buffer);
- Size = RetSize;
- }
- }while(Status == STATUS_INFO_LENGTH_MISMATCH);
- InfoBuffer = (PSYSTEM_MODULE_INFORMATION)Buffer;
- ModuleBase=(PVOID)(InfoBuffer->ModuleInfo[0].Base);
- Orig_krnl=(ULONG)ModuleBase;
- DbgPrint("AvlBoot:Orig_krnl Base=%x\n",ModuleBase);
- ModuleSize=InfoBuffer->ModuleInfo[0].Size;
- makeKernelCopy((ULONG)ModuleBase,ModuleSize);
- DbgPrint("AvlBoot:Avl_krnl Base=%x\n",Avl_krnl);
- ExFreePool(Buffer);
- }
等初始化之后,其他部件就可以通過訪問Avlboot.sys來獲得偽內(nèi)核的信息和內(nèi)核原始信息。
二、抄來的虛擬化
這里先簡單的向大家介紹下,Intel vt硬件虛擬化的步驟:
1、檢查vt環(huán)境
2、開啟vt功能
3、填充vt,存儲虛擬機狀態(tài)
4、設(shè)定需要攔截項目
5、設(shè)定從虛擬機中退出時返回的地址
6、啟動虛擬機,等待攔截項目觸發(fā)
7、攔截項目被觸發(fā),進入相關(guān)項目的處理例程
8、恢復(fù)虛擬機繼續(xù)運行
接下來是一些坑爹的代碼:
- /*
- Vmx初始化
- */
- NTSTATUS NTAPI VmxInitialize (
- PCPU Cpu,
- PVOID GuestEip,
- PVOID GuestEsp
- )
- {
- PHYSICAL_ADDRESS AlignedVmcsPA;
- ULONG VaDelta;
- NTSTATUS Status;
- // 為 VMXON region 申請內(nèi)存空間
- Cpu->Vmx.OriginaVmxonR = MmAllocateContiguousPages(
- VMX_VMXONR_SIZE_IN_PAGES,
- &Cpu->Vmx.OriginalVmxonRPA);
- if (!Cpu->Vmx.OriginaVmxonR)
- {
- DbgPrint("VmxInitialize(): Failed to allocate memory for original VMCS\n");
- return STATUS_INSUFFICIENT_RESOURCES;
- }
- DbgPrint("VmxInitialize(): OriginaVmxonR VA: 0x%x\n", Cpu->Vmx.OriginaVmxonR);
- DbgPrint("VmxInitialize(): OriginaVmxonR PA: 0x%llx\n", Cpu->Vmx.OriginalVmxonRPA.QuadPart);
- // 為 VMCS 申請內(nèi)存空間
- Cpu->Vmx.OriginalVmcs = MmAllocateContiguousPages(
- VMX_VMCS_SIZE_IN_PAGES,
- &Cpu->Vmx.OriginalVmcsPA);
- if (!Cpu->Vmx.OriginalVmcs)
- {
- DbgPrint("VmxInitialize(): Failed to allocate memory for original VMCS\n");
- return STATUS_INSUFFICIENT_RESOURCES;
- }
- DbgPrint("VmxInitialize(): Vmcs VA: 0x%x\n", Cpu->Vmx.OriginalVmcs);
- DbgPrint("VmxInitialize(): Vmcs PA: 0x%llx\n", Cpu->Vmx.OriginalVmcsPA.QuadPart);
- // 開啟vmx
- if (!NT_SUCCESS (VmxEnable (Cpu->Vmx.OriginaVmxonR)))
- {
- DbgPrint("VmxInitialize(): Failed to enable Vmx\n");
- return STATUS_UNSUCCESSFUL;
- }
- *((ULONG64 *)(Cpu->Vmx.OriginalVmcs)) =
- (MsrRead (MSR_IA32_VMX_BASIC) & 0xffffffff); //set up vmcs_revision_id
- // 填充VMCS結(jié)構(gòu)
- Status = VmxSetupVMCS (Cpu, GuestEip, GuestEsp);
- if (!NT_SUCCESS (Status))
- {
- DbgPrint("VmxSetupVMCS() failed with status 0x%08hX\n", Status);
- VmxDisable();
- return Status;
- }
- DbgPrint("VmxInitialize(): Vmx enabled\n");
- // 保存EFER
- Cpu->Vmx.GuestEFER = MsrRead (MSR_EFER);
- DbgPrint("Guest MSR_EFER Read 0x%llx \n", Cpu->Vmx.GuestEFER);
- // 保存控制寄存器
- Cpu->Vmx.GuestCR0 = RegGetCr0 ();
- Cpu->Vmx.GuestCR3 = RegGetCr3 ();
- Cpu->Vmx.GuestCR4 = RegGetCr4 ();
- CmCli ();
- return STATUS_SUCCESS;
- }
- /*
- 開啟vmx
- */
- NTSTATUS NTAPI VmxEnable (
- PVOID VmxonVA
- )
- {
- ULONG cr4;
- ULONG64 vmxmsr;
- ULONG flags;
- PHYSICAL_ADDRESS VmxonPA;
- // 設(shè)置cr4位,為啟用VM模式做準(zhǔn)備
- set_in_cr4 (X86_CR4_VMXE);
- cr4 = get_cr4 ();
- DbgPrint("VmxEnable(): CR4 after VmxEnable: 0x%llx\n", cr4);
- if (!(cr4 & X86_CR4_VMXE))
- return STATUS_NOT_SUPPORTED;
- // 檢測是否支持vmx
- vmxmsr = MsrRead (MSR_IA32_FEATURE_CONTROL);
- if (!(vmxmsr & 4))
- {
- DbgPrint("VmxEnable(): VMX is not supported: IA32_FEATURE_CONTROL is 0x%llx\n", vmxmsr);
- return STATUS_NOT_SUPPORTED;
- }
- //bochs的bug,要改IA32_FEATURE_CONTROL的Lock為1
- #if bochsdebug
- MsrWrite(MSR_IA32_FEATURE_CONTROL,5);
- #endif
- vmxmsr = MsrRead (MSR_IA32_VMX_BASIC);
- *((ULONG64 *) VmxonVA) = (vmxmsr & 0xffffffff); //set up vmcs_revision_id
- VmxonPA = MmGetPhysicalAddress (VmxonVA);
- DbgPrint("VmxEnable(): VmxonPA: 0x%llx\n", VmxonPA.QuadPart);
- //開啟VMX
- VmxTurnOn(VmxonPA);
- flags = RegGetEflags ();
- DbgPrint("VmxEnable(): vmcs_revision_id: 0x%x Eflags: 0x%x \n", vmxmsr, flags);
- return STATUS_SUCCESS;
- }
- /*
- 進入虛擬機
- */
- NTSTATUS NTAPI VmxVirtualize (
- PCPU Cpu
- )
- {
- ULONG esp;
- if (!Cpu)
- return STATUS_INVALID_PARAMETER;
- *((PULONG) (g_HostStackBaseAddress + 0x0C00)) = (ULONG) Cpu;
- VmxLaunch ();
- // never returns
- return STATUS_UNSUCCESSFUL;
- }
三、蛋疼的攔截處理
sysenter的處理方法:
由于硬件虛擬化(HVM)無法直接攔截sysenter指令,所以只能使用其他方法來獲得控制權(quán)。
這里有三種方法:
1、在kifastcallentery的頭部寫入cpuid,int3等利用中斷或特權(quán)指令進入vm。
2、使用調(diào)試寄存器在kifastcallentery下硬件執(zhí)行中斷,利用中斷進入vm
3、進入VMM后直接修改guest的sysenter_eip地址,通過控制msr的讀寫來欺騙其他訪問msr的程序。
為了不被內(nèi)存檢測和充分利用調(diào)試寄存器,Avalon中我選用了方案3來控制進程執(zhí)行sysenter后的運行流向。
部分代碼:
- static BOOLEAN NTAPI VmxDispatchMsrRead (
- PCPU Cpu,
- PGUEST_REGS GuestRegs,
- PNBP_TRAP Trap,
- BOOLEAN WillBeAlsoHandledByGuestHv
- )
- {
- ...
- switch (ecx) {
- case MSR_IA32_SYSENTER_CS:
- MsrValue.QuadPart = VmxRead (GUEST_SYSENTER_CS);
- break;
- case MSR_IA32_SYSENTER_ESP:
- MsrValue.QuadPart = VmxRead (GUEST_SYSENTER_ESP);
- break;
- case MSR_IA32_SYSENTER_EIP:
- MsrValue.QuadPart = Avlkrnlinfo->SysenterAddr;
- ...
- }
idt重定向處理方法:
1、idt地址欺騙
2、idt模擬投遞
第一種是指方案攔截sidt,lidt指令填充一份偽造的idt地址誤導(dǎo)訪問者(由系統(tǒng)投遞相對穩(wěn)定)。
第二種是指方案模擬idt的處理過程自己寫程序投遞idt。
因為方案一需要使用反匯編引擎分析具體的保存地址體積過大,所以本版的Avalon使用第二種方案即idt模擬投遞。
部分代碼:
- static BOOLEAN NTAPI VmxDispatchException (
- PCPU Cpu,
- PGUEST_REGS GuestRegs,
- PNBP_TRAP Trap,
- BOOLEAN WillBeAlsoHandledByGuestHv
- )
- {
- ...
- //SETP 7. SET EIP
- if((uIntrInfo & 0xff) == 1){
- ComPrint("VmxDispatchException():#BD hit /n");
- VmxWrite(GUEST_RIP,Avlkrnlinfo->Fake_IDTMap[0]);
- }
- else if ((uIntrInfo & 0xff) == 3){
- ComPrint("VmxDispatchException():#BP hit /n");
- VmxWrite(GUEST_RIP,Avlkrnlinfo->Fake_IDTMap[1]);}
- ...
- }
第三章 理想鄉(xiāng)的黃昏
一、(Avalon)阿瓦隆的檢測
對于基于硬件虛擬化(HVM)的程序,首先想到的方法必然就是直接檢測和對抗硬件虛擬化。
對硬件虛擬化的檢測主要有:efer的檢測,vme的檢測。
對于處理了中斷的vmm還能通過計算中斷前后的時間差來判斷自身是否在虛擬機中。
當(dāng)然針對Avalon還有其他的檢測方法(此處省略xx字)
二、未來的更新
Avalon才剛剛開始功能并不完善,還有好多功能想加進去:
1、將內(nèi)核移入EPT(NPT)讓你完全看不到
2、 和ring3程序交互…
3、其他隱藏功能
總結(jié)
Avalon只是硬件虛擬化應(yīng)用的冰山一角,還有更多的應(yīng)用等待著我們?nèi)ヌ剿?,小弟的水平有限以后還要向各位高手多多請教繼續(xù)努力學(xué)習(xí)。
該bin測試環(huán)境如下:
bochs2.4.5
windows xp sp3
注意:這個bin只是個簡單的樣品,真機上運行必藍(lán),且只針對ring0的中斷,ring3有3處bug未修復(fù)。