Insomni’hack CTF 2017 offered a serie of 3
challenges (i.e. 3 different flags) on the same binary, called
bender_safewas a Reversing challenge (50 pts) to discover the correct validation sequence;
bender_safer(this one) was a Pwnable challenge (300 pts), which could only be done once the first challenge was solved;
bender_safestwas a Shellcoding challenge (150 pts), which could only be reached done when the two challenges above were solved. The goal was to write a MIPS shellcode to establish a connection to the local port tcp/31337.
Close to the end, only 19 teams (out of 400+) had solved this challenge. I finished this challenge after the CTF, and since there was no write-up available, I chose to write one.
The vulnerable file
bender_safe, is a
32-bit MIPS (Big-Endian) binary.
No major protection, but I assumed ASLR active and therefore randomizing the stack (only the stack, as the binary is not PIE).
In addition, some regions were RWX:
The binary execution starts where the challenge
bender_safe left off, with the
OTP validation. We then get into a simple menu offering 3 choices:
which we can immediately spot in IDA with the function
enter_vault. IDA also
gives us a clear indication of the stack layout:
passwords variable is a 1028 (0x410-0xC) byte array, which is used to
store the passwords. When trying to populate the array (choice #2), the function
init_passwords will be hit, and prompt the user for the number of passwords to
store, which must be an integer strictly below 513.
store the number of passwords to store in 2 locations, a dedicated variable
(@ebp-0x414), but also as the first value of the array
passwords, @ebp-0x410). The number of passwords is used as a counter for a loop
that will read the passwords from stdin, thanks to the
After spending way too long spent trying to check for an arithmetic mistake, I
reviewed more thoroughly the function
read_passwords takes two arguments, a pointer to a buffer and a
integer, which corresponds to the size of data to read. The buffer is populated
one character at a time, in the following loop:
The interesting bit starts around 0x401640: when a
\n character is provided to
fill the byte at offset
buffer[i]), the function performs an additional
check to test if the preceding character (i.e.
\r and if so replace it with
\n. And the vulnerability (as subtle as it is)
is here: when overwriting the byte, the function does not check that
i>0. Because we are on big endian architecture, this can lead to size
overwrite. To do so, we need to
- Specify a number of passwords of
- The application will reply that we can store 13 passwords of 76 bytes;
- Enter a first password with only
This will overwrite the number of passwords stored in
passwords to 10,
allowing us to write 12 passwords of 102 bytes (i.e. 1224 bytes), which results
in a stack overflow.
The vulnerability can be asserted by setting a breakpoint before and after
the first call to
And if we populate the 12 remaining passwords with “A”*102 the return address
$ra register) gets corrupted, which we can observe by taking the exit:
So we are now able to make the program crash. To know the exact offset of
I’ve used the De Bruijn pattern from
And we now know that the PC is controlled at offset 921, as we are on a Big Endian architecture:
ROP-ing to a fixed area
So great, we can control
$ra, and therefore call any location. But the MIPS ABI
uses registers (from
$a3) to store parameters of function
calls so we need to control (at least some of) them.
To achieve code execution, I decided to reach control only of
which is then sufficient to call
read_passwords(buffer, length), and have a
shellcode copied into one of the fixed RWX location.
After seeing too many ROP tools for MIPS fail, I simply used
objdump -D to
find the following gadgets:
- 0x00403ba4: Control
$s2from a value given from the stack
- 0x403bbc: Use
- 0x00403b98: Use
- By re-using gadget@0x00403ba4 with 0x004038e8, we use
We can chain those 4 gadgets to entirely control
$a1 and then call
read_passwords to write a
execve('/bin/sh') shellcode into one of fixed RWX pages (I’ve
For some reasons, the different shellcodes I had from external resources did not
work. So I decided to use Keystone Engine to
write one. Instead of writing totally from scratch, I used a template created
earlier as part of my project
and adapted it:
Update: as @0xGrimmlin mentioned, during the CTF, the challenge was actually QEMU chroot-ed, so technically this shellcode would not have worked, but you could similarly build another one doing open/read/write(stdout)
We have now all the components to launch our exploit. The final version is available here.
This is it… Well not really. The ultimate challenge was to craft a shellcode to connect to tcp/31337. But the way we used to solve this challenge in the last sections of this blog post makes it trivial to extend (by simply establishing a TCP connection) and solve the final challenge. I will leave this to the reader’s curiosity ☺
I will just conclude this post by thanking the Insomni’hack team for putting up together such fun and original challenges. And also, huge congratz to the few teams who scored this challenge during the CTF.
Hope you enjoyed this article, and see you next time for more challenges…
Share this post: