検索
連載

Undocumentedなデータ構造体を知るリバースエンジニアリング入門(4)(2/3 ページ)

コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部)

PC用表示 関連情報
Share
Tweet
LINE
Hatena

シェルコードにおけるAPIアドレス取得方法

 シェルコードでは少しトリッキーな方法でAPIのアドレスを取得しています。自由にAPIを呼び出すため、以下の3ステップの処理を実行します。

  1. kernel32.dllのベースアドレスを取得する
  2. LoadLibraryGetProcAddressのアドレスを取得するとGetProcAddressのアドレスを取得する
  3. 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)を示します。

図6 リンクリストの構造
図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)。

図7 リンクリストのデータ構造の関係(クリックすると拡大します)
図7 リンクリストのデータ構造の関係(クリックすると拡大します)

 シェルコードはこれら3つのリンクリストのうちどれかをたどって、kernel32.dllに関するエントリを探し出します。

 kernel32.dllに対応するエントリを見つけた後、そのベースアドレスを取得する際、どのリストを使うかによって、ベースアドレスまでのオフセットが異なることに注意してください。InLoadOrderModuleListの場合はベースアドレスまでのオフセットは+18h、InMemoryOrderModuleListの場合は+10h、InInitializationOrderModuleListの場合はオフセット+8hとなります。

注3:図7は説明を簡略化するための概念図です。正確には、同じモジュールのエントリは同じデータ領域を指しています。

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る