web-dev-qa-db-ja.com

パックされた実行可能ファイルのIATを再構築するにはどうすればよいですか?

実行可能ファイルがUPXなどのツールでパックされると、実際のコードとデータセクションが暗号化または難読化され、挿入された復号化スタブを使用してメモリにロードされます。これは静的解析を不可能にします。

これを回避するために、私は通常、実行可能ファイルを実行し、デバッガーをアタッチし、メモリダンプを取得し、そのダンプを使用して解凍された実行可能ファイルを生成します。残念ながら、これによりインポートアドレステーブル(IAT)が破壊されます。

IATにパッチを適用するために使用できる特定のツールを知っていますが、それらが内部でどのように機能するのかわかりません。 IATを手動で再作成するにはどうすればよいですか?

10
Polynomial

わかりました、私は答えに行くつもりだと言いましたが、約束通りここにあります。

まず、実際に遊ぶターゲットを構築したかったのです。実用的でタッチ可能な例のようなものはなく、これは非常に手動のプロセスです。だから、これ以上面倒なことをせずに、MSVCでこれを/MTでコンパイルしました

#define WINVER 0x501
#define _WIN32_WINNT   0x0501
#define _WIN32_WINDOWS 0x0501
#define _WIN32_IE      0x0501
#define UNICODE

#include <wchar.h>
#include <windows.h>

int APIENTRY wWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    MessageBox(NULL, L"Hello, world.", L"A messagebox", MB_OK | MB_ICONEXCLAMATION);
    return 0;
}

私たちは、この獣がuser32.dll!MessageBoxWをインポートすることを十分に期待しており、実際にそれを確実にインポートします。

実行可能ファイル自体の中でこれを見つけるには、WinDbgを使用します。 WinDbgを使用する理由は2つあります。1つはIDAを購入できないこと、もう1つはカーネルドライバーに使用するデバッガー(IDAを使用する場合でも、そのエンジンを使用するもの)と同じであるため、少し直感的ではないものの、優れています。

UPXはWin64をサポートしていないため、実行可能ファイルをx86アーキテクチャ用のUnpackSimple.exeにコンパイルしました。これで、WinDbgでこれを実行したときに最初に気付くのは次の行です。

ModLoad: 01360000 01370000   UnpackSimple.exe

そこに、UnpackSimple.exeがマップされているアドレス範囲が表示されます。ヘッダーのすべてのアドレスは01360000を基準にしているため、これは重要です。

ヘッダー情報を見つけるには、次のようにリクエストするだけです。

!dh UnpackSimple.exe
OPTIONAL HEADER VALUES

    10B magic #
   11.00 linker version
    5E00 size of code
    6C00 size of initialized data
       0 size of uninitialized data
    1193 address of entry point
    1000 base of code
         ----- new -----
01360000 image base            <--- this is the image base address.
    1000 section alignment
     200 file alignment
       2 subsystem (Windows GUI)
    6.00 operating system version
    0.00 image version

    ... blah blah blah ....

   0 [       0] address [size] of Export Directory
8D54 [      3C] address [size] of Import Directory
D000 [     1E0] address [size] of Resource Directory
   0 [       0] address [size] of Exception Directory
   0 [       0] address [size] of Security Directory
E000 [     6D0] address [size] of Base Relocation Directory
7150 [      38] 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
89D8 [      40] address [size] of Load Configuration Directory
   0 [       0] address [size] of Bound Import Directory
7000 [     108] address [size] of Import Address Table Directory
   0 [       0] address [size] of Delay Import Directory
   0 [       0] address [size] of COR20 Header Directory
   0 [       0] address [size] of Reserved Directory

ああ、IAT 1つです。それは01360000+7000にあり、WinDbgを説得して、その情報をかなり単純な方法で提供することができます。これから使用するdpsコマンドはdump pointers, dereferencing with symbolsの略で、引数アドレスとエントリアドレスごとのスペースを使用します。

dps 01360000+7000 L108/4
01367000  75b3d7ea kernel32!TerminateProcessStub
01367004  75b23f3c kernel32!CreateFileWImplementation
01367008  75b2520b kernel32!GetCommandLineWStub
... <snip> ...
013670f4  75b47ab2 kernel32!WriteConsoleW
013670f8  75b21410 kernel32!CloseHandleImplementation
013670fc  00000000
01367100  76bafd3f USER32!MessageBoxW
01367104  00000000

