首页
社区
课程
招聘
[原创]虚拟机视角-读写客户机任意内存(win10)
发表于: 2024-9-26 17:32 3123

[原创]虚拟机视角-读写客户机任意内存(win10)

2024-9-26 17:32
3123

前言

图片描述
基于windows的内存分页机制,进程间的隔离让我们无法直接读写其他进程的地址空间,又因为copy on write让我们即使patch了系统dll也无法影响其他进程。但是这一根内存条就插在主板上,就不能有个方法能看到内容么?
在裸机上如果没有定制的外设是无解了,但是在虚拟化的框架下,这一切都显得再正常不过。

思路

在虚拟化的框架下,客户机的“内存条”实际上是宿主机上申请的一段普通内存,如果我们能够找到GVA与HVA之间的映射关系,就可以绕开任何限制,直接在宿主机上读写任意位置的客户机内存。

步骤一: 通过CR3寄存器获取页表基址

1
2
3
4
5
6
7
8
machine_->Pause();
 
bool success = false;
for (auto vcpu : machine_->vcpus()) {
 struct kvm_sregs sregs;
 if (ioctl(vcpu->fd(), KVM_GET_SREGS, &sregs) < 0)
   MV_PANIC("KVM_GET_REGS failed");
...

我们先暂停vm后,通过KVM_GET_SREGS即可获取cr3寄存器。

步骤二: 获取KPCR

gs寄存器在R0下存放的是KPCR,在R3下存放的是TEB

1
2
3
4
5
6
7
8
...
if ((sregs.cs.base & 3) != 0) {
  MV_LOG("current vcpu=%d is in usermode", vcpu->vcpu_id());
  continue;
}
 
auto kpcr = (uint8_t*)GuestVAToHostAddress((uint64_t)sregs.gs.base, sregs.cr3);
...

步骤三: 通过KPCR结构计算偏移即可得到KTHREAD

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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
//0xb080 bytes (sizeof)
struct _KPCR
{
    union
    {
        struct _NT_TIB NtTib;                                               //0x0
        struct
        {
            union _KGDTENTRY64* GdtBase;                                    //0x0
            struct _KTSS64* TssBase;                                        //0x8
            ULONGLONG UserRsp;                                              //0x10
            struct _KPCR* Self;                                             //0x18
            struct _KPRCB* CurrentPrcb;                                     //0x20
            struct _KSPIN_LOCK_QUEUE* LockArray;                            //0x28
            VOID* Used_Self;                                                //0x30
        };
    };
    union _KIDTENTRY64* IdtBase;                                            //0x38
    ULONGLONG Unused[2];                                                    //0x40
    UCHAR Irql;                                                             //0x50
    UCHAR SecondLevelCacheAssociativity;                                    //0x51
    UCHAR ObsoleteNumber;                                                   //0x52
    UCHAR Fill0;                                                            //0x53
    ULONG Unused0[3];                                                       //0x54
    USHORT MajorVersion;                                                    //0x60
    USHORT MinorVersion;                                                    //0x62
    ULONG StallScaleFactor;                                                 //0x64
    VOID* Unused1[3];                                                       //0x68
    ULONG KernelReserved[15];                                               //0x80
    ULONG SecondLevelCacheSize;                                             //0xbc
    ULONG HalReserved[16];                                                  //0xc0
    ULONG Unused2;                                                          //0x100
    VOID* KdVersionBlock;                                                   //0x108
    VOID* Unused3;                                                          //0x110
    ULONG PcrAlign1[24];                                                    //0x118
    struct _KPRCB Prcb;                                                     //0x180
};
 
//0x700 bytes (sizeof)
struct _KPRCB
{
    ULONG MxCsr;                                                            //0x0
    UCHAR LegacyNumber;                                                     //0x4
    UCHAR ReservedMustBeZero;                                               //0x5
    UCHAR InterruptRequest;                                                 //0x6
    UCHAR IdleHalt;                                                         //0x7
    struct _KTHREAD* CurrentThread;                                         //0x8
    struct _KTHREAD* NextThread;                                            //0x10
    struct _KTHREAD* IdleThread;                                            //0x18
    UCHAR NestingLevel;                                                     //0x20
    UCHAR ClockOwner;                                                       //0x21
    union
    {
        UCHAR PendingTickFlags;                                             //0x22
        struct
        {
            UCHAR PendingTick:1;                                            //0x22
            UCHAR PendingBackupTick:1;                                      //0x22
        };
    };
    UCHAR IdleState;                                                        //0x23
    ULONG Number;                                                           //0x24
    ULONGLONG RspBase;                                                      //0x28
    ULONGLONG PrcbLock;                                                     //0x30
    CHAR* PriorityState;                                                    //0x38
    CHAR CpuType;                                                           //0x40
    CHAR CpuID;                                                             //0x41
    union
    {
        USHORT CpuStep;                                                     //0x42
        struct
        {
            UCHAR CpuStepping;                                              //0x42
            UCHAR CpuModel;                                                 //0x43
        };
    };
    ULONG MHz;                                                              //0x44
    ULONGLONG HalReserved[8];                                               //0x48
    USHORT MinorVersion;                                                    //0x88
    USHORT MajorVersion;                                                    //0x8a
    UCHAR BuildType;                                                        //0x8c
    UCHAR CpuVendor;                                                        //0x8d
    UCHAR LegacyCoresPerPhysicalProcessor;                                  //0x8e
    UCHAR LegacyLogicalProcessorsPerCore;                                   //0x8f
    ULONGLONG TscFrequency;                                                 //0x90
    ULONG CoresPerPhysicalProcessor;                                        //0x98
    ULONG LogicalProcessorsPerCore;                                         //0x9c
    ULONGLONG PrcbPad04[4];                                                 //0xa0
    struct _KNODE* ParentNode;                                              //0xc0
    ULONGLONG GroupSetMember;                                               //0xc8
    UCHAR Group;                                                            //0xd0
    UCHAR GroupIndex;                                                       //0xd1
    UCHAR PrcbPad05[2];                                                     //0xd2
    ULONG InitialApicId;                                                    //0xd4
    ULONG ScbOffset;                                                        //0xd8
    ULONG ApicMask;                                                         //0xdc
    VOID* AcpiReserved;                                                     //0xe0
    ULONG CFlushSize;                                                       //0xe8
    ULONGLONG PrcbPad11[2];                                                 //0xf0
    struct _KPROCESSOR_STATE ProcessorState;                                //0x100
    struct _XSAVE_AREA_HEADER* ExtendedSupervisorState;                     //0x6c0
    ULONG ProcessorSignature;                                               //0x6c8
    ULONG ProcessorFlags;                                                   //0x6cc
    ULONGLONG PrcbPad12a;                                                   //0x6d0
    ULONGLONG PrcbPad12[3];                                                 //0x6d8
};

步骤四: 遍历进程链表active_process_links

通过偏移计算KTHREAD->KPROCESS->EPROCESS->active_process_links获得这个进程双链表结构,通过遍历active_process_links我们可以获取当前系统所有进程,包括每个进程的CR3。

步骤五: win10 分页解析

图片描述
关于windows的分页机制,文档很多不赘述直接贴出部分代码

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
void* MDebugger::GuestVAToHostAddress(uint64_t gva, uint64_t cr3) {
  void* hva;
  uint64_t gpa;
 
  auto pml4_gpa = cr3 & 0xFFFFFFFFFF000;
  auto pml4 = (uint64_t*)machine_->device_manager()->TranslateGuestMemory(pml4_gpa);
 
  auto pml4e_index = (gva >> 39) & 0x1FF;
  auto pml4e = pml4[pml4e_index];
  MV_ASSERT((pml4e & 0x80) == 0);
 
  if (!(pml4e & 0x1)) {
    MV_ERROR("page fault happened");
    return nullptr;
  }
 
  auto pdpt_gpa = pml4e & 0xFFFFFFFFFF000;
  auto pdpt = (uint64_t*)machine_->device_manager()->TranslateGuestMemory(pdpt_gpa);
 
  auto pdpte_index = (gva >> 30) & 0x1FF;
  auto pdpte = pdpt[pdpte_index];
 
  if (!(pdpte & 0x1)) {
    MV_ERROR("page fault happened");
    return nullptr;
  }
 
  if ((pdpte & 0x80) == 0) {
    auto pd_gpa = pdpte & 0xFFFFFFFFFF000;
    auto pd = (uint64_t*)machine_->device_manager()->TranslateGuestMemory(pd_gpa);
 
    auto pde_index = (gva >> 21) & 0x1FF;
    auto pde = pd[pde_index];
 
    if (!(pde & 0x1)) {
      MV_ERROR("page fault happened");
      return nullptr;
    }
 
    if ((pde & 0x80) == 0) {
      auto pte_gpa = pde & 0xFFFFFFFFFF000;
      auto pt = (uint64_t*)machine_->device_manager()->TranslateGuestMemory(pte_gpa);
 
      auto pte_index = (gva >> 12) & 0x1FF;
      auto pte = pt[pte_index];
 
      if (!(pte & 0x1)) {
        MV_ERROR("page fault happened");
        return nullptr;
      }
 
      auto offset = gva & 0xFFF;
      gpa = (pte & 0xFFFFFFFFFF000) + offset;
      hva = machine_->device_manager()->TranslateGuestMemory(gpa);
    } else {
      auto offset = gva & 0x1FFFFF;
      gpa = (pde & 0xFFFFFFFE00000) + offset;
      hva = machine_->device_manager()->TranslateGuestMemory(gpa);
    }
  } else {
    auto offset = gva & 0x3FFFFFFF;
    gpa = (pdpte & 0xFFFFFC0000000) + offset;
    hva = machine_->device_manager()->TranslateGuestMemory(gpa);
  }
 
  return hva;
}

总结

在能够获取所有进程cr3后,加上页表解析,可以轻松的在host上读写任意guest内存。当我们尝试在host抹掉guest进程notepad中的ntdll的PE头后,由于系统dll的特殊性加上直接修改了物理内存,系统也随之陷入一片混乱 图片描述
图片描述

项目地址

https://github.com/tenclass/mvisor
使用右ctrl+F8唤起debug窗口,目前仅支持win10 guest。

最后于 2024-9-27 13:39 被Justgoon编辑 ,原因:
收藏
免费 5
支持
分享
最新回复 (4)
雪    币: 24
活跃值: (604)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
楼主太帅了1
2024-10-6 18:40
0
雪    币: 4663
活跃值: (4087)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
KVM_GET_REGS是编译的kvm的源码吗
2024-10-6 18:53
0
雪    币: 413
活跃值: (597)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
4
牛人,真是不错。术业有专攻
2024-10-9 09:58
0
雪    币: 434
活跃值: (956)
能力值: ( LV5,RANK:70 )
在线值:
发帖
回帖
粉丝
5
木志本柯 KVM_GET_REGS是编译的kvm的源码吗
kvm提供的接口,用来获取vm寄存器信息的,mvisor/qemu这样的虚拟机都是通过kvm的接口来控制虚拟机
2024-10-10 23:49
0
游客
登录 | 注册 方可回帖
返回
//