Skip to main content

CTF

Tiles+ai

The binary is a static stripped ELF that refuses to run unless the CPU exposes Sapphire Rapids AMX features. Local execution in the sandbox was blocked by both the CPUID gate and the lack of AMX support, so the solve path had to come from static reconstruction of the AMX dataflow.

Throughthewall

Event: b01lers CTF 2026 Category: pwn Challenge: pwn/throughthewall Files: bzImage, initramfs.cpio.gz, start.sh Remote: ncat --ssl throughthewall.opus4-7.b01le.rs 8443 Flag: bctf{spray_those_dirty_pipes} This challenge was a kernel pwn packaged as a bootable QEMU image. The archive gave a kernel, an initramfs, and a launcher script. The remote service wrapped the same VM behind TLS and a proof-of-work gate, then dropped us into a BusyBox shell as the unprivileged ctf user. The only real goal was to turn that shell into root and read /flag.txt.

Shakespeares Revenge

Event: b01lers CTF Category: Reverse Engineering Challenge: rev/shakespeares-revenge Files: server.py, shakespeare, challenge.spl Remote: ncat --ssl shakespeares-revenge.opus4-7.b01le.rs 8443 Flag: bctf{4_p0und_0f_fl35h} This challenge looked like a Shakespeare-language calculator at first, but the real solve was a VM bug that turned the calculator into a syscall primitive. The interesting part was not the Python wrapper or the SPL script alone. It was the way the interpreter compiled that script, how it stored stack values, and how Scene VI quietly mapped to a hidden syscall operation.

Kyoto Protocol

Result # Challenge: kyoto_protocol / Kyoto reversing challenge Correct password: 111314212629363839424448535558616467727577828385969799 Flag: bctf{im_bash_ijng_it._Yeahhg_:3} Files inspected # The uploaded archive contained:

Favorite Potato

favorite_potato ships a Python wrapper, a tiny test.bin, and a large compressed code.bin.gz. The wrapper makes the challenge goal explicit:

Tinyirc

CODEGATE 2026 Quals - tinyIRC # Category: Pwn Challenge: tinyIRC Remote launcher: nc 15.165.70.236 20998 Solver: solve.py TL;DL # The wrapper port is not the IRC service. It prints the real port, keeps the wrapper process attached to the child, and becomes the side channel that later carries the leak and the flag. Inside the IRC server, QUIT clears a client slot while the recv loop is still using the stale pointer, and a reused slot can come back with a negative input_len. That negative length becomes a reusable cross-slot overwrite. I used it first to turn memmove() into printf() for a same-process libc leak, then to replace strtok@got with system() and run cat /home/ctf/flag >&2.

Oldschool

CODEGATE 2026 Quals - oldschool # Category: Reverse / AEG Challenge: oldschool Description: Back to the past Solver: solver.py Client helper: drive_client.py TL;DL # The provided Go client is only a courier. The real challenge is the ELF it downloads every round. Each round binary checks sha256(input[:4]), uses those same four bytes to decrypt a 7-instruction VM program, runs the remaining 60 bytes through that VM, applies one more generated bytewise transform, and compares the result against a target buffer in .rodata. I solved it by separating the stable part from the unstable part: recover the 4-byte prefix statically, invert the VM cleanly, and let one or two GDB probes reveal the final generated stage instead of trying to re-lift that tail by hand every round.

Greybox

CODEGATE 2026 Quals - Greybox # Category: Reverse Engineering Challenge: Oh! My Greybox Keeps Running! Files: deploy/prob, deploy/target Solver: solver.py TL;DL # The binary hides a small VM behind fake FILE state and libc teardown machinery. The hard part is not the arithmetic. The hard part is recognizing that the weird runtime wrapper is only there to make the VM harder to spot. Once I aligned the handler table correctly and confirmed how the scheduler dispatches handlers, the shortest reliable solve was to record one concrete trace, replay that trace symbolically, and let z3 recover the 64-byte accepted input.

Comqtt

CODEGATE 2026 Quals - comqtt # Category: Pwn Challenge: mqtt / comqtt Solver: solve.py TL;DL # The broker has a retained-message deletion bug that leaves a stale tail entry behind after compaction. On the next retained insert, that stale slot frees a payload pointer that a live retained entry still references. Because each client runs in its own thread and glibc tcache is per-thread, that one mistake becomes a cross-thread tcache-dup primitive. I used it first to build an arbitrary-read oracle, then to dump the live libc image, resolve system() from the in-memory ELF data, and finally overwrite free@GOT with the real runtime address instead of guessing a libc version.

Cogwartslang

CODEGATE 2026 Quals - CogwartsLang # Category: Reverse Engineering Challenge: CogwartsLang Solver: solve.grim TL;DL # The language syntax is mostly decoration. The real challenge is the oracle host module loaded by harness. Once I understood that the important state lived in the host and not in the source language, the solve became a timing problem: reconstruct the oracle’s arithmetic, identify the exact checkpoint and ticket values, and call the host functions in the right order without accidentally burning extra ticks.

Cobweb

CODEGATE 2026 Quals - Cobweb # Category: Web Challenge: Cobweb Description: I wanted to create a web application.. but I don't know how to use web frameworks. So I decided to use pure C to make a web application! Solver: exploit_admin_post_xss.py Transport helper: solve.py TL;DL # The challenge looks like a stored-XSS task at first, but that is only half right. The actual entry point is a one-byte stack overwrite in edit_post. If I make the escaped content length land exactly on 0x6000, the trailing NUL from html_escape() zeros the low byte of the saved user_id local. That pushes the request into the admin SQL branch, rewrites my post as user_id = 0, and then the admin-only render path decodes the escaped content back into raw HTML. Only after that ownership flip does the stored script become real JavaScript in the bot’s browser.