検索
連載

SQL Slammerのコードを解析せよ!リバースエンジニアリング入門(最終回)(2/3 ページ)

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

PC用表示 関連情報
Share
Tweet
LINE
Hatena

実際のマルウェアを相手にLet's 逆アセンブル!

 まず、SQL Slammerのエントリポイント(開始点)を見つけます。このエントリポイントを見つけるにはMS02-039の脆弱性についての理解が必要になってきますので、簡単にこの脆弱性の概要を説明します。

 MS02-039はスタックバッファオーバーフローの脆弱性です。バッファを溢れさせることにより、スタック上に置かれたリターンアドレスを上書きし、任意のコードへジャンプさせます。

 図2と図3でSQL Slammerがバッファを溢れさせ、エントリポイントへジャンプするまでの様子を説明します。まず、脆弱性を含む関数のリターンアドレスがスタックに置かれ、その上にバッファが確保されます(図2左)。SQL Slammerが送りつけたUDPパケットがこのバッファに格納されると、バッファが溢れ、リターンアドレスを42B0C9DChで上書きします(図2右)。

図2 リターンアドレスを書き換える様子
図2 リターンアドレスを書き換える様子

 この42B0C9DChの値はMSDEプロセス内のメモリアドレスを意味しており、このアドレスにはjmp esp命令があります。上書きされたリターンアドレスが読み込まれると、命令ポインタは42B0C9DChにあるjmp espをポイントし、この命令を実行します。

 一方で、スタックポインタ(esp)はリターンアドレスが配置されていたアドレスより4バイト下(アドレスとしては4バイト上位)を指しています(図3左)。espが指すアドレスには12バイト先にジャンプするjmp命令(jmp loc_C7)があります。eipがjmp esp命令を実行すると、このjmp loc_C7命令をポイントします(図3中央)。jmp loc_C7命令が実行されると12バイト先のnop命令にジャンプし(図3右)、そこからスライディングし、000000CFhにあるpush命令が実行されます(図4)。ここがSQL Slammerのエントリポイントになります。

図3 リターンアドレス書き換え後の動作(クリックすると拡大します)
図3 リターンアドレス書き換え後の動作(クリックすると拡大します)

 リターンアドレス周辺をIDAで見てみると、図4のようになっています。000000C7hから000000CEhにあるNOP命令の後の000000CFhがSQL Slammerのエントリポイントになっています。

図4 SQL Slammerのエントリポイント(クリックすると拡大します)
図4 SQL Slammerのエントリポイント(クリックすると拡大します)

 それではこのエントリポイントから、SQL Slammerのコードを解析していきましょう。今回のSQL Slammerのコードはスタックを多用します。各スタック操作が何をしているのか見失わないように、注意深く追いかけていくことにします。

 また、SQL Slammerのコードの中にはWin32 APIを呼び出すためにメモリアドレスを直接指定している個所があります。これらのメモリアドレスは本来SQL Slammerが実行されるプロセスのメモリ空間から探してきます。この各アドレスにどのような命令や関数が存在しているかを確認するには、MS02-039の脆弱性を持つSQL Server 2000の実行ファイルが必要になりますが、現在、このバージョンのファイルを入手することは困難だと考えられますので、今回は各アドレスの意味は既知のものとして扱って説明を進めていきます。

注意深くコードブロックを解析する

 さて、それでは最初のコードブロックです。この部分ではバッファを溢れさせるためのデータを作成しています。ここからスタック上に構築されるデータは、後ほどSQL Slammerが外部に送信するパケットに含まれるデータになります。つまり、先ほど見たslammer.pcapのペイロード部分のデータを構築していきます。

