Undocumentedなデータ構造体を知る:リバースエンジニアリング入門(4)(2/3 ページ)
コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部)
シェルコードにおけるAPIアドレス取得方法
シェルコードでは少しトリッキーな方法でAPIのアドレスを取得しています。自由にAPIを呼び出すため、以下の3ステップの処理を実行します。
- kernel32.dllのベースアドレスを取得する
- LoadLibraryとGetProcAddressのアドレスを取得するとGetProcAddressのアドレスを取得する
- LoadLibraryとGerProcAddressを利用して任意のAPIを呼び出す
今回は、1のkernel32.dllのベースアドレスを取得する方法について詳しく見ていきます。2、3に関しては次回以降に回したいと思います。
PEBを利用したkernel32.dllのベースアドレスの取得
kernel32.dllのベースアドレスを取得するには、いくつかの方法があります。その中でも、特にシェルコードに利用されるPEB(Process Environment Block)を利用する方法について詳しく説明します。
PEBは、システム上で動作する各プロセスに1つ割り当てられるデータ構造体で、そのプロセスが利用しているヒープの情報やファイルの情報、ロードしているモジュール(DLL)の情報を保持しています。PEBは実行中のプロセスのfs:30hのアドレスに常に配置されるようにWindowsで決められています。
PEBのデータ構造体は非常に大きく、ここではすべては掲載できませんので、最初の数十バイト分を示します。左側の数字は、データ構造体の先頭からのオフセットを表しています。このPEBデータ構造体のオフセット「+0Ch」のLoaderDataに、このプロセスにロードされているモジュールの情報が双方向リンクリストの形で保持されています。
typedef struct _PEB { +00h BOOLEAN InheritedAddressSpace; +01h BOOLEAN ReadImageFileExecOptions; +02h BOOLEAN BeingDebugged; +03h BOOLEAN Spare; +04h HANDLE Mutant; +08h PVOID ImageBaseAddress; +0Ch PPEB_LDR_DATA LoaderData; +10h PRTL_USER_PROCESS_PARAMETERS ProcessParameters; +14h PVOID SubSystemData; +18h PVOID ProcessHeap; ...(以下省略)...
このLoaderDataからリンクリストをたどることで、kenrel32.dllに対応する情報を見つけ出し、そこに保持されているベースアドレスの情報を取り出します。
PEB_LDR_DATA構造体の定義を見てみると、次のようになっています。
typedef struct _PEB_LDR_DATA { +00h ULONG Length; +04h BOOLEAN Initialized; +08h PVOID SsHandle; +0Ch LIST_ENTRY InLoadOrderModuleList; +14h LIST_ENTRY InMemoryOrderModuleList; +1Ch LIST_ENTRY InInitializationOrderModuleList; } PEB_LDR_DATA, *PPEB_LDR_DATA;
また、LIST_ENTRYの定義も併せてここで挙げておきます。
typedef struct _LIST_ENTRY { struct _LIST_ENTRY *Flink; struct _LIST_ENTRY *Blink; } LIST_ENTRY, *PLIST_ENTRY;
このうちモジュールの情報を保持しているのは、InLoadOrderModuleList、InMemoryOrderModuleList、InInitializationOrderModuleListの3つのリストになります。
これら3つのリストは、いずれもプロセスにロードされているモジュールの情報を持ちますが、リストにつながれているモジュールの順番が異なります。名前の通り、ロードされた順、メモリアドレス順、初期化順となっています。
ntdll.dllやkernel32.dllなどのコアモジュールに関してはつながれている個所が決まっており、その順番を頼りに、リンクリストをたどることでkernel32.dllに関するエントリを見つけ出すことができます。hoge.exeのプロセスを例にした概念図(図6)を示します。
ここで、それぞれのリンクリストにつながれているエントリのデータ構造体、LDR_MODULEの定義を見てみましょう。このデータ構造体の中で、先頭からのオフセット「+18h」にモジュールのベースアドレスが格納されています。
typedef struct _LDR_MODULE { +00h LIST_ENTRY InLoadOrderModuleList; +08h LIST_ENTRY InMemoryOrderModuleList; +10h LIST_ENTRY InInitializationOrderModuleList; +18h PVOID BaseAddress; +1Ch PVOID EntryPoint; ……(以下省略)…… } LDR_MODULE, *PLDR_MODULE;
LDR_MODULEは3つのLIST_ENTRYを持っていますが、このリストエントリはそれぞれ対応するリンクリストが利用するLIST_ENTRYになります。具体的には図7のような形になっています(注3)。
シェルコードはこれら3つのリンクリストのうちどれかをたどって、kernel32.dllに関するエントリを探し出します。
kernel32.dllに対応するエントリを見つけた後、そのベースアドレスを取得する際、どのリストを使うかによって、ベースアドレスまでのオフセットが異なることに注意してください。InLoadOrderModuleListの場合はベースアドレスまでのオフセットは+18h、InMemoryOrderModuleListの場合は+10h、InInitializationOrderModuleListの場合はオフセット+8hとなります。
Copyright © ITmedia, Inc. All Rights Reserved.