Hexo
VT 技术学习
发布于: 2023-02-05 更新于: 2023-05-16 分类于: 

VT技术入门。

0x00 VT 技术介绍

0x01 VT 环境搭建

在最新的Windows环境上,可能需要关闭Hyper-V,才能在VMware的虚拟机配置-处理器-虚拟化引擎中选中Intel VT-x/EPT

如果开启了Virtualization Based Security(基于虚拟化的安全性),请先关闭它

1
reg ADD HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\DeviceGuard /v EnableVirtualizationBasedSecurity /t REG_DWORD /d 0 /f

再关闭Hyper-V

1
bcdedit /set hypervisorlaunchtype off

如果只关闭Hyper-V,不关闭VBS,Hyper-V是关不掉的。
如果还开启了UEFI 锁等复杂情况,就要参考微软文档了。

0x02 总体流程

0x03 前置工作

CPUID

eax = 1执行CPUIDecx[5] = 1则支持VT-x

1
2
3
4
5
6
7
int ret[4];

__cpuid(ret, 1);
// 检查是否支持VT
if (!(ret[2] & 0x20)) { // cpuid(1).ecx[5] == 1
return FALSE;
}

msr

检查VT-x是否在BIOS中被开启
msr[0x3a]结果中的最低为为lock位,需要置1

1
2
3
4
uint64_t msr = __readmsr(0x3a);
if (!(msr & 1)) { // msr[0x3a].lock == 1
return FALSE;
}

CR4

CR4中的第13位VMXE置1打开启用VMXE指令扩展

1
2
3
4
5
uintptr_t cr4 = __readcr4();
// 检查VT-x 是否已经开启
if (cr4 & 0x2000) { //cr4.VMXE == 0
return FALSE;
}

0x04 VMXON

启用VMX扩展

1
2
3
4
5
6
void inline EnableVMXE()
{
uint32_t cr4 = __readcr4();
cr4 &= ~0x2000;
__writecr4(cr4);
}

分配VMXON 区域

直接分配4k即可

1
2
3
4
PHYSICAL_ADDRESS HighAddress = { 0 };
g_cpu.pVMXONRegion = ExAllocatePoolWithTag(NonPagedPool, 0x1000, 'vmon');
g_cpu.pVMXONRegion_PA = MmGetPhysicalAddress(g_cpu.pVMXONRegion);
RtlZeroMemory(g_cpu.pVMXONRegion, 0x1000);

设置VMXON区域

设置VMXON区域的四字节为msr[0x480]的低32位

1
2
3
uintptr_t msr = __readmsr(0x480);
*(ULONG*)(g_cpu.pVMXONRegion) = msr & 0xffffffff;

执行VMXON指令,出错则CF位置1

1
2
3
4
5
push _high
push _low
vmxon dword ptr [esp]
setc eax
add esp,8

0x05 VMCS

VMCS是Virtual Machine Control Structure,描述VM的一些属性。

首先需要初始化VMCS,再选中此VMCS。

1
2
3
4
// 初始化虚拟机
__vmx_vmclear(*(uint64_t*)(&g_cpu.pVMCSRegion_PA));
// 选中此虚拟机
__vmx_vmptrld(*(uint64_t*)(&g_cpu.pVMCSRegion_PA));

Intel 弄了一个标准的汇编指令来读写VMCS,vmwrite/vmread,不推荐直接修改内存。

VMCS域 包括以下三方面的内容:

1.宿主机执行域

2.虚拟机执行域

3.虚拟机执行控制域

