문제: DreamHack — 서비스 이용 약관 분류: misc / crypto 난이도: 🥉 Bronze 3 FLAG:
DH{Do_Y0u_Know_EUC-KR_enc0ding?}
문제 설명은 이렇다.
드림이는 드림핵에 적용할 새 서비스 이용 약관을 작성했어요. 그런데 이용 약관이 저장된 파일이 잘못된 방식으로 인코딩되어 드림이가 당황스러워하고 있어요. 여러분이 드림이를 도와 이용 약관을 복원해 주세요.
받은 건 tos.txt 하나. 서버도 없고 바이너리도 없다.
"잘못된 방식으로 인코딩" 이라는 한 문장이 문제의 전부인데, 여기서 말하는 인코딩이 한 겹이 아니다. 겉의 hex 를 벗기면 두 번째 인코딩이 나오고, 그걸 벗기면 세 번째가 나온다. 마지막 겹은 눈으로는 절대 안 보인다.
file extracted/tos.txt && wc -c extracted/tos.txt && head -c 96 extracted/tos.txt && echo
파일은 51,071바이트인데 file 이 "very long lines (51050)" 이라고 알려 준다.
즉 사실상 한 줄짜리 파일이다. 앞머리에 Encoded toc (hex) 라는 헤더가 붙어 있다.
tos 를 toc 로 적어 둔 건 출제자의 오타로 보인다.
문제 개요
| 항목 | 내용 |
|---|---|
| 문제명 | 서비스 이용 약관 |
| 난이도 | 🥉 Bronze 3 |
| 분류 | misc / crypto |
| 제공 파일 | tos.txt (ASCII, 51,071 B) |
| 서버 | 없음 — 완전 오프라인 |
| 핵심 기법 | hex → EUC-KR → 전각(fullwidth) ASCII → NFKC 정규화 |
| 함정 | 본문에 DH{...} 모양의 미끼가 박혀 있다 |
풀이 흐름은 짧다. hex 를 되돌리고, UTF-8 이 아니라 EUC-KR 로 읽고, 복원된 약관에서 한글 사이에 섞인 전각 라틴 문자만 골라내 NFKC 로 정규화하면 끝난다.
짧지만 각 단계마다 한 번씩 걸려 넘어질 자리가 있다.
🧩 배경 — EUC-KR 이라는 "잘못된 인코딩"
지금 한국어 텍스트는 거의 전부 UTF-8 이다. 하지만 UTF-8 이전에는 EUC-KR(과 그 확장인 CP949)이 사실상 표준이었고, 관공서 문서나 오래된 시스템이 뱉는 파일에는 아직도 남아 있다.
둘의 차이는 단순하다.
| 한글 한 글자 | ASCII 한 글자 | 첫 바이트 | |
|---|---|---|---|
| UTF-8 | 3바이트 | 1바이트 | 0xEA~0xED |
| EUC-KR | 2바이트 | 1바이트 | 0xB0~0xC8 등 |
"잘못 인코딩됐다" 는 문제 설명은 사실 읽는 쪽이 인코딩을 잘못 고르면 깨진다는 뜻이다. 파일 자체는 멀쩡하고, EUC-KR 로 읽어야 할 걸 UTF-8 로 읽으면 첫 바이트부터 실패한다.
여기까지는 흔한 이야기다. 이 문제가 한 걸음 더 나가는 지점은 뒤에 나온다.
🔬 정찰 — hex 한 줄을 뜯어본다
먼저 파일의 실제 바이트를 본다.
xxd -l 224 extracted/tos.txt
0d 0a 0d 0a 로 헤더가 끝나고, 그 뒤로는 3039, 6166 만 나온다.
ASCII 로 09, af. 헤더 말대로 hex 다.
51,050자니까 되돌리면 25,525바이트가 나온다. 짝수라 잘려 있지도 않다.
되돌린 바이트는 무슨 인코딩인가
hex 를 풀고 나서 바로 .decode() 를 부르면 안 된다. 어떤 코덱이 맞는지부터 확인한다.
probe_encoding.py 를 만들어 후보를 하나씩 찔러 봤다.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""hex 를 되돌린 뒤 어떤 문자 인코딩인지 실제로 찔러본다.
부수 효과로 extracted/tos.bin (hex 를 푼 날바이트) 을 남긴다.
iconv 같은 외부 도구로 교차확인할 때 이 파일을 쓴다.
"""
import binascii
from pathlib import Path
import chardet
HERE = Path(__file__).resolve().parent
TOS = HERE / "extracted" / "tos.txt"
BIN = HERE / "extracted" / "tos.bin"
raw = TOS.read_bytes()
hex_line = raw.split(b"\r\n")[2]
blob = binascii.unhexlify(hex_line)
BIN.write_bytes(blob)
print(f"[hex] {len(hex_line)}자 -> {len(blob)}바이트, {BIN.name} 로 저장")
print(f"[hex] 앞 16바이트 : {blob[:16].hex(' ')}")
print()
for enc in ("utf-8", "utf-16-le", "euc-kr", "cp949", "johab"):
try:
s = blob.decode(enc)
except UnicodeDecodeError as e:
print(f"[{enc:<9}] FAIL {e.reason} @ offset {e.start}")
except LookupError:
print(f"[{enc:<9}] SKIP 코덱 없음")
else:
print(f"[{enc:<9}] OK {len(s)}글자 | 첫 줄 = {s.splitlines()[0]}")
print()
guess = chardet.detect(blob)
print(f"[chardet] encoding={guess['encoding']} confidence={guess['confidence']:.2f} language={guess['language']}")python3 probe_encoding.py
UTF-8 은 첫 바이트(0xc0)에서 바로 죽는다. 0xc0 은 UTF-8 에서 아예 쓰이지 않는 값이다.
EUC-KR 과 CP949 는 둘 다 통과하고 첫 줄이 이용약관 으로 읽힌다.
chardet 도 EUC-KR 을 신뢰도 0.99 로 찍는다.
CP949 는 EUC-KR 의 상위호환이라 둘 다 되는 게 정상이다. 이 파일에는 CP949 확장 영역 글자가 없다는 뜻이기도 하다.
파이썬 말고 외부 도구로도 확인
한 도구만 믿기보다 계열이 다른 것으로 한 번 더 본다. iconv 는 glibc 가 제공하는 변환기다.
iconv -f UTF-8 -t UTF-8 extracted/tos.bin | wc -c; iconv -f EUC-KR -t UTF-8 extracted/tos.bin | sed -n "1,4p"
iconv -f UTF-8 -t UTF-8 은 "이 바이트열이 유효한 UTF-8 인가" 를 묻는 관용적인 방법이다.
0바이트를 내놓고 오류로 끝난다. EUC-KR 로 지정하면 약관이 그대로 나온다.
주식회사 티오리한국(이하 '회사')가 제공하는 Dreamhack 서비스... — 진짜 약관처럼 생긴 문서다.
문제 설명이 "이 스토리는 픽션" 이라고 못박아 뒀으니 내용은 그대로 믿을 게 아니다.
🐛 삽질 — 두 번 헛짚었다
첫 겹을 벗기고 나면 15,016글자짜리 약관이 손에 들어온다. 여기서 DH 를 찾으면 뭔가 나온다.
그런데 그게 함정이었다.
▶🐛 삽질 1 — 대놓고 박혀 있던 미끼 플래그
복원된 본문을 DH{ 로 찾으면 딱 한 군데 걸린다. 제27조다.
iconv -f EUC-KR -t UTF-8 extracted/tos.bin | grep -a -n -A1 "제27조"
조문 제목이 제27조 (플래그) 다. 그리고 본문은 이렇게 적혀 있다.
문자열
DH{New_Dreamhack_Terms_Of_Service}는 플래그가 아닙니다.
읽고 나서야 웃었다. 형식도 맞고 내용도 그럴듯해서 그냥 던져 볼 뻔했다. 출제자가 친절하게 아니라고 적어 둔 덕에 제출 시도는 안 했지만, "찾았다" 로 끝내면 안 된다는 신호를 여기서 받은 셈이다.
grep 에 -a 를 붙인 것도 이유가 있다. 이 스트림에는 CR 이 섞여 있어서 GNU grep 이
바이너리로 판정해 버리는 일이 있었다. -a 없이 돌리면 매치가 있는데도 조용히 종료코드 1 로 끝난다.
▶🐛 삽질 2 — 0xA3 을 그냥 grep 했다가 오탐 79개
뒤에 나오지만 진짜 플래그는 EUC-KR 의 A3 행에 들어 있다.
그걸 알고 나서 "그럼 0xA3 바이트만 세면 되겠네" 하고 바이트 단위로 훑었더니 엉뚱한 값이 나왔다.
runs = [m for m in re.finditer(rb'(?:\xa3.)+', b)] # 43개 — 대부분 가짜EUC-KR 은 가변 길이다. 0xA3 은 전각 글자의 선두 바이트이기도 하지만,
전혀 다른 한글의 뒷바이트로도 나온다. 파일 전체에서 0xA3 바이트는 79개인데
그 중 실제 선두는 36개뿐이다.
정규식으로 바이트를 훑으면 이 둘을 구분하지 못한다. 첫 바이트부터 순서대로 걸으면서
"지금 이 바이트가 글자의 시작인가" 를 유지해야 한다. solve.py 의 walk_euckr() 가 그 일을 한다.
이런 종류의 착각은 UTF-8 에서는 잘 안 생긴다. UTF-8 은 후속 바이트가 항상 10xxxxxx 라
바이트만 봐도 선두인지 알 수 있게 설계돼 있기 때문이다. EUC-KR 에는 그런 장치가 없다.
💣 핵심 — 한글 사이에 섞인 전각 ASCII
미끼에 속지 않으려면 "약관 본문에 있어서는 안 될 것" 을 찾아야 한다. 정상적인 한국어 약관이라면 글자는 한글 아니면 ASCII다. 그 밖의 것이 남으면 그게 심어진 것이다.
그래서 글자를 종류별로 세는 charscan.py 를 만들었다.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""복원된 약관 본문의 글자를 종류별로 세어 '튀는 글자' 를 찾아낸다.
약관은 한글 + ASCII 로만 이뤄져야 정상이다. 그 밖의 글자가 남으면
그게 곧 출제자가 심어 둔 것이다.
"""
import unicodedata
from collections import Counter
from pathlib import Path
HERE = Path(__file__).resolve().parent
BIN = HERE / "extracted" / "tos.bin"
text = BIN.read_bytes().decode("euc-kr")
def kind(ch: str) -> str:
o = ord(ch)
if o < 0x80:
return "ASCII"
if 0xAC00 <= o <= 0xD7A3:
return "한글"
if 0xFF01 <= o <= 0xFF5E:
return "전각ASCII"
return "기타"
tally = Counter(kind(c) for c in text)
print(f"본문 {len(text)}글자")
for k, n in tally.most_common():
print(f" {k:<9} {n:>6}")
print()
print("한글/ASCII 가 아닌 글자 (많은 순):")
odd = Counter(c for c in text if kind(c) not in ("ASCII", "한글"))
for ch, n in odd.most_common(12):
print(f" U+{ord(ch):04X} {ch} x{n:<3} {unicodedata.name(ch, '?')}")
print(f" ... 총 {len(odd)}종")
print()
hits = [i for i, c in enumerate(text) if kind(c) == "전각ASCII"]
print(f"전각 ASCII {len(hits)}글자, 위치 {hits[0]}~{hits[-1]}")
print(f"그 구간 원문 : {text[hits[0]:hits[-1] + 1]}")python3 charscan.py
15,016글자 중 전각 ASCII 가 정확히 36글자다. 위치도 11743~11791 한 군데에 몰려 있다. 그 구간을 그대로 찍어 보면 이렇게 나온다.
⑤ 플래그는 다음 문자열을 NFKC 정규화한 것입니다 : DH{Do_Y0u_…}
제18조 (유료 콘텐츠) 의 다섯 번째 항이다. 유료 콘텐츠 조문 한복판에 플래그 안내가 끼어 있다.
①~⑪ 같은 원문자와 ‘’ 같은 인용부호도 "기타" 로 잡히는데, 그건 약관 문서에 원래 흔한 것들이라
같이 나올 뿐 단서는 아니다.
NFKC 라는 지시어 자체도 전각으로 쓰여 있다는 게 재밌다. 전각 구간 36글자 = 지시어 4글자 + 플래그 32글자다.

