Step 281. ★ CTF #2: This Time as a Team — The Day You Learn the Efficiency of Division and the Cost of Communication at Once
Level 3 — Real-World CTF & Advanced Offensive Skills | Difficulty ★★★☆☆ | Estimated time: 1 weekend (competition 24–48 hours + 1-hour team retrospective)
Prerequisites: Step 279’s solo competition finished, Step 280’s debrief routine, Step 190’s draft team rules.
- What you need: a team of 2–4 (the team you built in Step 190, or one joined from a community), a Discord server (voice + text channels), a shared spreadsheet, each member’s
ctf_log.py, and today’s team board merge script. - ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime. CTF competitions registered on CTFtime are legal platforms the organizers opened for you to join — never attack anything beyond the challenge servers the competition provides, and remember that sharing solutions with other teams during the competition is a rules violation.
- Caution: competition platform and Discord screens are all screen examples. Only the output of the team log merge script is locally measured.
CTF is a team sport by nature. A team creates field coverage that is physically impossible solo. But a new discovery awaits you at the second competition — a team bills you communication costs equal to the efficiency of its division of labor. Today’s goal is not winning; it is experiencing both in the same competition.
1. Learning Objectives
By the end of this chapter, you will be able to:
- Agree on field assignments and clue-sharing rules in a 30-minute kickoff meeting
- Build a shared board (problem list / owner / status) and update it every hour
- Create cross-field collaboration with "anyone want this clue?" communication
- Swap owners of stuck problems with the 2-hour swap rule
- Merge teammates’ individual logs to produce the team board and the combined score
- Separate good division of labor from communication misses in a team retrospective
2. Background Knowledge — Today’s Tools and Concepts
Today’s Tools at a Glance
| Category | Details |
|---|---|
| Language/environment | Discord (voice/text), shared spreadsheet, individual log CSVs, Python (merge script) |
| Today’s screens/tools | Team kickoff agenda, board template, merge_board.py (log merge + duplicate-attempt warning) |
| Concepts needed | Field assignment, clue-sharing rules, 1-hour updates, 2-hour swap, duplicate attempts, team retrospective |
| Today’s deliverable | Team board + team_log.csv (combined team log) + collaboration retrospective memo |
2-1. Solo vs Team — Same Competition, Different Game
The optimal strategy for a solo competition was "allocating my time." A team competition adds two more axes.
| Axis | Solo | Team |
|---|---|---|
| Coverage | Only the fields I know | The union of every member’s fields |
| Handling being stuck | Drop by the 2-hour rule | Swap to a different pair of eyes |
| Knowledge flow | Only inside my playbook | Real-time movement via "anyone want this clue?" |
| Cost | None | The cost of communication, coordination, and duplicate attempts |
The last row is today’s central tension. Every team gain (coverage, swaps, knowledge sharing) only materializes when communication works. A team whose communication collapses is just "three solos eating lunch together" — and such a team is worse than the sum of three solos, because a new loss called the duplicate attempt appears.
2-2. Preparation — The Three-Piece Set of Channels, Board, and Rules
There are three things to prepare by the day before the competition.
① Discord channel structure. This minimal setup is enough.
📢 announce — competition notices and key agreements pinned
🚩 flags — flag-submission success reports (problem name + who)
💡 clues — dedicated to tossing "anyone want this clue?"
🔊 voice-lounge — always-on voice channel
The reason for splitting channels is the mid-competition chat flood. When everything mixes in one channel, flag announcements get buried in chatter and decisive clues vanish beyond the scroll.
② Shared board. Five columns in a spreadsheet are enough — problem | field | points | owner | status (not started / in progress / stuck / solved). Copying the entire problem list at the moment the competition starts is the first task.
③ Rule agreement. Polish Step 190’s draft team rules for live use. At minimum, three agreements are needed — record on the board immediately upon starting a problem, toss clues to the 💡 channel immediately upon discovery, update the board every hour.
2-3. The 30-Minute Kickoff — Agreements Before the Start
Thirty minutes before the start, gather in the voice channel. The agenda is fixed.
[Kickoff agenda — 30 minutes]
5 min: sweep the whole problem list (copying it to the board together)
10 min: field assignments — each declares their main field, check for gaps
5 min: clue-sharing rule agreement — "toss on discovery; the receiver judges"
5 min: swap rule agreement — stuck on one problem for 2 hours, declare "swap wanted" in channel
5 min: sleep shifts — who sleeps when (mandatory for a 48-hour competition)
The principle of field assignment is the same as Step 190 — no overlaps. If a gap field appears (e.g., no Pwn owner), declare it honestly: not "we won’t touch Pwn," but "we’ll all look at one easy Pwn problem together on the last morning" — handling the gap is part of the plan.
2-4. Running the Competition — Three Rhythms
Once the competition starts, the team runs on three clocks.
- The 1-hour rhythm — board updates. "What I’m looking at right now" must always be on the board. When this rhythm breaks, the team degenerates into a sum of solos.
- The 2-hour rhythm — swaps. If the solo 2-hour rule was a drop, on a team the swap comes first. A problem I stared at for 2 hours may be a 10-minute job for different eyes — because background knowledge differs. Swap etiquette: hand over a three-line summary of your attempts so far.
- The meal/sleep rhythm — per the shift table agreed in 2-3. If the team knows in advance when a member will disappear, the rest can fill the ownership gap.
2-5. "Anyone Want This Clue?" — The Team’s Unique Score-Generating Device
A team has a score generator that a solo player doesn’t. You found a strange header in a Web problem’s response and it seems unrelated to your problem — solo, you let it slide. On a team, you toss it to the 💡 channel.
💡 clues — screen example
minho: jwt-forgery response shows an X-Debug: /backup/config.bak header. Unrelated to my problem, I think. Anyone want it?
alice: that might be the upload path hint for my file-upload-rce. Taking it.
The mechanism by which this one line of dialogue becomes points: every competition always has cases where one problem’s byproduct is another problem’s key. The organizers design it that way. That’s why the rule "the receiver judges" matters — the moment the tosser self-censors with "this must be useless," the device stops.
3. Follow Along
3-1. Mustering the Team and Preparing (Screen Example)
Propose joining a competition to your Step 190 team (or a team you joined from a community). Pick a weekend competition of similar grade to 2-1’s first competition — team operations alone are a new variable; raising the competition difficulty too makes it two variables.
Preparation ends with making 2-2’s three-piece set. Build the board template in advance and confirm every teammate has edit permission — no time waste is more awkward than waiting for a permission request after the competition has started.
3-2. Putting Name Tags on Individual Logs
The individual log habit (Step 279) stays the same in a team competition. Just save each person’s log file under their name for the later merge — like alice.csv, bob.csv, minho.csv. There is only one rule: everyone uses the identical event format — use ctf_log.py‘s output format as-is and it aligns automatically.
3-3. The Team Log Merge Script (Measured)
A script that merges teammates’ logs during or after the competition to produce the team-wide board and combined score. Save it as merge_board.py.
# merge_board.py — merge teammates' individual logs into a shared board and a combined team log
import csv, sys
from datetime import datetime
FMT = "%m-%d %H:%M"
rows = []
for path in sys.argv[1:]:
owner = path.split(".")[0] # file name = teammate name
with open(path, newline="", encoding="utf-8") as f:
for r in csv.DictReader(f):
r["owner"] = owner
rows.append(r)
rows.sort(key=lambda r: r["time"]) # stack states in time order, not file order
problems = {}
for r in rows:
p = problems.setdefault(r["problem"], {"field": r["field"], "points": r["points"],
"owners": set(), "status": "not started",
"start": None, "end": None})
p["owners"].add(r["owner"])
t = datetime.strptime(r["time"], FMT)
if r["event"] == "start":
p["status"] = "in progress"
p["start"] = t if p["start"] is None else min(p["start"], t)
elif r["event"] == "stuck":
p["status"] = "stuck"
elif r["event"] == "drop":
if p["status"] != "solved":
p["status"] = "dropped"
elif r["event"] == "solve":
p["status"] = "solved"
p["end"] = t
print("problem | field | status | owner | elapsed")
print("--------------------|------------|-------------|-----------------|--------")
solved_pts = 0
for name, p in problems.items():
elapsed = "-"
if p["start"] and p["end"]:
elapsed = f"{int((p['end'] - p['start']).total_seconds() // 60)}min"
owners = ",".join(sorted(p["owners"]))
flag = " ⚠ duplicate attempt" if len(p["owners"]) > 1 else ""
if p["status"] == "solved" and p["points"]:
solved_pts += int(p["points"])
print(f"{name:<20}| {p['field']:<10} | {p['status']:<11} | {owners:<15} | {elapsed}{flag}")
n_solved = sum(1 for p in problems.values() if p["status"] == "solved")
print()
print(f"team total: {len(problems)} started / {n_solved} solved / {solved_pts} pts")
with open("team_log.csv", "w", newline="", encoding="utf-8") as f:
fields = ["time", "event", "field", "problem", "points", "memo", "owner"]
w = csv.DictWriter(f, fieldnames=fields)
w.writeheader()
w.writerows(sorted(rows, key=lambda r: r["time"]))
print("team_log.csv saved (combined team log)")
Two key behaviors: ① it stacks states after sorting all logs by time — if you stack in the order files are read, an old record from a later-read file overwrites the newer state from an earlier file. ② when a problem has two or more owners, it attaches a ⚠ duplicate attempt warning — the warning remains even if it was solved, because time burned in duplicate is a loss regardless of whether it was solved.
3-4. Running the Merge — The Board and the Duplicate Warning (Measured)
We ran it with the actual records of an example team (alice, bob, minho). This is the measured output from feeding the three members’ logs.
$ python merge_board.py alice.csv bob.csv minho.csv
problem | field | status | owner | elapsed
--------------------|------------|-------------|-----------------|--------
jwt-forgery | Web | solved | alice | 47min
bof-ret2win | Pwn | solved | bob | 142min
substitution-101 | Crypto | solved | minho | 34min
file-upload-rce | Web | solved | alice,minho | 670min ⚠ duplicate attempt
packed-binary | Rev | stuck | bob | -
usb-pcap | Forensics | solved | minho | 50min
disk-image-1 | Forensics | solved | alice | 45min
rsa-small-e | Crypto | solved | bob | 47min
team total: 8 started / 7 solved / 1300 pts
team_log.csv saved (combined team log)
How to read it: ① 8 started, 7 solved — the power of coverage is visible against the solo competition (6 started / 3 solved). ② But file-upload-rce carries a ⚠ duplicate attempt warning — the incident where alice started at 22:10, minho unknowingly started at 22:30, and 40 minutes burned before they checked the board and handed it over. ③ packed-binary ended stuck under bob — a problem where no swap happened. These two lines are the agenda of today’s team retrospective.
This script also went through mistakes. The first draft stacked states in file-processing order, so an old "stuck" record from a later-read file overwrote an earlier file’s "solved" (file-upload-rce showed as stuck despite being solved). Only after sorting the logs by time did the correct board come out — a code-level confirmation of the principle that "state only stacks in time order."
3-5. The Post-Competition Team Retrospective — A 1-Hour Agenda
Gather by voice within 24 hours of the end. Unlike the individual retrospective (Step 279), a team retrospective has three agenda items of its own.
[Team retrospective agenda — 60 minutes]
1. Good division of labor (15 min): which assignments made points
- e.g., alice/minho split the two Forensics problems and secured 350 pts
2. Communication misses (20 min): accidents the board/channels should have prevented
- e.g., file-upload-rce duplicate attempt, 40 min — start record came late
- e.g., packed-binary — bob never declared "swap wanted"
3. Next improvements (25 min): agree by turning them into rule sentences
- "start records happen in the very seat where you open the problem" — no batch logging later
- "asking for a swap is a duty, not a pride issue" — declare unconditionally past 2 hours
The retrospective’s output must be rule sentences. A resolution like "let’s communicate well" evaporates by the next competition, but "past 2 hours, declaring a swap request is mandatory" is a sentence you can keep. These improvements reappear in Step 283 alongside goals.
4. Missions & Exercises
Mission — Finish a Team Competition and Retrospect the Collaboration
- Register for a weekend competition as a team (2–4 people), and complete 2-2’s three-piece set (channels, board, rules) before the competition.
- Hold the 30-minute kickoff and pin the field assignments and swap rule agreement in the
announcechannel. - Update the board on the 1-hour rhythm during the competition, and toss at least 3 clues to the 💡 channel.
- After the end, merge the teammates’ logs with
merge_board.pyto produce the team board and combined score. - Hold the team retrospective with 3-5’s agenda, and fix and save 3 improvement rule sentences.
Exercises
Exercise 1. Explain, using the concept of duplicate attempts, why a team whose communication has collapsed is "worse than the sum of three solos."
Exercise 2. Explain why the solo 2-hour rule (drop) and the team 2-hour swap reach different conclusions.
Exercise 3. What happens without the clue-sharing rule "the receiver judges"?
Exercise 4. From 3-4’s measured board, derive two improvements for the team’s next competition, each with its data evidence.
5. Model Answers & Completion Criteria
Mission Model Answer
How to verify: ① are the kickoff agreements (field assignments, swap rule) left as sentences in the channel — verbal agreements evaporate mid-competition. ② does the board show update traces across the competition’s full time span (a board written only at the start and abandoned is decoration)? ③ were clues actually tossed to the 💡 channel, and did receivers reply "taking it"? ④ if the merge result has ⚠ duplicate attempts, did they make it onto the retrospective agenda — seeing the warning and not addressing it makes measurement meaningless. ⑤ are the improvement rules three sentences of the "when, what" form rather than the "let’s do well" form?
Exercise Answers
Answer 1. When three solos each play alone, even if all three look at the same problem, it’s each one’s own time and the results are independent. But when duplicate attempts happen while bound as a team, part of the team’s total resources (3 people × time) goes redundantly into the same problem while the gain of division (coverage) is given up. It’s a structure that pays the communication cost without collecting the division gain — so it falls below the sum.
Answer 2. A solo drop is the judgment "with my background knowledge, I can’t solve this within 2 hours." But background knowledge differs per person — a wall that’s 2 hours for me may be a familiar pattern to a teammate. Because a team has other eyes, the first prescription for being stuck is not giving up but replacing the viewpoint. Only a problem that fails even the swap becomes the team’s drop.
Answer 3. The tosser starts self-censoring usefulness. A judgment like "this is unrelated to my problem" is made inside one’s own field knowledge, so it inherently cannot see the possibility of being another problem’s key. Once censorship starts, the 💡 channel stops, and every incident where one problem’s byproduct becomes another’s key disappears.
Answer 4. ① file-upload-rce‘s ⚠ duplicate attempt (40-minute loss) → need the rule "start records happen in the very seat where you open the problem." ② packed-binary ended stuck (no swap happened) → need the rule "mandatory swap-wanted declaration past 2 hours." Both are improvements whose evidence is a specific row of the board, not abstract resolutions.
Completion Criteria Checklist
- [ ] I can explain the 30-minute kickoff agenda (assignments, clue rule, swap, sleep shifts)
- [ ] I can explain why channels are split by purpose
- [ ] I kept the 1-hour board-update rhythm throughout the competition
- [ ] I actually tossed 3 or more clues
- [ ] I merged the teammates’ logs and checked the combined score and duplicate warnings
- [ ] I fixed 3 improvement rule sentences in the team retrospective
- [ ] I can explain the difference between the solo and team 2-hour rules (drop vs swap)
6. Common Pitfalls & Fixes
Wall 1. Everyone solved separately and the competition ended
Symptom: the vibe was good, but when it ended, nobody knew who did what. The most common failure pattern of beginner teams.
Cause: board updates were agreed as "nice to have" rather than as a "rule."
Fix: make the 1-hour rhythm a device — hook a top-of-the-hour reminder bot into the voice channel, or have the captain type one line, "board time," every hour on the dot. When the board is alive, communication arises on its own. The reverse never works.
Wall 2. Two people were working the same problem
Symptom (measured — 3-4’s output):
file-upload-rce | Web | solved | alice,minho | 670min ⚠ duplicate attempt
Cause: minho didn’t check the board before starting, and alice also recorded her start late. Forty minutes of the two overlapped and burned on one problem.
Fix: reverse the order of starting — write your name on the board first, then open the problem. "Open the problem first, record later" is the mother of duplicates. The merge script’s warning is for after-the-fact confirmation; prevention is this single reversal of order.
Wall 3. I offered a swap but nobody took it
Top cause: you threw only "can someone look at this?" with no handover. The receiver would have to rebuild 2 hours of context from scratch, so they avoid it.
Fix: a swap comes with a three-line handover — ① facts confirmed so far, ② discarded hypotheses and why, ③ the next candidates I suspect. With these three lines, the receiver’s entry cost shrinks from tens of minutes to a few.
Wall 4. The voice channel drifts into chatter — or is dead silent
Cause: no agreement on the voice channel’s role.
Fix: designate voice as the "always-on lounge," and use the rule that everything needing a record (starts / solves / clues / agreements) goes to the text channels and the board. Spoken words evaporate, so an important agreement is not complete until it’s moved into announce as a sentence on the spot.
Wall 5. The skill gap is big and people get self-conscious
Symptom: the rookie hesitates to toss clues, and the ace monopolizes problems.
Cause: roles were divided only by "field," and no contribution path was designed for the rookie.
Fix: assign the rookie explicit roles — board manager, first responder on the 💡 channel, owner of easy (warm-up) problems. The board manager in particular watches the whole team’s progress, so regardless of skill, it’s the seat that learns the whole picture of a competition fastest. The ace, meanwhile, needs the role of "swap receiver for stuck problems" — that’s what breaks the monopoly.
7. Summary
Today’s Concepts
| Concept | One-line explanation |
|---|---|
| Efficiency of division | The team’s gain of wider coverage through the union of fields |
| Cost of communication | Coordination, recording, duplicate attempts — when it fails, below the sum of solos |
| Kickoff 30 minutes | Agree assignments, clue rules, swaps, sleep shifts as sentences before the competition |
| 1-hour rhythm | The board-update cycle — the device that keeps a team from degenerating into a sum of solos |
| 2-hour swap | Replacing the owner of a stuck problem — different eyes carry different background knowledge |
| 💡 clue toss | The team-unique device connecting one problem’s byproduct to another problem’s key |
| Duplicate attempt | The loss of two people starting the same problem — prevention is "record first, problem later" |
| Team retrospective | Separate good division from communication misses, fix improvements as rule sentences |
Today’s Commands & Tools
| Command/tool | What it does |
|---|---|
| Discord channels (announce/flags/clues) | Pin agreements, report solves, circulate clues |
| Shared board | The single source of truth for per-problem owner and status |
python merge_board.py alice.csv bob.csv ... |
Merge individual logs → board + duplicate warnings + team totals |
team_log.csv |
The merged team log — input for Step 283’s goal calculation |
| Three-line handover | Facts confirmed / discarded hypotheses / next candidates — cuts the swap’s entry cost |
The Core Instinct
What makes points in a team competition is not skill alone — it is the speed at which information moves. A team where a clue reaches the channel in 1 minute from discovery, and the right person’s eyes in 1 more minute, posts a grade-level-higher score even with identical individual skill. Every rule you made today is plumbing for that speed of movement.
And don’t throw away the improvement rules fixed in the retrospective. At the next competition — Step 283 — keeping those rules itself becomes a measurable goal.
Once every box is checked, Step 281 is complete.