许可优化
许可优化
产品
产品
解决方案
解决方案
服务支持
服务支持
关于
关于
软件库
当前位置:服务支持 >  软件文章 >  VT虚拟化框架编写教程(Virtualization Technology)

VT虚拟化框架编写教程(Virtualization Technology)

阅读数 2
点赞 0
article_banner

文章目录

  • 前言 VT架构基础 VT框架编写步骤一:检测VT是否开启 VMM和VM VMON和VMCS VT框架编写步骤二 填充VMON VT框架编写步骤三 进入VT VT框架编写步骤四 初始化VMCS VT框架编写步骤五 初始化VMCS数据区 VT框架编写步骤六 处理必要事件


前言

学习VT相关的知识,需要具备WIN32基础和内核相关的知识,包括 Windows  段机制,页机制等等,VT里面都会有所涉及,也就是得把Windows编程和驱动基本通关,新手建议劝退。

整个VT的知识结构有大量的新的概念,新的寄存器,新的指令还有各种标志位,初次学习VT建议结合代码来理解相关的理论基础,重点在于梳理整个VT的流程,不要深究具体的某个细节。整个流程走完之后就有了阅读别人代码的能力,等实际应用的时候再去查阅相关资料补齐技术细节的部分。我的笔记,很多能省的基本都省掉了,只把整体必须的流程整理出来。

VT架构基础

**什么是VT:**我理解的VT是将操作系统运行在自己模拟的一个虚拟环境之上,然后通过这个来截获所有的 事件 和中断,达到过反调试和过保护的效果,可以理解为一个HOOK系统吧,毕竟也是通过拦截的方式来处理事件。

三种虚拟化技术:

  1. 基于处理器的虚拟化VT-x
  2. 基于PCI总线设备实现的IO虚拟化 VT-d
  3. 基于网络的虚拟化 VT-c

目前主要研究的是基于处理器的 虚拟化  技术VT-x

VT框架编写步骤一:检测VT是否开启

编写VT框架的第一步,就是用代码实现判断当前环境是否支持VT。这里有三个地方需要进行判断

  1. 首先检测CPUID是否支持VT,ecx第六位,如果为1支持VT,否则不支持
typedef union _CPUID_ECX
{
	struct
	{
		unsigned SSE3 : 1;
		unsigned PCLMULQDQ : 1;
		unsigned DTES64 : 1;
		unsigned MONITOR : 1;
		unsigned DS_CPL : 1;
		unsigned VMX : 1;
		unsigned SMX : 1;
		unsigned EIST : 1;
		unsigned TM2 : 1;
		unsigned SSSE3 : 1;
		unsigned Reserved : 22;
	};

}CPUID_ECX,*PCPUID_ECX;

//检测CPUID是否支持VT
BOOLEAN VmxCheckCPUIDIsSupportVT()
{
	int cpuidInfo[4];
	__cpuidex(cpuidInfo, 1, 0);

	PCPUID_ECX cpuid = &cpuidInfo[2];
	
	return cpuid->VMX;
}

尽量用这种结构体位判断的写法,不要用左移右移的那种算法,比较直观,可读性好。

  1. 检测BIOS是否支持开启VT,在MSR寄存器里面
typedef union _IA32_FEATURE_CONTROL_MSR
{
	ULONG Value;
	struct
	{
		unsigned Lock : 1;		// Bit 0 is the lock bit - cannot be modified once lock is set
		unsigned Reserved1 : 1;		// Undefined
		unsigned EnableVmxon : 1;		// Bit 2. If this bit is clear, VMXON causes a general protection exception
		unsigned Reserved2 : 29;	// Undefined
		unsigned Reserved3 : 32;	// Undefined
	}Fields;

} IA32_FEATURE_CONTROL_MSR,*PIA32_FEATURE_CONTROL_MSR;