왜 눈에 안 띄었나
전각(fullwidth) 문자는 유니코드 U+FF01~U+FF5E 에 있는, ASCII 를 한글 폭에 맞춰 다시 그린 글자다.
D 와 D 는 의미상 같은 글자인데 코드포인트가 다르다.
EUC-KR 에서는 이게 더 얄궂다. 전각 ASCII 는 A3 행에 통째로 들어 있고,
뒷바이트가 정확히 ASCII | 0x80 이다.
| 글자 | 유니코드 | EUC-KR 바이트 |
|---|---|---|
D | U+0044 | 44 (1바이트) |
D | U+FF24 | a3 c4 (2바이트) |
{ | U+007B | 7b (1바이트) |
{ | U+FF5B | a3 fb (2바이트) |
즉 전각으로 쓰인 플래그는 EUC-KR 안에서 한글과 똑같은 2바이트 글자로 존재한다.
grep DH 로도 안 잡히고, strings 로도 안 잡힌다. 실제 바이트를 보면 분명하다.
xxd -s 19968 -l 144 extracted/tos.bin
0x4e1a 부터 a3c4 a3c8 a3fb a3c4 a3ef ... 가 이어진다.
a3c4 → 0xc4 - 0x80 = 0x44 → D. a3c8 → H. a3fb → {.
뒷바이트에서 0x80 만 빼면 그대로 ASCII 가 된다.
이 규칙 덕분에 유니코드 테이블 없이도 답을 낼 수 있다 —
EUC-KR 스트림을 걸으면서 선두 바이트가 0xA3 인 글자만 모으면 된다.
solve.py 는 이 경로를 교차검증용으로 따로 돌린다.
마지막 열쇠 — NFKC
본문이 시키는 NFKC 는 유니코드 정규화 형식 중 하나다.
NFC 가 "모양이 같은 것을 합치는" 수준이라면, NFKC 는 호환 문자를 대표 문자로 접는다.
전각 D → D, ① → 1, fi 합자 → fi 같은 변환이 여기서 일어난다.
파이썬에서는 unicodedata.normalize("NFKC", s) 한 줄이다.
전각 32글자를 여기에 통과시키면 평범한 ASCII 플래그가 된다.
🎯 교차확인 — CyberChef 로 같은 답이 나오는가
내 스크립트만으로 결론을 내면 스크립트 버그와 정답을 구분할 수 없다. 그래서 완전히 다른 구현으로 한 번 더 확인했다. CyberChef 는 브라우저에서 도는 인코딩 변환 도구다.
플래그가 든 구간의 hex 238자를 잘라 From Hex → Decode text (EUC Korean 51949) 레시피에 넣었다.
레시피와 입력은 URL 해시에 실려 있어 주소만 열면 그대로 재현된다.

Output 을 보면 글자 사이가 유난히 벌어져 있다. 그게 전각이라는 증거다.
브라우저 폰트로 렌더해도 DH{ 는 DH{ 보다 눈에 띄게 넓다.
파이썬이 준 답과 같은 문자열이 나왔으니 코덱 선택은 틀리지 않았다.
🚀 Full Exploit
지금까지의 판단을 한 파일로 묶었다. tos.txt 만 있으면 플래그까지 간다.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
DreamHack "서비스 이용 약관" (Bronze 3, misc/crypto) — solve
tos.txt 는 이렇게 생겼다.
Encoded toc (hex)\r\n
\r\n
c0ccbfebbee0b0fc0d0ac1a631c0e5... <- 51050자 한 줄
풀이는 세 겹이다.
1) 헤더를 떼고 hex 를 바이트로 되돌린다.
2) 그 바이트는 UTF-8 이 아니라 EUC-KR 이다 (= 문제가 말하는 "잘못된 인코딩").
3) 복원된 약관 본문 안에서 flag 는 '전각(fullwidth) 라틴' 으로 심어져 있다.
본문 제18조 ⑤ 가 "다음 문자열을 NFKC 정규화한 것" 이라고 알려 준다.
EUC-KR 에서 전각 ASCII 는 A3 행에 있고, 뒷바이트는 정확히 (ASCII | 0x80) 이다.
'D' = 0x44 -> 'D' = 0xA3 0xC4
그래서 EUC-KR 스트림을 걸으며 선두 바이트가 0xA3 인 글자만 모으면
유니코드 테이블 없이도 같은 답이 나온다 (verify_a3_row 가 그걸 확인한다).
"""
import binascii
import sys
import unicodedata
from pathlib import Path
HERE = Path(__file__).resolve().parent
TOS = HERE / "extracted" / "tos.txt"
def load_hex(path: Path) -> bytes:
"""헤더 2줄을 떼고 hex 본문만 바이트로 되돌린다."""
raw = path.read_bytes()
parts = raw.split(b"\r\n")
hex_line = parts[2]
print(f"[1] {path.name}: {len(raw)}바이트, 헤더={parts[0].decode()!r}")
print(f"[1] hex 본문 {len(hex_line)}자 -> {len(hex_line)//2}바이트")
return binascii.unhexlify(hex_line)
def pick_encoding(blob: bytes) -> str:
"""UTF-8 은 깨지고 EUC-KR 로만 읽힌다는 것을 실제로 보여 준다."""
for enc in ("utf-8", "euc-kr"):
try:
blob.decode(enc)
except UnicodeDecodeError as e:
print(f"[2] {enc:<7} 디코드 실패 - {e.reason} (offset {e.start})")
else:
print(f"[2] {enc:<7} 디코드 성공")
good = enc
return good
def walk_euckr(blob: bytes):
"""EUC-KR 스트림을 걸으며 (바이트열, 글자) 를 순서대로 내놓는다.
바이트를 그냥 grep 하면 안 된다. 0xA3 은 다른 한글의 '뒷바이트' 로도
나오기 때문에(파일 전체에 79개, 그 중 선두는 36개뿐) 선두/후행 구분을 지켜야 한다.
"""
i = 0
while i < len(blob):
n = 2 if blob[i] >= 0x80 else 1
chunk = blob[i:i + n]
yield chunk, chunk.decode("euc-kr")
i += n
def verify_a3_row(blob: bytes) -> str:
"""선두 바이트가 0xA3(전각 ASCII 행)인 글자만 모은다."""
out = []
for chunk, ch in walk_euckr(blob):
if len(chunk) == 2 and chunk[0] == 0xA3:
out.append(chr(chunk[1] - 0x80))
naive = blob.count(b"\xa3")
print(f"[5] 0xA3 을 단순 바이트로 세면 {naive}개 (후행바이트 오탐 포함)")
print(f"[5] EUC-KR 스트림을 걸어서 고른 전각 글자: {len(out)}개")
return "".join(out)
def extract_flag(text: str) -> str:
"""전각 라틴이 연속으로 이어지는 가장 긴 구간을 뽑아 NFKC 정규화한다."""
runs, cur = [], ""
for ch in text:
if 0xFF01 <= ord(ch) <= 0xFF5E:
cur += ch
else:
if cur:
runs.append(cur)
cur = ""
if cur:
runs.append(cur)
runs.sort(key=len, reverse=True)
print(f"[4] 전각 구간 {len(runs)}개, 가장 긴 것 {len(runs[0])}자")
print(f"[4] 원문 그대로 : {runs[0]}")
return unicodedata.normalize("NFKC", runs[0])
def find_decoy(text: str) -> str:
"""제27조가 '이건 플래그가 아니다' 라고 못박아 둔 미끼."""
decoy = "DH{New_Dreamhack_Terms_Of_Service}"
line = next(l for l in text.splitlines() if decoy in l)
print(f"[3] 본문에 박힌 미끼 {decoy}")
print(f"[3] 제27조 원문 : {line.strip()}")
return decoy
def main() -> int:
blob = load_hex(TOS)
enc = pick_encoding(blob)
text = blob.decode(enc)
find_decoy(text)
flag = extract_flag(text)
a3 = unicodedata.normalize("NFKC", verify_a3_row(blob))
assert flag in a3, "A3 행 교차검증 실패"
print(f"[6] 교차검증(0xA3 행 전체) : {a3}")
print()
print(f"FLAG: {flag}")
return 0
if __name__ == "__main__":
sys.exit(main())python3 solve.py
[5] 줄이 삽질 2에서 이야기한 그 차이다. 0xA3 바이트는 79개인데 실제 전각 글자는 36개.
두 경로(유니코드 범위로 고르기 / A3 행 바이트로 고르기)가 같은 문자열을 내놓아서 assert 가 통과한다.
[6] 에 NFKC 네 글자가 앞에 붙어 나오는 건 지시어까지 전각이라 같이 딸려 온 것이다.
플래그는 이것이다.
DH{Do_Y0u_Know_EUC-KR_enc0ding?}♻️ 재현
문제 폴더만 있으면 한 번에 다시 돌아가도록 reproduce.sh 를 남겨 뒀다.
정답과 대조까지 스크립트가 스스로 한다.
#!/usr/bin/env bash
# 서비스 이용 약관 (DreamHack Bronze 3, misc/crypto) — 한 방 재현
# tos.txt 만 있으면 flag 까지 간다. 서버·네트워크 불필요.
set -eu
cd "$(dirname "$(readlink -f "$0")")"
EXPECT=$(python3 -c "import json;print(json.load(open('문제.json'))['flag'])" 2>/dev/null || echo '')
if [ ! -f extracted/tos.txt ]; then
echo "❌ extracted/tos.txt 가 없습니다 (challenge_2189.zip 을 풀어 주세요)"
exit 1
fi
# tos.bin(hex 를 푼 날바이트)은 probe_encoding.py 가 만든다. solve.py 는 tos.txt 만으로도 돈다.
_probe=$(python3 probe_encoding.py 2>&1) || true
OUT=$(timeout 120 python3 solve.py 2>&1) || true
echo "$OUT" | tail -8
FLAG=$(printf '%s' "$OUT" | grep -aoE 'DH\{[^}]+\}' | tail -1)
if [ -n "$FLAG" ] && { [ -z "$EXPECT" ] || [ "$FLAG" = "$EXPECT" ]; }; then
echo; echo "✅ PASS $FLAG"; exit 0
fi
echo; echo "❌ FAIL (얻은 값: '${FLAG:-없음}' / 기대: '$EXPECT')"; exit 1./reproduce.sh
기대값 EXPECT 는 같은 폴더의 문제.json 에서 읽는데, 이 파일은 백업 단계에서 생긴다.
아직 없는 시점이라 위 실행은 "플래그를 뽑아냈는가" 만 보고 통과했다.
문제.json 이 놓이면 그때부터는 값까지 대조한다.
grep 에서 tail -1 을 쓴 이유도 있다. 출력에 DH{ 로 시작하는 문자열이 두 번 나온다 —
미끼와 진짜. head -1 로 잡으면 미끼를 정답으로 착각한 채 조용히 실패한다.
로컬 환경은 Ubuntu 25.10 / Python 3.13 / chardet 5.2.0 이다.
chardet 은 정찰용이라 없어도 solve.py 는 돈다.
📝 결론
미끼는 "형식이 맞는 것" 으로 온다.
이 문제에서 가장 잘 걸리는 자리는 인코딩이 아니라 제27조다.
DH{...} 형태를 찾자마자 손이 제출 버튼으로 갔다. 출제자가 아니라고 적어 두지 않았으면
오답 한 번은 먹었을 것이다.
CTF 든 실무든, 패턴 매칭으로 찾은 것은 "찾았다" 가 아니라 "후보를 하나 얻었다" 다.
보이지 않는 글자는 보이는 글자와 같은 취급을 받을 수 있다.
전각 D 와 반각 D 는 사람 눈에도, 대부분의 필터에도 다른 글자다.
그런데 NFKC 정규화를 거치면 같아진다. 이 성질은 실제 취약점의 재료가 된다.
검증을 먼저 하고 정규화를 나중에 하면 그대로 우회가 된다. <script> 는 태그 블랙리스트를
통과하지만 NFKC 를 지나면 <script> 가 된다. 파이썬 식별자, 파일 경로, 사용자명 중복 검사에서
같은 패턴이 반복해서 발견돼 왔다.
순서는 정규화 → 검증이다. 반대로 하면 검사한 문자열과 실제로 쓰이는 문자열이 달라진다.
인코딩을 추측하지 말고 측정한다.
chardet, iconv -f X -t X, CyberChef — 계열이 다른 도구 세 개가 같은 답을 주면 그때 믿는다.
이 문제는 EUC-KR 하나만 맞히면 되는 쉬운 축이지만, 실무에서 만나는 파일은
CP949 와 EUC-KR 이 섞여 있거나 UTF-8 로 두 번 인코딩된 것들이라 한 번에 안 맞는다.
가변 길이 인코딩에서 바이트 단위 검색은 틀린다.
0xA3 을 그냥 세면 79개, 실제로는 36개였다. UTF-8 은 후속 바이트가 10xxxxxx 로 표시돼 있어
바이트만 봐도 경계를 알 수 있지만 EUC-KR 에는 그런 표식이 없다.
레거시 인코딩을 다룰 때는 스트림을 처음부터 걸어야 한다. 이건 파서 구현에서
경계 혼동 취약점이 생기는 자리이기도 하다.
Comments
댓글
댓글을 남기려면 로그인이 필요해요. (네이버 · 구글 계정)
댓글 불러오는 중…