サイバーセキュリティモニタリング(マルウェア解析編)

サイバーセキュリティモニタリング(ハニーポット編 2) - あしのあしあと」の続き。
久しぶりに 「サイバーセキュリティモニタリング」。何もしていなかったわけではないが、なかなか書けなかった。
仕事しんどいス。最近の Web アプリケーションに、ついていけてないス。

実践サイバーセキュリティモニタリング

実践サイバーセキュリティモニタリング


もとい。いよいよマルウェア解析(の入門)。表層解析と動的解析を飛ばして(いや、やってあるのだが、満足できていないだけ)、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 のところが、うまくいかない。次のようなエラーが出る。本編の演習の方でも同じエラーが出たので、環境まわりがあやしい。

  • デバッグプロセスのメモリ読み込みができません(00400000...00412fff)
  • Bad DOS Signature!!


結果、環境面は、うまく解決できなかった。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 フォーマットから、きちんと勉強しろということだ。