//检测BIOS是否开启VT
BOOLEAN VmxCheckBIOSIsSupportVT()
{
	IA32_FEATURE_CONTROL_MSR msr;
	msr.Value = __readmsr(IA32_FEATURE_CONTROL);

	return msr.Fields.Lock;
}
  1. 检测CR4.VMXE位,这个位如果被设置为1代表有VT已经开启了,你不能再开启,否则可以开启
BOOLEAN VmxCheckCR4IsSupportVT()
{
	_CR0 cr0 = { .Value = __readcr0() };
	_CR4 cr4 = { .Value = __readcr4() };
	
	if (cr0.Fields.PE != 1 || cr0.Fields.PG != 1 || cr0.Fields.NE != 1)
	{
		return FALSE;
	}

	if (cr4.Fields.VMXE==1)
	{
		return FALSE;
	}

	return TRUE;
}

VMM和VM

在VMX架构下定义了两 类  软件的角色和环境

  • VMM 虚拟机监管者
  • VM 虚拟机

VMM代表一类在VMX架构下的管理者角色,拥有控制权,监管每个VM的运行。

VM代表 虚拟机  实例,一个VMX架构可以有多个实例,每个VM都是一套独立的环境,且VM本身不会意识到自己运行在虚拟机的环境里。

host端对应VMM,guest端对应VM

VMON和VMCS

在这里插入图片描述

在VMX架构下,至少有一个VMON和VMCS的物理区域,一个VMM可以有多个VM,所以也可以有多块区域。

VMON区域是给VMM用的,VMM使用这块区域对数据进行记录和维护;每个VM也需要有自己的VMCS区域。VMM使用VMCS区域来配置VM的运行环境

VT框架编写步骤二 填充VMON

在进入VMX模式前,必须为VMM准备一份VMXON区域,和VMCS区域,并配置VM的运行环境

填充VMON也分为三个步骤:

  1. 进入前的相关寄存器设置
  2. 申请VMON物理内存
  3. 初始化VMON区域

相关寄存器设置:

  1. CR0寄存器需要满足PG位 NE位 PE位都为1
  2. CR4需要开启VMXE位

代码如下:

	//初始化CR0 CR4
	ULONG64 vcr00 = __readmsr(IA32_VMX_CR0_FIXED0);
	ULONG64 vcr01 = __readmsr(IA32_VMX_CR0_FIXED1);
	ULONG64 vcr04 = __readmsr(IA32_VMX_CR4_FIXED0);
	ULONG64 vcr14 = __readmsr(IA32_VMX_CR4_FIXED1);

	ULONG64 mcr4 = __readcr4();
	ULONG64 mcr0 = __readcr0();

	mcr4 |= vcr04;
	mcr4 &= vcr14;

	mcr0 |= vcr00;
	mcr0 &= vcr01;

	//修改CR0 CR4
	__writecr0(mcr0);
	__writecr4(mcr4);

申请VMON物理内存

接着需要分配一块物理内存作为VMON区域,以4KB对齐,大小和内存cache类型可以通过检IA32_VMX_BASIC寄存器来获得

	//给VMON申请一块内存
	PHYSICAL_ADDRESS lowphys,highphy;
	lowphys.QuadPart = 0;
	highphy.QuadPart = -1;
	pVcpucb->VmxOnAddr = MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE,
		lowphys, highphy, lowphys, MmCached);
	if (!pVcpucb->VmxOnAddr)
	{
		return -1;
	}


	RtlZeroMemory(pVcpucb->VmxOnAddr, PAGE_SIZE);

	//获取物理地址
	pVcpucb->VmxOnAddrPhys = MmGetPhysicalAddress(pVcpucb->VmxOnAddr);

初始化VMON区域

VMXON区域前四个字节是VMCS ID,下一个四字节是VMX- abort indicator字段(这个不需要我们填),如图(其实就是一个指针):

在这里插入图片描述

