Challenge Author: sherl0ck
This challenge was originally meant to be solved using srop (SigReturn Oriented Programming). But it ended up having a bug with which it was possible to directly invoke the execve syscall. This write-up will be focusing on solving the challenge using srop.
As usual, let’s start off by checking the permissions of the given binary.
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
The disassembly is small and the bug is an obvious buffer overflow. The lack of a stack canary highlights that this is indeed the way to proceed. Okay, so we can write as much as we want on the stack. But there is no way to leak memory. A search for gadgets in the binary, using ROPgadget, shows that there is indeed a syscall gadget, but it’s not followed by a ret instruction. So we can use this gadget only once.
As mentioned before, this write-up will follow the srop approach, after a stack pivot, but there is a simpler way to solve it. We will first pivot to the bss, call gets again and craft the ROP chain in such a way that we will force alarm to return 15 (that is the syscall for sigreturn). We will then give a call to the syscall gadget and immediately after, pass the sigreturn frame.
But before that, we have to set the sigreturn frame. The rip should contain the address of the syscall gadget. The rax register should be set to 59 (syscall for execve) and the rdi should point to the string “/bin/sh”. All other registers can be null. Since we do not have a memory leak, the only way to get a pointer to “/bin/sh”, is writing this string to a known address, i.e an address in the bss (as this is not affected by ASLR). Hence the idea behind the stack pivot to the bss and second call to gets.
Now for the task of getting alarm to return 15. Firstly, note that the alarm function returns the seconds left for the previous alarm to complete. If we call alarm with the argument ’15’ and immediately after, call it again, the time lag will be almost zero seconds and it will return 15 itself. Keep in mind that the return value of a function is always stored in the rax register. Also, when a syscall instruction is encountered, the syscall corresponding to the value in rax is invoked. Thus if we give the syscall instruction after the two alarm call’s, the syscall corresponding to 15 (sigreturn) is invoked.
Right, so let’s get everything straightened out –
- Using the overflow in the initial call to gets in main, overwrite the saved ebp with a bss address and specify the return address as the prologue for gets.
- Give the input as “/bin/sh\x00” and pad it with junk to make it of length 0x38 (size of the buffer)
- Check the address where the string is written (it will be rbp-0x30, and we set the rbp in 1st step) and use this to contruct the sigreturn frame.
- Now for the ROP chain. First give the ddress to the gadget pop rdi followed by 15 (syscall for sigreturn).
- Then give the address of alarm twice. Now rax is set to 15.
- Next, pass the address of the syscall gadget.
- Lastly, pass the entire sigreturn frame.
Here is the exploit script –
from pwn import * import sys syscall = 0x40063e alarm = 0x4004b0 pop_rdi = 0x4006a3 if len(sys.argv)>1: r=remote('18.104.22.168',5555) else: r = process('./stupidrop') if __name__=='__main__': context.arch = 'amd64' ''' Construct the sigreturn frame ''' frame = SigreturnFrame(kernel='amd64') frame.rip = syscall frame.rdi = 0x601220 frame.rsi = 0 frame.rdx = 0 frame.rax = 0x3b r.sendline('A'*0x30+p64(0x601250)+p64(0x400626)) # junk+rbp+rip payload = '/bin/sh\x00'.ljust(0x38, 'B') payload += p64(pop_rdi) payload += p64(15) payload += p64(alarm) # will be alarm(15) payload += p64(alarm) # again alarm(15), but now rax is set to 15 payload += p64(syscall) # sigreturn syscall is invoked payload += str(frame) r.sendline(payload) r.interactive()
And on running it –
[+] Starting local process ‘./stupidrop’: pid 9752
[*] Switching to interactive mode
And the flag was –