crewCTF 2025 -- Hypervison

Hyper-V is a well known, well documented virtualisation framework with excellent tooling. Wait, no that's not it.

Windows Hypervisor Platform

首先创建虚拟机和处理器,映射内存。都是一些 API 调用。

image

接下来是初始化寄存器。
从 WHvSetVirtualProcessorRegisters 看出,一共有 20 个寄存器。动态调试看看有那些。

image

其中前 10 个寄存器是固定的,后 10 个寄存器用输入的 flag 值来填充。

image

验证一下,导出 RegisterValues

image

image

从图中可以看出,flag 前 64 个字填充到 r8 - r15,正好是 8 个 64 位寄存器(8 * 64 = 512)。后面的内容忽略,最后两个寄存器 rbx 和 rdx 是 0。

前 10 个寄存器大部分是控制寄存器,写个程序。

点击查看代码
#include <stdio.h>
#include <stdint.h>

#define FIELD(r, x, len) (((r)>>(x)) & ((1ull<<(len))-1))
#define BIT(r, x) FIELD(r, x, 1)

#define CR0_PE(r) BIT(r, 0)
#define CR0_MP(r) BIT(r, 1)
#define CR0_EM(r) BIT(r, 2)
#define CR0_TS(r) BIT(r, 3)
#define CR0_ET(r) BIT(r, 4)
#define CR0_NE(r) BIT(r, 5)
#define CR0_WP(r) BIT(r, 16)
#define CR0_AM(r) BIT(r, 18)
#define CR0_NW(r) BIT(r, 29)
#define CR0_CD(r) BIT(r, 30)
#define CR0_PG(r) BIT(r, 31)

#define CR3_PWT(r) BIT(r, 3)
#define CR3_PCD(r) BIT(r, 4)
#define CR3_PML4_BASE_ADDRESS(r) FIELD(r, 12, 40)

#define CR4_VME(r) BIT(r, 0)
#define CR4_PVI(r) BIT(r, 1)
#define CR4_TSD(r) BIT(r, 2)
#define CR4_DE(r) BIT(r, 3)
#define CR4_PSE(r) BIT(r, 4)
#define CR4_PAE(r) BIT(r, 5)
#define CR4_MCE(r) BIT(r, 6)
#define CR4_PGE(r) BIT(r, 7)
#define CR4_PCE(r) BIT(r, 8)
#define CR4_OSFXSR(r) BIT(r, 9)
#define CR4_OSXMMEXCEPT(r) BIT(r, 10)
#define CR4_OSXSAVE(r) BIT(r, 18)

#define EFER_SCE(r) BIT(r, 0)
#define EFER_LME(r) BIT(r, 8)
#define EFER_LMA(r) BIT(r, 10)
#define EFER_NXE(r) BIT(r, 11)

#pragma pack(push, 8)
typedef struct _WHV_X64_SEGMENT_REGISTER
{
    uint64_t Base;
    uint32_t Limit;
    uint16_t Selector;
    #pragma pack(push, 8)
    union
    {
        #pragma pack(push, 8)
        struct
        {
            uint16_t SegmentType : 4;
            uint16_t NonSystemSegment : 1;
            uint16_t DescriptorPrivilegeLevel : 2;
            uint16_t Present : 1;
            uint16_t Reserved : 4;
            uint16_t Available : 1;
            uint16_t Long : 1;
            uint16_t Default : 1;
            uint16_t Granularity : 1;
        };
        #pragma pack(pop)
        uint16_t Attributes;
    };
    #pragma pack(pop)
}WHV_X64_SEGMENT_REGISTER;
#pragma pack(pop)