VMCS ID的值可以通过IA32_VMX_BASIC获取,执行VMXON指令前必须将这个ID写入第一个四字节的位置。示例代码如下:

	//读IA32_VMX_BASIC寄存器
	ULONG64 vmxBasic= __readmsr(IA32_VMX_BASIC);

	//填充ID
	*(PULONG)pVcpucb->VmxOnAddr = (ULONG)vmxBasic;

VT框架编写步骤三 进入VT

处理器进入VMX模式需要执行VMXON指令,而这个指令需要提供一个指向VMXON区域的物理指针

int error= __vmx_on(&pVcpucb->VmxOnAddrPhys.QuadPart);

成功调用__vmx_on指令,表示处理器已经成功进入VMX Operation模式,也就是已经处于root环境。到这里我们就已经进入VT环境了,上来一堆概念和乱八七糟的字段标志已经给我干蒙了,没办法,先把流程整理清楚,后面再慢慢理解。

VT框架编写步骤四 初始化VMCS

进入VT之后,我们还需要再设置一些结构。VMCS结构存放在一个物理区域内,也是4KB对齐,有一个8字节的头部额VMCS数据区域。

在这里插入图片描述

VMCS的前8个字节也是由IA32_VMX_BASIC寄存器得到,VMCS的初始化代码和VMON区域几乎差不多

//初始化VMCS区域
int VmxInitVmcs()
{
	//获取当前核
	PVMXCPUPCB pVcpucb = VmxGetCurrentCpucb();

	//给VMCS申请一块内存
	PHYSICAL_ADDRESS lowphys, highphy;
	lowphys.QuadPart = 0;
	highphy.QuadPart = -1;
	pVcpucb->VmxCsAddr = MmAllocateContiguousMemorySpecifyCache(PAGE_SIZE,lowphys, highphy, lowphys, MmCached);
	if (!pVcpucb->VmxCsAddr)
	{
		return -1;
	}
	RtlZeroMemory(pVcpucb->VmxCsAddr, PAGE_SIZE);

	//获取物理地址
	pVcpucb->VmxCsAddrPhys = MmGetPhysicalAddress(pVcpucb->VmxCsAddr);

	//读IA32_VMX_BASIC寄存器
	ULONG64 vmxBasic = __readmsr(IA32_VMX_BASIC);

	//填充ID
	*(PULONG)pVcpucb->VmxCsAddr = (ULONG)vmxBasic;

	//清空以前的状态
	__vmx_vmclear(&pVcpucb->VmxCsAddrPhys.QuadPart);

	//加载新的VMCS
	__vmx_vmptrld(&pVcpucb->VmxCsAddrPhys.QuadPart);
}

VT框架编写步骤五 初始化VMCS数据区

上面我们的VMCS区域还没有初始化完成,只是初始化了前8个字节,还有一块数据区需要进行初始化。

VMCS的数据区包括了6个区域:

  1. guest-state区域,保存处理器的状态信息
  2. host-state区域,保存处理器的状态信息
  3. VM-execution控制区域,这里就是VM控制区了,可以用来设置和拦截中断
  4. VM-exit控制区域,处理退出VM的行为
  5. VM-entry控制区域,处理进入VM的行为
  6. VM-exit信息区域,记录退出VM事件的原因和相关信息和错误码

这六个数据区,都要一一进行初始化设置。

访问VMCS字段

软件必须通过VMREAD和VMWRITE来访问VMCS字段,每个字段定义一个唯一ID值来对应,这个字段ID值提供给VMREAD作为index值来访问对应字段。

在这里插入图片描述

关于字段的值和具体含义,这个不需要记,有定义好的宏配合__vmx_vmwrite函数,直接拿来用就行了。

初始化VMHost和VMGuest就是把这些乱八七糟的位都给他置上对应的值,太多了,就不一个个拆开细讲了。


