本番中には解けなかった問題.(forensic200が解けたのが終了1時間前なので仕方ないとは思うけど…)

“我々の会社がハッキングされた! ハッカーが使用可能なコマンドを調べてほしい” みたいな問題文.
渡されるデータは7zだが,解凍するとunknown.dataというtcpdump captureファイルが得られるので,Wiresharkで開く.
大量に通信があるが,Statistics->Conversationsあたりで適当に見ると,192.168.0.1と192.168.0.2間の通信が非常に多いことに気づくので,通信を追ってみる.

しばらく見てるとこれがルーターの設定画面との通信ということがわかってくる.

tcp stream 431にfwup.cgiというフォームからファームウェアらしきものをアップロードしている箇所が見つかる.

POST /fwup.cgi HTTP/1.1
Host: 192.168.0.1
Connection: keep-alive
Content-Length: 3674846
Cache-Control: max-age=0
Origin: http://192.168.0.1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.84 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryGdvVUTSBtR6HMFiC
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Referer: http://192.168.0.1/tools_firmware.php
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.8
Cookie: uid=DZUdPpbrAw

アップロードされたファイルfwを取り出して,binwalkにかけてみる.

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             DLOB firmware header, boot partition: "dev=/dev/mtdblock/2"
108           0x6C            LZMA compressed data, properties: 0x5D, dictionary size: 33554432 bytes, uncompressed size: 3479564 bytes
1179756       0x12006C        PackImg section delimiter tag, little endian size: 12592384 bytes; big endian size: 2473984 bytes
1179788       0x12008C        Squashfs filesystem, little endian, version 4.0, compression:lzma, size: 2492422 bytes,  1481 inodes, blocksize: 131072 bytes, created: Wed Jun 22 12:28:57 2016

どうやらファームウェアで間違いないっぽい. firmware-mod-kitを用いてファームウェアを展開しようとするが,python-magicのバージョン違いなどにやられ進まない.
firmware-mod-kitの使用を諦めて改変したファームウェアのファイルシステム(squashfs部分)を展開しようとするが,lzma形式ということもありかなり手こずる(通常はunsquashfsはlzmaに対応していない).

最終的にfirmware-mod-kitに同梱されていたunsquashfs-all.shを使ったらsquashfs-4.0-lzmaで解凍できた(squashfsはバージョン違いによる差が激しく,互換性があまり無いらしい).

ここで@shift_cropsが対象ルータの型番と標準FWを特定してくれていたので,DIR-600_Bx_FW218WWb01.binから同様にファイルシステムを抽出して,diffをとってみる.

diff --git a/squashfs-root/etc/init.d/S20init.sh b/../squashfs-root-old/etc/init.d/S20init.sh
index 8641cfb..8d94e7e 100755
--- a/squashfs-root/etc/init.d/S20init.sh
+++ b/../squashfs-root-old/etc/init.d/S20init.sh
@@ -5,6 +5,8 @@ xmldb -n $image_sign -t > /dev/console &
 servd -d schedule_off > /dev/console 2>&1 &
 sleep 1
 /etc/scripts/dbload.sh
+sleep 1
+/usr/sbin/upnpsd&
 service LOGD start
 echo "1" > /proc/sys/kernel/panic
 exit 0
diff --git a/../squashfs-root-old/usr/sbin/upnpsd b/../squashfs-root-old/usr/sbin/upnpsd
new file mode 100755
index 0000000..621481c
Binary files /dev/null and b/../squashfs-root-old/usr/sbin/upnpsd differ

明らかに/usr/sbin/upnpsdが怪しい.

upnpsdを取り出して解析するも,時間が足りなくてここでタイムアップ(MIPS解析したことがないのでつらい).

終わった後,気合でreversingしたら2段階のpackerがかけられていて,最終的に0x400000-0x40a000の範囲にelfらしきデータがマッピングされていた. strace,IDAとにらめっこしながらgdbでデバッグしてみたところ,以下のような流れになっていた.

  • dlinkupdate.ddns.netの名前解決をする
  • 名前解決に3回失敗したら,tcp://192.168.0.2:164に接続

試しに192.168.0.2というIPを持たせて,TCP 164番ポートをListenさせてみたところ,接続が確立された.

Listening on [0.0.0.0] (family 0, port 164)
Connection from [172.16.10.2] port 164 [tcp/cmip-agent] accepted (family 2, sport 57941)
BUILD MIPSEL

あとは適当にデータを送りつけてみるとrecv:という表示が出てきたので,適当な場所にブレークポイントを仕掛けてゴリゴリデバッグしていく.

loc_408244で文字列比較を行っていて,strstr(trim(recvdata), “PING”)のような処理をしている. ida1.png

recvdataがPINGから始まっていれば分岐先のloc_408064で”PONG”という文字列を送信している.

Listening on [0.0.0.0] (family 0, port 164)
Connection from [172.16.10.2] port 164 [tcp/cmip-agent] accepted (family 2, sport 57941)
BUILD MIPSEL
PING
PONG

もう片方の分岐先である0x4082c0では,strstr(recvdata, “DUP”)のような処理をしている.
ida2.png

recvdataがDUPから始まっていればloc_4087c0に遷移するが,どうやらexitしてるっぽい? ida3.png

試しに”DUP”を送信してみたところ,フラグが降ってきた.

Listening on [0.0.0.0] (family 0, port 164)
Connection from [172.16.10.2] port 164 [tcp/cmip-agent] accepted (family 2, sport 57943)
BUILD MIPSEL
PING
PONG
DUP
TMCTF{Y0U2_R0U73R_1S_MY_8oT}

シナリオとしてはファームウェアのアップロード機能を用いてRATを送り込んだ,みたいなものだろうか.面白かった.