stupidrop
So, we have an obvious buffer overflow here, but no possibility to leak anything and ASLR
is enabled for sure.
Since this is the only function in the binary, we also don’t have many libc functions we can abuse
This challenge could be solved only with these at hand (in fact, there’s a similar on pwnable.tw for example, which only provides gets
). But the challenge was made easier on purpose by forging one syscall
gadget into the code.
Though it’s never called, you can find it at 0x40063e
.
With syscall
, we could just start creating our ropchain and do an execve("/bin/sh", 0, 0)
, though you’ll face the problem how to set rax
to an arbitrary value.
To make this short: While the ctf was running I missed the fact that calling alarm
will set rax
to the value of the argument passed to it. That should be the intended way, because it’s way easier, than what I did here.
So, if you want to see the shortest and easiest solution to this task, go look somewhere else…
Since I missed the easy way out by using alarm
, I created an ropchain instead, which will also be able to set rax
to an arbitrary value, but in a bit more “creative” way.
For this let’s take a look at the assembly of main
:
So, not much too work with, but we can abuse this code for setting rax
.
What I did there:
- Overwrite
setvbuf
got with a call toret
- Overwrite
alarm
got with a call topop rbp; ret
- Overwrite
stdout
pointer with 59 (execve syscall
) - In the ropchain jump to
mov rax, cs:stdout
What this will do:
Set rax
to 59
Set exc
to 0
, set edx
to 2
and esi
to 0
. It will then store the value of rax
into rdi
(so rdi
also contains 59).
It will then try to call setvbuf
, but since it’s now just a ret
call it will just continue with execution.
This will now set edi
to 2
and do a call alarm
, which will push the current address onto the stack (for returning after the call). But since we overwrote it with pop rbp; ret
it will just pop this return address and instead continue execution with the next address in our ropchain.
But now rax
will contain the value we wrote into stdout
, so it’s 59 now and we’re prepared to do an execve
syscall :)
Well, calling alarm
twice with 59
in rdi
might have been easier, but…
Though rax
now is set correctly, we’re facing another issue. rdx
is now set to 2
which will fail in execve
, since this will be the envptr
. And there’s no pop rdx
gadget to change this.
But there’s also help to this :)
There’s a nice gadget in __libc_csu_init
which will get us out of this misery:
Not only will this help us setting rdx
, it also initializes rsi
and rdi
and does a call to r12+rbx*8
. Since rbx
is currently 0
, we can call whatever we want by setting r12
accordingly (we’ll just need a memory address where the address, we want to call, is stored, but gets
will also help on this).
So we can also use the pop gadget in __libc_csu_init
also to initialize r12
-r15
So, with these tools we can forge the attack plan
- Store
/bin/sh
in bss - Store a pointer to the
syscall
gadget in bss - Overwrite
setvbuf
withret
- Overwrite
alarm
withpop rbp; ret
- Overwrite
stdout
with59
- Jump back into main, so the “setvbuf-alarm-rax-initialization-chain” gets triggered :)
- Call the r12-r15 initialization gadget and setup the registers
- Call the “calling” gadget from
__lib_csu_init
which will setuprdx
,rsi
,rdi
and then call the previously storedsyscall
gadget
Exploit for this
I know, there’s an easier solution to this challenge, just wanted to show this alternative way of setting rax (which might get handy if there’s no alarm
but another call, that can be overwritten).