void VmxInitVmGuest(ULONG64 GuestEip, ULONG64 GuestEsp)
{
	//填充GDT表项
	FillGdtDataItem(0, AsmReadES());
	FillGdtDataItem(1, AsmReadCS());
	FillGdtDataItem(2, AsmReadSS());
	FillGdtDataItem(3, AsmReadDS());
	FillGdtDataItem(4, AsmReadFS());
	FillGdtDataItem(5, AsmReadGS());
	FillGdtDataItem(6, AsmReadLDTR());


	//取GDT表
	GdtTable gdtTable = { 0 };
	AsmGetGdtTable(&gdtTable);

	ULONG trSelector = AsmReadTR();

	//可能是三环的 要处理下
	trSelector &= 0xFFF8;

	ULONG trlimit = __segmentlimit(trSelector);

	LARGE_INTEGER trBase = { 0 };
	PULONG trItem = (PULONG)(gdtTable.Base + trSelector);

	//读TR.Base
	trBase.LowPart = ((trItem[0] >> 16) & 0xFFFF)|((trItem[1]&0xFF)<<16)| (trItem[1] & 0xFF000000);
	trBase.HighPart = trItem[2];

	//属性
	ULONG attr = (trItem[1] & 0x00F0FF00) >> 8;


	__vmx_vmwrite(GUEST_TR_BASE, trBase.QuadPart);
	__vmx_vmwrite(GUEST_TR_LIMIT , trlimit);
	__vmx_vmwrite(GUEST_TR_AR_BYTES , attr);
	__vmx_vmwrite(GUEST_TR_SELECTOR, trSelector);


	__vmx_vmwrite(GUEST_CR0, __readcr0());
	__vmx_vmwrite(GUEST_CR3, __readcr3());
	__vmx_vmwrite(GUEST_CR4, __readcr4());
	__vmx_vmwrite(GUEST_DR7, __readdr(7));
	__vmx_vmwrite(GUEST_RFLAGS, __readeflags());
	__vmx_vmwrite(GUEST_RSP, GuestEsp);
	__vmx_vmwrite(GUEST_RIP, GuestEip);

	__vmx_vmwrite(GUEST_SYSENTER_CS, __readmsr(0x174));
	__vmx_vmwrite(GUEST_SYSENTER_ESP, __readmsr(0x175));
	__vmx_vmwrite(GUEST_SYSENTER_EIP, __readmsr(0x176));


	__vmx_vmwrite(GUEST_IA32_DEBUGCTL, __readmsr(IA32_MSR_DEBUGCTL));
	__vmx_vmwrite(GUEST_IA32_PAT, __readmsr(IA32_MSR_PAT));
	__vmx_vmwrite(GUEST_IA32_EFER, __readmsr(IA32_MSR_EFER));
	__vmx_vmwrite(GUEST_FS_BASE, __readmsr(IA32_FS_BASE));
	__vmx_vmwrite(GUEST_GS_BASE, __readmsr(IA32_GS_BASE));
	__vmx_vmwrite(VMCS_LINK_POINTER, -1);


	//IDT GDT
	GdtTable idtTable;
	__sidt(&idtTable);

	__vmx_vmwrite(GUEST_GDTR_BASE, gdtTable.Base);
	__vmx_vmwrite(GUEST_GDTR_LIMIT, gdtTable.limit);

	__vmx_vmwrite(GUEST_IDTR_BASE, idtTable.Base);
	__vmx_vmwrite(GUEST_IDTR_LIMIT, idtTable.limit);
}

初始化Host也跟上面的代码类似。

初始化VM_ENTRY

void VMInitEntry()
{
	ULONG64 vmxBasic = __readmsr(IA32_VMX_BASIC);

	ULONG64 mseregister = ((vmxBasic >> 55) & 1) ? IA32_MSR_VMX_TRUE_ENTRY_CTLS : IA32_VMX_ENTRY_CTLS;

	ULONG64 value= VmxAdjustContorls(0x200, mseregister);
	__vmx_vmwrite(VM_ENTRY_CONTROLS, value);
	__vmx_vmwrite(VM_ENTRY_MSR_LOAD_COUNT, 0);
	__vmx_vmwrite(VM_ENTRY_INTR_INFO_FIELD, 0);
}

