CPCTF 2026 Writeup
CPCTF 2026 was a large Japanese CTF with 54 challenges spanning PPC, Crypto, Web, Pwn, Forensics, Reversing, OSINT, Shell, and Misc. This post groups the solved challenges by category, keeps the insights that matter, and skips the noise.
PPC
These were the bulk of the contest: fast algorithmic problems with big constraints. Two highlights.
Sum of Prod of Root
The statement asks for
[ \sum_{i=1}^{N}\prod_{k=1}^{\infty}\left\lfloor i^{1/k}\right\rfloor ]
modulo (998244353), with (N) up to (10^{18}). The naïve product is impossible, but most factors are 1.
Define
[ F(n)=n\cdot\lfloor\sqrt n\rfloor\cdot H(n),\qquad H(n)=\prod_{k=3}^{\infty}\left\lfloor n^{1/k}\right\rfloor. ]
(H(n)) only changes at perfect powers (x=b^k) for (k\ge 3), and there are only (O(N^{1/3})) such events. Between events, (H) is constant, so the problem reduces to a prefix sum of (n\lfloor\sqrt n\rfloor), which has a closed form using power-sum formulas. Sweep the event list, merge duplicate powers, and add each interval contribution.
Complexity: (O(N^{1/3}\log N^{1/3})).
Brackets Stack Query 2
A string is good if you can delete every ( | ) triple. Online queries either append one character or undo the last append. The trick is to store the number of collapses caused by each append. When undoing, reverse those collapses by pushing back ( | ) that many times, then pop the appended character. Amortized (O(1)) per operation.
Crypto
Dualcast
A Python script prints c = bytes_to_long(flag_bytes) using PyCryptodome. Invert directly:
from Crypto.Util.number import long_to_bytes
flag = long_to_bytes(c)
Flag: CPCTF{wh47_7yp3_15_y0ur_477r1bu73?}
Web
Template Playground
The server builds a lodash imports object from user-supplied helper names and passes it to _.template. Import keys become parameter names in an internally generated Function(...), so arbitrary default-parameter expressions can be injected.
Send a helper name like:
x=process.mainModule.require("fs").readFileSync("/flag.txt","utf8")
Then render <%= x %>. The default expression runs before the template body is evaluated, reading the flag into scope.
{
"template": "<%= x %>",
"data": {},
"use": [
"x=process.mainModule.require(\"fs\").readFileSync(\"/flag.txt\",\"utf8\")"
]
}
Flag: CPCTF{l0d4sh_t3mpl4t3_1mp0rts_1nj3ct10n}
Mirage
The challenge page lists many fake flag strings with wrong prefixes. Only one real CPCTF{...} string is present; copy it.
Flag: CPCTF{CH4R4C73R5_4L50_D159U153}
Pwn
campaign
The binary has a format string in printf(name) and a global type[] = "ai". The program calls system("cat flag.txt") when strcmp(type, "human") == 0. The binary is not PIE, so type is at a fixed address.
A pwntools fmtstr_payload overwrites type to "human" in three short writes; the payload fits under the 96-byte fgets limit. After the overwrite, the forced command prints the flag.
Flag: CPCTF{b3_c4r3fu1_0f_ph15h1ng_m3s54g3s}
Forensics
Authorized Whale
We are given an 8 GiB raw disk image of a compromised Ubuntu 24.04 box, plus a live SSH target using publickey-only auth.
Key findings on disk:
/var/www/html/index.htmlbrags about settingcap_dac_override+epon/usr/bin/curl. With that capability, any user can bypass DAC and access/var/run/docker.sockorfile:///root/flag.txt./root/.ssh/authorized_keyswas tampered with an Ed25519 key that runscat /root/flag.txtas a forced command.- The attacker generated the keypair in
/dev/shm, so the private key is not on disk as a regular file. However, Docker container state JSON under/var/lib/docker/containers/survived deletion, including a bind mount/root:/host_rootand environment variables containing the reversed public key and a base64 private-key blob.
Recovering the private key and reversing the public-key payload gives an SSH key that matches the forced authorized_keys entry. Logging into the live target with it returns the flag.
Flag: CPCTF{9r0t3ct_y0ur_d0ck3r_s0ck3t_str1ctLy}
Reversing
viGor
A stripped Go binary named “viGor”. It reads exactly three 4-byte UTF-8 emojis, extracts bytes 3 and 4 from each to build a 6-byte user array, then computes:
out[i] = consts[i] ^ key[i] ^ user[i % 6]
for (i = 0 \ldots 29). The output is printed directly, with no equality check. Because the first six bytes of out must be CPCTF{, we recover user[0..5], pack them back into three emojis, and feed those to the program.
user = [0xa5, 0xb0, 0x98, 0xa1, 0x98, 0xad]- Emojis:
🥰😡😭
Running the binary with that input prints:
Flag: CPCTF{Br4ck3ts_7ouch_My_H34rt}
OSINT
Night View
A single photo of the Tokyo waterfront at night, taken from a high indoor viewpoint through glass, with Rainbow Bridge visible. After many wrong guesses around Toyosu, the decisive clue was a blog post (likearamen.xii.jp) showing the same viewpoint from a wider angle, placing the author’s work/lunch radius in 田町 / 芝浦 instead. The correct building is msb Tamachi 田町ステーションタワーN, OpenStreetMap way 333413426.
Flag: CPCTF{333413426}
Shell
ssh2
After SSH login, /flag/flag.txt is readable. The server bans a few file-printing commands (cat, tac, less, more, head, tail, nl, rev), but the blacklist is literal and does not block interpreters.
python3 -c 'print(open("/flag/flag.txt").read())'
Flag: CPCTF{8ury_0n35_h34d_1n_7h3_54nd80x}
Misc
QRRRRRRRRRR
A version-3 QR code that every standard decoder refuses. Sampling and Reed-Solomon decoding confirm the bit layout is valid, but the byte-mode character-count indicator was zeroed. A compliant decoder sees cc=0 and stops. Skipping the char-count field and reading the raw payload after the mode bits reveals the flag embedded immediately after the fake zero-length indicator.
Flag: CPCTF{z3r0_l3ngth_h1dd3n_d4t4}
Quick wins
| Challenge | Category | One-liner |
|---|---|---|
| sanity-check | Misc | Follow the instructions. |
| hidden | Reversing | strings hidden | grep CPCTF |
| welcome-cli | Shell | Connect and read the banner. |
| il0v3pdfs | Forensics | Extract text from the PDF. |
| zero-one-workshop | PPC | Ad-hoc binary string rule. |
Takeaways
- PPC: When a sum has a multiplicative factor that only changes at special points, look for (O(N^{1/k})) event sweeps and closed-form prefix sums.
- Web: Template engines that turn import names into function parameters are injection surfaces even when the template body is sanitized.
- Forensics: Docker state JSON and capabilities can persist in unallocated blocks; recovering a deleted container config can be as good as recovering the file itself.
- OSINT: One geolocated source photo beats fifty guesses.