[2022]HyperMap -通过PTE自引用直接读取物理内存 huoji hypervisor,PTE 2022-10-28 673 次浏览 0 次点赞 在hypervisor中的host直接操作guest物理内存的情况中,我遇到了一个棘手的问题,即所有的API都是page_code的.使用他们不安全(即便是AMD有GIF的情况下). 所以需要设计一个高效、快速的内存访问系统.即通过设置一个魔术数字,实现base + offset的guest物理内存读取.我把它称之为hypermap 如果对页表不熟悉,可以参考欧洲bro的文章: https://back.engineering/23/08/2020/ ### 自引用技术 windows x64的PML4管理使用了一个叫做 自引用技术,所谓自引用技术,当内核想要访问分页结构时,它必须找到它们的虚拟地址。处理器指令只允许操作虚拟地址.而不是物理地址。因此,内核需要一种方法将这些分页结构映射到虚拟内存中,此 PML4 条目不会引用 PDPT,而是引用 PML4 本身,并移动其他值以为新的自引用字段腾出空间。因此,不是引用内存页中的特定内存字节,而是引用 PTE。可以通过多次使用相同的自引用条目来检索更多结构。注意,这个设计造成了非常多的安全问题,最经典的问题是KASLR就形同虚设了,后来windows直接把自引用的index也随机化了 关于自引用技术,更直白的可以看这里: https://www.coresecurity.com/core-labs/articles/getting-physical-extreme-abuse-of-intel-based-paging-systems-part-2-windows ### 直接物理内存访问 Windows x64中的PML4 0-255是user space后面的是kernel space.在system的CR3中,0-255不使用.因此我们可以利用它来做物理内存翻译. 在初始化host的pml4中,我们先将pml4的255位自引用为系统的pdpt: ```cpp auto pml4e = &host_pml4->pml4[PML4_SELF_REF]; pml4e->AsUInt64 = 0; pml4e->Fields.Valid = 1; pml4e->Fields.Write = 1; pml4e->Fields.PageFrameNumber = tools::get_physical_address(&host_pml4->phys_pdpt) >> page_shift; ``` 然后填充这一块区域,将他翻译成512G的映射(也就是说这一块的PDPT拥有访问512G以内的物理内存的能力) ```cpp for (uint64_t i = 0; i < host_map_physical_memory_idx; ++i) { auto pdpte = &host_pml4->phys_pdpt[i]; pdpte->AsUInt64 = 0; pdpte->Fields.Valid = 1; pdpte->Fields.Write = 1; pdpte->Fields.PageFrameNumber = tools::get_physical_address(&host_pml4->phys_pds[i][0]) >> page_shift; for (uint64_t j = 0; j < 512; ++j) { auto pde = &host_pml4->phys_pds[i][j]; uintptr_t translationPa = (i * 512) + j; pde->AsUInt64 = 0; pde->Fields.Valid = 1; pde->Fields.Write = 1; pde->Fields.LargePage = 1; pde->Fields.PageFrameNumber = translationPa; } } ``` 最后通过这个python脚本,算出来我们的虚拟地址从第几位开始可以访问到255的entry: ```cpp import sys import os entry_size = 8 address = 0x7F8000000000 shifted_address = address >> 12 pte_offset = shifted_address & 0x1ff shifted_address = shifted_address >> 9 pde_offset = shifted_address & 0x1ff shifted_address = shifted_address >> 9 pdpte_offset = shifted_address & 0x1ff shifted_address = shifted_address >> 9 pmle_offset = shifted_address & 0x1ff print ( "entry: PML4:0x%x PDPT:0x%x PD:0x%x PT:0x%x" % ( pmle_offset , pdpte_offset , pde_offset , pte_offset ) ) print ( "offset: PML4:0x%x PDPT:0x%x PD:0x%x PT:0x%x" % ( pmle_offset * entry_size , pdpte_offset * entry_size , pde_offset * entry_size , pte_offset * entry_size ) ) ``` 可以看到 是 `0x7F8000000000` 正好对应 pml4 的255的entry.意味着,我们只需要访问 `0x7F8000000000 + offset`就相当于访问了物理内存 0 开始到512gb位置的权限 然后就是正常的线性地址翻译.... ```cpp void* guest_va_to_host_va(uintptr_t dir_base, void* const guest_virtual_address, size_t* const offset_to_next_page) { #ifdef use_npt if (offset_to_next_page) *offset_to_next_page = 0; pml4_virtual_address const vaddr = {guest_virtual_address}; // guest PML4 auto pml4 = (_pml4_entry_4kb*)(host_physical_memory_base + dir_base); //host_physical_memory_base = 0x7F8000000000 if (pml4 == nullptr) return nullptr; ``` 本文由 huoji 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。 点赞 0
还不快抢沙发