It’s 2022 and Matrix released their annual challs. This year, I chose to focus on the pwnables.
The first two(‘Connection Failed’ and ‘Cookies’) were quite trivial, and involved stuff like: buffer overflows, fighting w/ fork()
and leveraging an integer overflow to predict a stack canary. This writeup is about the 3rd chall ‘Mirror’, which got me intrigued due to the constraints it had. So, without further ado, let’s begin.
Challenge Description
We are given a binary file:
- Statically linked
- No PIE
- No libc (oof)
All it does is getting 0x70 bytes of input with a read()
syscall and prints it back to the user with a write()
syscall.
The stack frame size is 0x10 bytes and the read()
syscall gets 0x70 bytes. So, it’s quite obvious that we have 0x60 bytes to overflow.
My initial thought was ‘is this another SROP chall? Let’s try to craft a Sigreturn-Frame’. However, that’s not the case here, we don’t have enough bytes to craft a Sigreturn Frame(only 0x60 bytes to overflow). So instead, we’ll build a regular ROP payload.
Obstacles when Crafting a ROP chain
The binary already has a print_flag
function in it, so we’ll try to perform ret2print_flag. However, it doesn’t read flag.txt
, but rather false_flag.txt
:
During my attempts, I tried crafting a ROP chain that will:
- Put
false_flag+6
(='flag.txt'
) intoRDI
with apop rdi
gadget - Return to the middle of
print_flag
, at0x40104C
right when it opens the path insideRDI
and reads it. - Let the
print_flag
function continue normally
It didn’t work, because when I jumped to 0x40104C
I skipped the 1st instruction of print_flag
, which is mov eax, 2
. The value of eax
is crucial for the syscall
instruction(SYS_open
==2).
I needed to find a way to control the value of the eax
register. To do that, I thought of leveraging the read()
syscall: this syscall returns the number of characters the user entered. So we can: Enter 2 bytes to adjust/set eax
to 2, perform ret2print_flag
as described above and win. Sounds straight-forward, but in reality, it doesn’t work.
First: the binary verifies that you entered at least 0x55 bytes. If you typed less than 0x55 bytes, it will call exit()
and kill your ROP chain. Which means that the only syscalls you can trigger are between 0x56 and 0x70.
Second: the binary is extermly small, which makes it harder to find useful gadgets.
Third: It has a add rsp, 0x10
at the end of the mirror
function, which basically shrink your ROP payload even more and limits your ‘range of motion’/number of gadgets when exploiting this bof.
Solution
After browsing through Linux syscall definitions to look for a suitable candidate, I noticed something very interesting in the description of the umask
syscall:
https://man7.org/linux/man-pages/man2/umask.2.html
Return value: This system call always succeeds and the previous value of the mask is returned.
Nice! So, if we want to control the value of eax
while satisfying the rest of the constraints(mentioned above) we can:
- Send a 1st ROP with a size of 0x5f(
SYS_umask
):- jump to
pop rdi; syscall; ret
- ‘plant’ our desired value using the
umask
syscall, let’s say we ROP toumask(1337)
. - return back to
mirror
- jump to
- Send a 2nd ROP with a size of 0x5f(
SYS_umask
):- jump to
pop rdi; syscall; ret
- This time we will call
umask
again but it will return 1337. Which will turneax
to 1337. - return to a
syscall; ret
gadget in order to trigger a 1337 syscall.
- jump to
This way, we can achieve arbitrary values into eax
using umask
, and leverage that into triggering arbitrary syscalls. Or as I like to call it, ‘The House of umask~’ :^) lol
Below is the full solution:
output:
[+] Opening connection to 0.cloud.chals.io on port 14397: Done
prep step 1(set rax=0x2) >
launch step 1(call SYS_open) >
prep step 2(set rax=0x0) >
launch step 2(call SYS_read) >
[*] Switching to interactive mode
AAAAAAAAAAAAAAAA*\x10\x00\x00\x00\x00\x00\x00\x00;@\x00\x00\x00QQQQQQQQQQQQQQQ\x00@\x00\x00\x00CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAA*\x10\x00\x00\x00 @\x00\x00\x00\x00\x00\x00QQQQQQQQQQQQQQQQ,\x10\x00\x00\x00QQQQQQQQQQQQQQQ\x00@\x00\x00\x00CCCCCCAAAAAAAAAAAAAAAA*\x10\x00\x00\x00\x00\x00\x00\x00;@\x00\x00\x00QQQQQQQQQQQQQQQ\x00@\x00\x00\x00CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCAAAAAAAAAAAAAAAA*\x10\x00\x00\x00\x00\x00\x00\x00;@\x00\x00\x00QQQQQQQQQQQQQQQ,\x10\x00\x00\x00QQQQQQQQQQQQQQQt\x10\x00\x00\x00CCCCCCMCL{D1D_y0u_U53_50f7_0R_H4Rd_L1Nk?}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00
[*] Got EOF while reading in interactive
we got the flag :D
MCL{D1D_y0u_U53_50f7_0R_H4Rd_L1Nk?}
Even though it was an un-intended solution, I thought it was quite original and worth sharing.
ty for the challenge.