初始化VM_EXIT

void VMInitExit()
{
	ULONG64 vmxBasic = __readmsr(IA32_VMX_BASIC);

	ULONG64 mseregister = ((vmxBasic >> 55) & 1) ? IA32_MSR_VMX_TRUE_EXIT_CTLS : IA32_MSR_VMX_TRUE_EXIT_CTLS;

	ULONG64 value = VmxAdjustContorls(0x200|0x8000, mseregister);
	__vmx_vmwrite(VM_EXIT_CONTROLS, value);
	__vmx_vmwrite(VM_EXIT_MSR_LOAD_COUNT, 0);
	__vmx_vmwrite(VM_EXIT_INTR_INFO, 0);
}

初始化控制区


//初始化VM控制区
void VMInitControls()
{
	ULONG64 vmxBasic = __readmsr(IA32_VMX_BASIC);

	ULONG64 mseregister = ((vmxBasic >> 55) & 1) ? IA32_MSR_VMX_TRUE_PINBASED_CTLS : IA32_MSR_VMX_PINBASED_CTLS;

	ULONG64 value = VmxAdjustContorls(0, mseregister);
	__vmx_vmwrite(PIN_BASED_VM_EXEC_CONTROL, value);


	mseregister = ((vmxBasic >> 55) & 1) ? IA32_MSR_VMX_TRUE_PROCBASED_CTLS : IA32_MSR_VMX_PROCBASED_CTLS;
	value = VmxAdjustContorls(0, mseregister);
	__vmx_vmwrite(CPU_BASED_VM_EXEC_CONTROL, value);
}

至于为什么要这么初始化,我也不知道,先把流程整理出来再说。到这里整个VT框架就完成了,下一步就可以开始拦截事件。

VT框架编写步骤六 处理必要事件

到这里我们的框架就已经完成了,现在可以开始处理自己想要拦截的事件。但是在这些事件里面,有一些事件是我们必须要处理掉的,我们写完这一部分的代码,才能开始拦截我们自己想要拦截的事件

在这里插入图片描述

如上图所示,上图中的指令引起的VM-exit事件,就是我们必须要处理掉的,代码如下:

EXTERN_C VOID VmxExitHandler(PGuestContext context)
{
	ULONG64 reason = 0;
	ULONG64 intstLen = 0;
	ULONG64 instinfo = 0;
	ULONG64 mrip = 0;
	ULONG64 mrsp = 0;

	//退出事件码
	__vmx_vmread(VM_EXIT_REASON, &reason);

	//指令长度
	__vmx_vmread(VM_EXIT_INSTRUCTION_LEN, &intstLen);

	//指令详细信息
	__vmx_vmread(VMX_INSTRUCTION_INFO, &instinfo);

	//获取客户机触发VT事件的地址
	__vmx_vmread(GUEST_RIP, &mrip);
	__vmx_vmread(GUEST_RSP, &mrsp);


	//拿退出码的前16位 对下面的事件进行处理
	reason = reason & 0xFFFF;


	switch (reason)
	{
	case EXIT_REASON_CPUIhttps://www.gofarlic.com
		VmxExitHandlerCpuid(context);
		break;
	case EXIT_REASON_GETSEC:
	{
		DbgBreakPoint();
		DbgPrint("EXIT_REASON_GETSEC reson:%x rip:%llx\n",reason,mrip);
	}
		break;
	case EXIT_REASON_TRIPLE_FAULT:
	{
		DbgBreakPoint();
		DbgPrint("EXIT_REASON_TRIPLE_FAULT reson:%x rip:%llx\n", reason, mrip);
	}
	break;
	case EXIT_REASON_INVhttps://www.gofarlic.com
	{
		AsmInvd();
	}
	break;
	case EXIT_REASON_VMCALL:
	case EXIT_REASON_VMCLEAR:
	case EXIT_REASON_VMLAUNCH:
	case EXIT_REASON_VMPTRLhttps://www.gofarlic.com
	case EXIT_REASON_VMPTRST:
	case EXIT_REASON_VMREAhttps://www.gofarlic.com
	case EXIT_REASON_VMRESUME:
	case EXIT_REASON_VMWRITE:
	case EXIT_REASON_VMXOFF:
	case EXIT_REASON_VMXON:
	{
		//统一返回错误,执行我们的VT后 就不往下执行了
		ULONG64 rflags = 0;
		__vmx_vmread(GUEST_RFLAGS, &rflags);
		rflags |= 0x41;
		__vmx_vmwrite(GUEST_RFLAGS, &rflags);
	}
	break;
	case EXIT_REASON_MSR_REAhttps://www.gofarlic.com
	{
		VmxExitHandlerReadMsr(context);
	}
		break;
	case EXIT_REASON_MSR_WRITE:
	{
		VmxExitHandlerWriteMsr(context);
	}
		break;
	case EXIT_REASON_XSETBV:
	{
		ULONG64 value = MAKE_REG(context->mRax , context->mRdx);
		_xsetbv(context->mRcx, value);
	}
	break;

	default:
		break;
	}

	//写入RIP和RSP  让VT能正确返回
	__vmx_vmwrite(GUEST_RIP, mrip+intstLen);
	__vmx_vmwrite(GUEST_RSP, mrsp);

}

到这里,我们的VT框架必须填充的部分就已经完成了。


VOID VmxHandlerCpuid(PGuestContext context)
{
	if (context->mRax == 0x8888)
	{
		context->mRax = 0x11111111;
		context->mRbx = 0x22222222;
		context->mRcx = 0x33333333;
		context->mRdx = 0x44444444;
	}
	else 
	{
		int cpuids[4] = {0};
		__cpuidex(cpuids,context->mRax, context->mRcx);
		context->mRax = cpuids[0];
		context->mRbx = cpuids[1];
		context->mRcx = cpuids[2];
		context->mRdx = cpuids[3];
	}
}

这里我们加一个测试的代码,拦截cpuid指令,然后修改周围的寄存器。

在这里插入图片描述

这里比较蛋疼的一个点,框架部分代码一旦出现错误没法调试,也没有错误信息,直接就是虚拟机关闭。

而且这个部分的代码都是初始化和设置各种标志位和字段,能单步调试,其实也没有多大意义,根本看不出来异常,只能一行一行代码对照。

在这里插入图片描述

如果你的VT框架可以正常加载,并且执行这两条指令以后,寄存器被修改,说明事件可以正常拦截,那么恭喜各位框架搭建成功。

鬼手PC逆向交流群


免责声明:本文系网络转载或改编,未找到原创作者,版权归原作者所有。如涉及版权,请联系删

相关文章
技术文档
QR Code
微信扫一扫,欢迎咨询~
customer

online

联系我们
武汉格发信息技术有限公司
湖北省武汉市经开区科技园西路6号103孵化器
电话:155-2731-8020 座机:027-59821821
邮件:tanzw@gofarlic.com
Copyright © 2023 Gofarsoft Co.,Ltd. 保留所有权利
遇到许可问题?该如何解决!?
评估许可证实际采购量? 
不清楚软件许可证使用数据? 
收到软件厂商律师函!?  
想要少购买点许可证,节省费用? 
收到软件厂商侵权通告!?  
有正版license,但许可证不够用,需要新购? 
联系方式 board-phone 155-2731-8020
close1
预留信息,一起解决您的问题
* 姓名:
* 手机:

* 公司名称:

姓名不为空

姓名不为空

姓名不为空
手机不正确

手机不正确

手机不正确
公司不为空

公司不为空

公司不为空