Step 282. CTF Debrief Blocks A + B: Writing Three Write-ups — A Reproducible Write-up Is Proof of Skill
Level 3 — Real-World CTF & Advanced Offensive Skills | Difficulty ★★★☆☆ | Estimated time: 2 days (2–3 hours per write-up)
Prerequisites: Steps 279–281’s competitions #1–#2 finished, Step 280’s debrief block A routine.
- What you need: the list of problems you solved at competitions #1–#2 (logs and boards), in-competition captures and code, a blog or team repository, and today’s write-up skeleton generator script.
- ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. Publishing solutions to CTFtime-registered competition problems after the competition ends is the community’s standard culture — but if the competition rules specify a publication window (e.g., no publication before the end), those rules take precedence.
- Caution: blog and CTFtime registration screens are screen examples. The output of the write-up skeleton generator script is locally measured.
If debrief block A (Step 280) was training that learns from others’ answers, today’s block B is training that writes up problems I solved myself. Write a write-up once and you profit three times — review material for your future self, knowledge sharing with teammates and the community, and a career portfolio. From now on, writing a write-up for every problem you solve at every competition becomes your rule.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Apply the 6-part structure of a write-up a reader can reproduce (problem info / observation / attempts / failures / solution / lessons)
- Execute the rule of writing the first draft within 48 hours of the competition’s end
- Self-review your writing against the quality standard of code, captures, and reproduction steps ("can it be followed?")
- Accumulate and manage write-ups with per-competition folders and a field/technique tag system
- Explain the procedure and cautions (rule checking) of blog publication and CTFtime link registration
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Markdown, blog/team repository, Python (skeleton generator script) |
| Today’s tools | writeup_init.py (auto-generates folder + skeleton), the 6-part structure template, the tag system |
| Concepts needed | Debrief block B, the 48-hour draft rule, reproducibility, recording failure paths, the portfolio effect |
| Today’s deliverable | 3 published write-ups + an accumulating folder system |
2-1. Why Write — The Write-up’s Triple Effect
First, review material for your future self. When you meet a similar problem at a competition three months from now, memory is hazy but your write-up is exact. Writing in your own words re-reads at a different speed than someone else’s write-up.
Second, knowledge sharing with team and community. As Step 281 showed, a team’s skill is the speed of information movement. A post-competition "write-up explaining the problems I solved" is sharing that outlasts any team meeting. And a public write-up becomes your business card for recruiting teammates for the next competition (Step 190’s path).
Third, a career portfolio. In security hiring, a "CTF write-up blog" is a different kind of evidence from certifications — simultaneous proof of three abilities: facing real problems, actually solving them, and explaining them so others understand.
2-2. The 6-Part Structure — The Skeleton of a Reproducible Write-up
| Part | Section | What it holds | Common deficiency |
|---|---|---|---|
| 1 | Problem info | Competition, field, points, summary of the problem statement | Missing competition name — unsearchable later |
| 2 | Observation | What you saw first — files, responses, oddities | Conclusions only, no observation |
| 3 | Attempts | Hypotheses and their tests, in order | Only the successful hypothesis |
| 4 | Failures | Abandoned paths and the grounds for abandoning them | The section itself missing — a half write-up |
| 5 | Solution | Complete code/commands that yield the flag when followed exactly | Key steps omitted ("now just solve it") |
| 6 | Lessons | 1–3 techniques this problem taught | Without it a diary; with it a textbook |
The quality standard compresses into one sentence — "can a reader who never saw the competition reproduce it from this write-up alone?" This one standard determines the length of parts 2, 3, and 5. The reason captures are needed and the reason every command is written without omission are both this sentence.
2-3. Why the Failure Section Takes Up Half
Beginners write only the success path. Such a write-up leaves the reader with nothing but the impression "this person must be a genius." What a reader truly learns is "why that path was abandoned" — recall the part you were most grateful for when reading others’ write-ups in Step 280’s debrief. Most of the time it was a record of failure saying "I also wandered here."
Your competition log shines here. The stuck memos are part 4’s material as-is. A person without a log must reconstruct failures from memory; a person with a log just copies.
2-4. The 48-Hour Draft Rule — The Half-Life of Detail
Forty-eight hours after a competition ends, the solution’s details start to evaporate — points appear where you can’t recall "why did I use that payload back then." So the rule is this: write the first draft (a rough version with only the structure complete) within 48 hours of the end. Polishing can wait until the following week. With a draft, polishing is always possible; without a draft, the write-up never exists.
If you’re on a team, add a split of labor — the solver writes, the non-solver reviews (Step 190’s team rules). A point where the reviewer says "I don’t get this part" is a point readers won’t get either. Review is a free reader test.
2-5. Accumulating Management — If Finding Takes Longer Than Writing, You Lose
Past ten write-ups, you need a management system. The recommended structure is per-competition folders + tag headers.
writeups/
Weekend_CTF_1/
Web__warmup-cookie/writeup.md
Crypto__caesar-training/writeup.md
Weekend_CTF_2/
Web__jwt-forgery/writeup.md
The tag header (front matter) holds the field and techniques — like tags: [web, jwt, auth-bypass]. Three months later, the search "how did I solve JWT problems again" runs on these tags. Putting the competition and field first in the file path beats embedding dates for retrievability.
3. Follow Along
3-1. Selecting Targets — 3 of the Solved Problems
Pull the solve lists of competitions #1–#2 from the logs and team board. Three from Step 279’s solo competition, problems I solved at Step 281’s team competition — pick 3 among these. The selection criterion is the opposite of block A’s — is it worth explaining to others. A problem that broke you once makes better material than an overly easy one ("the input is the flag as-is").
3-2. The Skeleton Generator Script (Measured)
To avoid typing the 6-part structure by hand every time, use a script that makes the folder and skeleton. Save it as writeup_init.py.
# writeup_init.py — create a write-up folder and skeleton markdown
import os, re, sys
TEMPLATE = """---
contest: {contest}
category: {field}
problem: {problem}
points:
tags: [{field_lower}]
date:
---
# {contest} — {problem} ({field})
## 1. Problem info
- contest / category / points / summary of the problem statement (core only)
## 2. Observation — what I saw first
- What stood out in the files/service/responses. Attach captures.
## 3. Attempts — hypotheses and experiments
- Hypotheses in order, with the command/code that tested each.
## 4. Failures — abandoned paths
- Approaches that didn't work, and the grounds for abandoning them. (A write-up without this section is a half.)
## 5. Solution — the final exploit
- Complete code/commands a reader can follow exactly to the flag.
- Mask part of the flag format (e.g., flag{{...}} — publish the real value only after checking the competition rules)
## 6. Lessons — what I'll use right away next time
- 1–3 techniques this problem taught. Which playbook item they connect to.
"""
def safe(s):
return re.sub(r"[^\w\-]", "_", s)
if __name__ == "__main__":
if len(sys.argv) < 4:
print(__doc__)
sys.exit(1)
contest, field, problem = sys.argv[1], sys.argv[2], sys.argv[3]
d = os.path.join("writeups", safe(contest), f"{safe(field)}__{safe(problem)}")
os.makedirs(d, exist_ok=True)
p = os.path.join(d, "writeup.md")
with open(p, "w", encoding="utf-8") as f:
f.write(TEMPLATE.format(contest=contest, field=field,
field_lower=field.lower(), problem=problem))
print("created:", p)
Run result (measured):
$ python writeup_init.py "Weekend CTF 1" "Web" "warmup-cookie"
created: writeups\Weekend_CTF_1\Web__warmup-cookie\writeup.md
$ python writeup_init.py "Weekend CTF 1" "Crypto" "caesar-training"
created: writeups\Weekend_CTF_1\Crypto__caesar-training\writeup.md
$ python writeup_init.py "Weekend CTF 2" "Web" "jwt-forgery"
created: writeups\Weekend_CTF_2\Web__jwt-forgery\writeup.md
Checking the generated structure:
writeups/Weekend_CTF_1/Crypto__caesar-training/writeup.md
writeups/Weekend_CTF_1/Web__warmup-cookie/writeup.md
writeups/Weekend_CTF_2/Web__jwt-forgery/writeup.md
A 6-part skeleton sits in folders ordered contest → category·problem. Now you just fill them in.
3-3. Writing One to the End — A Filled Example
An example of how to fill the skeleton: a write-up of the xor-note-type problem reproduced in Step 280 (a writing example based on the local reproduction — not an actual competition problem).
## 2. Observation — what I saw first
The problem file was a single text. A note saying "the ciphertext is
repeating XOR, key length at most 10, and the plaintext is guaranteed
to start with 'flag{'", plus 32 bytes of hex.
→ The word "guaranteed" was suspicious. Possibly a key, not a hint.
## 4. Failures — abandoned paths
Hypothesis 1: brute-force key lengths 1–10 and eyeball for "readable plaintext"
→ Read all 10 outputs but couldn't judge. Grounds for discarding: eyeballing
collapses as the key length grows. The criterion "is it readable" is vague too.
Lesson: brute force without a criterion is gambling, not an answer.
## 5. Solution — the final exploit
XOR crib = b"flag{" with the ciphertext's first 5 bytes → key fragment b'K7mK7'.
The fragment repeats with length 3 → key length fixed at 3, full decryption:
(full code attached — xor_solve.py, 20 lines)
Decryption result: flag{x0r_k3y_...} ← real value published only after checking competition rules
Tips for filling: always write part 4’s "grounds for discarding." Not "it didn’t work" but "why it was abandoned" — that is the reader’s wealth. Part 5’s code is not abbreviated — write-up code is not for reading but for copying and running.
3-4. Publishing and Registering (Screen Example)
Publish finished write-ups on a blog (personal blog, team repository, etc.). Two pre-publication checks.
[Pre-publication checks]
□ Competition rules: is write-up publication allowed, and is the permitted timing specified
□ Flag handling: if the rules prohibit publishing flag originals, mask part of it
Then register the write-up link on the competition’s CTFtime page (screen example — the Write-ups section of the competition page has an "Add write-up"-style registration feature). Once registered, traffic flows to your writing from the competition results page. This is the actual path of the saying "one write-up is a business card that draws the next team offer."
3-5. After Writing Three — The Self-Review Table
Once you’ve written 3, review each with this table.
| Question | Pass standard |
|---|---|
| Can someone who never saw the competition reproduce it? | Reach flag from environment setup using only parts 1, 2, 5 |
| Is there a failure path? | Part 4 has 1+ discarded hypothesis + grounds for discarding |
| Is the code complete? | A complete script whose result regenerates on copy-and-run |
| Is it searchable? | Contest name, category, technique tags in the header |
| Did it follow the rules? | Publication-window rule checked, flag masking decided |
The reason for three is not the number but repetition — the first teaches the structure, the second builds speed, the third grows "your style." Once these three accumulate, the next competition’s write-up is not work but routine.
4. Missions & Exercises
Mission — Publish 3 Write-ups
- Select 3 of the problems solved at competitions #1–#2, and write one line of selection rationale each.
- Make skeletons with
writeup_init.py, and complete the 3 first drafts within 48 hours of the end (or of starting today). - Review each against 3-5’s self-review table, and shore up anything that fails.
- Publish on a blog or team repository (after checking competition rules). Register the link on CTFtime if possible.
- If you have a team, cross-review each other’s write-ups and point out at least one "I don’t get this" spot each.
Exercises
Exercise 1. Explain why "reproducibility" is the write-up’s quality standard, together with the roles of part 2 (observation) and part 5 (solution).
Exercise 2. State concretely the harm a write-up that records only the success path does to its reader.
Exercise 3. Explain why the 48-hour draft rule separates the "draft" from the "final version."
Exercise 4. Give two advantages of the split where the solver writes and the non-solver reviews.
5. Model Answers & Completion Criteria
Mission Model Answer
How to verify: ① do all 3 have the 6-part structure, and in particular does part 4 (failures) have discarded hypotheses and grounds — an empty part 4 means unfinished. ② is part 5’s code actually runnable and complete (running it yourself is the surest verification)? ③ does each write-up’s header have contest, category, and technique tags so it can be searched? ④ is there a trace of checking the competition rules (publication window, flag handling) before publishing? ⑤ is the draft’s timestamp within 48 hours of the competition’s end? ⑥ were the cross-review’s points reflected?
Exercise Answers
Answer 1. The reader’s goal is not appreciation but reproduction. Only when part 2 (observation) is faithful can the reader stand on the same starting line, and only when part 5 (solution) is complete code can the reader reach the same finish line. Because reproducibility is achieved only through the faithfulness of these two sections, it becomes the body of the quality standard.
Answer 2. The reader learns a false model — "the author knew the answer in one shot" — and loses the standard for judging whether their own being stuck is normal or a skill deficit. Even when they actually fall into the same trap, the write-up has no such trap, so they can’t learn how to get out. It is writing with the most valuable information — the abandoned paths and their grounds — deleted.
Answer 3. What the 48 hours demand is information storage while memory is alive, and polishing is expression improvement that can be done anytime. Bundle the two into one job and the pressure of completion postpones the write-up itself; after detail’s half-life passes, there’s no material left to write from. Separated, the draft completes lightly, and the final version is made later on top of living material.
Answer 4. ① A non-solver reviewer is a real reader in the state of not knowing the problem, so their "I don’t get this" accurately predicts where actual readers will drop off. ② From the reviewer’s side, verifying a write-up teaches them that problem’s solution — so the review doubles as an in-team block A debrief: one write-up grows two teammates.
Completion Criteria Checklist
- [ ] I can recite the write-up’s 6-part structure in order
- [ ] I can explain why a write-up without part 4 (failures) is a half
- [ ] I completed 3 first drafts within 48 hours
- [ ] I reviewed the 3 write-ups with the five self-review items
- [ ] I have the management system of per-competition folders + tag headers
- [ ] I checked the competition rules (publication window, flags) before publishing
- [ ] I can explain the advantages of the solver-writes / non-solver-reviews split
6. Common Pitfalls & Fixes
Wall 1. I have nothing to write — it’s all stuff I already know
Cause: what’s "obvious" by your standards is a wall for the reader. The write-up’s reader is yesterday’s you.
Fix: look at your competition log’s stuck memos — the person who stopped at those points is your first reader. "What I didn’t know" is exactly what to write. If you still can’t see material, fill part 4 (failures) first. Writing out failures forces you to explain why they failed — and that becomes the body of the piece.
Wall 2. I didn’t capture during the competition, so I have no evidence
Cause: captures were missing from Step 279’s log habit.
Fix: for this write-up, you can remake the evidence with a local reproduction (Step 280’s method) — build the reproduction environment and capture its screens; those make even better follow-along material for the reader. And from the next competition, put "capture the decisive moments" into your start checklist.
Wall 3. The script can’t find the path
Symptom (measured — the reproduction script run from a different directory):
$ python xor_solve.py
File ".../xor_solve.py", line 4, in <module>
text = open("challenge.txt", encoding="utf-8").read()
FileNotFoundError: [Errno 2] No such file or directory: 'challenge.txt'
Cause: a script looks for files in the current directory. As the write-up folder structure deepens, it’s easy to lose track of which directory you ran from.
Fix: before running, check with ls (dir on Windows) that the target file is in the current directory. Keeping reproduction scripts and their data in the same folder and running from that folder is the answer.
Wall 4. I referenced others’ write-ups and ended up nearly plagiarizing
Cause: this is an ethics issue — plagiarism is the most fatal reputational damage in the community.
Fix: block B’s targets are problems I solved myself. If you want to write about a problem you solved using someone else’s solution, write it as a block A record, not block B, and cite the source. Even for a solution you reproduced by hand, if you learned the core of the approach from a specific write-up, disclosing it with a link is both courtesy and skill.
Wall 5. I wrote three and nobody reads them
Cause: first write-ups’ view counts are inherently low. View count is not this stage’s metric.
Fix: at this stage, a write-up has three readers — future me, my teammates, and a someday hiring manager. None of the three shows up in the view counter. Rising views come after 20–30 accumulated, once algorithms and search attach. What to do now is not volume but repetition of structure.
7. Summary
Today’s Concepts
| Concept | One-line explanation |
|---|---|
| Debrief block B | The debrief of writing up problems I solved — the growth engine paired with block A |
| 6-part structure | Problem info → observation → attempts → failures → solution → lessons |
| Reproducibility | "Can a reader who never saw the competition follow this write-up alone" — the sole quality standard |
| Failure section | Abandoned paths and grounds — where the reader truly learns |
| 48-hour draft rule | Store information within detail’s half-life; polish later |
| Tag system | Per-competition folders + field/technique headers — retrievability of the accumulation |
| Triple effect | Review material + knowledge sharing + career portfolio |
Today’s Commands & Tools
| Command/tool | What it does |
|---|---|
python writeup_init.py "contest" "category" "problem" |
Generate folder + 6-part skeleton writeup.md |
| Self-review table (5 items) | Review reproducibility, failure paths, complete code, searchability, rules |
| Blog / team repository | Publication and accumulation |
| CTFtime write-up registration | Secure the inflow path from the competition page |
The Core Instinct
Something strange happens when you write write-ups — problems you thought you solved turn out to be less than solved. The moment you try to write in a sentence "why that command worked," you see a step you can’t explain. Filling those blanks is writing’s hidden harvest. Solving is the beginning of understanding; writing is its completion.
And this habit is compound interest. Three per competition, ten competitions later: a searchable knowledge warehouse of thirty — and that warehouse becomes not your memory but your asset.
Once every box is checked, Step 282 is complete. Click the checkbox in the sidebar to save your progress.