从头开始相识和运用Hypervisor(第2部份) | 申博官网
登录
  • 欢迎进入申博官网!
  • 如果您觉得申博官网对你有帮助,那么赶紧使用Ctrl+D 收藏申博官网并分享出去吧
  • 这里是申博官方网!
  • 申博官网是菲律宾sunbet官网品牌平台!
  • 申博开户专业品牌平台!

从头开始相识和运用Hypervisor(第2部份)

申博_安全预警 申博 94次浏览 已收录 0个评论

从头入手下手相识和运用Hypervisor(第1部份)

DbgView的问

不幸的是,由于某些未知缘由,我没法检察DbgPrint()的效果。假如能够看到效果,则能够跳过此步骤,然则假如中心遇到问题,请实行以下步骤:

在regedit中,增加一个密钥:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Session Manager\Debug Print Filter

接着,增加一个名为IHVDRIVER的DWORD值,其值为0xFFFF。

重新启动盘算机,你就能够入手下手了。而且该要领一直有效(除了在MacBook运转外),而且我在很多盘算机上也举行过测试。

为相识决此问题,你须要找到一个名为nt!Kd_DEFAULT_Mask的Windows内核全局变量,该变量担任在DbgView中显现效果,它有一个我不晓得的掩码,所以我只安排了0xffffffff使其简朴地显现一切内容!

为此,你须要运用Windbg举行Windows当地内核调试。

1.以管理员身份翻开敕令提示符窗口,输入bcdedit /debug on;

2.假如还没有将盘算机设置为调试传输的目标,请输入bcdedit/dbgsettings local;

3.重新启动盘算机;

以后,你须要运用UAC管理员权限翻开Windbg,转到“文件”>“内核调试”>“当地”>按OK,然后在当地Windbg中运用以下敕令找到nt!Kd_DEFAULT_Mask:

prlkd> x nt!kd_Default_Mask
fffff801`f5211808 nt!Kd_DEFAULT_Mask = <no type information>

如今将它的值变动成0xffffffff。

lkd> eb fffff801`f5211808 ff ff ff ff

从头开始相识和运用Hypervisor(第2部份)

以后,你应当看到效果,如今你能够入手下手了。

请记着,以上讲的步骤很重要。由于假如我们看不到任何内核详细信息,那末我们将没法调试。

从头开始相识和运用Hypervisor(第2部份)

检测管理顺序支撑

在启用VT-x之前,起首要斟酌发明对vmx的支撑,这在之前已引见过了。

假如CPUID.1:ECX.VMX[bit 5] = 1,则能够运用CPUID晓得VMX的存在,然后支撑VMX操纵。

起首,我们须要晓得我们是不是在基于Intel的处理器上运转,这能够经由历程搜检CPUID指令并找到供应商字符串“GenuineIntel“.来明白。

以下的函数以CPUID指令的情势返回供应商字符串:

string GetCpuID()
{
	//Initialize used variables
	char SysType[13]; //Array consisting of 13 single bytes/characters
	string CpuID; //The string that will be used to add all the characters to
				  //Starting coding in assembly language
	_asm
	{
		//Execute CPUID with EAX = 0 to get the CPU producer
		XOR EAX, EAX
		CPUID
		//MOV EBX to EAX and get the characters one by one by using shift out right bitwise operation.
		MOV EAX, EBX
		MOV SysType[0], al
		MOV SysType[1], ah
		SHR EAX, 16
		MOV SysType[2], al
		MOV SysType[3], ah
		//Get the second part the same way but these values are stored in EDX
		MOV EAX, EDX
		MOV SysType[4], al
		MOV SysType[5], ah
		SHR EAX, 16
		MOV SysType[6], al
		MOV SysType[7], ah
		//Get the third part
		MOV EAX, ECX
		MOV SysType[8], al
		MOV SysType[9], ah
		SHR EAX, 16
		MOV SysType[10], al
		MOV SysType[11], ah
		MOV SysType[12], 00
	}
	CpuID.assign(SysType, 12);
	return CpuID;
}

末了一步是搜检VMX是不是存在,你能够运用以下代码举行搜检:

bool VMX_Support_Detection()
{

	bool VMX = false;
	__asm {
		xor    eax, eax
		inc    eax
		cpuid
		bt     ecx, 0x5
		jc     VMXSupport
		VMXNotSupport :
		jmp     NopInstr
		VMXSupport :
		mov    VMX, 0x1
		NopInstr :
		nop
	}

	return VMX;
}

