Step 180. CTF 맛보기 5: Forensics/기타 3개 — 수상한 파일의 해부
Level 3 — CTF 실전과 공격 스킬 심화 | 난이도 ★★★☆☆ | 예상 소요 시간 5시간
전제: Step 176~179(CTF 형식, Crypto 입문). 파일을 바이트로 다루는 파이썬 기초를 압니다.
⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. 드림핵(dreamhack.io)은 풀라고 만들어진 합법 학습 플랫폼입니다.
- 준비물: WSL 우분투(
file,xxd,strings— 실측: file 5.45), 파이썬 3(실측: 3.12.14). - 주의: binwalk, exiftool 등 실전 도구는 이 환경에 없으므로 "출력 예시"로만 소개하고, 핵심 루틴은 직접 만든 세 개의 수상한 파일로 실측합니다.
CTF의 Forensics(포렌식) 종목은 "수상한 파일 하나"를 던져 줍니다. 이미지, 패킷 덤프, 메모리 덤프 — 겉은 멀쩡한데 안에 플래그가 숨어 있습니다. 출발점은 언제나 같은 의심입니다: "겉으로 보이는 것과 파일의 실체가 다르다." 오늘은 포렌식의 기본 루틴을 몸에 붙이고, 세 개의 미니 사건 파일을 직접 해결합니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 매직 바이트(파일 시그니처)로 파일의 실제 종류를 판별한다
- 포렌식 기본 루틴(
file→strings→xxd→ 추출 도구)을 순서대로 수행한다 - PNG의 청크 구조(IHDR, IDAT, IEND)를 읽고, IEND 뒤의 숨은 데이터를 추출한다
- 확장자 위장 파일을
file로 간파한다 - 증거 파일의 분석 과정을 "루틴 문서"로 정리한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | WSL 우분투 bash + 파이썬 3 (실측: file 5.45, xxd, strings / Python 3.12.14) |
| 오늘의 명령어 | file 파일, strings 파일 | grep -i flag, xxd 파일 | head, 파이썬 bytes.find(b"IEND") |
| 필요한 개념 | 매직 바이트, PNG 청크 구조, 파일 끝 뒤 데이터(append), 확장자 위장 |
| 오늘의 산출물 | 세 사건 파일의 해결 기록 + 나만의 포렌식 루틴 문서 |
2-1. 매직 바이트 — 파일의 주민등록번호
파일의 진짜 종류는 확장자가 아니라 앞쪽 몇 바이트가 결정합니다. 이 약속된 시작 바이트를 매직 바이트(magic bytes) 또는 시그니처라 합니다.
| 파일 종류 | 시작 바이트 (hex) | 모양 |
|---|---|---|
| PNG | 89 50 4E 47 0D 0A 1A 0A |
.PNG.... |
| JPEG | FF D8 FF |
... |
| ZIP | 50 4B 03 04 |
PK.. |
| ELF(리눅스 실행 파일) | 7F 45 4C 46 |
.ELF |
file 명령은 이 매직 바이트를 읽어 종류를 알려 줍니다. 확장자가 .png인데 file이 "ASCII text"라고 하면 — 그 파일은 위장한 것입니다.
2-2. PNG의 청크 구조 — 상자의 연속
PNG 파일은 시그니처 8바이트 뒤에 청크(chunk)라는 상자들이 이어집니다. 각 청크는 길이 4바이트 + 종류 4바이트 + 데이터 + 검증값 4바이트 구조입니다.
IHDR: 그림의 크기·색 정보 (항상 첫 청크)IDAT: 실제 픽셀 데이터IEND: "여기가 끝" 표시
핵심은 이것입니다 — IEND 뒤에 오는 바이트는 PNG 규격상 아무 의미가 없습니다. 그림 뷰어도 무시합니다. 그래서 출제자가 플래그를 숨기는 단골 자리입니다.
2-3. 포렌식 기본 루틴 — 싸고 빠른 것부터
수상한 파일을 받으면, 가벼운 검사부터 순서대로:
1. file 파일 → 진짜 종류 확인 (1초)
2. strings 파일 | grep -i flag → 보이는 문자열 즉시 검색 (1초)
3. xxd 파일 | head/tail → 앞뒤 바이트 직접 보기 (시그니처·꼬리 확인)
4. 전문 도구 → binwalk(내장 파일), exiftool(메타데이터), steghide/zsteg(이미지 스테가노그래피)
실전 문제의 상당수는 1~3단계에서 끝납니다. 도구가 안 잡을 때 마지막 수단은 hex 에디터로 파일 앞뒤를 직접 보는 것 — 오늘 우리가 할 일입니다.
2-4. 실전 도구 지도 — 출력 예시
이 환경에는 없지만 실전 표준인 도구들을 출력 예시로 봅니다 (화면 예시 — 실제 실행하지 않았습니다):
# 화면 예시 — binwalk: 파일 안에 들어 있는 파일을 찾는 도구
$ binwalk image.png
DECIMAL HEXADECIMAL DESCRIPTION
0 0x0 PNG image
69436 0x10F3C Zip archive data, at least v2.0 to extract
# 화면 예시 — exiftool: 메타데이터(촬영 정보 등)를 읽는 도구
$ exiftool photo.jpg
Camera Model Name : iPhone 13
Comment : DH{m3t4d4t4_1s_4_g1ft}
binwalk는 "PNG인 척하는 파일 안에 ZIP이 통째로 붙어 있는" 문제를, exiftool은 "사진의 메모 칸에 쓰인 플래그"를 잡습니다. pcap 파일은 Wireshark로 열어 Follow TCP Stream — 대화 속 플래그를 읽는 흐름이 표준입니다.
3. 따라 하기
3-1. 사건 파일 생성 — 세 개의 수상한 파일
아래 스크립트로 오늘의 사건 파일 세 개를 만듭니다 (이 챕터의 로컬 출력은 2026-09-09 실측입니다).
입력 (forensics_lab.py)
import struct, zlib
def chunk(typ, data):
c = struct.pack(">I", len(data)) + typ + data
return c + struct.pack(">I", zlib.crc32(typ + data) & 0xFFFFFFFF)
def make_png(path):
sig = b"\x89PNG\r\n\x1a\n"
ihdr = chunk(b"IHDR", struct.pack(">IIBBBBB", 1, 1, 8, 2, 0, 0, 0))
idat = chunk(b"IDAT", zlib.compress(b"\x00\xff\x00\x00"))
iend = chunk(b"IEND", b"")
with open(path, "wb") as f:
f.write(sig + ihdr + idat + iend)
make_png("sig.png") # 사건 0: 정상 PNG (기준)
make_png("hidden.png") # 사건 1: IEND 뒤에 뭔가 있다
with open("hidden.png", "ab") as f:
f.write(b"DH{1s_th3r3_s0m3th1ng_4ft3r_iend}")
with open("fake.png", "w") as f: # 사건 2: 확장자 위장
f.write("I am not an image. The flag is DH{n0t_4_r34l_png}\n")
출력
(파일 세 개 생성 — sig.png 69바이트, hidden.png 102바이트, fake.png)
3-2. 사건 0: 정상 PNG — 기준선 세우기
먼저 멀쩡한 파일이 어떻게 생겼는지 봐야 수상한 것이 보입니다. WSL에서:
file sig.png
sig.png: PNG image data, 1 x 1, 8-bit/color RGB, non-interlaced
xxd sig.png | head -6
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
00000010: 0000 0001 0000 0001 0802 0000 0090 7753 ..............wS
00000020: de00 0000 0c49 4441 5478 9c63 f8cf c000 .....IDATx.c....
00000030: 0003 0101 00c9 fe92 ef00 0000 0049 454e .............IEN
00000040: 44ae 4260 82 D.B`.
(2026-09-09 실측.)
출력 읽는 법: 첫 8바이트 89 50 4e 47 ...가 PNG 시그니처(오른쪽에 .PNG로도 보입니다). 이어서 IHDR(길이 13 = 0000000d), IDAT(픽셀 데이터), 맨 끝 IEND(49 45 4e 44). 정상 PNG는 IEND에서 끝납니다. 이 배치가 오늘의 기준선입니다.
3-3. 사건 1: IEND 뒤의 불청객
hidden.png에 루틴을 적용합니다. file은 조용합니다:
file hidden.png
hidden.png: PNG image data, 1 x 1, 8-bit/color RGB, non-interlaced
주목: 크기가 102바이트인데 정상 PNG(sig.png)는 69바이트였습니다. file은 시그니처만 보고 "PNG"라고 답할 뿐, 뒤에 붙은 33바이트는 신고하지 않습니다. file을 믿되 맹신하지 않는 이유입니다.
꼬리를 직접 봅니다:
xxd hidden.png | tail -3
00000040: 44ae 4260 8244 487b 3173 5f74 6833 7233 D.B`.DH{1s_th3r3
00000050: 5f73 306d 3374 6831 6e67 5f34 6674 3372 _s0m3th1ng_4ft3r
00000060: 5f69 656e 647d _iend}
(2026-09-09 실측. `D.B“까지가 IEND와 그 검증값, 그 뒤로 플래그가 그대로 보입니다.)
strings로도 잡힙니다:
strings hidden.png | grep DH
DH{1s_th3r3_s0m3th1ng_4ft3r_iend}
파이썬으로 정식 추출 — IEND의 위치를 계산해 그 뒤를 잘라 냅니다:
data = open("hidden.png", "rb").read()
pos = data.find(b"IEND")
end = pos + 8 # IEND 4바이트 + 검증값 4바이트
print(f"파일 크기: {len(data)}, IEND 위치: {pos}, 정상 끝: {end}")
print("숨은 데이터:", data[end:].decode())
파일 크기: 102, IEND 위치: 61, 정상 끝: 69
숨은 데이터: DH{1s_th3r3_s0m3th1ng_4ft3r_iend}
출력 읽는 법: "정상 끝(69) < 파일 크기(102)" — 이 부등식 하나가 위장을 폭로합니다. 이 계산이 binwalk가 하는 일의 축소판입니다.
3-4. 사건 2: 확장자 위장
fake.png는 이름만 PNG입니다. 루틴 1단계로 즉시 끝납니다:
file fake.png
fake.png: ASCII text, with CRLF line terminators
strings fake.png | grep -i flag
I am not an image. The flag is DH{n0t_4_r34l_png}
(2026-09-09 실측.)
읽는 법: 매직 바이트가 PNG의 것이 아니므로 file이 즉시 "ASCII text"를 선고했습니다. 확장자는 사용자 인터페이스의 장식일 뿐, 파일의 신원은 앞쪽 바이트가 증명합니다.
3-5. 실제 플랫폼에서의 모습 — 화면 예시
드림핵 Forensics 카테고리에서의 흐름입니다 (화면 예시 — 이 환경에서는 접속하지 않았습니다):
# 화면 예시 — 플랫폼 문제 페이지
[Forensics] hidden-in-image 난이도: 1
첨부: evidence.zip ← 수상한 파일을 받는다
$ file evidence.png
$ strings evidence.png | grep -i "DH{"
$ binwalk -e evidence.png # 내장 파일 추출
$ steghide extract -sf photo.jpg # JPG 스테가노그래피 (비밀번호 없이 Enter)
오늘 익힌 루틴 — file부터 시작해 꼬리를 직접 보기까지 — 이 그대로 첫 두 수가 됩니다. 도구가 바뀔 뿐, 의심의 순서는 같습니다.
4. 미션과 연습문제
미션 — 사건 파일 하나를 직접 만들고 루틴 문서 쓰기
- 여러분만의 플래그(
DH{...})를 숨긴 사건 파일을 하나 만든다 — 방법은 셋 중 하나: ① IEND 뒤 추가, ② 확장자 위장, ③strings로 잡히지 않는 곳(청크 데이터 속)에 삽입 - 동료 역할이 되어, 자신의 파일에 2-3의 루틴을 처음부터 적용하며 각 단계의 명령과 결과를 기록한다
- 그 기록을 "나의 포렌식 루틴 문서"로 정리한다 — 명령, 기대 결과, 안 되면 다음 수
연습문제
문제 1. xxd로 어떤 파일을 열었더니 시작이 50 4B 03 04였습니다. 이 파일의 진짜 종류는? 확장자가 .jpg라면 무엇을 의심해야 하나요?
문제 2. file이 "PNG image data"라고 답한 파일에서도 플래그가 숨어 있을 수 있습니다. 그 이유를 file이 검사하는 범위로 설명하세요.
문제 3. PNG 파일의 크기가 500바이트인데, IEND 청크가 끝나는 위치가 450바이트입니다. 무엇을 의심하고 어떤 파이썬 코드로 확인하나요?
문제 4. strings가 하는 일을 설명하고, 왜 플래그가 strings에 안 잡히는 문제가 더 어려운 등급인지 추측해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
루틴 문서의 예 (형식은 자유):
[나의 포렌식 루틴 v1]
1. file {파일} → 종류 확인. 확장자와 다르면 위장 의심
2. ls -l {파일} → 크기 기록 (같은 종류의 정상 파일과 비교)
3. strings | grep -iE "flag|DH\{" → 즉답 가능성 검사
4. xxd | head → 시그니처 확인
5. xxd | tail → 꼬리 확인 (정상 종료 위치 뒤의 데이터)
6. 안 나오면: 파이썬으로 청크/구조 단위 분석 → 전문 도구(binwalk 등) 고려
검증하는 법: ① 만든 사건 파일을 루틴 문서만 보고 다시 풀 수 있는가(문서의 완결성). ② ③번 방법(청크 데이터 속)을 택했다면 strings에 안 잡히는 이유 — 데이터가 압축됐거나 바이트가 깨져서 — 를 설명할 수 있는가. IDAT 안에 글자를 넣으면 zlib 압축이 문자열을 갈아 버려 strings가 못 잡습니다.
연습문제 해답
문제 1 해답. 50 4B 03 04(PK..)는 ZIP의 시그니처입니다. 확장자가 .jpg라면 ZIP을 JPG로 위장한 파일 — 이름을 바꿔 압축을 풀거나(unzip), binwalk로 내용물을 추출해야 합니다. CTF의 가장 흔한 첫 단추입니다.
문제 2 해답. file은 파일 앞쪽의 시그니처를 중심으로 판별하고, 전체 내용을 검증하지 않습니다. 그래서 시그니처가 올바르면 뒤에 무엇이 붙어 있든 "PNG"라고 답합니다 — 사건 1에서 크기 102바이트(정상 69바이트)인데도 조용했던 이유입니다.
문제 3 해답. IEND 뒤 50바이트의 불청객을 의심합니다. 확인 코드:
data = open("mystery.png", "rb").read()
pos = data.find(b"IEND") + 8
print(len(data), pos) # 500 vs 450 — 차이가 증거
print(data[pos:]) # 숨은 데이터 출력
문제 4 해답. strings는 파일에서 "출력 가능한 문자가 4개 이상 연속된" 구간을 뽑아 줍니다. 플래그가 압축·암호화·XOR된 채 숨어 있으면 출력 가능 문자열이 아니라서 안 잡힙니다. 그래서 그런 문제는 한 단계의 복호화가 더 필요한, 더 어려운 등급이 됩니다.
완료 기준 체크리스트
- [ ] PNG·JPEG·ZIP·ELF의 매직 바이트를 표로 외웠다
- [ ]
file→strings→xxd루틴을 순서대로 수행할 수 있다 - [ ] 정상 PNG의 청크 배치(IHDR → IDAT → IEND)를 설명할 수 있다
- [ ] IEND 뒤 숨은 데이터를 파이썬으로 추출했다
- [ ] 확장자 위장 파일을
file로 간파했다 - [ ] binwalk/exiftool/steghide가 각각 무엇을 찾는 도구인지 안다
- [ ] 미션: 사건 파일을 만들고 루틴 문서를 완성했다
6. 흔한 실수와 해결
벽 1. binwalk: command not found
증상: bash: line 1: binwalk: command not found (2026-09-09 실측 — 이 환경에는 binwalk가 없습니다).
원인: 도구가 설치되지 않았습니다. 포렌식 도구는 종류가 많아 처음부터 다 깔 수 없습니다.
해결: 오늘의 루틴(file, strings, xxd, 파이썬)은 전부 기본 설치로 됩니다. binwalk가 정말 필요한 순간(내장 파일 추출)이 오면 그때 sudo apt install binwalk를 고려하세요 — 도구부터 까는 것이 아니라, 문제가 요구할 때 하나씩 추가하는 것이 포렌식 장비 관리의 정석입니다.
벽 2. file만 믿고 넘어갔다가 플래그를 놓친다
증상: file이 "PNG"라고 해서 정상이라 판단했는데 정답은 뒤에 있었습니다.
원인: file은 시그니처 중심 판별이라 꼬리의 추가 데이터를 신고하지 않습니다 (사건 1 실측).
해결: file 다음에는 반드시 크기 비교와 xxd | tail을 붙이세요. 루틴은 한 단계가 아니라 사슬입니다.
벽 3. xxd 출력에서 글자를 못 찾겠다
증상: 16진수만 보이고 무슨 글자인지 모르겠습니다.
원인: xxd의 맨 오른쪽 열이 ASCII 번역입니다. 거기를 안 봤을 뿐입니다.
해결: 오른쪽 열만 훑으세요 — .은 출력 불가 바이트, 글자는 그대로 보입니다. 사건 1의 DH{1s_th3r3...이 그 열에서 발견됐습니다.
벽 4. 파이썬 data.find(b"IEND")가 -1을 돌려준다
증상: IEND를 찾았는데 -1(없음)이 나옵니다.
원인: 그 파일은 PNG가 아니거나, 텍스트 모드("r")로 열어 바이트가 변형됐습니다.
해결: 반드시 바이너리 모드로 여세요 — open(path, "rb"). 텍스트 모드는 줄바꿈을 바꿔 버려 바이트 위치 계산이 틀어집니다. 그리고 find 결과가 -1이면 시그니처부터 다시 의심하세요.
벽 5. 추출했는데 깨진 문자가 나온다
증상: IEND 뒤를 잘랐는데 읽을 수 없는 바이트입니다.
원인: 숨은 데이터가 평문이 아니라 인코딩·XOR된 것입니다 — Crypto와 혼합된 문제입니다.
해결: Step 179의 루틴을 이어서 적용하세요. 포렌식으로 "꺼내고", Crypto로 "읽는" 2단 문제는 실전에서 흔합니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 매직 바이트 | 파일 앞쪽의 종류 표시 바이트 — 신원은 확장자가 아니라 이것이 증명 |
| PNG 청크 | 길이+종류+데이터+검증값의 상자 연속 — IHDR, IDAT, IEND |
| IEND 뒤 데이터 | 규격상 무의미한 영역 — 플래그 은닉의 단골 자리 |
| 포렌식 루틴 | 싸고 빠른 검사부터: file → strings → xxd → 전문 도구 |
| 위장 파일 | 시그니처와 실제 내용이 다른 파일 — file이 1차 심문관 |
| 스테가노그래피 | 데이터를 이미지 등에 숨기는 기술 — steghide, zsteg가 탐지 |
오늘의 명령어
| 명령 | 하는 일 |
|---|---|
file 파일 |
매직 바이트로 진짜 종류 판별 |
strings 파일 | grep -i flag |
보이는 문자열 즉시 검색 |
xxd 파일 | head / | tail |
앞(시그니처)·뒤(꼬리) 직접 보기 |
data.find(b"IEND") + 8 |
PNG 정상 끝 위치 계산 |
binwalk -e 파일 (예시) |
내장 파일 추출 |
exiftool 파일 (예시) |
메타데이터 읽기 |
명령어보다 중요한 감각
포렌식의 무기는 도구 목록이 아니라 의심의 순서입니다. "겉과 실체가 다르다"로 시작해, 싼 검사부터 비싼 검사로. 그리고 도구가 침묵할 때(file이 "정상"이라 할 때) 침묵을 증거로 착각하지 않는 것 — 침묵은 "시그니처는 정상"이라는 뜻일 뿐입니다.
이 눈은 CTF를 넘어 실제 침해 사고 분석의 첫 동작과 같습니다. 로그인 기록, 다운로드된 첨부 파일 — 전부 "수상한 파일 하나"로 시작합니다. Step 181에서 분야를 고를 때, 오늘처럼 "증거를 뒤지는 과정"이 즐거웠다면 그것도 중요한 신호입니다.
전부 체크되면 Step 180 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.