IATのいくつかの重要な機能をここで見ることができます。最初は、左側のアドレス-IATエントリアドレスです。左側のアドレスはDLLのメモリ内アドレスです。最後に、いくつかのゼロのエントリがあることに注意してください。各DLLインポートはゼロエントリで終了し、ロードするエントリがもうないことをローダーに通知します。

ここで、メイン関数の分解を見てみましょう。これを行うには、次の2つのコマンドを入力します

bp UnpackSimple!wWinMain
g

次に、上記のwWinMainへのエントリで中断します。さて、よく見ると:

UnpackSimple!wWinMain:
    01361000 6a30            Push    30h
    01361002 68a0893601      Push    offset UnpackSimple!`string' (013689a0)
    01361007 68bc893601      Push    offset UnpackSimple!`string' (013689bc)
    0136100c 6a00            Push    0
    0136100e ff1500713601    call    dword ptr [UnpackSimple!_imp__MessageBoxW (01367100)]
    01361014 33c0            xor     eax,eax
    01361016 c21000          ret     10h

当然のことながら、Nice IATエントリを参照する関数_imp__MessageBoxWがあります。

では、問題は、いったいどのように後退するかということです。さて、まず機能しないものを最初にビルドするには、パックされた実行可能ファイルを取得する必要があります。

そのために、upx.exe -o UnpackSimplePacked.exe UnpackSimple.exeを実行して、Niceパックバイナリを生成しました。このバイナリをWinDbgにロードすると、最初に気付くのは次のことです。

!dh UnpackSimplePacked.exe

まったく何もしません。ただし、次のことに注意してください。

ModLoad: 01220000 01233000   image01220000

私たちが必要とする画像はメモリにあります。オンディスクイメージとしては存在しません。この段階では、NTローダーを使用するのではなく、upxが実際にDLLを直接ロードしていると思います。これは2つの方法で証明できます。最初に、WinDbgにsxe ldと入力すると、すべてのモジュールロードで中断します。 user32.dllのブレークは発生せず、すでにロードされていません。

第二に、あなたが見たかったように:

    0 [       0] address [size] of Import Address Table Directory

それは完全に空です。

ただし、読み込まれたモジュールのリストを確認すると、次のようになります。

lm
start    end        module name
00210000 00223000   image00210000   (deferred)             
74c20000 74c2c000   CRYPTBASE   (deferred)             
74c30000 74c90000   SspiCli    (deferred)             
74d80000 74e20000   ADVAPI32   (deferred)             
74eb0000 74f4d000   USP10      (deferred)             
74f50000 74f5a000   LPK        (deferred)             
75030000 750dc000   msvcrt     (deferred)             
755a0000 755b9000   sechost    (deferred)             
75720000 757b0000   GDI32      (deferred)             
757e0000 758d0000   RPCRT4     (deferred)             
75b10000 75c20000   kernel32   (deferred)             
76ab0000 76af7000   KERNELBASE   (deferred)             
76b40000 76c40000   USER32     (deferred)             
77550000 776d0000   ntdll      (pdb symbols) 

ご覧のとおり、USER32にはすぐに使用できるスペースが割り当てられています。

ここでいくつかの策略があります。 UnpackSimple.exeバージョンで別のWinDbgウィンドウを開いたところ、user32!MessageBoxWのアドレスが見つかりました。これを取得すると、76bafd3fのベースオフセットから76b40000であることがわかり、6FD3Fのオフセットが得られました。パックバージョンでは、bp 76b40000+6FD3Fにブレークポイントを設定しましたが、スタックトレースがありました。

k
003cfd0c 013a1014 USER32!MessageBoxW
WARNING: Stack unwind information not available. Following frames may be wrong.
003cfd6c 75b233aa image013a0000+0x1014
003cfd78 77589ef2 kernel32!BaseThreadInitThunk+0xe
003cfdb8 77589ec5 ntdll!__RtlUserThreadStart+0x70
003cfdd0 00000000 ntdll!_RtlUserThreadStart+0x1b

image0...の場所にある逆アセンブリは次のようになります。

013a100e ff1500713a01    call    dword ptr [image013a0000+0x7100 (013a7100)]
013a1014 33c0            xor     eax,eax
013a1016 c21000          ret     10h

今、私たちはどこかに着いています!そのアドレスでいくつかの興味深いデータを見つけるでしょう:

db 013a7100
013a7100  3f fd ba 76 00 00 00

おかしなことに、私たちの住所があります!古き良きリトルエンディアンの76bafd3f。少し後戻りしましょう:

