API名のハッシュ化テクニックを理解せよ!リバースエンジニアリング入門(6)(3/3 ページ)

» 2012年04月19日 00時00分 公開
[青木一史日本電信電話株式会社]
前のページへ 1|2|3       

前回の復習をしながらAPIのアドレスにたどり着こう!

 モジュール名のハッシュ値がどのように取得されているかが分かったら、シェルコードの続きを読み進めましょう。

24:	                mov     eax, [edx+3Ch]	; IMAGE_DOS_HEADER.e_lfanewをeaxに代入
25:	                add     eax, edx	; RVAをVAに変換
26:	                mov     eax, [eax+78h]	; IMAGE_OPTIONAL_HEADER32.DataDirectory[0].VirtualAddressをeaxに代入
27:	                test    eax, eax 
28:	                jz      short loc_89	; エクスポート関数に関する情報がなければloc_89にジャンプ
29:	                add     eax, edx	; RVAをVAに変換
30:	                push    eax
31:	                mov     ecx, [eax+18h]	; NumberOfNamesの値をecxに代入
32:	                mov     ebx, [eax+20h]	; AddressOfNames(RVA)を代入
33:	                add     ebx, edx	; AddressOfNamesのRVAをVAに変換
34:	loc_4A:
35:	                jecxz   short loc_88	; ecxが0ならloc_88にジャンプ(全エクスポート関数名のチェックが終わるとecxは0になる)
36:	                dec     ecx		; ecxを1減算
37:	                mov     esi, [ebx+ecx*4]; エクスポート関数名の文字列へのポインタ(RVA)をesiに代入
38:	                add     esi, edx	; RVAをVAに変換
39:	                xor     edi, edi	; ediを0に初期化
	……(省略)……
66:	loc_88:
67:	                pop     eax
68:	loc_89:
69:	                pop     edi
70:	                pop     edx
71:	                mov     edx, [edx]	; 次のモジュールのLDR_MODULEのアドレスを取得
72:	                jmp     short loc_15	; loc_15にジャンプし、次のモジュールに対して処理を実施

 この部分の処理は、前回解説した内容を思い出せばすぐに理解できるでしょう。PEフォーマットを解釈してエクスポート関数に関する情報にたどり着いたら、AddressOfNamesを順番にたどってエクスポートしている関数名を読み込む、という処理です。

 モジュールにエクスポート関数がない場合や、モジュールの全エクスポート関数に対して処理が終わった場合には、LDR_MODULEのLIST_ENTRYのFlinkを読み込み、次のモジュールに対して処理を実施するようになっています。

 ちなみに、67行目のpop命令は30行目のpush命令に対応しています。これは、IDAでスタックポインタを表示する設定にしておけばすぐに確認できます。67行目のpop命令では、スタックポインタは0x2Cとなっています。

 IDAの逆アセンブリ画面をみると、31行目からスタックポインタが0x2Cになっているのが分かります。この直前のpush命令で値がスタックに積まれたからです。従って、67行目のpop命令は30行目のpush命令で積まれた値を取り出している、と読み取ることができるのです(図1)。

 それでは、残りの部分を読み解いてみましょう。ここから先も、大半は前回の内容の復習です。