seg000:000000CF push    42B0C9DCh	;スタックに42B0C9DChを積む
seg000:000000D4 mov     eax, 1010101h	;eaxに1010101hを格納
seg000:000000D9 xor     ecx, ecx	;ecxの値を0クリア
seg000:000000DB mov     cl, 18h		;cl(ecxの下位レジスタ)に18hを格納
seg000:000000DD
seg000:000000DD loc_DD:  
seg000:000000DD push    eax		;eax(01010101h)をスタックに積む
seg000:000000DE loop    loc_DD		;ecxが0でなければloc_DDへジャンプ(つまり24回、1010101hをスタックに積む)

 最初にスタックに積んでいる42B0C9DChの値は、先ほど説明したjmp esp命令が格納されているメモリアドレスになります。seg000:000000DD〜seg000:000000DEでは、01010101hをスタックに24(18h)回積み、バッファを溢れさせるデータを作り出します。

 上記のアセンブリコードを実行した後のスタックの様子は図5のようになります。

図5 スタックの様子(1)
図5 スタックの様子(1)

 上記のコードブロックを実行後のeaxには1010101hが、ecxには0が入っています。

 それでは、次のブロックを見てみます。

seg000:000000E0 xor     eax, 5010101h	;eax(1010101h) XOR 5010101h=4000000h
seg000:000000E5 push    eax		;eax(4000000h)をスタックに積む
seg000:000000E6 mov     ebp, esp	;espの値をebpに格納
seg000:000000E8 push    ecx		;ecx(00000000h)をスタックに積む
seg000:000000E9 push    6C6C642Eh	;'.dll'をスタックに積む
seg000:000000EE push    32336C65h	;'el32'をスタックに積む
seg000:000000F3 push    6E72656Bh	;'kern'をスタックに積む

 seg000:000000E9〜seg000:000000F3の部分で4バイトの値を3回スタックにPUSHしています。一見、何をしているか分からないのですが、pushしている値を文字列としてASCII表示してみると、“.dll”“el32”“kern”という文字列を積んでいることが分かります。

 このコードブロックが実行された後のスタックの状態を図示すると、図6のようになります。seg000:000000E9〜seg000:000000F3でスタックに積んだ3つの4バイトは“kernel32.dll”となります。

 SQL Slammerはこれと同様の方法を使って、ロードするモジュール名や呼び出したいAPI名などの文字列をスタック上に用意していきます。

図6 スタックの様子(2)
図6 スタックの様子(2)

 次のコードブロックでも同様に、スタック上にモジュールやAPI名の文字列を用意しています。各4バイトの値を文字列として表示してみましょう。

seg000:000000F8 push    ecx		; ecx(00000000h)をスタックに積む
seg000:000000F9 push    746E756Fh	; 'ount'をスタックに積む
seg000:000000FE push    436B6369h	; 'ickC'をスタックに積む
seg000:00000103 push    54746547h	; 'GetT'をスタックに積む
seg000:00000108 mov     cx, 6C6Ch	; ecxの下位(cx)に'll'を格納
seg000:0000010C push    ecx		; 'll\0\0'をスタックに積む
seg000:0000010D push    642E3233h	; '32.d'をスタックに積む
seg000:00000112 push    5F327377h	; 'ws2_'をスタックに積む
seg000:00000117 mov     cx, 7465h	; ecxの下位(cx)に'et'を格納
seg000:0000011B push    ecx		; 'et\0\0'をスタックに積む
seg000:0000011C push    6B636F73h	; 'sock'をスタックに積む
seg000:00000121 mov     cx, 6F74h	; ecxの下位(cx)に'to'を格納
seg000:00000125 push    ecx 		; 'to\0\0'をスタックに積む
seg000:00000126 push    646E6573h	; 'send'をスタックに積む

 上記のアセンブリ命令を実行した後のスタックの状況を図示すると、図7のようになります。「kernel32.dll」「GetTickCount」「ws2_32.dll」「socket」「sendto」の4つの文字列をスタック上に構築しています。

図7 スタックの様子(3)
図7 スタックの様子(3)

Copyright © ITmedia, Inc. All Rights Reserved.

ページトップに戻る