db 013a7098
013a7098  a0 22 57 77 60 22 57 77-c9 14 b2 75 ff 10 b2 75  ."Ww`"Ww...u...u
013a70a8  7b 44 b2 75 9c 17 b2 75-91 d1 b4 75 71 51 b2 75  {D.u...u...uqQ.u
013a70b8  45 49 b2 75 c4 d1 b4 75-13 49 b2 75 b3 d1 b4 75  EI.u...u.I.u...u
013a70c8  46 e0 57 77 f1 24 59 77-0d 17 b2 75 46 19 b2 75  F.Ww.$Yw...uF..u
013a70d8  2a 30 58 77 61 48 ba 75-83 46 b2 75 07 7c bc 75  *0XwaH.u.F.u.|.u
013a70e8  28 13 b2 75 bf 45 ba 75-ef c7 b3 75 b2 7a b4 75  (..u.E.u...u.z.u
013a70f8  10 14 b2 75 00 00 00 00-3f fd ba 76 00 00 00 00  ...u....?..v....

ポインタが見えます!

これは、以前に見つけたIAT、または少なくともIMAGE_IMPORT_DESCRIPTORサンクの配列です。

これまでのところ、何が残っているのでしょうか?まあ、既知のWindows DLLと一致する関数を見つけるために、ダンプされたメモリを検索する必要があります。そこから、新しいPEファイルの完全なIATの再構築を開始できます。プロセスは疑わしい Vivの回答で説明されているように、ImpRecが通過する は、手動で行ったようにこれらのエントリを見つけようとすることです。私はブレークポイントを使用することを選択しましたが、これを異なる方法で自動化できる方法はいくつかあると思います。手作業でプロセスを実行する自動化ツールを書いたことはありません...!とはいえ、推測でcall ptr命令をフーバーし、マップされた領域の外でそれらのエントリを調べ、既知のDLLオフセットに対してそれらを見つけようと試みると、トリックが実行されます。

2つのメモ:

  1. この答えは、「このツールを使用する」ではなく、「ここに手動で攻撃する方法」タイプの回答であり、関連する背景が含まれています。
  2. ASLRに注意してください。これらのモジュールアドレスは変化し続けるので、モジュールベースの変更に注意してください!
12
user2213

まず、pe形式の一般的な概要については、マイクロソフトから提供されている pecoff ファイル形式を読むことをお勧めします。ほとんどのパッカーによって、インポートテーブルが部分的または完全に破壊されます。 Imprecは通常、IAT(インポートアドレステーブル)の再構築に推奨される選択肢ですが、詳細を知りたい場合は、この優れた論文を読むことができます 悪意のあるソフトウェアで使用されるPEパッカー のPaul Craig氏セキュリティ評価チーム。オープンソースツールを試すこともできます scylla

Windows実行可能ファイルでは、インポートテーブルは、そのイメージのすべてのインポート情報(DLLの名前とアドレススペースで参照される関数)を含むテーブル(データディレクトリ)です。

IMAGE_IMPORT_DESCRIPTORの形式は次のとおりです。

struct IMAGE_IMPORT_DESCRIPTOR {
DWORD   OriginalFirstThunk; //same as FirstThunk
DWORD   TimeDateStamp;
DWORD   ForwarderChain;     //usually set to 0
DWORD   Name;               //name of the dll
DWORD   FirstThunk;         //before loading,contains the pointer to the api name thunk array 
};

インポートされた記述子ごとに1つの記述子がありますDLLそして最後の記述子はNULLで構成されます。Windowsローダーは参照されたすべてのDLLをロードします(各IMAGE_IMPORT_DESCRIPTORに対して1つのDLLを覚えてください)構造体の名前フィールドを確認して、実行可能ファイルで。次に、次の方法でIATを構築しようとします。FirstThunkが指す配列内の名前ごとに、そのアドレスをAPIの実際のアドレスに置き換えます。そのAPI名のアドレスがFirstThunkに見つからない場合、OriginalFirstThunkに移動してそこから情報を取得しようとします(FirstThunkのバックアップと見なしてください)。上書きされたFirstThunkがIATです(これはすべて、命令ポインターの前に発生します。つまり、eipがexeのエントリポイントに到達します)。

新しいインポートテーブルを作成する(または古いテーブルを修正する)間、Imprecは最初にコードまたはユーザーが提供する値を自動的に検索してIATを見つけます。その後、新しいIATからAPI名を見つけますアドレスを入力してIMAGE_IMPORT_DESCRIPTORsに入力します。これにより、ローダーがインポートテーブル内のすべての情報を検索できるようになるため、作業が簡単になります。これは lena's チュートリアル番号で視覚的に詳しく説明されています。 21。

5
viv