40:	loc_54:
41:	                xor     eax, eax		; eaxを0に初期化
42:	                lodsb				; eaxにエクスポート関数名をeaxに1バイト読み込む
43:	                ror     edi, 0Dh		; ediの値を13ビット右にローテート
44:	                add     edi, eax		; ediの値にeaxを加える
45:	                cmp     al, ah
46:	                jnz     short loc_54		; 文字列のすべての文字を処理するまでloc_54を繰り返す
47:	                add     edi, [ebp-8]		; API名のハッシュ値(edi)にモジュール名のハッシュ値([ebp-8])を加える
48:	                cmp     edi, [ebp+24h]		; 呼び出したいAPIのハッシュ値と比較
49:	                jnz     short loc_4A		; 違っていたらloc_4Aに戻り、別のAPI名のハッシュ値を求める
50:	                pop     eax			; IMAGE_EXPORT_DIRECTORYのアドレスをスタックから取り出してeaxに代入
51:	                mov     ebx, [eax+24h]		; 序数テーブルのRVAをebxに代入
52:	                add     ebx, edx		; RVAをVAに変換
53:	                mov     cx, [ebx+ecx*2]		; 対象となるAPIの序数をcxに代入
54:	                mov     ebx, [eax+1Ch]		; エクスポートアドレステーブルのRVAをebxに代入
55:	                add     ebx, edx		; RVAをVAに変換
56:	                mov     eax, [ebx+ecx*4]	; 序数を基にエクスポートアドレステーブルから対象となるAPIのアドレス(RVA)を取得
57:	                add     eax, edx		; RVAをVAに変換
58:	                mov     [esp+28h+var_4], eax	; APIのエントリポイントのアドレスをスタック上(後のpopaを実行したときにeaxに代入されるような位置)に書き込み
59:	                pop     ebx			; スタックポインタの巻き戻し
60:	                pop     ebx			; スタックポインタの巻き戻し
61:	                popa				; スタックに積んであった汎用レジスタの値を戻す
62:	                pop     ecx			; 関数呼び出し時にスタックに積んだreturn addressをecxに取り出す
63:	                pop     edx			; 関数呼び出し時に引数として積んでいたAPIのハッシュ値を取り出す
64:	                push    ecx			; return addressをもう一度スタックに戻す
65:	                jmp     eax			; 目的のAPIを呼び出す

 41行目から46行目は、モジュール名のハッシュ値を求めたとき(11行目〜20行目)と同じように、ハッシュ値を格納するレジスタであるediを13ビット右にローテートし、文字列のデータを1バイトずつ取り出してediに加える、という処理を繰り返しています。ハッシュ値を取得する対象がモジュール名からAPI名に変わっただけです。ただ、API名はワイド文字列ではないことに注意しておいてください。

 47行目では、先に求めておいたモジュール名のハッシュ値とAPI名から求めたハッシュ値の和を計算しています。この2つのハッシュ値の和が、この関数の引数として設定されているハッシュ値と一致する場合、IMAGE_EXPORT_DIRECTORYの序数テーブルとエクスポートアドレステーブルをたどり、呼び出したいAPIのエントリポイントのアドレスを取得します(50行目〜57行目)。IMAGE_EXPORT_DIRECTORYをたどる部分については前回解説したのと同じ方法です。

 最終的には65行目のjmp eaxでAPIを呼び出すのですが、その前に、この関数で操作してきたスタックを巻き戻しています(59行目〜61行目)。

 なお、popa命令を実行したときにAPIのエントリポイントのアドレスがeaxに代入されるように、あらかじめスタック上にAPIのアドレスを配置しています(58行目)。また、APIの呼び出し前に、この関数の引数としてスタックに積んであったAPIのハッシュ値を取り除き、あらかじめスタックに積んでおいたAPI自体が使う引数を使えるように処理しています(62行目〜64行目)。

ハッシュ値をIDAに読み込ませて解析しよう!

 ここまでで、どのようにしてハッシュ値が計算されるのかは理解できたかと思います。

 解析を進めるに当たり、まずは先ほど読み解いたハッシュ値計算アルゴリズムを基に、ハッシュ値とAPI名を対応させたC言語形式の列挙型データを作りましょう。IDAでは、C言語のヘッダファイルを読み込ませることで解析を行いやすくできます。

 列挙型データを定義したファイルを作成するPythonのサンプルスクリプト“get_hash.py”はこちらです。このサンプルスクリプトでは、シェルコードで比較的使われることが多いkernel32.dll、ws2_32.dll、urlmon.dllのエクスポート関数を確認し、ハッシュ値を計算します。計算した結果は、C言語のヘッダ形式のファイルとして出力します。

 もし、これ以外のDLLがエクスポートしているAPIのハッシュ値が必要な場合は、いったんサンプルスクリプトで取得したAPIのハッシュ値を使ってシェルコードの解析を進め、LoadLibrary関数の呼び出し個所で引数を確認し、追加で必要となるDLLを把握しましょう。その後、必要なDLLについてもハッシュ値を取得すれば問題ありません。

 このサンプルスクリプトを実行すると、次のように“hash_(モジュール名)_(API名)”=“ハッシュ値”というデータで構成される列挙型データの定義が作成されます。出力結果は後で使うので、リダイレクトしてファイルに保存しておきます。

