実験:物理メモリダンプでint i = 0xDEADBEEFを探す

KDPV







簡単な実用的な例から始めると、仮想アドレス空間とアドレス変換アルゴリズムの研究がはるかに簡単になります。 これを行うには、ローカル変数のアドレスを表示する簡単なプログラムを作成します。







int main() { unsigned i = 0xDEADBEEF; std::cout << "address of i is " << std::hex << &i; std::cin.get(); //    return 0; }
      
      





次に、物理アドレスを見つけて、このアドレスの値を表示してみてください。









64ビットはより複雑なので、32ビットWindows(物理アドレス拡張なし)を検討します。 変換の説明は簡略化されていますが、実験には十分です。 virtualkaにチェックインすることをお勧めします。 どちらでも構いませんが、最後にVirtualBoxでメモリダンプをアンロードする方法を示します。







基本



私の場合、アドレスは0x22FF2Cでした。 一般に、プログラムを開始するたびに変わる可能性があります( ASLRを参照)。 これは物理アドレスではなく仮想アドレスであるため、このアドレスの他のプロセスは独自の値を持つ場合があります。 おそらく、仮想アドレス空間の主な目的は、各プロセスに独自のアドレス空間を提供し、他のプロセスに干渉しないようにすることです。 仮想アドレス空間のサイズはプラットフォームによって異なります。 x86の場合、理論上の最大サイズは4 GBです。 デフォルトでは、前半(0-0x7FFFFFFF)はユーザーのプロセススペースで、現在のプロセスの実行可能ファイルのイメージ、そのスタック、ヒープなどが含まれています。 後半(0x80000000-0xFFFFFFFF)-システム。 一部の予約では、ユーザープロセススペースは各プロセスに固有であり、システムは1つだけであると想定できます。 アドレス0x22FF2Cは、明らかに、前半に当たります。







仮想アドレス空間は、サイズがそれぞれ4096バイトの0x100000(1048576)ページに分割されます。 物理メモリは、ページブロックと呼ばれる同じサイズのページにも分割されます。 ページ(すべてではない)はページブロックにマップされるため、各ページには物理メモリ内のその場所に関する情報が必要です。 すべての0x100000ページは、PTE(ページテーブルエントリ)と呼ばれる同じ数の4バイトエントリに対応しています。 仮想空間では、アドレス範囲0xC0000000〜0xC03FFFFFにあり、ページテーブルと呼ばれる1,024ページを占有します。 レコードの取得は簡単です。k番目のページはk番目のレコードに対応します。







仮想アドレス指定

オレンジはページテーブルを示します。







 virtual_address = 0x22FF2C page_index = virtual_address / 4096 pte_addr = 0xC0000000 + page_index * 4
      
      





PTEは4バイトなので、4を掛けます。 この場合、pte_addr = 0xC00008BCになります







素朴な試み



PTEアドレスがあります。そこにあるものを見つけてください。







 std::cout << "PTE is " << std::hex << *(unsigned*)0xC00008BC;
      
      





まあ、ああ。 ハードウェア例外。 そしてすべては、システム空間から読み取ろうとしたためです。 ReadProcessMemoryも役に立ちません。 VirtualQueryを呼び出すと、PAGE_NOACCESSが通知されます。 カーネルモード権限を取得することによってのみアクセスできます。 おそらく、私たちの研究タスクの最も簡単な方法は、カーネルデバッガーを使用することです。







カーネルデバッガーの使用



KDLiveKdを配置します。 LiveKDを使用すると、Windows用のデバッグツールキットの一部であるMicrosoft KdおよびWindbgカーネルデバッガーを、ローカルモードのライブシステムで実行できます。 最後のリンクには、インストールとヘルプに関する小さなヘルプもあります。







サンプルを実行します(main.exeという名前にします)。 LiveKdを起動します。 実行中のすべてのプロセスをリストするために「 !process 0 0



」、またはすぐに「 !process 0 0 main.exe



」と!process 0 0 main.exe









 0: kd> !process 0 0 main.exe PROCESS 86530118 SessionId: 1 Cid: 0dcc Peb: 7ffdd000 ParentCid: 0428 DirBase: 2402e000 ObjectTable: 8879f430 HandleCount: 16. Image: main.exe
      
      





PROCESSという語の後のアドレスに興味があります(これは、プロセス属性を含むEPROCESS構造体のアドレスです)。 プロセスに接続しています:







 0: kd> .process 86530118 Implicit process is now 86530118
      
      





0x22FF2Cの内容をチェックして、すべてが正しく行われていることを確認します。







 0: kd> dd 22FF2C L1 0022ff2c deadbeef
      
      





デフォルトでは、16進数が使用されます。 dd



は、指定された仮想アドレスから始まるいくつかの4バイト値を表示します。 L1-1つの値のみの出力。







PTEを読む



 0: kd> dd C00008BC L1 c00008bc 6612f847
      
      





以下を考慮しないこともできました。







 dd C0000000 + (22FF2C >> 0xC) * 4 L1 c00008bc 6612f847
      
      





PTEレコード6612f847の値では、最初の20ビット(5桁の16進数)はページブロックのインデックスであり、残りはさまざまなフラグです。 ページブロックアドレスを取得するには、インデックスにブロックサイズ(4096バイト)を掛ける必要があります。







 page_block_index = 0x6612F page_block_address = page_block_index << 12 = 0x6612F000 //  4096
      
      





ページとページブロック内のバイトオーダーは同じであるため、ページ内のオフセットを計算し、ページブロックアドレスに追加する必要があります。







 virtual_adress = 0x22FF2C offset = virtual_adress & 0xFFF = 0xF2C //  hex- phisycal_address = page_block_address + offset = 0x6612FF2C
      
      





私たちはチェックします:







 0: kd> !dd 6612FF2C L1 #6612ff2c deadbeef
      
      





!dd



dd



に似ており、物理アドレスのみを受け入れます。







住所は次のように表現できることがわかりました。







 0x22FF2C = b 00000000001000101111 111100101100 20  12  page_index byte_offset
      
      





ただし、見つかったPTEは、イン​​デックス0x22Fを含む0番目のページテーブルにあります。 また、住所は次のように表すことができます。







 0x22FF2C = b 0000000000 1000101111 111100101100 10  10  12  table_idx PTE_index byte_offset
      
      





さらに深くする必要があります(PDE)



仮想PTEアドレスの使用はスポーツマンらしくありません。 結局のところ、ページブロックを見つける必要がある通常のページでもあります。 その場合、これらのページのPTEを見つけるだけです。 合計で、1024個のそのようなページ(ページテーブルと呼ばれる)があり、それらのすべてのPTEは1ページに配置されます。 このページはページディレクトリと呼ばれ、ページテーブル上のアドレスを持つ1,024エントリ(PDE-ページディレクトリエントリと呼ばれる)が含まれています。







仮想アドレス指定

テーブルディレクトリは青、ページテーブルはオレンジです。







すでに行ったのとまったく同じことを行います。







 pte_addr = 0xC00008BC page_index = pte_addr / 4096 = 0xC0000 pde_addr = 0xC0000000 + page_index * 4 = 0xC0300000
      
      





アドレスPDE = 0xC0300000を取得しました(すべてのPDEは0xC0300000のページに格納され、ゼロPDEにあります)。 内容を確認してください:







 0: kd> dd C0300000 L1 c0300000 0b21d867
      
      





まったく同じ方法で:0b21d867を含むPDEは、ページテーブルを持つページブロックのアドレス0x0B21D000を提供します。 目的のPTEを見つけるために残っています。 アドレス0x22FF2Cは、0番目のテーブルのインデックス0x22FのPTE(オフセット0x22F * 4)に対応することを思い出してください。 したがって、PTEは0x0B21D000 + 0x22F * 4にあります。







 0: kd> !dd 0b21d000 + 0x22f * 4 # b21d8bc 6612f847
      
      





すでにアドレス6612f847を使用しています。







(仮想アドレスを使用してPDEを受け取ったため)ディレクトリが物理メモリのどこにあるかを調べることは残っています。 アドレスは、コマンド「 !process 0 0 main.exe



Process !process 0 0 main.exe



」でプロセス情報を見たときにDirBaseで指定されました。 この例では、DirBase = 2402e000







 0: kd> !dd 2402e000 #2402e000 0b21d867
      
      





最終式



 0x22FF2C = b 0000000000 1000101111 111100101100 10  10  12  PDE_index PTE_index byte_offset pde_addr = DirBase + PDE_index * 4 pte_addr = ((*pde_addr) & 0xFFFFF000) | (PTE_index * 4) value_addr = ((*pte_addr) & 0xFFFFF000) | byte_offset
      
      





ダンプを見る



実行中のシステムからダンプを削除するのは多少問題があると思うので、VirtualBoxから削除しましょう。 これを行うには、デバッグモードで実行します。







 VirtualBox.exe --dbg --startvm VM_name
      
      





メニューの[デバッグ]-> [コマンドライン...]を選択して、次のように入力します。







 .pgmphystofile "path_to_dump_file"
      
      





ファイルを開き(HxDを使用)、6612ff2cに移動します。







ダンプビュー







DirBaseと仮想アドレスがわかれば、デバッガーなしでダンプ内の値をすぐに検索できます。 一般に、ダンプではプロセスの名前でDirBaseの値を見つけることができますが、それは別の話です。








All Articles