Snoozy

1.Sleep-inducing; tedious.

仮想アドレスから物理アドレスを求めてみる

仮想アドレスから物理アドレスを求めてみる

本稿では,Windows10 x64システム上で動作する,4-level pagingについてみていく.

最初に4-level pagingによるアドレス変換の概要に触れた後,後半では実際にWinDbgを使ってシステム上の仮想アドレスから物理アドレスへの変換にトライしてみる.

ページング

Intel Developers Manualを参照すると,ページングの方式には大きく3種類あることが分かる.

  • 32bit paging
  • 32bit PAE paging
  • 4-level paging

レジスタに適切な値をセットすることでこれらページングのモードが変わる.

Windows10 x64システムでは通常4-level pagingが使用される.

具体的には,仮想アドレスの変換には以下のような処理を行っている.

f:id:snoozekvn:20200325103814j:plain

引用元: Intel® 64 and IA-32 Architectures Software Developer Manual: Vol 3

この図から,アドレスの変換に4つのルックアップを行うことが分かる.

  1. PML4Tから正しいPDPTE(PDPTエントリ)を見つけるためのルックアップ
  2. PDPT から正しいPDE(PDエントリ)を見つけるためのルックアップ
  3. PDから正しいPTE(PEエントリ)を見つけるためのルックアップ
  4. PTから正しいPhysical Addrを見つけるためのルックアップ

本稿ではこれを手計算で辿っていき,仮想アドレスから物理アドレスへの変換を体験してみる.

PTE(Page Table Entry)

本稿の主題である,仮想アドレスから物理アドレスへの変換に直接関係するわけではないが,PTEはセキュリティ等で重要であるため合わせて載せておく.

x64 ハードウェア PTEは,物理アドレス上のページに関する種々の情報を保持する.

f:id:snoozekvn:20200325111135j:plain

引用元: http://index-of.es/EBooks/NX-bit.pdf

NX bitやValid bit,Dirty bitなどはよく知られているだろう.

例えば,あるページを指すPTEのNX bitが立っていれば,そのページ内でのコード実行が許可されていないことを示し,Dirty bitが立っていれば,何らかの書き込み操作があったことを示す.

また最下位bitはデマンドページングに使用される.

対応するページアクセス時にもし最下位bitが0ならばページフォールトが発生し,システムは物理メモリへのページ割り当て処理へと移行する.

PTEに関する話はこのぐらいにして,以降では実際にWinDbgを使って仮想アドレスから物理アドレスへの変換に挑戦してみる.

WinDbgを使って手動で仮想アドレスを物理アドレスに変換する

まず事前準備としてVirtualBox上のWindows10 64bitのカーネルへ,ホストからWinDbgを使ってアタッチする.

変換対象となる仮想アドレスはなんでもよいのだが,今回は以下のようなプログラムを使って変換を行う.

// Ex1.cpp
#include<iostream>
#include <cstdlib>
int main() {
    unsigned i = 0xDEADBEEF;
    std::cout << "address :  " << std::hex << &i << std::endl;
    std::cout << i << std::endl;
    system("PAUSE");
    return 0;
}

メモリ上に確保された符号なし整数値0xDEADBEEFのアドレスを出力するものだが,当然出力されるアドレスは仮想メモリ空間のアドレスである.

このプログラムを実行すると筆者の環境では0xDEADBEEFのアドレスは0x254dcf584だと表示された.

では,Intel Developers Manualを見つつ,手動で仮想アドレス0x254dcf584を物理アドレスに変換してみよう.

最初にWinDbgでEx1.exeプロセスのコンテキストに移動する.

0: kd> !process 0 0 Ex1.exe
PROCESS ffffa7893d8b9080
    SessionId: 1  Cid: 1c98    Peb: 254e82000  ParentCid: 1388
    DirBase: 11a13002  ObjectTable: ffffb800c9723500  HandleCount:  41.
    Image: Ex1.exe
0: kd> .process /i /p ffffa7893d8b9080
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:
fffff805`67fca210 cc              int     3

ddで確認すると確かにこの仮想アドレスに0xDEADBEEFが見つかる.

0: kd> dd 254dcf584
00000002`54dcf584  deadbeef cccccccc cccccccc cccccccc
00000002`54dcf594  cccccccc cccccccc cccccccc cccccccc
00000002`54dcf5a4  cccccccc cccccccc cccccccc cccccccc
00000002`54dcf5b4  cccccccc cccccccc cccccccc cccccccc
00000002`54dcf5c4  cccccccc cccccccc cccccccc cccccccc
00000002`54dcf5d4  cccccccc cccccccc cccccccc cccccccc
00000002`54dcf5e4  cccccccc cccccccc cccccccc cccccccc
00000002`54dcf5f4  cccccccc cccccccc cccccccc cccccccc

これを物理アドレスに変換する.

事前準備として仮想アドレス0x254dcf584をマニュアルに従って分割しておく.

f:id:snoozekvn:20200325103807j:plain

まず,CR3レジスタに格納された値からPML4テーブルへのアドレスを取得する.

0: kd> r cr3
cr3=0000000011a13002
0: kd> !dq 0000000011a13002
#11a13000 8a000000`3bb20867 00000000`00000000
#11a13010 8a000000`41629867 00000000`00000000
#11a13020 00000000`00000000 00000000`00000000
#11a13030 00000000`00000000 00000000`00000000
#11a13040 00000000`00000000 00000000`00000000
#11a13050 00000000`00000000 00000000`00000000
#11a13060 00000000`00000000 00000000`00000000
#11a13070 00000000`00000000 00000000`00000000

エントリが2つあるが,今回は仮想アドレスの47~39bitの9bitが全て0であるため,最初のエントリ8a000000`3bb20867が求める値だとわかる.

