Writeup - The Great Escape (DUCTF 2023)
DUCTF 2023 - The Great Escape
Description
1 | Do you have an escape plan? |
Writeup
No source code was provided for this one, but the decomp was pretty easy to obtain through Ghidra:
1 | bool main(void) { |
It reads in 127 bytes of input from the user, places it in a custom RWX section in memory, and runs it as shellcode. However, the enable_jail()
function is called, which sets up seccomp and only enables the following syscalls (found by running seccomp-tools dump ./escape
):
read
nanosleep
exit
openat
These syscalls mean you can open /chal/flag.txt
, read it into memory, but can’t write it to stdout. However, using nanosleep
and exit
can be used to side channel it by bruting bit by bit. This is an example of how it would flow:
- Open
/chal/flag.txt
and get the file descriptor0x3
back - Read x bytes from fd 3 into somewhere in memory (I choose the stack cuz it’s easy)
- Put a single byte from the stack into a register, and compare it to a hex value (like
0x61
, or'a'
) - If the values match,
nanosleep
for 2 seconds. If they don’t, exit immediately.
Using this method, we can run the program and if it takes 2 seconds longer than normal to exit, we know we guessed the right letter. Then, we move on to the next letter and start all over again.
This does take quite a while because a new network connection needs to be established for each guess, and the flag was somewhat long. I ended up getting many false positives because of network jitter, and (for some reason I don’t understand) the program would only wait 0.02 seconds longer with nanosleep no matter what. I ended up implementing a double-check system where if the time it took the program to exit exceeded my defined threshold, it would run it again just to make sure it wasn’t a mistake.
After about an hour of tweaking and waiting, I finally extracted the whole flag.
*It’s also important to note that the program uses fgets
to read the input, meaning it stops once it hits a newline (0x0a
). Luckily, there weren’t any newlines in the shellcode, except when I had to test the flag at offset 0x0a. However, this was obviously an underscore so it was easy to guess.
Solve Script
1 | from pwn import * |
Flag: DUCTF{S1de_Ch@nN3l_aTT4ckS_aRe_Pr3tTy_c00L!}