C:\work\python get_hash.py
enum API_HASH{
        hash_kernel32_AcquireSRWLockExclusive=0xFD8452C6,
        hash_kernel32_AcquireSRWLockShared=0x74FE289C,
……中略……
        hash_urlmon_WriteHitLogging=0x40924BA1,
        hash_urlmon_ZonesReInit=0xE6ECC7FF
};

 この出力結果をIDAに読み込ませるには、メニューから[File]→[Load file]→[Parse C header file…]とたどり、先ほど保存しておいたファイルを選びます。このとき、「Please setup the compiler options in Option->Compiler first」という警告が表示されたら、指示されたとおり、[Option]→[Compiler]とたどってコンパイラの設定をします。CompilerがUnknownになっていると思いますので、今回はとりあえずVisual C++を選んでおきます。

 サンプルスクリプトで作成したヘッダファイルを正しく読み込ませることができたら、このデータを解析に活用できるよう、IDAの列挙型データ(enum)として登録します。

 登録の方法ですが、まず、Enumsタブを選び、Insertキーを押すか、左上の「Define a new enumeration type」ボタンをクリックします。そうすると「Add enum type」というウィンドウが出てくるので、ここで「Add standard enum by enum name」ボタンをクリックし、「Please chose a enum」というウィンドウを表示させます。ここに、先ほど読み込ませたヘッダファイルに記載してあったenumのエントリがありますので、選んで「OK」を押しましょう(図5)。

図5 読み込んだデータをIDAのenumに登録する(クリックすると拡大します) 図5 読み込んだデータをIDAのenumに登録する(クリックすると拡大します)

 IDAのenumへの登録が成功すれば、図6のように、モジュール/API名に対応するハッシュ値の一覧がEnumsウィンドウで確認できるはずです(登録されたenumデータが折りたたまれて表示されている場合がありますが、そのときはenumのエントリを選んで「+」キーを押すと展開されます)。

図6 IDAのenumに登録されたAPI名とハッシュ値(クリックすると拡大します) 図6 IDAのenumに登録されたAPI名とハッシュ値(クリックすると拡大します)

 それでは、これらの値を逆アセンブリウィンドウでの解析に活用しましょう。

 まずは、APIのハッシュ値を選択して、右クリックからコンテキストメニューを表示します。ハッシュ値と合致する値がenumにあれば、コンテキストメニューの[Symbolic constant]の先に、ハッシュ値に該当する[hash_(モジュール名)_(API名)]という文字列が現れます。これを選ぶことで、逆アセンブリウィンドウ中のハッシュ値が「hash_(モジュール名)_(API名)」という名前に変わります(図7)。

図7 コンテキストメニューの[Symbolic constant]の先に、ハッシュ値に該当する文字列が現れる(クリックすると拡大します) 図7 コンテキストメニューの[Symbolic constant]の先に、ハッシュ値に該当する文字列が現れる(クリックすると拡大します)

 すべてのハッシュ値についてenumから読み込んだ名前に変更しておけば、これまで逆アセンブリの画面を見ていただけでは分からなかった、呼び出そうとしているAPIを容易に把握できるようになります。

shell_bind_tcpがやろうとしていることを解明しよう!

 今回は、ハッシュ値を使ってAPIを呼び出す方法を解説しました。ですが、shell_bind_tcpのシェルコードが全体としてどのような動作をするのかについては説明していません。

 第1回から今回までの内容をしっかりと身に着けることができれば、きっとこのshell_bind_tcpというシェルコードの動作も理解できるはずです。ぜひ、shell_bind_tcpがどのような動作をするのかを自分の手で確かめてみてください。

 次回もお楽しみに!

筆者紹介

NTT情報流通プラットフォーム研究所 研究員

青木 一史(あおき かずふみ)

2006年 日本電信電話株式会社入社。セキュアプラットフォーム研究所(SC研)所属。入社以来、ハニーポット技術やマルウェア解析技術の研究開発に従事。最近はマルウェアの動的解析技術に関する研究を行っている。「アナライジング・マルウェア」の著者。



前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。