miscfreemedium

reflections

b01lersc

Task: a remote service accepts up to two calc1 compiler submissions and validates each by comparing its stdout to a trusted compiler, printing mismatches in hex. Solution: emit a plain executable shell script instead of a real compiler and use the validation mismatch oracle to leak the flag from stdout.

$ ls tags/ techniques/
mismatch_oracle_exfiltrationexecutable_script_outputdirect_hex_payload_emission

$ cat /etc/rate-limit

Rate limit reached (20 reads/hour per IP). Showing preview only — full content returns at the next hour roll-over.

reflections — b01lers CTF 2026

Description

The man in the mirror nods his head

We are given a service that accepts calc1 source, compiles it with the current compiler, marks the result executable, and then runs validation tests against it. The obvious theme is Ken Thompson's “Reflections on Trusting Trust”: upload a compiler, make it become the next trusted compiler, and maybe smuggle a Trojan through the self-hosting chain.

The trick is that we do not need a two-stage trusting-trust backdoor at all. The server itself provides a much simpler oracle: when a validation test fails, it prints both the expected and actual stdout in hex. If we can make the produced executable print the flag, the first failed test leaks it immediately.

Summary

The service processes up to two submissions separated by the raw token &&. Each submission is compiled by /app/compiler_wrapper, which dispatches to /tmp/compiler; initially that symlink points to /app/calc1. The produced file is only required to be executable, not a valid ELF binary, so we can emit a plain shell script that prints the flag.

When the server validates the new "compiler", it computes the expected output with the current trusted compiler and the actual output with our generated executable. On the first mismatch it prints both values as hex, capped to 64 bytes. Since a normal flag is shorter than that cap, one failing test is enough to exfiltrate the whole flag.

Recon

Relevant files were:

  • server.py
  • compiler_wrapper.c
  • calc1, calc1.he, calc1_compat.c
  • local solve helpers make_payload.py and payload.he

The core flow in server.py is:

...