# Page Table - Virtual address to physical address > Author: 堇姬Naup ## 前言 把之前寫到一半的完善好了,丟上來 ![v2-34572763f08e87d7169c529ad41c3aea_1440w](https://hackmd.io/_uploads/HJlPCfJvee.jpg) ![v2-ad8aa8be5bc899a98f5bb6885ecf2fd8_1440w](https://hackmd.io/_uploads/BkpNCMkwxg.jpg) ## qemu monitor https://qemu-project.gitlab.io/qemu/system/monitor.html 可以用他來讀寫物理記憶體或是做很多事情 把 Qemu 加上 `-monitor unix:/tmp/qemu-monitor.sock,server,nowait` 後 load 這支 https://github.com/Naupjjin/slub-gdbext/tree/main 就可以通過 socket,來去操作 qemu monitor `qemu_monitor 'monitor_command_string'` ```py import socket class QemuMonitorCommand(GenericCommand): """ Send a command to QEMU monitor via UNIX socket. -monitor unix:/tmp/qemu-monitor.sock,server,nowait by. naup """ _cmdline_ = "qemu_monitor" _syntax_ = "qemu_monitor 'monitor_command_string'" def __init__(self): super(QemuMonitorCommand, self).__init__(self._cmdline_, gdb.COMMAND_OBSCURE) self.monitor_path = "/tmp/qemu-monitor.sock" def do_invoke(self, argv): if not argv: err("Usage: qemu_monitor 'command'") return cmd = " ".join(argv) + "\n" try: with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as sock: sock.connect(self.monitor_path) sock.sendall(cmd.encode()) sock.settimeout(0.2) response = b"" while True: try: data = sock.recv(4096) if not data: break response += data except socket.timeout: break print(response.decode(errors="ignore").strip()) except Exception as e: err(f"Failed to connect to QEMU monitor: {e}") QemuMonitorCommand() ``` 實際用起來長這樣 ![image](https://hackmd.io/_uploads/HyEnk_1Dlg.png) ## 原理 Virtual address 對應的就是 page table offset 通過他來查表可以查出 physical address VA 實際上只會用到 48 bits,其餘是 Sign Extension 9 bits PML4I (Page-Map Level-4 Index) 9 bits PDPI (Page Directory Pointer Index) 9 bits PDI (Page Directory Index) 9 bits PTI (Page Table Index) 12 bits Physical Page Offset `0xffffffff82b6a3a0` Unused (bits 63–48) : 0xffff (65535) PML4 Index : 0x1ff (511) PDPT Index : 0x1fe (510) PD Index : 0x15 (21) PT Index : 0x16a (362) Offset : 0x3a0 (928) 參考該docs的2.5(p.3069) [Intel® 64 and IA-32 Architectures Software Developer’s Manual Combined Volumes: 1, 2A, 2B, 2C, 2D, 3A, 3B, 3C, 3D, and 4](https://www.intel.com.tw/content/www/tw/zh/content-details/782158/intel-64-and-ia-32-architectures-software-developer-s-manual-combined-volumes-1-2a-2b-2c-2d-3a-3b-3c-3d-and-4.html) ![image](https://hackmd.io/_uploads/Skqog-yPex.png) CR3 register contains the base address of the page-directorypointer table. With 4-level paging and 5-level paging, the CR3 register contains the base address of the PML4 table and PML5 table 0 PWT(Page-level Write-Through) 1 PCD(Page-level Cache Disable) 11:2 保留 63:12 Page table base address(PML4 physical address) 可以從 cr3 來找到 Page table 最上層 只要把最低 12 bits mask 掉,就是 page table physical address,把他轉為 virtual address 來看比較方便 ![v2-34572763f08e87d7169c529ad41c3aea_1440w](https://hackmd.io/_uploads/HJlPCfJvee.jpg) ![v2-ad8aa8be5bc899a98f5bb6885ecf2fd8_1440w](https://hackmd.io/_uploads/BkpNCMkwxg.jpg) 每一個 Table 為 1 Page (4 KB) 每一個 Entry 為 8 Bytes 因此每一個 Table 含有 512 個 Entries (分別就對應 9 bits, 2^9 = 512) 高四個 page table 找到想找的 page 後 通過最後低位 12 bits 找到該 page 具體位置 就是個先映射到實際 page,在從該 page 找實際位置的轉換方式 cr3 找到第一個頁表的位置後,就可以通過 VA 算出的第一個值,找 offset,以此類推 `512**4*0x1000-1 = 0xffffffffffff` 就是 48 bits ## gdb (2MB page) 目標將 ```gdb gef> p &slab_caches $3 = (struct list_head *) 0xffffffff82b6a3a0 <slab_caches> gef> v2p 0xffffffff82b6a3a0 Virt: 0xffffffff82b6a3a0 -> Phys: 0x2b6a3a0 ``` 手動轉成 physical address 這邊不知道他是 2MB 還是 4KB,不過前三張找法是一樣的 Unused (bits 63–48) : 0xffff (65535) PML4 Index : 0x1ff (511) PDPT Index : 0x1fe (510) PD Index : 0x15 (21) ### cr3 to PML4 ![j1NHnHW](https://hackmd.io/_uploads/ByGvsWyPel.png) 先找 cr3 ![image](https://hackmd.io/_uploads/S1peq-kvex.png) `0x0000000004866000 & ~0xfff = 0x0000000004866000` 這樣就找到 PML4 base (PA) = 0x0000000004866000 VA = 0xffff888004866000 ![image](https://hackmd.io/_uploads/H1OV9ZJwgx.png) ### PML4 to PDP PML4,Linux Kernel 的 PGD (Page Global Directory) PML4 entry 長這樣,整張 PML4 頁表共有 512 個 ![ZnzFnmo](https://hackmd.io/_uploads/Hy0uoWkwll.png) 高 9 bits 轉 offset,找到對應 PML4E `0xffff888004866000 + 511 * 8 = 0xffff888004866ff8` ![image](https://hackmd.io/_uploads/B1Bo9Z1wgx.png) 根據 PML4E 去掉低 12 bits,跟高 1 bits `0x0000000002a33067 & ~0xfff = 0x2a33000` ![image](https://hackmd.io/_uploads/HJ9mlf1Dex.png) 這樣就找到 PDP base (PA) = 0x2a33000 VA = 0xffff888002a33000 ### PDP 2 PD PDP, Linux Kernel 的 PUD (Page Upper Directory) 接下來從 PDP 找出 PD base PDPE 長這樣 ![QJBSa64](https://hackmd.io/_uploads/Hk5cxMJPeg.png) 算 offset 找到 `0xffff888002a33000 + 510 * 8 = 0xffff888002a33ff0` ![image](https://hackmd.io/_uploads/SyTdef1Del.png) 根據 PDPE 去掉低 12 bits,跟高 1 bits `0x0000000002a34063 & ~0xfff = 0x2a34000` ![image](https://hackmd.io/_uploads/Sk9SZGJPll.png) 這樣就找到 PD base (PA) = 0x2a34000 VA = 0xffff888002a34000 ### PD find any PD,Linux Kernel 的 PMD (Page Middle Directory) PDE 長這樣 ![e1q6rr8](https://hackmd.io/_uploads/rknFZf1vlg.png) ![SxU6TVd](https://hackmd.io/_uploads/r1mefzkvxg.png) ![image](https://hackmd.io/_uploads/BkS_sGJDgl.png) 算 offset 找到 `0xffff888002a34000 + 0x15 * 8 = 0xffff888002a340a8` ![image](https://hackmd.io/_uploads/H1iUzGJPxl.png) 找到的 PDE 長這樣 `0x8000000002a000e3` `0b1000000000000000000000000000000000000010101000000000000011100011` 這邊跟先前的不太一樣 可以關注到這邊的 PDE 有兩種形式 在 2 MB / 4 KB Page Size 的情況結構不同 會根據 PS 來標示是哪種 1 的話是 2 MB 4KB 找到的是 PT base 在 2MB 狀況下沒有 PT,是直接找到該 physical page frame 所以轉換一下 `0x0000000002a000e3 & ~0xfffff = 0x2a00000` PS: 這邊附上 PT 的 PTE ![nVlUQZm (1)](https://hackmd.io/_uploads/BkApsGJwex.png) ### page frame + offset 所以前面的重新計算一下 2MB: Unused (bits 63–48) : 0xffff (65535) PML4 Index : 0x1ff (511) PDPT Index : 0x1fe (510) PD Index : 0x15 (21) Offset (2MB page) : 0x16a3a0 (1483680) physical page frame = 0x2a00000 加上 offset `0x16a3a0` `0x2a00000 + 0x16a3a0 = 0x2b6a3a0` 這樣就找到 physical adress 啦 ![image](https://hackmd.io/_uploads/rJbdJXkwle.png) ## gdb (4KB page) 這次把 kaslr 打開來,並搭配 monitor 要找 0xffffffff8538adc0 ```gdb gef> x/6xg 0xffffffff8538adc0 0xffffffff8538adc0: 0x89482775c38948ff 0x8948000011e2e8ef 0xffffffff8538add0: 0x8948fffff7bae8ef 0x8548000012c2e8ef 0xffffffff8538ade0: 0x5d5bfb230f0374db 0x0fc031ccccccccc3 gef> v2p 0xffffffff8538adc0 Virt: 0xffffffff8538adc0 -> Phys: 0x1478adc0 ``` ![image](https://hackmd.io/_uploads/B1JqQ5kPex.png) ### cr3 to PML4 ![j1NHnHW](https://hackmd.io/_uploads/ByGvsWyPel.png) 先找 cr3 ![image](https://hackmd.io/_uploads/rkx0ZcJvgx.png) `0x2068000 & ~0xfff = 0x2068000` 這樣就找到 PML4 base (PA): 0x2068000 ### PML4 to PDP PML4,Linux Kernel 的 PGD (Page Global Directory) PML4 entry 長這樣,整張 PML4 頁表共有 512 個 ![ZnzFnmo](https://hackmd.io/_uploads/Hy0uoWkwll.png) 高 9 bits 轉 offset,找到對應 PML4E `0x2068000 + 511 * 8 = 0x2068ff8` ![image](https://hackmd.io/_uploads/HysVN9yvgx.png) 根據 PML4E 去掉低 12 bits,跟高 1 bits `0x0000000015033067 & ~0xfff = 0x15033000` 這樣就找到 PDP base (PA) = 0x15033000 ### PDP 2 PD PDP, Linux Kernel 的 PUD (Page Upper Directory) 接下來從 PDP 找出 PD base PDPE 長這樣 ![QJBSa64](https://hackmd.io/_uploads/Hk5cxMJPeg.png) 算 offset 找到 `0x15033000 + 510 * 8 = 0x15033ff0` ![image](https://hackmd.io/_uploads/rk4CNcJPxl.png) 根據 PDPE 去掉低 12 bits,跟高 1 bits `0x0000000015034063 & ~0xfff = 0x15034000` 這樣就找到 PD base (PA) = 0x15034000 ### PD find PT PD,Linux Kernel 的 PMD (Page Middle Directory) PDE 長這樣 ![e1q6rr8](https://hackmd.io/_uploads/rknFZf1vlg.png) ![SxU6TVd](https://hackmd.io/_uploads/r1mefzkvxg.png) ![image](https://hackmd.io/_uploads/BkS_sGJDgl.png) 算 offset 找到 `0x15034000 + 0x29 * 8 = 0x15034148` ![image](https://hackmd.io/_uploads/HJwUB5JPee.png) 0x000000000240e063 = 0b10010000001110000001100011 第 7 個是 0,是 4 KB,有 PT `0x000000000240e063 & ~0xfff = 0x240e000` 這樣就找到 PT base (PA) = 0x240e000 ### PT to physical page frame PT PTE 長這樣 ![nVlUQZm (1)](https://hackmd.io/_uploads/BkApsGJwex.png) `0x240e000 + 0x18a * 8 = 0x240ec50` ![image](https://hackmd.io/_uploads/HJfdIcyvlg.png) `0x000000001478a021 & ~0xfff = 0x1478a000` 這樣就找到那張 page 了 page base 是 0x1478a000 ### page frame + offset `0x1478a000 + 0xdc0 = 0x1478adc0` 長的一模一樣,找到了 ![image](https://hackmd.io/_uploads/B1Hev5yPlg.png) ## after all 以上就是如何轉換