能够看到,它用EAX=1搜检CPUID,假如第5(6)位是1,则支撑VMX操纵,我们也能够在内核驱动顺序中实行雷同的操纵。

总而言之,我们的重要代码应以下所示:

int main()
{
	string CpuID;
	CpuID = GetCpuID();
	cout << "[*] The CPU Vendor is : " << CpuID << endl;
	if (CpuID == "GenuineIntel")
	{
		cout << "[*] The Processor virtualization technology is VT-x. \n";
	}
	else
	{
		cout << "[*] This program is not designed to run in a non-VT-x environemnt !\n";
		return 1;
	}
	
	if (VMX_Support_Detection())
	{
		cout << "[*] VMX Operation is supported by your processor .\n";
	}
	else
	{
		cout << "[*] VMX Operation is not supported by your processor .\n";
		return 1;
	}
	_getch();
    return 0;
}

终究效果以下:

从头开始相识和运用Hypervisor(第2部份)

启用VMX操纵

假如我们的处理器支撑VMX操纵,那末就应当启用它。如前所述,IRP_MJ_CREATE是用于启动操纵的第一个函数。

在体系软件能够进入VMX操纵之前,它会经由历程设置CR4.VMXE [bit 13] = 1来启用VMX。然后经由历程实行VMXON指令进入VMX操纵。假如以CR4.VMXE = 0实行,VMXON会致使无效操纵码非常(#UD)。一旦进入VMX操纵,就没法消灭CR4.VMXE。体系软件经由历程实行VMXOFF指令退出VMX操纵。实行VMXOFF后,能够在VMX操纵以外消灭CR4.VMXE。

VMXON也由IA32_FEATURE_CONTROL MSR(MSR地点3AH)掌握,当逻辑处理器被重置时,这个MSR被消灭为0。

1.位0是锁定位,假如消灭此位,则VMXON会致使通例庇护非常。假如设置了锁定位,则此MSR的WRMSR会致使平常庇护非常;除非重新启动,不然不能修正MSR。体系BIOS能够运用此位为BIOS供应设置选项,以禁用对VMX的支撑。要在平台上启用VMX支撑,BIOS必需将位1,位2或同时设置这两个位以及锁定位。

2.位1启用了SMX操纵中的VMXON,假如消灭此位,则在SMX操纵中实行VMXON会致使通例庇护非常,试图在差别时支撑VMX操纵和SMX操纵的逻辑处理器上设置这个位会致使通用庇护非常。

3.位2启用SMX以外的VMXON操纵。假如消灭此位,则在SMX操纵以外实行VMXON会致使通例庇护非常,试图在不支撑VMX操纵的逻辑处理器上设置这个位会致使通用庇护非常。 

你还记得我通知过你如安在Windows Driver Kit x64中建立内联顺序集吗?

如今,你应当建立一些函数来在汇编中实行此操纵。

我们仅需在标头文件(本文示例为Source.h)中声明你的函数:

extern void inline Enable_VMX_Operation(void);

然后在汇编文件(在我的状况下为SourceAsm.asm)中增加此函数,将Cr4的13或14位设置为1。

Enable_VMX_Operation PROC PUBLIC
push rax			; Save the state

xor rax,rax			; Clear the RAX
mov rax,cr4
or rax,02000h		        ; Set the 14th bit
mov cr4,rax

pop rax				; Restore the state
ret
Enable_VMX_Operation ENDP

别的,在SourceAsm.asm中声明你的函数。

PUBLIC Enable_VMX_Operation

上面的函数应当在DrvCreate中挪用:

NTSTATUS DrvCreate(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp)
{
	Enable_VMX_Operation();	// Enabling VMX Operation
	DbgPrint("[*] VMX Operation Enabled Successfully !");
	return STATUS_SUCCESS;
}

末了,你应当从用户形式挪用以下函数:

	HANDLE hWnd = CreateFile(L"\\\\.\\MyHypervisorDevice",
		GENERIC_READ | GENERIC_WRITE,
		FILE_SHARE_READ |
		FILE_SHARE_WRITE,
		NULL, /// lpSecurityAttirbutes
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL |
		FILE_FLAG_OVERLAPPED,
		NULL); /// lpTemplateFile

假如看到以下效果,则申明你已胜利完成。

从头开始相识和运用Hypervisor(第2部份)

重要申明:请注重你的.asm文件应与驱动顺序主文件(.c文件)具有差别的称号,比方,假如驱动顺序文件为“Source.c”,则运用称号“Source.asm”会在Visual Studio中致使新鲜的链接毛病。所以你应将.asm文件的称号变动成“SourceAsm.asm”之类,以防止涌现此类链接器毛病。

以上,我引见了建立Windows Driver Kit顺序所需相识的基本知识,然后就能够进入虚拟环境。本文所讲的源代码,你能够点此检察。

如今,我们就能够设置我们的第一个虚拟机了。下面我们将演示怎样从Windows用户形式(IOCTL 调理顺序)与VMM举行交互,然后在特别的内核中处理关联性和运转代码的问题。 我们终究的目标是初始化VMXON地区和VMCS地区,然后将虚拟机管理顺序地区加载到每一个内核中,并完成自定义函数来运用管理顺序指令和很多与虚拟机掌握数据构造(VMCS)相干的内容。该历程的完全源代码,你能够点此下载。

从用户形式与VMM驱动顺序举行交互

对我们来讲,IRP MJ函数中最重要的函数是DrvIOCTLDispatcher(IRP_MJ_DEVICE_CONTROL),这是由于能够从用户形式运用特别的IOCTL编号挪用此函数,这意味着你能够在驱动顺序中运用特别的代码并完成与之对应的特别函数。别的运用该代码,还能够要求驱动顺序实行要求,可见此函数何等有效。

如今,让我们找到挪用IOCTL代码的函数,并从内核形式驱动顺序中打印代码的函数。

据我所知,有几种要领能够用来调理IOCTL,比方METHOD_BUFFERED,METHOD_NIETHER,METHOD_IN_DIRECT,METHOD_OUT_DIRECT。这些要领应由用户形式挪用者完成,区分在于缓冲区在用户形式和内核形式之间转移,反之亦然,我只是复制了一些完成,并从Microsoft的Windows Driver Samples中举行了一些小修正。用户形式和内核形式的完全代码,假如轻易你能够相识一下。

假定我们有以下IOCTL代码:

//
// Device type           -- in the "User Defined" range."
//
#define SIOCTL_TYPE 40000

//
// The IOCTL function codes from 0x800 to 0xFFF are for customer use.
//
#define IOCTL_SIOCTL_METHOD_IN_DIRECT \
    CTL_CODE( SIOCTL_TYPE, 0x900, METHOD_IN_DIRECT, FILE_ANY_ACCESS  )

#define IOCTL_SIOCTL_METHOD_OUT_DIRECT \
    CTL_CODE( SIOCTL_TYPE, 0x901, METHOD_OUT_DIRECT , FILE_ANY_ACCESS  )

#define IOCTL_SIOCTL_METHOD_BUFFERED \
    CTL_CODE( SIOCTL_TYPE, 0x902, METHOD_BUFFERED, FILE_ANY_ACCESS  )

#define IOCTL_SIOCTL_METHOD_NEITHER \
    CTL_CODE( SIOCTL_TYPE, 0x903, METHOD_NEITHER , FILE_ANY_ACCESS  )

定义IOCTL的规则是如许的,简朴来讲,IOCTL是一个32位数字。前两个低位定义“传输范例”,能够是METHOD_OUT_DIRECT,METHOD_IN_DIRECT,METHOD_BUFFERED或METHOD_NEITHER。

StrandHogg安卓漏洞分析

Promon安全研究人员发现了一个危险的安卓漏洞——StrandHogg。攻击者利用该漏洞可以将恶意软件伪装成合法的APP,而且用户并不会意识到自己被攻击了。该漏洞影响所有的安卓版本,包括最新的安卓 10,研究人员同时发现有36个恶意app正在利用该漏洞,同时top 500的app都处于危险中。 漏洞详情 漏洞概述 StrandHogg是一种唯一且独特的攻击方式,可以在无需设备root权限的情况下对设备发起复杂的攻击。该漏洞利用安卓多任务系统中的弱点来使恶意app可以伪装成设备中的其他app来发起攻击。该漏洞利用是基于taskAffinity的,该安卓控制设备允许任意app(包括恶意app)在多任务系统中自由显示为任意身份。 攻击者通过获取危险的权限来实现这一目的。 请求权限 漏洞使得恶意app可以在伪装成合法app时请求不同的权限。攻击者可以请求任意

下一组位从2到13定义“函数代码”,高位称为“自定义位”。这用于肯定用户定义的IOCTL与体系定义的IOCTL。这意味着函数代码0x800及更高版本的定义相似于WM_USER对Windows音讯的事情体式格局。

接下来的两位定义了发出IOCTL所需的接见权限,假如句柄没有以准确的接见体式格局翻开,I/O管理器能够经由历程这类体式格局谢绝IOCTL要求。比方,接见范例为FILE_READ_DATA和FILE_WRITE_DATA。

末了一名代表IOCTL写入的装备范例。高位再次代表用户定义的值。

在IOCTL调理顺序中,IO_STACK_LOCATION的“Parameters.DeviceIoControl.IoControlCode”包含正在挪用的IOCTL代码。

关于METHOD_IN_DIRECT和METHOD_OUT_DIRECT,IN和OUT之间的区分在于运用IN时,你能够运用输出缓冲区来通报数据,而OUT仅用于返回数据。

METHOD_BUFFERED是一个从这个缓冲区中复制数据的缓冲区,缓冲区建立为两个大小中较大的一个,即输入缓冲区或输出缓冲区。然后将读取的缓冲区复制到此新缓冲区,返回之前,你只需将返回数据复制到统一缓冲区中即可。返回值放入IO_STATUS_BLOCK中,而且I/O管理器将数据复制到输出缓冲区中,个中METHOD_NEITHER是雷同的。

以以下示例为例,起首,引见一切须要的变量。请注重,PAGED_CODE宏可确保挪用线程在足以许可分页的IRQL中运转。

NTSTATUS DrvIOCTLDispatcher( PDEVICE_OBJECT DeviceObject, PIRP Irp)
{
	PIO_STACK_LOCATION  irpSp;// Pointer to current stack location
	NTSTATUS            ntStatus = STATUS_SUCCESS;// Assume success
	ULONG               inBufLength; // Input buffer length
	ULONG               outBufLength; // Output buffer length
	PCHAR               inBuf, outBuf; // pointer to Input and output buffer
	PCHAR               data = "This String is from Device Driver !!!";
	size_t              datalen = strlen(data) + 1;//Length of data including null
	PMDL                mdl = NULL;
	PCHAR               buffer = NULL;

	UNREFERENCED_PARAMETER(DeviceObject);

	PAGED_CODE();

	irpSp = IoGetCurrentIrpStackLocation(Irp);
	inBufLength = irpSp->Parameters.DeviceIoControl.InputBufferLength;
	outBufLength = irpSp->Parameters.DeviceIoControl.OutputBufferLength;

	if (!inBufLength || !outBufLength)
	{
		ntStatus = STATUS_INVALID_PARAMETER;
		goto End;
	}

...

然后,我们必需经由历程IOCTL运用switch-case,只需复制缓冲区并从DbgPrint()中显现出来即可。

	switch (irpSp->Parameters.DeviceIoControl.IoControlCode)
	{
	case IOCTL_SIOCTL_METHOD_BUFFERED:

		DbgPrint("Called IOCTL_SIOCTL_METHOD_BUFFERED\n");
		PrintIrpInfo(Irp);
		inBuf = Irp->AssociatedIrp.SystemBuffer;
		outBuf = Irp->AssociatedIrp.SystemBuffer;
		DbgPrint("\tData from User :");
		DbgPrint(inBuf);
		PrintChars(inBuf, inBufLength);
		RtlCopyBytes(outBuf, data, outBufLength);
		DbgPrint(("\tData to User : "));
		PrintChars(outBuf, datalen);
		Irp->IoStatus.Information = (outBufLength < datalen ? outBufLength : datalen);
		break;

...

PrintIrpInfo是如许的:

VOID PrintIrpInfo(PIRP Irp)
{
	PIO_STACK_LOCATION  irpSp;
	irpSp = IoGetCurrentIrpStackLocation(Irp);

	PAGED_CODE();

	DbgPrint("\tIrp->AssociatedIrp.SystemBuffer = 0x%p\n",
		Irp->AssociatedIrp.SystemBuffer);
	DbgPrint("\tIrp->UserBuffer = 0x%p\n", Irp->UserBuffer);
	DbgPrint("\tirpSp->Parameters.DeviceIoControl.Type3InputBuffer = 0x%p\n",
		irpSp->Parameters.DeviceIoControl.Type3InputBuffer);
	DbgPrint("\tirpSp->Parameters.DeviceIoControl.InputBufferLength = %d\n",
		irpSp->Parameters.DeviceIoControl.InputBufferLength);
	DbgPrint("\tirpSp->Parameters.DeviceIoControl.OutputBufferLength = %d\n",
		irpSp->Parameters.DeviceIoControl.OutputBufferLength);
	return;
}

在本文的其余部份,我们仅运用IOCTL_SIOCTL_METHOD_BUFFERED要领。

假如你还记得上述我们运用CreateFile建立句柄(HANDLE)的示例,那如今我们能够运用DeviceIoControl来挪用DrvIOCTLDispatcher (IRP_MJ_DEVICE_CONTROL)以及来自用户形式的参数。

	char OutputBuffer[1000];
	char InputBuffer[1000];
	ULONG bytesReturned;
	BOOL Result;

	StringCbCopy(InputBuffer, sizeof(InputBuffer),
		"This String is from User Application; using METHOD_BUFFERED");

	printf("\nCalling DeviceIoControl METHOD_BUFFERED:\n");

	memset(OutputBuffer, 0, sizeof(OutputBuffer));

	Result = DeviceIoControl(handle,
		(DWORD)IOCTL_SIOCTL_METHOD_BUFFERED,
		&InputBuffer,
		(DWORD)strlen(InputBuffer) + 1,
		&OutputBuffer,
		sizeof(OutputBuffer),
		&bytesReturned,
		NULL
	);

	if (!Result)
	{
		printf("Error in DeviceIoControl : %d", GetLastError());
		return 1;

	}
	printf("    OutBuffer (%d): %s\n", bytesReturned, OutputBuffer);

你能够点此检察,IOCT调理的差别范例。

接下来,看看怎样运用Windows来构建VMM。

从头开始相识和运用Hypervisor(第2部份)

每一个处理器的设置和关联设置

与特别逻辑处理器的关联性是运用虚拟机管理顺序时应斟酌的重要内容之一,不幸的是,在Windows中,没有相似on_each_cpu的内容(就像在Linux内核模块中一样),因而我们必需手动变动关联设置,才能在每一个逻辑处理器上运转。在我的Intel Core i7 6820HQ中,我有4个物理内核,且每一个内核能够同时运转2个线程(由于存在超线程),因而我们有8个逻辑处理器,固然另有8套寄存器(包含通用寄存器和MSR寄存器),因而我们应当设置VMM,以便在8个逻辑处理器上事情。

要取得逻辑处理器的数目,能够运用KeQueryActiveProcessors(),然后我们应当向KeSetSystemAffinityThread通报一个KAFFINITY掩码,以设置当前线程的体系关联。

能够运用简朴的power函数来设置KAFFINITY掩码:

int ipow(int base, int exp) {
	int result = 1;
	for (;;)
	{
		if ( exp & 1)
		{
			result *= base;
		}
		exp >>= 1;
		if (!exp)
		{
			break;
		}
		base *= base;
	}
	return result;
}

那末我们应当运用以下代码来变动处理器的关联性,并在一切逻辑内核中离别运转我们的代码:

	KAFFINITY kAffinityMask;
	for (size_t i = 0; i < KeQueryActiveProcessors(); i++)
	{
		kAffinityMask = ipow(2, i);
		KeSetSystemAffinityThread(kAffinityMask);
		DbgPrint("=====================================================");
		DbgPrint("Current thread is executing in %d th logical processor.",i);
		// Put you function here !

	}

物理地点和虚拟地点之间的转换

VMXON地区和VMCS地区(请拜见下文)能够运用物理地点作为VMXON和VMPTRLD指令的操纵数,因而我们应当建立一个将虚拟地点转换为物理地点的函数:

UINT64 VirtualAddress_to_PhysicallAddress(void* va)
{
	return MmGetPhysicalAddress(va).QuadPart;
}

只需我们不能在庇护形式下直接运用物理地点举行修正,那末我们就必需将物理地点转换为虚拟地点。

UINT64 PhysicalAddress_to_VirtualAddress(UINT64 pa)
{
	PHYSICAL_ADDRESS PhysicalAddr;
	PhysicalAddr.QuadPart = pa;

	return MmGetVirtualForPhysical(PhysicalAddr);
}

经由历程内核查询虚拟机管理顺序

如今,我们已从用户形式查询虚拟机管理顺序的存在,但我们也应斟酌从内核形式搜检虚拟机管理顺序。这就减少了未来涌现内核毛病的大概性,或许有大概运用锁定位禁用了管理顺序。趁便说一下,以下代码会搜检IA32_FEATURE_CONTROL MSR(MSR地点3AH),以检察是不是设置了锁定位 。

BOOLEAN Is_VMX_Supported()
{
	CPUID data = { 0 };

	// VMX bit
	__cpuid((int*)&data, 1);
	if ((data.ecx & (1 << 5)) == 0)
		return FALSE;

	IA32_FEATURE_CONTROL_MSR Control = { 0 };
	Control.All = __readmsr(MSR_IA32_FEATURE_CONTROL);

	// BIOS lock check
	if (Control.Fields.Lock == 0)
	{
		Control.Fields.Lock = TRUE;
		Control.Fields.EnableVmxon = TRUE;
		__writemsr(MSR_IA32_FEATURE_CONTROL, Control.All);
	}
	else if (Control.Fields.EnableVmxon == FALSE)
	{
		DbgPrint("[*] VMX locked off in BIOS");
		return FALSE;
	}

	return TRUE;
}

以上函数中运用的构造声明以下:

typedef union _IA32_FEATURE_CONTROL_MSR
{
	ULONG64 All;
	struct
	{
		ULONG64 Lock : 1;                // [0]
		ULONG64 EnableSMX : 1;           // [1]
		ULONG64 EnableVmxon : 1;         // [2]
		ULONG64 Reserved2 : 5;           // [3-7]
		ULONG64 EnableLocalSENTER : 7;   // [8-14]
		ULONG64 EnableGlobalSENTER : 1;  // [15]
		ULONG64 Reserved3a : 16;         //
		ULONG64 Reserved3b : 32;         // [16-63]
	} Fields;
} IA32_FEATURE_CONTROL_MSR, *PIA32_FEATURE_CONTROL_MSR;

typedef struct _CPUID
{
	int eax;
	int ebx;
	int ecx;
	int edx;
} CPUID, *PCPUID;

VMXON地区

在实行VMXON之前,软件应分派一个天然对齐的4 KB内存地区,逻辑处理器能够运用该地区来支撑VMX操纵。该地区称为VMXON地区。 VMXON地区的地点(VMXON指针)在一个操纵数中供应给VMXON。

VMM能够为每一个逻辑处理器运用差别的VMXON地区,不然行动是“未定义的”。

注重:第一个支撑VMX操纵的处理器要求VMX操纵中的以下位为1:CR0.PE,CR0.NE,CR0.PG和CR4.VMXE。对CR0.PE和CR0.PG的限定意味着仅在分页庇护形式(包含IA-32e形式)中支撑VMX操纵。因而,客户软件不能在未分页庇护形式或现实地点形式下运转。

由于我们正在设置虚拟机管理顺序,因而我们应当有一个形貌虚拟机状况的全局变量,为此我建立了以下构造。现在,虽然我们只要两个字段(VMXON_REGION和VMCS_REGION),然则我们将在未来的构造中增加新的字段。

typedef struct _VirtualMachineState
{
	UINT64 VMXON_REGION;                        // VMXON region
	UINT64 VMCS_REGION;                         // VMCS region
} VirtualMachineState, *PVirtualMachineState;

固然,另有一个全局变量。

extern PVirtualMachineState vmState;

为此,我建立了以下函数(在memory.c中)来分派VMXON地区并运用分派地区的指针实行VMXON指令。

	BOOLEAN Allocate_VMXON_Region(IN PVirtualMachineState vmState)
{
	// at IRQL > DISPATCH_LEVEL memory allocation routines don't work
	if (KeGetCurrentIrql() > DISPATCH_LEVEL)
		KeRaiseIrqlToDpcLevel();


	PHYSICAL_ADDRESS PhysicalMax = { 0 };
	PhysicalMax.QuadPart = MAXULONG64;


	int VMXONSize = 2 * VMXON_SIZE;
	BYTE* Buffer = MmAllocateContiguousMemory(VMXONSize + ALIGNMENT_PAGE_SIZE, PhysicalMax);  // Allocating a 4-KByte Contigous Memory region

	PHYSICAL_ADDRESS Highest = { 0 }, Lowest = { 0 };
	Highest.QuadPart = ~0;

	//BYTE* Buffer = MmAllocateContiguousMemorySpecifyCache(VMXONSize + ALIGNMENT_PAGE_SIZE, Lowest, Highest, Lowest, MmNonCached);
	
	if (Buffer == NULL) {
		DbgPrint("[*] Error : Couldn't Allocate Buffer for VMXON Region.");
		return FALSE;// ntStatus = STATUS_INSUFFICIENT_RESOURCES;
	}
	UINT64 PhysicalBuffer = VirtualAddress_to_PhysicallAddress(Buffer);

	// zero-out memory 
	RtlSecureZeroMemory(Buffer, VMXONSize + ALIGNMENT_PAGE_SIZE);
	UINT64 alignedPhysicalBuffer = (BYTE*)((ULONG_PTR)(PhysicalBuffer + ALIGNMENT_PAGE_SIZE - 1) &~(ALIGNMENT_PAGE_SIZE - 1));

	UINT64 alignedVirtualBuffer = (BYTE*)((ULONG_PTR)(Buffer + ALIGNMENT_PAGE_SIZE - 1) &~(ALIGNMENT_PAGE_SIZE - 1));

	DbgPrint("[*] Virtual allocated buffer for VMXON at %llx", Buffer);
	DbgPrint("[*] Virtual aligned allocated buffer for VMXON at %llx", alignedVirtualBuffer);
	DbgPrint("[*] Aligned physical buffer allocated for VMXON at %llx", alignedPhysicalBuffer);

	// get IA32_VMX_BASIC_MSR RevisionId

	IA32_VMX_BASIC_MSR basic = { 0 };


	basic.All = __readmsr(MSR_IA32_VMX_BASIC);

	DbgPrint("[*] MSR_IA32_VMX_BASIC (MSR 0x480) Revision Identifier %llx", basic.Fields.RevisionIdentifier);


	//* (UINT64 *)alignedVirtualBuffer  = 04;

	//Changing Revision Identifier
	*(UINT64 *)alignedVirtualBuffer = basic.Fields.RevisionIdentifier;


	int status = __vmx_on(&alignedPhysicalBuffer);
	if (status)
	{
		DbgPrint("[*] VMXON failed with status %d\n", status);
		return FALSE;
	}

	vmState->VMXON_REGION = alignedPhysicalBuffer;

	return TRUE;
}

让我们解释一下上面的函数:

	// at IRQL > DISPATCH_LEVEL memory allocation routines don't work
	if (KeGetCurrentIrql() > DISPATCH_LEVEL)
		KeRaiseIrqlToDpcLevel();

该代码用于将当前的IRQL级别变动成DISPATCH_LEVEL,然则只需我们运用MmAllocateContiguousMemory,我们就能够疏忽此代码。然则假如你想为VMXON地区运用另一种范例的内存,则应当运用MmAllocateContiguousMemorySpecifyCache(解释),你能够运用其他范例的内存,详细的请点击这里。

请注重,为确保VMX操纵中的准确行动,应在回写机制可缓存内存中保护VMCS地区和相干构造。或许,你能够运用UC内存范例映照这些地区或构造中的任何一个。强烈建议不要如许做,由于如许做会致使运用这些构造的转换机能显著受损。

回写是一种存储要领,个中,每次发作变动时,数据都邑写入高速缓存,然则仅在指定的时候距离或特定条件下,数据才会写入主存储器中的响应位置。可缓存或不可缓存能够从分页构造(PTE)中的缓存禁用位中肯定。

趁便说一句,我们应当分派8192字节,由于不能保证Windows会分派对齐的内存,因而我们能够找到以8196字节对齐的4096字节,(对齐是指物理地点能够被4096整除)。

以我的履历,MmAllocateContiguousMemory分派老是对齐的,这大概是由于PFN中的每一个页面都是按4096字节分派的,只需我们须要4096字节,它就会对齐。

假如你对页面框架编号(PFN)感兴趣,则能够浏览Windows内部页面框架编号(PFN)–第1部份和Windows内部页面框架编号(PFN)–第2部份。

	PHYSICAL_ADDRESS PhysicalMax = { 0 };
	PhysicalMax.QuadPart = MAXULONG64;

	int VMXONSize = 2 * VMXON_SIZE;
	BYTE* Buffer = MmAllocateContiguousMemory(VMXONSize, PhysicalMax);  // Allocating a 4-KByte Contigous Memory region
	if (Buffer == NULL) {
		DbgPrint("[*] Error : Couldn't Allocate Buffer for VMXON Region.");
		return FALSE;// ntStatus = STATUS_INSUFFICIENT_RESOURCES;
	}

如今,我们应当将分派的内存的地点转换为其物理地点,并确保其对齐。

MmAllocateContiguousMemory分派的内存未初始化。内核形式驱动顺序必需起首将此内存设置为零。以以下示例为例,我们应当运用RtlSecureZeroMemory。

	UINT64 PhysicalBuffer = VirtualAddress_to_PhysicallAddress(Buffer);

	// zero-out memory 
	RtlSecureZeroMemory(Buffer, VMXONSize + ALIGNMENT_PAGE_SIZE);
	UINT64 alignedPhysicalBuffer = (BYTE*)((ULONG_PTR)(PhysicalBuffer + ALIGNMENT_PAGE_SIZE - 1) &~(ALIGNMENT_PAGE_SIZE - 1));
	UINT64 alignedVirtualBuffer = (BYTE*)((ULONG_PTR)(Buffer + ALIGNMENT_PAGE_SIZE - 1) &~(ALIGNMENT_PAGE_SIZE - 1));

	DbgPrint("[*] Virtual allocated buffer for VMXON at %llx", Buffer);
	DbgPrint("[*] Virtual aligned allocated buffer for VMXON at %llx", alignedVirtualBuffer);
	DbgPrint("[*] Aligned physical buffer allocated for VMXON at %llx", alignedPhysicalBuffer);

在实行VMXON之前,软件应将VMCS订正标识符写入VMXON地区。详细地说,它应将31位VMCS订正标识符写入VMXON地区的前4个字节的 位30:0;位31被消灭。

它不须要以任何其他体式格局初始化VMXON地区,软件应为每一个逻辑处理器运用一个零丁的地区,而且不应在该逻辑处理器上实行VMXON和VMXOFF之间接见或修正逻辑处理器的VMXON地区,不然大概致使没法展望的行动。

因而,让我们从IA32_VMX_BASIC_MSR取得订正标识符,并将其写入我们的VMXON地区。

		// get IA32_VMX_BASIC_MSR RevisionId

	IA32_VMX_BASIC_MSR basic = { 0 };


	basic.All = __readmsr(MSR_IA32_VMX_BASIC);

	DbgPrint("[*] MSR_IA32_VMX_BASIC (MSR 0x480) Revision Identifier %llx", basic.Fields.RevisionIdentifier);

	//Changing Revision Identifier
	*(UINT64 *)alignedVirtualBuffer = basic.Fields.RevisionIdentifier;

末了一部份用于实行VMXON指令。

	int status = __vmx_on(&alignedPhysicalBuffer);
	if (status)
	{
		DbgPrint("[*] VMXON failed with status %d\n", status);
		return FALSE;
	}

	vmState->VMXON_REGION = alignedPhysicalBuffer;

	return TRUE;

__vmx_on是实行VMXON的固有函数:

0示意操纵胜利;

1示意操纵失利,而且扩大状况在当前VMCS的VM指令毛病字段中可用;

2示意操纵失利,没有可用状况;

假如我们运用VMXON设置VMXON地区,但失利,则状况=1。假如没有任何VMCS,则状况= 2;假如操纵胜利,则状况= 0。

假如你在不实行VMXOFF的状况下两次实行了以上代码,则肯定会失足。

如今,我们的VMXON地区已准备就绪。

本文翻译自:https://rayanfam.com/topics/hypervisor-from-scratch-part-2/ 与 https://rayanfam.com/topics/hypervisor-from-scratch-part-3/


申博|网络安全巴士站声明:该文看法仅代表作者自己,与本平台无关。版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明从头开始相识和运用Hypervisor(第2部份)
喜欢 (0)
[]
分享 (0)
发表我的评论
取消评论
表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址