int main()
{
    FILE* fp = fopen("reg_value.bin","rb");

    uint8_t buffer[16];
    fread(buffer, 1, 16, fp);
    uint64_t cr0 = *(uint64_t*)buffer;

    printf("CR0 value: 0x%016llx\n", cr0);
    printf("bit  0  :  %d  -  PE (Protection Enable)\n", CR0_PE(cr0));
    printf("bit  1  :  %d  -  MP (Monitor Coprocessor)\n", CR0_MP(cr0));
    printf("bit  2  :  %d  -  EM (Emulation)\n", CR0_EM(cr0));
    printf("bit  3  :  %d  -  TS (Task Switched)\n", CR0_TS(cr0));
    printf("bit  4  :  %d  -  ET (Extension Type)\n", CR0_ET(cr0));
    printf("bit  5  :  %d  -  NE (Numeric Error)\n", CR0_NE(cr0));
    printf("bit 16  :  %d  -  WP (Write Protect)\n", CR0_WP(cr0));
    printf("bit 18  :  %d  -  AM (Alignment Mask)\n", CR0_AM(cr0));
    printf("bit 29  :  %d  -  NW (Not Write-through)\n", CR0_NW(cr0));
    printf("bit 30  :  %d  -  CD (Cache Disable)\n", CR0_CD(cr0));
    printf("bit 31  :  %d  -  PG (Paging)\n", CR0_PG(cr0));
    printf("\n");

    fread(buffer, 1, 16, fp);
    uint64_t cr3 = *(uint64_t*)buffer;

    printf("CR3 value: 0x%016llx\n", cr3);
    printf("bit  3  :  %d  -  PWT (Page Write Through)\n", CR3_PWT(cr3));
    printf("bit  4  :  %d  -  PCD (Page Cache Disable)\n", CR3_PCD(cr3));
    printf("PML4 base Address  :  %x000\n", CR3_PML4_BASE_ADDRESS(cr3));
    printf("\n");

    fread(buffer, 1, 16, fp);
    uint64_t cr4 = *(uint64_t*)buffer;

    printf("CR4 value: 0x%016llx\n", cr4);
    printf("bit  0  :  %d  -  VME (Virtual-8086 Mode Extensions)\n", CR4_VME(cr4));
    printf("bit  1  :  %d  -  PVI (Protected-Mode Virtual Interrupts)\n", CR4_PVI(cr4));
    printf("bit  2  :  %d  -  TSD (Time Stamp Disable)\n", CR4_TSD(cr4));
    printf("bit  3  :  %d  -  DE (Debugging Extensions)\n", CR4_DE(cr4));
    printf("bit  4  :  %d  -  PSE (Page Size Extensions)\n", CR4_PSE(cr4));
    printf("bit  5  :  %d  -  PAE (Physical Address Extension)\n", CR4_PAE(cr4));
    printf("bit  6  :  %d  -  MCE (Machine Check Enable)\n", CR4_MCE(cr4));
    printf("bit  7  :  %d  -  PGE (Page-Global Enable)\n", CR4_PGE(cr4));
    printf("bit  8  :  %d  -  PCE (Performance-Monitoring Counter Enable)\n", CR4_PCE(cr4));
    printf("bit  9  :  %d  -  OSFXSR (OS FXSAVE/FXRESTOR Support)\n", CR4_OSFXSR(cr4));
    printf("bit 10  :  %d  -  OSXMMEXCEPT (OS Unmasked Exception Support)\n", CR4_OSXMMEXCEPT(cr4));
    printf("bit 18  :  %d  -  OSXSAVE (XSAVE and Processor Extended States Enable Bit)\n", CR4_OSXSAVE(cr4));
    printf("\n");

    fread(buffer, 1, 16, fp);
    uint64_t efer = *(uint64_t*)buffer;

    printf("EFER value: 0x%016llx\n", efer);
    printf("bit  0  :  %d  -  SCE (SYSCALL Enable)\n", EFER_SCE(efer));
    printf("bit  8  :  %d  -  LME (IA-32e Mode Enable)\n", EFER_LME(efer));
    printf("bit 10  :  %d  -  LMA (IA-32e Mode Active)\n", EFER_LMA(efer));
    printf("bit 11  :  %d  -  NXE (Execution Disable Bit Enable)\n", EFER_NXE(efer));
    printf("\n");

    WHV_X64_SEGMENT_REGISTER seg;
    fread(&seg, 16, 1, fp);
    printf("CS\n");
    printf("base = %016llx, limit = %08x, selector = %04x\n", seg.Base, seg.Limit, seg.Selector);
    printf("SegmentType              = %d\n", seg.SegmentType);
    printf("NonSystemSegment         = %d\n", seg.NonSystemSegment);
    printf("DescriptorPrivilegeLevel = %d\n", seg.DescriptorPrivilegeLevel);
    printf("Present                  = %d\n", seg.Present);
    printf("Available                = %d\n", seg.Available);
    printf("Long                     = %d\n", seg.Long);
    printf("Default                  = %d\n", seg.Default);
    printf("Granularity              = %d\n", seg.Granularity);
    printf("\n");

    fread(&seg, 16, 1, fp);
    printf("SS\n");
    printf("base = %016llx, limit = %08x, selector = %04x\n", seg.Base, seg.Limit, seg.Selector);
    printf("SegmentType              = %d\n", seg.SegmentType);
    printf("NonSystemSegment         = %d\n", seg.NonSystemSegment);
    printf("DescriptorPrivilegeLevel = %d\n", seg.DescriptorPrivilegeLevel);
    printf("Present                  = %d\n", seg.Present);
    printf("Available                = %d\n", seg.Available);
    printf("Long                     = %d\n", seg.Long);
    printf("Default                  = %d\n", seg.Default);
    printf("Granularity              = %d\n", seg.Granularity);
    printf("\n");

    fread(&seg, 16, 1, fp);
    printf("DS\n");
    printf("base = %016llx, limit = %08x, selector = %04x\n", seg.Base, seg.Limit, seg.Selector);
    printf("SegmentType              = %d\n", seg.SegmentType);
    printf("NonSystemSegment         = %d\n", seg.NonSystemSegment);
    printf("DescriptorPrivilegeLevel = %d\n", seg.DescriptorPrivilegeLevel);
    printf("Present                  = %d\n", seg.Present);
    printf("Available                = %d\n", seg.Available);
    printf("Long                     = %d\n", seg.Long);
    printf("Default                  = %d\n", seg.Default);
    printf("Granularity              = %d\n", seg.Granularity);
    printf("\n");

    fread(&seg, 16, 1, fp);
    printf("ES\n");
    printf("base = %016llx, limit = %08x, selector = %04x\n", seg.Base, seg.Limit, seg.Selector);
    printf("SegmentType              = %d\n", seg.SegmentType);
    printf("NonSystemSegment         = %d\n", seg.NonSystemSegment);
    printf("DescriptorPrivilegeLevel = %d\n", seg.DescriptorPrivilegeLevel);
    printf("Present                  = %d\n", seg.Present);
    printf("Available                = %d\n", seg.Available);
    printf("Long                     = %d\n", seg.Long);
    printf("Default                  = %d\n", seg.Default);
    printf("Granularity              = %d\n", seg.Granularity);
    printf("\n");

    fread(buffer, 1, 16, fp);
    uint64_t rip = *(uint64_t*)buffer;
    printf("RIP = 0x%016llx\n", rip);
    printf("\n");

    fread(buffer, 1, 16, fp);
    uint64_t rsp = *(uint64_t*)buffer;
    printf("RSP = 0x%016llx\n", rsp);
    printf("\n");

    fclose(fp);
    return 0;
}

