検索
連載

Undocumentedなデータ構造体を知るリバースエンジニアリング入門(4)(3/3 ページ)

コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部)

PC用表示 関連情報
Share
Tweet
LINE
Hatena
前のページへ |       

レッツ逆アセンブル!

 さて、やっとここからアセンブリコードの解析に入ります。上で説明した知識を頭に入れておくと、今回のアセンブリコードはスラスラ読めると思います。

 それでは今回も、前回までと同様に、Metasploitで生成できるdownload_exec.binのシェルコードを解析対象とします(注4)。第3回目でdownload_exec.binのデコーダ部分の解析を行いましたので、今回はデコード処理後のシェルコードを見ていきます。

 まず前準備として、fsレジスタの値を修正します。デフォルトの設定だとfsレジスタが0x00000000を指しているため、0000001Dで「fs:30h」と表示したいところを、IDAが自動的に紛らわしい参照を作ってしまい、「fs:loc_2F+1」と表示してしまいます(図8)。

図8 修正前のfsレジスタ(クリックすると拡大します)
図8 修正前のfsレジスタ(クリックすると拡大します)

 fsレジスタの値を修正して、この紛らわしい参照を消します。メニューの[Edit]→[Segments]→[Set default segment register value]を選び、fsの値を0から、無効な値0xFFFFFFFFに修正してください(図9)。

図9 fs修正ダイアログ
図9 fs修正ダイアログ

 これで「fs:[30h]」となると思います(図10)。

図10 修正後のfs(クリックすると拡大します)
図10 修正後のfs(クリックすると拡大します)

 それではアセンブリコードの解析に入ります。まずは少しトリッキーな動作からです。

loc_17:
                  jmp     loc_F1		;loc_F1にジャンプ
  sub_1C          proc near
                  pop     edx
                  mov     eax, large fs:30h
  ……
  loc_F1:
                  call    sub_1C

 loc_17(00000017)ではloc_F1にジャンプしています。そして、loc_F1(000000F1)を見てみると、すぐにcall命令でsub_1Cに戻ります。

 loc_F1の周辺でIDA proのメモリダンプ画面(HexView-Aのタブ)を見てみてください。「GetProcAddress」や「WinExec」などの文字列が確認できると思います(図11)。

図11 loc_F1の周辺のメモリダンプ(クリックすると拡大します)
図11 loc_F1の周辺のメモリダンプ(クリックすると拡大します)

 これは、call sub_1C命令より下の領域では、コードではなくデータ(文字列)が格納されているためです。

 ここで可読性を高めるために、データ部分を逆アセンブリのウィンドウで文字列として定義します。

 まずは、コードとして解釈されている部分をUndefinedします(注5)。000000F6にカーソルを合わせて、キーボードの「u」を押してください。コードとしての解釈が解かれて、単なるバイト列になったと思います。

 次にこれを文字列として解釈させていきます。000000F6にカーソルを合わせてキーボードの「a」を押してください。“GetProcAddress”という文字列がひとまとまりになりました(図12)。

図12 文字列として解釈させた様子(クリックすると拡大します)
図12 文字列として解釈させた様子(クリックすると拡大します)

 以下、「a」を押して文字列として解釈できるところを全部変換してください。

図13 すべての変換が終わった様子(クリックすると拡大します)
図13 すべての変換が終わった様子(クリックすると拡大します)

 この文字列を見ているだけで、ある程度シェルコードの動作は予想できますが、ここでは詳細を追っていきたいと思います。アセンブリコードのsub_1Cに戻ります。

sub_1C          proc near 
                pop     edx                 	;スタックに積んだアドレス(文字列データの先頭アドレス)を取得
                mov     eax, large fs:30h	;PEBのアドレスを取得
                mov     eax, [eax+0Ch] 	      ; LoaderDataのアドレスを取得
                mov     esi, [eax+1Ch]		;InInitializationOrderModuleListのアドレスを取得(ntdll.dllのエントリ取得)
                lodsd    	                ;ntdll.dllのLDR_MODULEのエントリ内のInInitializationOrderModuleList.Flink(kernel32.dllのLDR_MODULEエントリへのポインタ)を取得

 まず、最初のpop edxは000000F1で、call sub_1Cが呼ばれた時に積まれたリターンアドレス(000000F6)を取り出して、edxに格納しています。このedxは後ほど利用します(注6)。

 次に、fs:30hの値をeaxに格納しています。これは、前で説明したとおりPEBデータ構造体へのポインタになります。ここから先は、先ほどの図7を見ながら読むと分かりやすいかもしれません。

 このPEBデータ構造体のオフセット+0Chのアドレスに格納されている値を、eaxに格納しています。これは先ほどのデータ構造体の定義で確認すると、LoaderDataに相当します。

 続いて、LoaderDataのオフセット+1Chのアドレスに格納されている値を、esiに格納します。この値はInInitializationOrderModuleListに相当します。InIntializationOrderModuleListはnttdll、kernel32.dll……の順番で、リンクリストの形で各モジュールの情報を保持しています。

 lodsd命令は、DS:ESIの指している内容を、4バイト分eaxにコピーする命令です。

 この時esiは、InIntializationOrderModuleListの1つ目のエントリ(ntdll.dll)のFlinkを指しています。Flinkの指す先は2つ目のエントリになりますので、lodsd命令を実行することで、2つ目のエントリのデータ構造体の先頭アドレスがeaxに格納されます。InInitializatoinOrderModuleListの2つ目の要素はkernel32.dllなので、オフセット+8hにアクセスし、kernel32.dllのベースアドレスを取得しています。

 これでkernel32.dllのベースアドレスを取得することができました。

注4:download_exec.binシェルコードの生成方法は第1回目を参照してください
注5:もともとコードとして解釈されていない場合は、この操作は必要ありません。
注6:余談ですが、シェルコードは特定の個所のアドレスを取得するために、しばしばこのcall/popの動作を行います。前回解析したデコーダ部分のloc_12/sub_2でも、call/popを利用してデコードの開始アドレスを取得しています。


 次回はこのベースアドレスからAPIのアドレスを取得する方法について解析したいと思います。

筆者紹介

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

川古谷 裕平(かわこや ゆうへい)

2005年 日本電信電話株式会社入社。入社以来、ハニーポット技術やマルウェア解析技術の研究開発に従事。国内外のカンファレンスや学会で研究成果の発表を行っている。「アナライジング・マルウェア」の著者。



前のページへ |       

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る