「サイバーセキュリティモニタリング(ハニーポット編 2) - あしのあしあと」の続き。
久しぶりに 「サイバーセキュリティモニタリング」。何もしていなかったわけではないが、なかなか書けなかった。
仕事しんどいス。最近の Web アプリケーションに、ついていけてないス。
- 作者: 八木毅,青木一史,秋山満昭,幾世知範,高田雄太,千葉大紀
- 出版社/メーカー: コロナ社
- 発売日: 2016/03/28
- メディア: 単行本
- この商品を含むブログ (2件) を見る
もとい。いよいよマルウェア解析(の入門)。表層解析と動的解析を飛ばして(いや、やってあるのだが、満足できていないだけ)、p.157 の設問【8】(唯一の静的解析の問題)に取り組んだ記録を残す。うち、アンパックについては、本編 5.4.4 の演習とほとんど変わらないので、作業だけであれば、ぜんぜん難しくない。
仮想環境で対象となる検体を実行してみると、パスワードの入力画面が起動する。入力したパスワードが正しいかどうかチェックして終了する。特に、ファイルやレジストリへのアクセスや、通信なども発生していない。これだけのプログラムに見える。このプログラムの正しいパスワードを見つけなさいという問題。CTF な雰囲気。全然マルウェアっぽくない。
本編の演習と同様、まずは Detect It Easy で、パッキングされているかどうか確認する。
これで、UPX でパッキングされていることがわかる。ここが唯一、マルウェアっぽいところ。それだけなのに、ウイルス対策ソフトにひっかかり、消される。むきー。
さて、本編 5.4.4 と同様に、アンパックを行う。アンパックに、UPX や Themida のようなツールは使わない。マニュアルアンパックが、マルウェア解析のはじめの一歩として大事なところなのだろう。
まずは、デバッガを用い、OEP(Original Entry Point)を探す。パッキングされ、圧縮・難読化されたコードであっても、どこかのタイミングで、オリジナルコードをメモリ上に展開することになる。このオリジナルコードの開始位置が、OEP。
次のような箇所を探し、OEP を推測するところからスタート。
- パックを展開する(OEP 到達する)前に、パックを展開するためのコードが実行される。これにあたって、レジスタの退避と復元が行われるはず。この復元処理の後に、OEP があるのでは?
- OEP の移動は、PE フォーマットのセクションをまたいだ移動になる。このような JMP, RET, CALL 命令が実行された後に、OEP があるのでは?
本編の演習と同様、デバッガ OllyDbg を起動して、解析していくことにする。しかし、どうしても OllyDump のところが、うまくいかない。次のようなエラーが出る。本編の演習の方でも同じエラーが出たので、環境まわりがあやしい。
結果、環境面は、うまく解決できなかった。Immunity Debugger を使ってみたら、こっちは問題なく動作したので、以下は、Immunity Debugger を使って実施した記録。
まず、ターゲットのファイルをロードする。すると、本編の演習と同様、PUSHAD 命令から始まっている。PUSHAD 命令は、全ての汎用レジスタ(ESP のみを除く)を、いっきにスタックに退避する命令。「F8」ボタンで 1 ステップ進めると、レジスタの値が、ごそっとスタックに退避される。
この退避した値を「復元する」箇所の近くに、EOP があるはず。次に、この PUSHAD で退避した値を復元する POPAD 命令を探す。
レジスタの値を復元するとなると、何らかの形で、今、退避した値にアクセスすることになる。そこで、スタック上にある退避したレジスタの値に、ハードウェアブレイクポイントを設定しておく。
設定の仕方は、本編の演習(p.153)の箇所に、詳しく書いてある。右上(レジスタペイン)の ESP を選択し、右クリックから Follow in Dump を選択する。これで、左下(ダンプペイン)に、スタックの状態が表示される。このうち、汎用レジスタ(上の画像では EAX の値)を選択し、右クリックして BreakPoint -> Hardware -> on access -> DWORD を選択する。
さてさて、準備が整ったので、「F9」ボタンで再開する。予想通り、POPAD 命令の箇所で停止する。その後に JMP 命令が見える。
この JMP 命令を実行すると、セクションをまたいだ移動(UPX1 セクションから UPX2 セクションまで、大きくジャンプ)をしている。まず間違いなく、ココ 0x01091655 が OEP だ。
ここで、メモリのダンプを取得するのだが、前述したとおり、OllyDump ではうまくいかなかった。ImportREC を使ってみるも、やはりダメだった。Immunity Debugger の OllyDumpEx だけ、うまくいった。助かった。
これで、オリジナルコードが展開されているメモリのダンプは取れた。そして、Scylla(スキラ)を使って IAT(Import Address Table)を再構築する。この部分を手動でやれと言われれば、間違いなくできない。
これで、アンパックが完了。
再度、Detect It Easy で、ファイルの内容を確認すると、アンパックされたことがわかる。
これで、ようやく本丸のコードが読めるようになった。IDA(アイダ)を使って静的解析を行ってみる。まずは、IDA に、アンパックしたファイルを読み込ませる。きわめて小さいプログラムなのだが、そこそこ分量がある(涙)。全部読むのは骨が折れる、というか、自分のスキルでは不可能なので、入力値とパスワードを比較しているところを探す。
普通にプログラムを起動し、パスワードを入力してみると、Invalid password と怒られる。この、文字列 Invalid password を表示している直前が、パスワードを比較しているところだろうと推測できる。なので、IDA に戻り、文字列 Invalid password で検索をかける。「x(エックス)」を押して、この文字列の呼び出し元へ行ってみると、それっぽいサブルーチンが出てきた。IDA が、分岐などを見やすく整理してくれているので、このレベルのプログラムであれば、アセンブラの知識があまりなくても、なんとか読み解くことができる。
さて、このサブルーチンの先頭を見ると、変数(アドレス)が定義されていて、それぞれのアドレスの先を初期化している。var_5 の指す先だけ byte なので、1 文字が入る。なんとなく、入力したパスワードの 1 文字が入るんだろうな。
そして、とても怪しい文字列 N%qt{j%wj{jwxj%jslnsjjwnsl3 がいる。パスワードっぽい。「これだ!」と思って実行してみると、案の定、うまくいかない。
sub_1261010 proc near loc_1261094:var_10= dword ptr -10h loc_1261094:var_C= dword ptr -0Ch loc_1261094:var_5= byte ptr -5 loc_1261094:var_4= dword ptr -4 loc_1261094:push ebp loc_1261094:mov ebp, esp loc_1261094:sub esp, 10h loc_1261094:push esi loc_1261094:mov [ebp+var_C], 0 loc_1261094:mov [ebp+var_10], 0 loc_1261094:push offset aNQtJWjJwxjJsln ; "N%qt{j%wj{jwxj%jslnsjjwnsl3" loc_1261094:call sub_1261440 loc_1261094:add esp, 4 loc_1261094:mov [ebp+var_10], eax loc_1261094:push offset dword_126C000 loc_1261094:call sub_126137A loc_1261094:add esp, 4 loc_1261094:mov [ebp+var_4], 0 loc_1261094:jmp short loc_1261054
解析を再開。この文字列を使っているところを、もう少し細かく見ていく。それが、次の箇所。
loc_1261094: mov ecx, [ebp+var_4] movsx esi, byte ptr aNQtJWjJwxjJsln[ecx] ; "N%qt{j%wj{jwxj%jslnsjjwnsl3" movzx edx, [ebp+var_5] push edx call sub_1261000 add esp, 4 movsx eax, al cmp esi, eax jnz short loc_12610BB
EBP + var_4 にある値は、ループのたび、インクリメントされる。なので、怪しい文字列の ECX 番目の文字が、ESI に入る。EDX には、パスワードの 1 文字が入ったと思われる。そいつを引数に sub_1261000(引数に 5 を足すだけ)をコールし、EAX へ。ESI と EAX を比較している。
パスワードに 5 を足した値が、"N%qt{j%wj{jwxj%jslnsjjwnsl3" になっているのだろう。
そうしたら、Python で、、と言ったらかっこいいのだが、Python は勉強中。PowerShell で、次のように実行する。
foreach($ascii in [int][char]"N%qt{j%wj{jwxj%jslnsjjwnsl3") { Write-Host -NoNewline ([char]($ascii - 5)) }
出てきた値を使うと、今度は、うまくいった。
一応の達成感は得られたものの、なんか、モヤモヤしっぱなし。PE フォーマットから、きちんと勉強しろということだ。