Step 282. CTF 복기 블록 A + 블록 B: Write-up 3개 작성 — 재현 가능한 글이 실력의 증거다
Level 3 — CTF 실전과 공격 스킬 심화 | 난이도 ★★★☆☆ | 예상 소요 시간 2일 (Write-up 편당 2~3시간)
전제: Step 279~281의 대회 #1~#2 완주, Step 280의 복기 블록 A 루틴.
- 준비물: 대회 #1~#2에서 푼 문제 목록(로그와 상태표), 대회 중 캡처와 코드, 블로그 또는 팀 저장소, 오늘 만드는 Write-up 뼈대 생성 스크립트.
- ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. CTFtime 등록 대회의 문제 풀이를 대회 종료 후 공개하는 것은 커뮤니티의 표준 문화입니다 — 단, 대회 규정에 Write-up 공개 시점 규정이 있으면(종료 전 공개 금지 등) 그 규정이 우선입니다.
- 주의: 블로그·CTFtime 등록 화면은 화면 예시입니다. Write-up 뼈대 생성 스크립트의 출력은 로컬 실측입니다.
복기 블록 A(Step 280)가 남의 정답으로 배우는 훈련이었다면, 오늘의 블록 B는 내가 푼 문제를 글로 쓰는 훈련입니다. Write-up은 한 번 쓰면 세 번 이득을 봅니다 — 미래의 나를 위한 복습 자료, 팀원·커뮤니티와의 지식 공유, 그리고 커리어의 포트폴리오. 이제부터 모든 대회에서 푼 문제의 Write-up을 쓰는 것이 여러분의 규칙이 됩니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 독자가 재현 가능한 Write-up의 6단 구조(문제 정보/관찰/시도/실패/해결/배운 것)를 적용한다
- 대회 종료 48시간 안에 초고를 쓰는 규칙을 실행한다
- 코드·캡처·재현 절차의 품질 기준("따라 하면 되는가")으로 글을 자가 검수한다
- 대회별 폴더와 분야·기법 태그 체계로 Write-up을 누적 관리한다
- 블로그 게재와 CTFtime 링크 등록의 절차와 주의점(규정 확인)을 설명한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | 마크다운, 블로그/팀 저장소, 파이썬(뼈대 생성 스크립트) |
| 오늘의 도구 | writeup_init.py(폴더+뼈대 자동 생성), 6단 구조 템플릿, 태그 체계 |
| 필요한 개념 | 복기 블록 B, 48시간 초고 규칙, 재현 가능성, 실패 경로의 기록, 포트폴리오 효과 |
| 오늘의 산출물 | Write-up 3편 게재 + 누적 관리 폴더 체계 |
2-1. 왜 쓰는가 — Write-up의 삼중 효과
첫째, 미래의 나를 위한 복습 자료. 세 달 뒤의 대회에서 비슷한 문제를 만나면, 기억은 흐릿하지만 내 Write-up은 정확합니다. 내가 쓴 글은 내 언어로 돼 있어 남의 Write-up보다 다시 읽는 속도가 다릅니다.
둘째, 팀과 커뮤니티의 지식 공유. Step 281에서 봤듯 팀의 실력은 정보의 이동 속도입니다. 대회 후 "내가 푼 문제를 설명하는 글"은 팀 회의보다 오래 남는 공유입니다. 그리고 공개된 Write-up은 다음 대회의 팀원 영입 명함이 됩니다(Step 190의 경로).
셋째, 커리어 포트폴리오. 보안 직군의 채용에서 "CTF Write-up 블로그"는 자격증과 다른 종류의 증거입니다 — 실제 문제를, 실제로 풀고, 남이 이해하게 설명할 수 있다는 세 가지 능력의 동시 증명입니다.
2-2. 6단 구조 — 독자가 재현할 수 있는 글의 뼈대
| 단 | 섹션 | 담는 것 | 흔한 결핍 |
|---|---|---|---|
| 1 | 문제 정보 | 대회·분야·배점·문제 설명 요약 | 대회명 누락으로 나중에 검색 불가 |
| 2 | 관찰 | 처음 보인 것 — 파일, 응답, 수상한 점 | 결론만 있고 관찰이 없음 |
| 3 | 시도 | 가설과 검증을 순서대로 | 성공 가설만 있음 |
| 4 | 실패 | 버린 경로와 버린 근거 | 섹션 자체가 없음 — 반쪽짜리 글 |
| 5 | 해결 | 그대로 따라 하면 플래그가 나오는 완결 코드/명령 | 핵심 단계 생략("이제 풀면 됩니다") |
| 6 | 배운 것 | 이 문제가 가르친 기법 1~3개 | 없으면 일지이고 있으면 교재 |
품질 기준은 하나로 압축됩니다 — "대회를 안 본 독자가 이 글만으로 재현할 수 있는가." 이 기준 하나가 2·3·5단의 분량을 결정합니다. 캡처가 필요한 이유도, 명령을 빠짐없이 적는 이유도 전부 이 문장입니다.
2-3. 실패 섹션이 반을 차지하는 이유
Write-up 초보자는 성공 경로만 씁니다. 그런 글은 독자에게 "저 사람은 천재인가 보다"라는 감상만 남깁니다. 독자가 진짜 배우는 것은 "왜 그 경로를 버렸는가" 입니다 — 여러분이 Step 280의 복기에서 남의 Write-up을 읽으며 가장 고마워했던 부분을 떠올려 보세요. 대부분 "나도 거기서 헤맸다"는 실패의 기록이었을 것입니다.
여러분의 대회 로그가 여기서 빛납니다. stuck 메모들이 그대로 4단의 재료입니다. 로그가 없는 사람은 기억으로 실패를 재구성해야 하고, 로그가 있는 사람은 복사하면 됩니다.
2-4. 48시간 초고 규칙 — 디테일의 반감기
대회가 끝나고 48시간이 지나면 풀이의 디테일이 증발하기 시작합니다 — "그때 왜 그 페이로드를 썼지"가 생각나지 않는 지점이 생깁니다. 그래서 규칙은 이렇습니다: 종료 후 48시간 안에 초고(구조만 완성한 거친 버전)를 쓴다. 다듬는 것은 그 다음 주에 해도 됩니다. 초고가 있으면 다듬는 것은 언제든 가능하지만, 초고가 없으면 글은 영원히 없습니다.
팀이라면 여기에 분담을 얹습니다 — 푼 사람이 쓰고, 못 푼 사람이 리뷰합니다(Step 190의 팀 규칙). 리뷰어가 "여기서 무슨 말인지 모르겠다"고 한 지점이 독자도 모르는 지점입니다. 리뷰는 무료 독자 테스트입니다.
2-5. 누적 관리 — 쓰는 것보다 찾는 것이 오래 걸리면 진다
Write-up이 10편을 넘어가면 관리 체계가 필요합니다. 권장 구조는 대회별 폴더 + 태그 머리말입니다.
writeups/
Weekend_CTF_1/
Web__warmup-cookie/writeup.md
Crypto__caesar-training/writeup.md
Weekend_CTF_2/
Web__jwt-forgery/writeup.md
태그 머리말(프런트매터)에는 분야와 기법을 넣습니다 — 태그: [web, jwt, 인증우회]처럼. 세 달 뒤 "JWT 문제 어떻게 풀었더라"의 검색이 이 태그 위에서 돌아갑니다. 파일명에 날짜를 넣는 방식보다 대회·분야가 앞에 오는 방식이 재검색에 유리합니다.
3. 따라 하기
3-1. 대상 선정 — 푼 문제 중 3개
로그와 팀 상태표에서 대회 #1~#2의 해결 목록을 꺼냅니다. Step 279 솔로 대회에서 3개, Step 281 팀 대회에서 내가 푼 문제 — 이 중 3개를 고릅니다. 고르는 기준은 블록 A와 반대입니다 — 남에게 설명할 가치가 있는가. 지나치게 쉬운 문제("입력값이 그대로 플래그")보다, 한 번은 꺾였던 문제가 좋은 소재입니다.
3-2. 뼈대 생성 스크립트 (실측)
매번 6단 구조를 손으로 치지 않도록, 폴더와 뼈대를 만드는 스크립트를 씁니다. writeup_init.py로 저장하세요.
# writeup_init.py — Write-up 폴더와 뼈대 마크다운을 만든다
import os, re, sys
TEMPLATE = """---
대회: {contest}
분야: {field}
문제: {problem}
배점:
태그: [{field_lower}]
작성일:
---
# {contest} — {problem} ({field})
## 1. 문제 정보
- 대회 / 분야 / 배점 / 문제 설명 요약 (원문 핵심만)
## 2. 관찰 — 처음 보인 것
- 파일/서비스/응답에서 눈에 띈 것. 캡처 첨부.
## 3. 시도 — 가설과 실험
- 세운 가설을 순서대로. 각 가설의 검증 명령/코드 포함.
## 4. 실패 — 버린 경로
- 통하지 않은 접근과, 버린 근거. (이 섹션이 없는 Write-up은 반쪽짜리)
## 5. 해결 — 최종 익스플로잇
- 독자가 그대로 따라 하면 플래그가 나오는 완결 코드/명령.
- 플래그 형식 일부 마스킹 (예: flag{{...}} → 실제값은 대회 규정 확인 후 게재)
## 6. 배운 것 — 다음에 바로 쓸 것
- 이 문제가 가르친 기법 1~3개. 내 플레이북의 어느 항목에 연결되는지.
"""
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("생성:", p)
실행 결과 (실측):
$ python writeup_init.py "Weekend CTF 1" "Web" "warmup-cookie"
생성: writeups\Weekend_CTF_1\Web__warmup-cookie\writeup.md
$ python writeup_init.py "Weekend CTF 1" "Crypto" "caesar-training"
생성: writeups\Weekend_CTF_1\Crypto__caesar-training\writeup.md
$ python writeup_init.py "Weekend CTF 2" "Web" "jwt-forgery"
생성: writeups\Weekend_CTF_2\Web__jwt-forgery\writeup.md
생성된 구조를 확인하면:
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
대회 → 분야·문제 순의 폴더에 6단 뼈대가 들어 있습니다. 이제 채우기만 하면 됩니다.
3-3. 한 편을 끝까지 써 보기 — 채운 예시
뼈대를 어떻게 채우는지, Step 280에서 재현한 xor-note 유형 문제의 Write-up 예시입니다 (로컬 재현본 기준의 작성 예시 — 실제 대회 문제가 아닙니다).
## 2. 관찰 — 처음 보인 것
문제 파일은 텍스트 하나. "암호문은 반복 XOR, 키 길이 10 이하,
평문은 'flag{'로 시작한다는 것이 보장"이라는 안내와 32바이트 hex.
→ "보장한다"는 표현이 수상했다. 힌트가 아니라 열쇠일 가능성.
## 4. 실패 — 버린 경로
가설 1: 키 길이 1~10 전수 조사로 "읽히는 평문"을 눈으로 찾기
→ 10개 출력을 다 읽었으나 판별 불가. 폐기 사유: 눈 판별은
키 길이가 커질수록 무너진다. "읽히는가"의 기준도 모호.
교훈: 기준 없는 전수 조사는 답이 아니라 도박.
## 5. 해결 — 최종 익스플로잇
crib = b"flag{"와 암호문 앞 5바이트를 XOR → 키 조각 b'K7mK7'.
조각이 길이 3으로 반복 → 키 길이 3 확정, 전체 복호:
(코드 전문 첨부 — xor_solve.py, 20줄)
복호 결과: flag{x0r_k3y_...} ← 실제값은 대회 규정 확인 후 게재
채우기의 요령: 4단의 "폐기 사유"를 반드시 쓰세요. "안 됐다"가 아니라 "왜 버렸는가"가 독자의 재산입니다. 5단의 코드는 축약하지 않습니다 — Write-up의 코드는 읽는 것이 아니라 복사해서 실행하는 것입니다.
3-4. 게재와 등록 (화면 예시)
완성된 Write-up은 블로그(개인 블로그, 팀 저장소 등)에 게재합니다. 게재 전 체크 두 가지입니다.
[게재 전 체크]
□ 대회 규정: Write-up 공개가 허용되는가, 공개 가능 시점이 정해져 있는가
□ 플래그 처리: 대회 규정상 플래그 원문 게재가 금지된 경우 일부 마스킹
그리고 CTFtime의 해당 대회 페이지에 Write-up 링크를 등록합니다 (화면 예시 — 대회 페이지의 Write-ups 섹션에 "Add write-up" 형태의 등록 기능이 있습니다). 등록하면 대회 결과 페이지에서 여러분의 글로 유입이 생깁니다. 이것이 "Write-up 한 편이 다음 팀 제안을 부르는 명함"이라는 말의 실제 경로입니다.
3-5. 세 편을 쓰고 나서 — 자가 검수표
3편을 쓰면 각 편을 이 표로 검수합니다.
| 질문 | 통과 기준 |
|---|---|
| 대회를 안 본 사람이 재현할 수 있는가 | 1·2·5단만으로 환경 구성부터 플래그까지 도달 |
| 실패 경로가 있는가 | 4단에 버린 가설 1개 이상 + 폐기 사유 |
| 코드는 완결인가 | 복사·실행으로 결과가 재생되는 완결 스크립트 |
| 검색 가능한가 | 대회명·분야·기법 태그가 머리말에 있음 |
| 규정을 지켰는가 | 공개 시점 규정 확인, 플래그 마스킹 여부 결정 |
세 편을 쓰는 이유는 숫자가 아니라 반복입니다 — 첫 편에서 구조를 배우고, 둘째 편에서 속도가 붙고, 셋째 편에서 "나의 문체"가 생깁니다. 이 세 편이 쌓이면 다음 대회의 Write-up은 작업이 아니라 루틴이 됩니다.
4. 미션과 연습문제
미션 — Write-up 3편 게재
- 대회 #1~#2에서 푼 문제 중 3개를 선정하고, 선정 이유를 한 줄씩 적습니다.
writeup_init.py로 뼈대를 만들고, 종료(또는 오늘 시작) 기준 48시간 안에 3편의 초고를 완성합니다.- 각 편을 3-5의 자가 검수표로 검수하고, 통과 못한 항목을 보완합니다.
- 블로그 또는 팀 저장소에 게재합니다 (대회 규정 확인 후). 가능하면 CTFtime에 링크를 등록합니다.
- 팀이 있다면 서로의 Write-up을 교차 리뷰하고 "모르겠다" 지점을 하나 이상 지적합니다.
연습문제
문제 1. Write-up의 "재현 가능성"이 품질 기준인 이유를, 2단(관찰)과 5단(해결)의 역할과 함께 설명해 보세요.
문제 2. 성공 경로만 적은 Write-up이 독자에게 주는 피해를 구체적으로 말해 보세요.
문제 3. 48시간 초고 규칙에서 "초고"와 "완성본"을 분리하는 이유를 설명해 보세요.
문제 4. 푼 사람이 쓰고 못 푼 사람이 리뷰하는 분담의 장점 두 가지를 말해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
검증하는 법: ① 3편 모두 6단 구조가 있고, 특히 4단(실패)에 버린 가설과 폐기 사유가 있는가 — 4단이 비어 있으면 미완성입니다. ② 5단의 코드가 실제로 실행 가능한 완결본인가(직접 실행해 보는 것이 가장 확실한 검증). ③ 각 편의 머리말에 대회·분야·기법 태그가 있어 검색되는가. ④ 게재 전에 대회 규정(공개 시점·플래그 처리)을 확인한 흔적이 있는가. ⑤ 초고의 타임스탬프가 대회 종료 48시간 이내인가. ⑥ 교차 리뷰의 지적이 반영됐는가.
연습문제 해답
문제 1 해답. 독자의 목표는 감상이 아니라 재현입니다. 2단(관찰)이 충실해야 독자가 같은 출발선에 설 수 있고, 5단(해결)이 완결 코드여야 독자가 같은 결승선에 도달합니다. 재현 가능성은 이 두 섹션의 충실도로만 달성되므로 품질 기준의 본체가 됩니다.
문제 2 해답. 독자는 "저자는 한 번에 정답을 알았다"는 잘못된 모델을 학습하고, 자기가 막혔을 때 그것이 정상인지 실력 부족인지 판단할 기준을 잃습니다. 실제로 같은 함정에 빠져도, 글에 그 함정이 없으니 빠져나올 방법을 배우지 못합니다. 가장 가치 있는 정보 — 버린 경로와 그 근거 — 가 삭제된 글입니다.
문제 3 해답. 48시간 안에 요구되는 것은 기억이 살아 있을 때의 정보 저장이고, 다듬기는 언제 해도 되는 표현 개선입니다. 둘을 한 작업으로 묶으면 완성 압박 때문에 글 자체가 미뤄지고, 디테일의 반감기가 지난 뒤에는 쓸 재료가 사라집니다. 분리하면 초고는 가볍게 완성되고, 완성본은 살아 있는 재료 위에서 나중에 만들어집니다.
문제 4 해답. ① 못 푼 리뷰어는 그 문제의 지식이 없는 상태의 진짜 독자라, "여기서 모르겠다"는 지적이 실제 독자의 이탈 지점을 정확히 예측합니다. ② 리뷰어 입장에서는 Write-up을 검증하며 그 문제의 풀이를 배우게 되므로, 리뷰 자체가 팀 내 블록 A 복기의 역할을 겸합니다 — 한 편의 글로 팀원 둘이 성장합니다.
완료 기준 체크리스트
- [ ] Write-up의 6단 구조를 순서대로 말할 수 있다
- [ ] 4단(실패)이 없는 글이 반쪽인 이유를 설명할 수 있다
- [ ] 48시간 안에 초고 3편을 완성했다
- [ ] 자가 검수표 다섯 항목으로 3편을 검수했다
- [ ] 대회별 폴더 + 태그 머리말의 관리 체계를 갖췄다
- [ ] 게재 전 대회 규정(공개 시점·플래그)을 확인했다
- [ ] 푼 사람이 쓰고 못 푼 사람이 리뷰하는 분담의 장점을 설명할 수 있다
6. 흔한 실수와 해결
벽 1. 쓸 게 없어요 — 다 알고 있는 내용이라
원인: 여러분의 기준에서 "당연한 것"이 독자에게는 벽입니다. Write-up의 독자는 어제의 여러분입니다.
해결: 대회 로그의 stuck 메모를 보세요 — 그 지점에서 멈췄던 사람이 첫 독자입니다. "나도 몰랐던 것"이 바로 쓸 내용입니다. 그래도 쓸 게 안 보이면 4단(실패)부터 채우세요. 실패를 적다 보면 왜 실패했는지를 설명하게 되고, 그것이 글의 본문이 됩니다.
벽 2. 대회 중에 안 캡처해 둬서 증거가 없어요
원인: Step 279의 로그 습관에 캡처가 빠져 있었습니다.
해결: 이번 글은 로컬 재현(Step 280의 방법)으로 증거를 다시 만들 수 있습니다 — 재현 환경을 만들고 그 화면을 캡처하면, 그것이 오히려 독자가 따라 하기 좋은 자료가 됩니다. 그리고 다음 대회부터는 "결정적 순간 캡처"를 착수 체크리스트에 넣으세요.
벽 3. 스크립트가 경로를 못 찾아요
증상 (실측 — 재현 스크립트를 다른 디렉터리에서 실행한 경우):
$ 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'
원인: 스크립트는 현재 디렉터리에서 파일을 찾습니다. Write-up 폴더 구조가 깊어지면 어느 디렉터리에서 실행했는지 헷갈리기 쉽습니다.
해결: 실행 전 ls(윈도우라면 dir)로 대상 파일이 현재 디렉터리에 있는지 확인하세요. 재현에 쓰는 스크립트와 데이터는 항상 같은 폴더에 두고 그 폴더에서 실행하는 습관이 답입니다.
벽 4. 남의 Write-up을 참고하다가 거의 베낀 글이 됐어요
원인: 이것은 윤리 문제입니다 — 표절은 커뮤니티에서 가장 치명적인 평판 손상입니다.
해결: 블록 B의 대상은 내가 푼 문제입니다. 남의 풀이로 푼 문제를 쓰고 싶다면 그것은 블록 B가 아니라 블록 A의 기록으로 쓰고, 출처를 명시하세요. 내 손으로 재현한 풀이라도 접근의 핵심을 특정 Write-up에서 배웠다면 링크와 함께 밝히는 것이 성의이자 실력입니다.
벽 5. 세 편을 썼는데 아무도 안 읽어요
원인: 첫 Write-up들의 조회수는 원래 낮습니다. 조회수는 이 단계의 지표가 아닙니다.
해결: 지금 단계에서 Write-up의 독자는 셋입니다 — 미래의 나, 팀원, 그리고 언젠가의 채용 담당자. 셋 다 조회수 카운터에 나타나지 않습니다. 조회수가 오르는 것은 누적 20~30편쯤에서 알고리즘과 검색이 붙은 뒤의 일입니다. 지금 할 일은 양이 아니라 구조의 반복입니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 복기 블록 B | 내가 푼 문제를 글로 쓰는 복기 — 블록 A와 쌍을 이루는 성장 엔진 |
| 6단 구조 | 문제 정보 → 관찰 → 시도 → 실패 → 해결 → 배운 것 |
| 재현 가능성 | "대회를 안 본 독자가 이 글만으로 따라 할 수 있는가" — 유일한 품질 기준 |
| 실패 섹션 | 버린 경로와 폐기 사유 — 독자가 진짜 배우는 곳 |
| 48시간 초고 규칙 | 디테일의 반감기 안에 정보 저장, 다듬기는 나중 |
| 태그 체계 | 대회별 폴더 + 분야·기법 머리말 — 누적의 검색성 |
| 삼중 효과 | 복습 자료 + 지식 공유 + 커리어 포트폴리오 |
오늘의 도구와 명령어
| 도구·명령 | 하는 일 |
|---|---|
python writeup_init.py "대회" "분야" "문제" |
폴더 + 6단 뼈대 writeup.md 생성 |
| 자가 검수표 (5문항) | 재현성·실패 경로·완결 코드·검색성·규정 검수 |
| 블로그/팀 저장소 | 게재와 누적 |
| CTFtime Write-up 등록 | 대회 페이지에서의 유입 경로 확보 |
핵심 감각
Write-up을 쓰다 보면 이상한 일이 일어납니다 — 풀었다고 생각한 문제를 덜 풀었다는 사실이 드러납니다. "왜 그 명령이 통했는지"를 문장으로 쓰려는 순간, 설명할 수 없는 단계가 보입니다. 그 빈칸을 메우는 과정이 글쓰기의 숨은 수확입니다. 푸는 것은 이해의 시작이고, 쓰는 것이 이해의 완성입니다.
그리고 이 습관은 복리입니다. 대회 한 번에 3편씩, 열 번의 대회가 지나면 30편의 검색 가능한 지식 창고 — 그 창고는 여러분의 기억이 아니라 자산이 됩니다.
전부 체크되면 Step 282 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.