
HintInject は、私が PE ファイルと Import Directory Table 構造をいじりながら開発したシェルコード埋め込み兼ローダーです。生のシェルコードファイルを受け取り、シェルコードをチャンクに分割して Hint/Name Table エントリ(ローダー実行ファイル上の偽のインポート DLL エントリの Import Lookup Table から到達可能)に配置します。その後、ローダーがこれらのチャンクをマージしてシェルコードを実行します。これが新しい手法かどうかはわかりませんが、面白いので共有したいと思います。
HintInject の仕組みを説明する前に、Stack Overflow の投稿を参考にしながら、DLL の読み込みプロセスについて簡単に触れておきます。
必要な DLL と関数/インポートに関する情報を取得するには、最初に Import Directory Table を確認する必要があります。Import Directory Table はエントリのテーブルで、インポートされる DLL ごとに 1 つのエントリがあります。これらのエントリは、インポートされる DLL 名へのポインタ、Import Lookup Table へのポインタ、Import Address Table へのポインタ、およびその他のさまざまな情報のためのフィールドを保持します。
簡単に言うと、Import Lookup Table はインポートに関する情報を指し示し、Import Address Table はインポートのアドレスを指し示します。ただし、実行ファイルがディスク上にある場合、または DLL ローディングプロセスの直前では、Import Address Table は Import Lookup Table と同一です。Import Address Table の内容は、DLL ローディングプロセス中にインポートのアドレスで上書きされます。
詳細には立ち入らずに、以下の図を見て、これら 3 つのテーブルの関係を確認できます。
もう少し詳しく見ると、Import Lookup Table はインポートの名前を直接保持しているわけではありません。名前でインポートされる関数の場合、Hint/Name Table エントリの RVA を保持します。これらのエントリは、関数名を NULL 終端の ASCII 文字列として格納します。したがって、Hint/Name Table エントリの構造体定義は次のようになります。
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
ここでの Hint フィールドは、実際には DLL のエクスポート名ポインタテーブルへのインデックスです。これは、そのインポートの位置を高速に見つけるために使用されます。したがって、Import Lookup Table と Hint/Name テーブルの関係は、以下の図でまとめられます。
まとめると、DLL とそのインポートを見つけるには、次の手順に従う必要があります。
最近、x86matthew が共有した ImportDLLInjection テクニックを調べているときに、メモリに読み込まれた DLL の Import Directory に偽のエントリを追加する方法を学びました。その後、楽しみと知識のリフレッシュを兼ねて、ディスク上のバイナリのインポートテーブルに直接偽のエントリを追加する小さなプロジェクトを開発したいと思いました。このプロジェクトを開発するために DLL ローディングプロセスを再確認していると、このプロセスで使用される Hint/Name Table エントリの Hint フィールドが目に留まりました。
MSDN のドキュメントによると、このフィールドは Windows ローダーが、名前でインポートされた関数のアドレスを、その関数が含まれる DLL のエクスポート名テーブルから直接見つけるために使用されることがわかりました。ただし、同じドキュメントには、このフィールドを使用して関数が見つからなかった場合、その関数は DLL のエクスポート名テーブル内で二分探索によって検索されるとも記載されていました。この記述に基づき、このフィールドに不正な値を入れても DLL ローディングプロセスは中断されないと考えました。
上で述べたように、インポートされる関数ごとに Hint/Name テーブルへのエントリが 1 つあります。つまり、インポートごとに 2 バイトを使用できることになります。複数のインポートを組み合わせることで、悪意のあるシェルコードを格納するのに十分な Hint フィールドを確保できます。その結果、偽のインポート DLL エントリの Hint/Name エントリにシェルコードを埋め込んだローダーバイナリを作成できます。そのローダーバイナリは、実行時にシェルコードをマージするためにこれらのエントリにアクセスできます。
HintInject は、Hint/Name テーブルにシェルコードを保持するようなローダーを作成するために使用できます。まず、.rrdata という名前の新しいセクションを作成し、現在のインポートディレクトリをこのセクションにコピーします。その後、インポートが入力シェルコードを保持するために使用される新しい偽のエントリを追加します。セクションの残りのバイトは、新しい偽のエントリの Import Lookup Table、Import Address Table、DLL 名、および Hint/Name テーブルを格納するために使用されます。最終ステップとして、HintInject はインポートの Hint フィールドを使用して入力シェルコードのチャンクを配置します。
ローダーバイナリのインポートの例:
HintInject.exe を、生の形式のシェルコードと出力パスを指定して実行し、シェルコードローダーを作成します: HintInject.exe <Shellcode File> <Output Name>Loader.exe <PID>HintInjectLoader/Main.cpp ファイル内の InjectShellcode 関数を変更することで、シェルコードの注入方法を変更できます。Windows 10 19044 および Visual Studio 2019 でテスト済み:
DllNamesForFakeImports.h ファイルの dllNames 配列に他の DLL を追加できます。DllNamesForFakeImports.h ファイルの dllNames 配列も編集する必要があります。デフォルトでは、HintInject は配列の順序に従って DLL から関数を選択します。つまり、ツールは最初に user32.dll からエクスポートされた関数を選択し、シェルコードのサイズが 2 * user32.dll のエクスポート数 より大きい場合は、advapi32.dll などのエクスポートも使用し始めます。static LPCSTR dllNames[] = {"user32.dll","advapi32.dll","gdi32.dll","wininet.dll","comctl32.dll","shell32.dll","wsock32.dll","oleaut32.dll","ws2_32.dll","urlmon.dll"};
許可されたセキュリティテストにのみ使用してください。明示的な許可なくシステムに対してこのツールを悪用することは違法です。