bombGive Me File

Restricted shellcode, limited characters and whitelist seccomp.

Problem Description

I want to get the flag file with a shellcode but the char is limited :(.

Author: farisv

Analysis

We are given two binaries, called runner and sandbox. Checking them in checksec shows that both of them has full protection.

Both the binaries has full protection.

We're also given a Dockerfile, which shows an important information: the flag's file name on the server. The flag file name is unusually long, so this could potentially be a part of the challenge.

Here, I've decompiled some of the important functions from both binaries to examine.

runner.main()

sandbox.main()

sandbox.run_target()

sandbox.run_debugger()

From the decompilation, the program's main (intended) flow is pretty straightforward:

  • runner is run from sandbox (in simpler terms, ./sandbox ./runner). Sandbox acts as a wrapper to "monitor" runner's process.

  • runner asks for shellcode with a maximum of 700 characters (175 bytes).

  • From the shellcode, sandbox decides whether the syscalls from the shellcode are all allowed or not. If a syscall isn't on the whitelist, the process gets killed. Otherwise, the shellcode gets executed.

For the inputted shellcode itself, there are two limitations: from length (only 700 chars/175 bytes allowed) and seccomp, which keywords can be seen throughout sandbox's run_debugger. The binary doesn't use the usual seccomp library, so we can't use seccomp-tools. Because of that, we need to do a manual analysis to find what syscalls are being banned. In run_debugger, we have this.

There are global variables syscall_numbers and syscall_len. Looking at the loop, it goes through the syscall numbers and sets local_fc to 1 if the current syscall number being examined from the shellcode matches any of the iterated. From here, we know that syscall_numbers contains allowed syscalls instead of banned syscalls. From syscall_len, we know that there are 20 allowed syscalls, and we can see them using Ghidra.

Syscalls numbers' length.
The allowed shellcodes in four bytes.

After mapping the syscall numbers to its syscall operation, here are the allowed syscalls:

  • 0x0 (read)

  • 0x1 (write)

  • 0x2 (open)

  • 0x3 (close)

  • 0x4 (stat)

  • 0x5 (fstat)

  • 0x9 (mmap)

  • 0xa (mprotect)

  • 0xb (munmap)

  • 0xc (brk)

  • 0x11 (rt_sigaction)

  • 0x15 (access)

  • 0x3c (exit)

  • 0x9e (arch_prctl)

  • 0xda (set_tid_address)

  • 0x101 (openat)

  • 0x111 (set_robust_list)

  • 0x12e (prlimit64)

  • 0x13e (getrandom)

  • 0x14e (rseq)

Solution Breakdown

From here, it's safe to say that the intention of the problemsetter was to make us perform open-read-write (ORW) to read the flag file from the server, since get shell syscalls like execve aren't being whitelisted but ORW shellcodes are. But, as previously mentioned, the flag file name is unusually long. Now, the challenge is how can we craft a <= 175 bytes shellcode that writes the long flag file name into memory and still fits the entirety of the ORW shellcode?

Initial Shellcode Crafting

Initially, I came up with this assembly code, soon-to-be converted to shellcode:

Yes, this is ridiculously inefficient because initially, I forgot loops exist in assembly. It does the job though: writes the long flag file name into memory, stores it to the stack, then pointed by rdi, then used to perform ORW. But it exceeds the limitations mentioned, because apparently the shellcode is more than 175 bytes long.

Shellcode Optimization

We can optimize this assembly in two ways:

  • No need to mov rax, 0x6767...before every mov [rsp+i], raxinstructions. The raxregister content stays the same, so no need to move the exact same value repeatedly to the exact same register.

  • Use loops.

Here's the final assembly I came up with.

This gives us just 142 bytes of shellcode, which is more than enough! Compiling the assembly to shellcode, then converting it to the bytes format (\x prefix) and sending it to the server gives us the flag.

Sending the shellcode payload to the server.

Flag

CJ{7f1fdbd71a25cc6be8f6fa7c49f73cc2}

Full Solver Script

Last updated