输出结果太长了,比较重要的大概是这些:

CR3 value: 0x00000000001fc000
bit  3  :  0  -  PWT (Page Write Through)
bit  4  :  0  -  PCD (Page Cache Disable)
PML4 base Address  :  1fc000

RIP = 0x0000000000000008

RSP = 0x000000000badc0de

运行结束后,首先检查退出必须是 hlt 指令。其次查询脏页(被修改的内存页)位图,位图要刚好和输入的 flag 相同。

image

主程序分析完了,我们来看看虚拟机程序。把 SourceAddress 导出,长度为 0x200000(2MB),保存为 code.bin

用 ida 打开(MetaPC,64-bit mode),发现每个页(4KB)几乎一模一样,结构如图

image

写个脚本批量设置函数

for ea in range(0,0x200000,0x1000):

	idc.del_items(ea)
	idc.del_items(ea + 8)
	idc.create_insn(ea + 8)

	if not idc.add_func(ea + 8,ea + 0x70):
		print(f'0x{ea:x} add_func failed')

	if not idc.create_align(ea + 0x71, 0, 12):
		print(f'0x{ea:x} create_align failed')

分析函数的功能。首先检查 r15 某些位是否是 1。如果是,执行一个 push;否则进入循环。
循环体相当于把 r8 ~ r15 组成一个大寄存器,每次左移 1 位,通过溢出的位决定 cr3 的赋值

image

posted @ 2026-03-04 17:45  矛盾空间  阅读(4)  评论(0)    收藏  举报