次にPDPT内の対応するエントリへのアドレスを求める.

8a000000`3bb20867の51~12bitと,仮想アドレスのの38~30bitを使って対応するアドレスを求める.

0x8による乗算は各エントリのサイズによるものだ.

0: kd> !dq 000000`3bb20000 + 0x9 * 0x8  
#3bb20048 0a000000`2ef21867 00000000`00000000
#3bb20058 00000000`00000000 00000000`00000000
#3bb20068 00000000`00000000 00000000`00000000
#3bb20078 00000000`00000000 00000000`00000000
#3bb20088 00000000`00000000 00000000`00000000
#3bb20098 00000000`00000000 00000000`00000000
#3bb200a8 00000000`00000000 00000000`00000000
#3bb200b8 00000000`00000000 00000000`00000000

これでPDEが求まった. 同じように,0a000000`2ef21867の51~12bitと,仮想アドレスのの29~21bitを使って対応するアドレスを求める.

0: kd> !dq 000000`2ef21000 + 0xa6 * 0x8
#2ef21530 0a000000`67131867 0a000000`64722867
#2ef21540 00000000`00000000 00000000`00000000
#2ef21550 00000000`00000000 00000000`00000000
#2ef21560 00000000`00000000 00000000`00000000
#2ef21570 00000000`00000000 00000000`00000000
#2ef21580 00000000`00000000 00000000`00000000
#2ef21590 00000000`00000000 00000000`00000000
#2ef215a0 00000000`00000000 00000000`00000000

これでPTEが求まった. 続けて,0a000000`67131867の51~12bitと,仮想アドレスのの20~12bitを使って対応するアドレスを求める.

0: kd> !dq 000000`67131000 + 0x1cf * 0x8
#67131e78 84000000`417d3867 00000000`00000000
#67131e88 00000000`00000000 00000000`00000000
#67131e98 00000000`00000000 00000000`00000000
#67131ea8 00000000`00000000 00000000`00000000
#67131eb8 00000000`00000000 00000000`00000000
#67131ec8 00000000`00000000 00000000`00000000
#67131ed8 00000000`00000000 00000000`00000000
#67131ee8 00000000`00000000 00000000`00000000

最後に,84000000`417d3867の51~12bitと,仮想アドレスのの11~0bitを使って物理アドレスを求める.

0: kd> !dq 000000`417d3000 + 0x584
#417d3580 deadbeef`cccccccc cccccccc`cccccccc
#417d3590 cccccccc`cccccccc cccccccc`cccccccc
#417d35a0 cccccccc`cccccccc cccccccc`cccccccc
#417d35b0 cccccccc`cccccccc cccccccc`cccccccc
#417d35c0 cccccccc`cccccccc cccccccc`cccccccc
#417d35d0 cccccccc`cccccccc cccccccc`cccccccc
#417d35e0 cccccccc`cccccccc cccccccc`cccccccc
#417d35f0 cccccccc`cccccccc cccccccc`cccccccc

これにより,確かに0xDEADBEEFが存在する仮想アドレス0x254dcf584から,物理アドレス0x417d3584へと変換することができた.

WinDbgには,!vtopや!pte等のエクステンションコマンドが用意されており,物理アドレスを求めるには通常こちらを使えばよい.

例えば!pteでは以下のようにして仮想アドレスから物理アドレスを求める.

0: kd> !pte 254dcf584                                           VA 0000000254dcf584
PXE at FFFFF8FC7E3F1000    PPE at FFFFF8FC7E200048    PDE at FFFFF8FC40009530    PTE at FFFFF880012A6E78
contains 8A0000003BB20867  contains 0A0000002EF21867  contains 0A00000067131867  contains 84000000417D3867
pfn 3bb20     ---DA--UW-V  pfn 2ef21     ---DA--UWEV  pfn 67131     ---DA--UWEV  pfn 417d3     ---DA--UW-V

PFNが417d3であるから,物理アドレス0x417d3000に仮想アドレスの下位3バイト0x584を加算したものが物理アドレスである.

メモリダンプから確認する

FTK imagerなどでメモリダンプを取り,それをバイナリエディタで開く. 以下はHxDでオフセット417d3580を見ると,0xDEADBEEFが見つかり確かに物理アドレスに変換できていることが確認できる.

f:id:snoozekvn:20200325103810j:plain

以上.

参考

babyron64.hatenablog.com

mumumu-bin.hatenadiary.jp

www.triplefault.io

rayanfam.com

https://i.blackhat.com/USA-19/Thursday/us-19-Sardar-Paging-All-Windows-Geeks-Finding-Evil-In-Windows-10-Compressed-Memory-wp.pdf

http://index-of.es/EBooks/NX-bit.pdf

https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf