実行可能ファイルがUPXなどのツールでパックされると、実際のコードとデータセクションが暗号化または難読化され、挿入された復号化スタブを使用してメモリにロードされます。これは静的解析を不可能にします。
これを回避するために、私は通常、実行可能ファイルを実行し、デバッガーをアタッチし、メモリダンプを取得し、そのダンプを使用して解凍された実行可能ファイルを生成します。残念ながら、これによりインポートアドレステーブル(IAT)が破壊されます。
IATにパッチを適用するために使用できる特定のツールを知っていますが、それらが内部でどのように機能するのかわかりません。 IATを手動で再作成するにはどうすればよいですか?
わかりました、私は答えに行くつもりだと言いましたが、約束通りここにあります。
まず、実際に遊ぶターゲットを構築したかったのです。実用的でタッチ可能な例のようなものはなく、これは非常に手動のプロセスです。だから、これ以上面倒なことをせずに、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つのメモ:
まず、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。