3.1 VM 执行控制(#VMExit 事件设置)44

3.2 VM 退出控制(#VMExit 保存内容设置)

3.3 VM 进入控制(进入#VMEntry 加载设置)

  • 宿主机执行域

    获取段描述符基址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    PVOID GetSegmentDescriptor(uint16_t index)
    {
    KGDTENTRY* GdtEntry = Asm_GetGdtBase();
    KGDTENTRY TargetEntry = GdtEntry[index >> 3];
    uintptr_t ret = TargetEntry.HighWord.Bytes.BaseHi;
    ret <<= 8;
    ret |= TargetEntry.HighWord.Bytes.BaseMid;
    ret <<= 16;
    ret |= TargetEntry.BaseLow;
    return (PVOID)ret;
    }

    HOST_RSP的值,需要提前申请

    & 0xfff8必须清除RPL,和TI

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    __vmx_vmwrite(HOST_CR0, __readcr0());
    __vmx_vmwrite(HOST_CR3, __readcr3());
    __vmx_vmwrite(HOST_CR4, __readcr4());

    __vmx_vmwrite(HOST_ES_SELECTOR, GetES() & 0xFFF8);
    __vmx_vmwrite(HOST_CS_SELECTOR, GetCS() & 0xFFF8);
    __vmx_vmwrite(HOST_DS_SELECTOR, GetDS() & 0xFFF8);
    __vmx_vmwrite(HOST_FS_SELECTOR, GetFS() & 0xFFF8);
    __vmx_vmwrite(HOST_GS_SELECTOR, GetGS() & 0xFFF8);
    __vmx_vmwrite(HOST_SS_SELECTOR, GetSS() & 0xFFF8);
    __vmx_vmwrite(HOST_TR_SELECTOR, GetTR() & 0xFFF8);

    // KGDTENTRY* GdtEntry = Asm_GetGdtBase();


    __vmx_vmwrite(HOST_TR_BASE, 0x80042000);

    __vmx_vmwrite(HOST_GDTR_BASE, Asm_GetGdtBase());
    __vmx_vmwrite(HOST_IDTR_BASE, Asm_GetIdtBase());

    __vmx_vmwrite(HOST_IA32_SYSENTER_CS, __readmsr(MSR_IA32_SYSENTER_CS) & 0xFFFFFFFF);
    __vmx_vmwrite(HOST_IA32_SYSENTER_ESP, __readmsr(MSR_IA32_SYSENTER_ESP) & 0xFFFFFFFF);
    __vmx_vmwrite(HOST_IA32_SYSENTER_EIP, __readmsr(MSR_IA32_SYSENTER_EIP) & 0xFFFFFFFF); // KiFastCallEntry

    __vmx_vmwrite(HOST_RSP, ((ULONG)g_VMXCPU.pStack) + 0x2000); //Host 临时栈
    __vmx_vmwrite(HOST_RIP, (ULONG)VMMEntryPoint); //这里定义我们的VMM处理程序入口

    很好,Intel你要提供HOST_TR_BASEHOST_FS_BASEHOST_GS_BASE

    如果要它的话需要在GDT表找,然后拼接基地址。

    这里偷了一份KGDTEntry的结构

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    typedef struct _KGDTENTRY
    {
    USHORT LimitLow; //0x0
    USHORT BaseLow; //0x2
    union
    {
    struct
    {
    UCHAR BaseMid; //0x4
    UCHAR Flags1; //0x5
    UCHAR Flags2; //0x6
    UCHAR BaseHi; //0x7
    } Bytes; //0x4
    struct
    {
    ULONG BaseMid : 8; //0x4
    ULONG Type : 5; //0x4
    ULONG Dpl : 2; //0x4
    ULONG Pres : 1; //0x4
    ULONG LimitHi : 4; //0x4
    ULONG Sys : 1; //0x4
    ULONG Reserved_0 : 1; //0x4
    ULONG Default_Big : 1; //0x4
    ULONG Granularity : 1; //0x4
    ULONG BaseHi : 8; //0x4
    } Bits; //0x4
    } HighWord; //0x4
    } KGDTENTRY;
    #else


    //0x10 bytes (sizeof)
    typedef union _KGDTENTRY
    {
    struct
    {
    USHORT LimitLow; //0x0
    USHORT BaseLow; //0x2
    };
    struct
    {
    UCHAR BaseMiddle; //0x4
    UCHAR Flags1; //0x5
    UCHAR Flags2; //0x6
    UCHAR BaseHigh; //0x7
    } Bytes; //0x4
    struct
    {
    struct
    {
    ULONG BaseMiddle : 8; //0x4
    ULONG Type : 5; //0x4
    ULONG Dpl : 2; //0x4
    ULONG Present : 1; //0x4
    ULONG LimitHigh : 4; //0x4
    ULONG System : 1; //0x4
    ULONG LongMode : 1; //0x4
    ULONG DefaultBig : 1; //0x4
    ULONG Granularity : 1; //0x4
    ULONG BaseHigh : 8; //0x4
    } Bits; //0x4
    ULONG BaseUpper; //0x8
    };
    struct
    {
    ULONG MustBeZero; //0xc
    LONGLONG DataLow; //0x0
    };
    LONGLONG DataHigh; //0x8
    }KGDTENTRY;
  • 虚拟机执行控制域

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
`GUEST_ES_AR_BYTES`为ES段的属性,设置为`0x10000`不可用。

```c
__vmx_vmwrite(GUEST_CR0, __readcr0());
__vmx_vmwrite(GUEST_CR3, __readcr3());
__vmx_vmwrite(GUEST_CR4, __readcr4());

__vmx_vmwrite(GUEST_DR7, 0x400);
__vmx_vmwrite(GUEST_RFLAGS, __readeflags() & ~0x200); // IF = 0

__vmx_vmwrite(GUEST_ES_SELECTOR, GetES() & 0xFFF8);
__vmx_vmwrite(GUEST_CS_SELECTOR, GetCS() & 0xFFF8);
__vmx_vmwrite(GUEST_DS_SELECTOR, GetDS() & 0xFFF8);
__vmx_vmwrite(GUEST_FS_SELECTOR, GetFS() & 0xFFF8);
__vmx_vmwrite(GUEST_GS_SELECTOR, GetGS() & 0xFFF8);
__vmx_vmwrite(GUEST_SS_SELECTOR, GetSS() & 0xFFF8);
__vmx_vmwrite(GUEST_TR_SELECTOR, GetTR() & 0xFFF8);

// 设置 Segment Unusable
__vmx_vmwrite(GUEST_ES_AR_BYTES, 0x10000);
__vmx_vmwrite(GUEST_FS_AR_BYTES, 0x10000);
__vmx_vmwrite(GUEST_DS_AR_BYTES, 0x10000);
__vmx_vmwrite(GUEST_SS_AR_BYTES, 0x10000);
__vmx_vmwrite(GUEST_GS_AR_BYTES, 0x10000);
__vmx_vmwrite(GUEST_LDTR_AR_BYTES, 0x10000);

// 需要构造描述符线长、属性等,我要偷大懒
// 在guest里面刷一下选择子
__vmx_vmwrite(GUEST_CS_AR_BYTES, 0xc09b);
__vmx_vmwrite(GUEST_CS_BASE, 0);
__vmx_vmwrite(GUEST_CS_LIMIT, 0xffffffff);

__vmx_vmwrite(GUEST_TR_AR_BYTES, 0x008b);
__vmx_vmwrite(GUEST_TR_BASE, 0x80042000);
__vmx_vmwrite(GUEST_TR_LIMIT, 0x20ab);


__vmx_vmwrite(GUEST_GDTR_BASE, Asm_GetGdtBase());
__vmx_vmwrite(GUEST_GDTR_LIMIT, Asm_GetGdtLimit());
__vmx_vmwrite(GUEST_IDTR_BASE, Asm_GetIdtBase());
__vmx_vmwrite(GUEST_IDTR_LIMIT, Asm_GetIdtLimit());

__vmx_vmwrite(GUEST_IA32_DEBUGCTL, __readmsr(MSR_IA32_DEBUGCTL) & 0xFFFFFFFF);
__vmx_vmwrite(GUEST_IA32_DEBUGCTL_HIGH, __readmsr(MSR_IA32_DEBUGCTL) >> 32);

__vmx_vmwrite(GUEST_SYSENTER_CS, __readmsr(MSR_IA32_SYSENTER_CS) & 0xFFFFFFFF);
__vmx_vmwrite(GUEST_SYSENTER_ESP, __readmsr(MSR_IA32_SYSENTER_ESP) & 0xFFFFFFFF);
__vmx_vmwrite(GUEST_SYSENTER_EIP, __readmsr(MSR_IA32_SYSENTER_EIP) & 0xFFFFFFFF); // KiFastCallEntry

__vmx_vmwrite(GUEST_RSP, ((ULONG)g_cpu.pStack) + 0x1000); //Guest 临时栈
__vmx_vmwrite(GUEST_RIP, (ULONG)GuestEntry); // 客户机的入口点

// 物理地址意义上的nullptr
__vmx_vmwrite(VMCS_LINK_POINTER, 0xffffffff);
__vmx_vmwrite(VMCS_LINK_POINTER_HIGH, 0xffffffff);

GuestEntry是GUEST VM的入口点

__vmx_vmread(VM_INSTRUCTION_ERROR)@24.9.1

  • VM 控制域

    通过设置Flags标志位来控制VM的一些行为,Intel为了防止你写玩具VMM的时候把这段全填0,贴心的设置了一些预留位需要置1的位,真好。

    通过查询MSR寄存器来了解哪些预留位为1,哪些为0

    比如

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    kd> rdmsr 481
    msr[481] = 0000003f`00000016
    kd> .formats 0000003f`00000016
    Evaluate expression:
    Hex: 0000003f`00000016
    Decimal: 270582939670
    Octal: 0000000003740000000026
    Binary: 00000000 00000000 00000000 00111111 00000000 00000000 00000000 00010110
    Chars: ...?....
    Time: Mon Jan 1 15:30:58.293 1601 (UTC + 8:00)
    Float: low 3.08286e-044 high 8.82818e-044
    Double: 1.33686e-312

    MSR值中前32位中二进制为0的位,设置对应域时该位必须为0

    MSR值中后32位中二进制为1的位,设置对应域时该位必须为1

    1
    2
    3
    4
    5
    6
    7
    ULONG VTAdjustExcuteControls(ULONG Value,ULONG msr)
    {
    uint64_t mask = __readmsr(msr);
    Value &= (mask >> 32);
    Value |= (mask & 0xffffffff);
    return Value;
    }
    • VM 执行控制

      Pin-Based VM-Execution Controls 基于CPU针脚的VM执行控制 (Intel 3a @24.6.1)

      定义发生外部中断、NMI等的是否退出虚拟机。

      1
      __vmx_vmwrite(PIN_BASED_VM_EXEC_CONTROL, VTAdjustExcuteControls(0, MSR_IA32_VMX_PINBASED_CTLS));

      Processor-Based VM-Execution Controls 基于处理器的VM执行控制(Intel 3a @24.6.2)

      定义执行一些CPU指令(HLT/INVLPG/RDTSC/CR3切换/内部中断)是否需要退出虚拟机

      1
      __vmx_vmwrite(CPU_BASED_VM_EXEC_CONTROL, VTAdjustExcuteControls(0, MSR_IA32_VMX_PROCBASED_CTLS));
    • VM 退出控制

      Intel 3a@24.7.1

      1
      __vmx_vmwrite(VM_EXIT_CONTROLS, VTAdjustExcuteControls(0,MSR_IA32_VMX_EXIT_CTLS));
    • VM 进入控制

      Intel 3a@24.7.1

      1
      __vmx_vmwrite(VM_ENTRY_CONTROLS, VTAdjustExcuteControls(0, MSR_IA32_VMX_ENTRY_CTLS));

0x06 VM 启动

启动后,CPU会跳到GUEST_RIP,ESP设置为GUEST_RSP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
__asm{
pushad
pushfd
mov guest_sp,esp
mov guest_ip, offset Ret

}

// VMLANUCH 虚拟机启动,跳转到 GuestEntry 就不回来了
__vmx_vmlaunch();
// 这条指令下的语句不会执行
DbgPrint("VT Failed With Error Code: 0x%x",__vmx_vmread(VM_INSTRUCTION_ERROR));
// 这里的Ret是用来接收跳转,正常返回
Ret:
__asm{
popfd
popad
}

GuestEntry的定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
__declspec(naked) void GuestEntry(void)
{
__asm {

mov ax, es
mov es, ax

mov ax, ds
mov ds, ax

mov ax, fs
mov fs, ax

mov ax, gs
mov gs, ax

mov ax, ss
mov ss, ax
}

__asm {

mov esp, guest_sp
jmp guest_ip
}
}

0x06 #VMExit

当虚拟机执行到设置的#VMExit的指令后,会退出虚拟机,执行HOST_RIP函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
__declspec(naked) void VMExitHandler(void)
{
__asm {
mov g_GuestRegs.eax, eax
mov g_GuestRegs.ecx, ecx
mov g_GuestRegs.edx, edx
mov g_GuestRegs.ebx, ebx
mov g_GuestRegs.esp, esp
mov g_GuestRegs.ebp, ebp
mov g_GuestRegs.esi, esi
mov g_GuestRegs.edi, edi

pushfd
pop eax
mov g_GuestRegs.eflags, eax

mov ax, fs
mov fs, ax
mov ax, gs
mov gs, ax
}
VMExitHandlerDispatcher();
__asm {
mov eax, g_GuestRegs.eax
mov ecx, g_GuestRegs.ecx
mov edx, g_GuestRegs.edx
mov ebx, g_GuestRegs.ebx
mov esp, g_GuestRegs.esp
mov ebp, g_GuestRegs.ebp
mov esi, g_GuestRegs.esi
mov edi, g_GuestRegs.edi

//vmresume
__emit 0x0f
__emit 0x01
__emit 0xc3
}
}

VMExitHandlerDispatcher函数用于处理#VMExit事件:

ExitReason指定了退出原因。

ExitInstructionLength指定了导致退出指令的长度。

g_GuestRegs.eip = __vmx_vmread(GUEST_RIP);读取了导致退出指令的地址。

处理完指令的退出后,设置GUEST_RIP为下一跳指令。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
static void  VMExitHandlerDispatcher(void)
{
ULONG ExitReason;
ULONG ExitInstructionLength;
ULONG GuestResumeEIP;

ExitReason = __vmx_vmread(VM_EXIT_REASON);
ExitInstructionLength = __vmx_vmread(VM_EXIT_INSTRUCTION_LEN);

g_GuestRegs.eflags = __vmx_vmread(GUEST_RFLAGS);
g_GuestRegs.esp = __vmx_vmread(GUEST_RSP);
g_GuestRegs.eip = __vmx_vmread(GUEST_RIP);

switch (ExitReason)
{
case EXIT_REASON_CPUID:
HandleCPUID();
Log("EXIT_REASON_CPUID", 0)
break;

case EXIT_REASON_VMCALL:
HandleVmCall();
Log("EXIT_REASON_VMCALL", 0)
break;

case EXIT_REASON_CR_ACCESS:
HandleCrAccess();
//Log("EXIT_REASON_CR_ACCESS", 0)
break;
case 19:
case 20:
case 21:
case 22:
case 23:
case 24:
case 25:
case 26:
case 27:
break;
default:
Log("not handled reason: %p", ExitReason);
__asm int 3
}

//Resume:
GuestResumeEIP = g_GuestRegs.eip + ExitInstructionLength;
__vmx_vmwrite(GUEST_RIP, GuestResumeEIP);
__vmx_vmwrite(GUEST_RSP, g_GuestRegs.esp);
__vmx_vmwrite(GUEST_RFLAGS, g_GuestRegs.eflags);
}

0x07 EPT

看了一下要重建页表我就不想看了。

0x08 代码

这个代码魔改的周壑的VT_Learn,建议不要乱魔改,坑非常多。

--- 我是有底线的 ---