## 一:背景 ### 1. 講故事 我發現有很多的 .NET程式員 寫了很多年的代碼都沒弄清楚什麼是 `虛擬地址`,更不用談什麼是 `物理地址` 以及Windows是如何實現地址映射的了?這一篇我們就來聊一聊這兩者之間的聯繫。 ## 二:地址映射研究 ### 1. 找虛擬地址 怎麼去找 `虛擬地址 ...
一:背景
1. 講故事
我發現有很多的 .NET程式員 寫了很多年的代碼都沒弄清楚什麼是 虛擬地址
,更不用談什麼是 物理地址
以及Windows是如何實現地址映射的了?這一篇我們就來聊一聊這兩者之間的聯繫。
二:地址映射研究
1. 找虛擬地址
怎麼去找 虛擬地址
呢?相信很多朋友都知道應用程式
用的是虛擬地址,所以從應用程式中取一個就好了,這裡就拿 notepad 舉例子吧。
開啟一個裝有 win10 的虛擬機,然後打開 notepad.exe,使用 windbg 進行它的內核態調式,參考代碼如下:
0: kd> !process 0 0 notepad.exe
PROCESS ffffe0011f9c9840
SessionId: 1 Cid: 11a8 Peb: 7ff63d8ff000 ParentCid: 0bf4
DirBase: 23c6d000 ObjectTable: ffffc00088bdcbc0 HandleCount: <Data Not Accessible>
Image: notepad.exe
0: kd> .process /i /p ffffe0011f9c9840
You need to continue execution (press 'g' <enter>) for the context
to be switched. When the debugger breaks in again, you will be in
the new process context.
0: kd> g
Break instruction exception - code 80000003 (first chance)
nt!DbgBreakPointWithStatus:
fffff801`bed59c50 cc int 3
1: kd> .reload /user
Loading User Symbols
....................................
Press ctrl-c (cdb, kd, ntsd) or ctrl-break (windbg) to abort symbol loads that take too long.
Run !sym noisy before .reload to track down problems loading symbols.
......
1: kd> lm
start end module name
00007ff6`3e1e0000 00007ff6`3e21a000 notepad (deferred)
00007ff9`83e60000 00007ff9`83fac000 UIAutomationCore (deferred)
...
1: kd> dB 00007ff6`3e1e0000+0x50 L30
00007ff6`3e1e0050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
00007ff6`3e1e0060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
00007ff6`3e1e0070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
從卦中可以看到 00007ff63e1e0050
處是一段字元串,接下來我們就以它為例吧。
2. 如何用 Windbg 推算
到底是如何映射的呢?如果你瞭解 Windows 的源碼可能你就很清楚,不瞭解也沒關係,我們可以用 WinDbg 幫我們計算,在 windbg 中有一個 !vtop
命令可以一鍵查找,輸出如下:
1: kd> !vtop 0 00007ff63e1e0050
Amd64VtoP: Virt 00007ff63e1e0050, pagedir 0000000023c6d000
Amd64VtoP: PML4E 0000000023c6d7f8
Amd64VtoP: PDPE 000000002360aec0
Amd64VtoP: PDE 000000000b910f80
Amd64VtoP: PTE 000000001fa51f00
Amd64VtoP: Mapped phys 000000000ad38050
Virtual address 7ff63e1e0050 translates to physical address ad38050.
1: kd> !dB ad38050 L30
# ad38050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
# ad38060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
# ad38070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
從卦中可以清晰的看到,虛擬地址 00007ff63e1e0050
所對應的物理地址為 ad38050
,然後用 !dB
去觀察物理地址也確實如此。
這裡要提醒一下,如果你還想知道這個物理地址所屬的 PDE (頁目錄項)
和 PTE (頁表項)
,可以用 !pte
命令幫我們一鍵顯示,輸出如下:
1: kd> !pte 00007ff63e1e0050
VA 00007ff63e1e0050
PXE at FFFFF6FB7DBED7F8 PPE at FFFFF6FB7DAFFEC0 PDE at FFFFF6FB5FFD8F80 PTE at FFFFF6BFFB1F0F00
contains 009000002360A867 contains 00E000000B910867 contains 00F000001FA51867 contains 810000000AD38025
pfn 2360a ---DA--UWEV pfn b910 ---DA--UWEV pfn 1fa51 ---DA--UWEV pfn ad38 ----A--UR-V
從卦中可以看到,x64的地址有四級結構,不僅有 PDE,PTE
,還有 PXE, PPE
,並且從 pfn ad38
可以清楚的看到它的物理頁號是 ad38
,加上虛擬地址後的 12bit(050) 偏移,最後的物理地址也就是 ad38050
。
用 WinDbg 推算雖然簡單,但不利於我們瞭解原理,為了加深理解,我們需要手工的去推算。
3. 如何手工推算
要明白手工推算,在腦子中一定要有一張架構圖,有了這張架構圖就方便行事了。
卦圖中有幾點要解釋。
- 二進位怎麼出來的?
可以用 windbg 的 .formats 命令。
1: kd> .formats 00007ff63e1e0050
Evaluate expression:
Hex: 00007ff6`3e1e0050
Decimal: 140695580835920
Binary: 00000000 00000000 01111111 11110110 00111110 00011110 00000000 01010000
- CR3 是什麼?
CR3 是Windows的控制寄存器,它記錄著這個進程所屬的虛擬地址首地址,專業點就是 BaseDir (基目錄)
地址,參考如下輸出:
1: kd> !process 0 0 notepad.exe
PROCESS ffffe0011f9c9840
SessionId: 1 Cid: 11a8 Peb: 7ff63d8ff000 ParentCid: 0bf4
DirBase: 23c6d000 ObjectTable: ffffc00088bdcbc0 HandleCount: <Data Not Accessible>
Image: notepad.exe
- 各級頁表占用多少bit位數?
- PXE 占用 9bit(39-47)
- PPE 占用 9bit(30-38)
- PDE 占用 9bit(21-29)
- PTE 占用 9bit(12-20)
有了這些信息之後,最後就是手工推算了,這裡要提醒一下,每個表的首地址都把後 12bit 抹為0,因為他們是表的meta信息,詳細輸出如下:
1: kd> !process 0 0 notepad.exe
PROCESS ffffe0011f9c9840
SessionId: 1 Cid: 11a8 Peb: 7ff63d8ff000 ParentCid: 0bf4
DirBase: 23c6d000 ObjectTable: ffffc00088bdcbc0 HandleCount: <Data Not Accessible>
Image: notepad.exe
1: kd> r cr3
cr3=0000000023c6d000
1: kd> !dp 23c6d000 + (0y011111111*8) L1
#23c6d7f8 00900000`2360a867
1: kd> !dp 2360a000+(0y111011000*8) L1
#2360aec0 00e00000`0b910867
1: kd> !dp 0b910000 + (0y111110000*8) L1
# b910f80 00f00000`1fa51867
1: kd> !dp 1fa51000+(0y111100000*8) L1
#1fa51f00 81000000`0ad38025
從卦中可以看到最後推算出來的是 810000000ad38025
,抹掉 高32bit
和 末 12bit
之後就變成了 ad38
,這個就是我們的 pfn (頁幀號)
,如果你想核算一下 !dp 出來的值對不對,可以看下 !pte
命令中的 contains xxx
是不是這個值? 輸出如下:
1: kd> !pte 00007ff63e1e0050
VA 00007ff63e1e0050
PXE at FFFFF6FB7DBED7F8 PPE at FFFFF6FB7DAFFEC0 PDE at FFFFF6FB5FFD8F80 PTE at FFFFF6BFFB1F0F00
contains 009000002360A867 contains 00E000000B910867 contains 00F000001FA51867 contains 810000000AD38025
pfn 2360a ---DA--UWEV pfn b910 ---DA--UWEV pfn 1fa51 ---DA--UWEV pfn ad38 ----A--UR-V
從卦中可以看到,四個地址和pfn都是對的,最後 pfn+頁內偏移 = ad38050
,也就是我們苦苦尋找的 物理地址
,再次輸出一下結果。
1: kd> !dB ad38050 L30
# ad38050 69 73 20 70 72 6f 67 72-61 6d 20 63 61 6e 6e 6f is program canno
# ad38060 74 20 62 65 20 72 75 6e-20 69 6e 20 44 4f 53 20 t be run in DOS
# ad38070 6d 6f 64 65 2e 0d 0d 0a-24 00 00 00 00 00 00 00 mode....$.......
三:總結
手工推算是不是非常的有意思,可以讓我們更加的理解Windows底層玩法,WinDbg在手,天下我有!