[🥉 Bronze 2] 자판 위에 글자를 그리는 암호 — DreamHack keyboard toxicosis 풀이

2026-10-12·1분 읽기·

[🥉 Bronze 2] 자판 위에 글자를 그리는 암호 — DreamHack keyboard toxicosis 풀이

쓰러진 용찬이가 남긴 암호는 QWE WSX 같은 키 묶음 다섯 줄뿐이다. 치환 암호처럼 보이지만, 쓰인 글자가 자판 왼쪽 아홉 칸에만 갇혀 있다는 게 단서다. 키 세 개가 긋는 직선을 3×3 격자에 겹치면 글자 모양이 떠오른다. 획을 좌표로 분류하고 격자에 렌더해 TOXIC을 복원했다.

문제: DreamHack — keyboard toxicosis 분류: Misc 난이도: 🥉 Bronze 2 FLAG: cause{TOXIC}

자판 왼쪽 아홉 칸 블록 다섯 개 위에 주황 획을 그린 요약도. 위 가로와 가운데 세로는 T, 네 변은 O, 두 대각선은 X, 위아래 가로와 가운데 세로는 I, 위아래 가로와 왼쪽 세로는 C 가 되어 다섯 글자 TOXIC 을 이룬다
자판 왼쪽 아홉 칸 블록 다섯 개 위에 주황 획을 그린 요약도. 위 가로와 가운데 세로는 T, 네 변은 O, 두 대각선은 X, 위아래 가로와 가운데 세로는 I, 위아래 가로와 왼쪽 세로는 C 가 되어 다섯 글자 TOXIC 을 이룬다

집에 온 용찬이 어머니가 쓰러진 용찬이를 발견한다. 컴퓨터 화면에는 이상한 문자열이 암호처럼 적혀 있었고, 쓰러진 원인을 그 암호에서 찾아 cause{...} 안에 넣어 제출하는 게 목표다. 제공 파일은 딱 하나, help.txt다. 열어 보면 알파벳 대문자가 세 개씩 묶여 다섯 줄 들어 있다. 겉보기엔 평범한 치환 암호 같지만, 쓰인 글자를 세어 보면 자판 왼쪽 귀퉁이에만 모여 있다. 그 관찰 하나가 풀이의 전부다. 위 그림은 결론을 미리 요약한 것이고, 아래에서 어떻게 거기에 도달했는지 하나씩 풀어 간다.


문제 개요

항목내용
문제명keyboard toxicosis
난이도🥉 Bronze 2
분류Misc
제공 파일help.txt (61바이트, 5줄)
서버없음 (파일만)
핵심 기법자판 제스처 암호 — 키 묶음을 자판 위 획으로 보고 격자에 겹쳐 글자 복원

풀이는 세 단계다. ① help.txt에 쓰인 글자가 자판의 특정 아홉 칸에만 갇혀 있다는 걸 알아챈다. ② 키 세 개짜리 묶음이 그 블록 위에서 직선 하나(획)를 긋는다고 해석한다. ③ 한 줄의 획들을 3×3 격자에 겹쳐 글자를 읽는다. 결과는 TOXIC — 중독, 제목의 toxicosis와 그대로 맞물린다. 문제 설명의 "쓰러진 원인"이라는 서사가 곧 정답 단어를 가리키는 힌트였던 셈이다.

제목의 toxicosis는 의학에서 "독성 물질에 의한 중독 상태"를 뜻한다. 문제 제목이 keyboard toxicosis이니, 자판(keyboard)으로 중독(toxic)을 표현한다는 이중 힌트다. 암호를 풀기 전에도 답이 toxic 언저리일 거라는 짐작은 할 수 있지만, 그걸 실제로 자판 위에서 복원해 내는 게 문제의 본령이다.

🔬 정찰 — 제공 파일 뜯어보기

받은 파일이 무엇인지부터 본다. 압축을 풀면 extracted/ 안에 파일 하나만 떨어진다. 크기와 종류를 먼저 확인했다.

ls -la extracted/; echo; du -b extracted/help.txt
작업 폴더 extracted 디렉토리 목록. help.txt 한 파일만 있고 크기는 61바이트, 10월 12일 생성이다. 이어진 du 명령도 61바이트로 확인해 주어 제공 파일이 이 작은 텍스트 하나뿐임을 보여 준다
작업 폴더 extracted 디렉토리 목록. help.txt 한 파일만 있고 크기는 61바이트, 10월 12일 생성이다. 이어진 du 명령도 61바이트로 확인해 주어 제공 파일이 이 작은 텍스트 하나뿐임을 보여 준다

파일이 61바이트짜리 텍스트 하나뿐이니, 날것으로 전부 들여다봐도 된다. 종류와 줄 수, 그리고 눈에 안 보이는 제어문자까지 한 번에 확인했다.

file extracted/help.txt; wc -l extracted/help.txt; cat -A extracted/help.txt

cat -A는 탭을 ^I, 캐리지리턴을 ^M, 줄 끝을 $로 드러낸다. 각 줄 끝에 ^M$가 붙어 있으니 윈도우(CRLF)에서 만든 파일이고, 본문은 아래 다섯 줄이다.

file 명령 출력은 CRLF 줄바꿈의 ASCII 텍스트, wc 는 다섯 줄로 센다. cat -A 결과 QWE WSX 식 대문자 세 글자 묶음이 공백으로 나뉘고 줄 끝마다 캐럿 M 과 달러 기호가 붙어 윈도우 줄바꿈임이 드러난다
file 명령 출력은 CRLF 줄바꿈의 ASCII 텍스트, wc 는 다섯 줄로 센다. cat -A 결과 QWE WSX 식 대문자 세 글자 묶음이 공백으로 나뉘고 줄 끝마다 캐럿 M 과 달러 기호가 붙어 윈도우 줄바꿈임이 드러난다
QWE WSX
QAZ ZXC CDE EWQ
QSC ESZ
QWE WSX ZXC
QWE QAZ ZXC

바이트가 진짜 이게 전부인지 헥스덤프로 못을 박았다. 추가로 섞인 공백이나 숨은 바이트가 없는지, 혹시 트레일링 공백으로 또 다른 정보가 인코딩돼 있지는 않은지 확인하는 단계다.

xxd extracted/help.txt
xxd 헥스덤프. 왼쪽 16진수 51 57 45 20 이 오른쪽 ASCII QWE WSX 묶음에 대응하고, 줄 사이는 0d 0a 두 바이트뿐이라 숨은 바이트 없이 내용이 대문자 묶음과 공백, 줄바꿈이 전부임을 바이트 단위로 확인해 준다
xxd 헥스덤프. 왼쪽 16진수 51 57 45 20 이 오른쪽 ASCII QWE WSX 묶음에 대응하고, 줄 사이는 0d 0a 두 바이트뿐이라 숨은 바이트 없이 내용이 대문자 묶음과 공백, 줄바꿈이 전부임을 바이트 단위로 확인해 준다

헥스덤프로 보니 공백은 20, 줄바꿈은 0d 0a 뿐이고 숨은 바이트는 없다. 61바이트를 분해해 보면 더 분명하다. 세 글자 묶음 14개가 42바이트, 묶음 사이 공백이 9바이트(줄별로 1·3·1·2·2개), 줄 끝 CRLF 다섯 번이 10바이트 — 합이 정확히 61이다. 트레일링 공백이나 널 패딩 같은 여분이 끼어들 자리가 없다. CRLF는 파일을 윈도우에서 만들었다는 흔적일 뿐, 거기에 추가 정보가 숨어 있지는 않다. 즉 정보는 보이는 글자와 그 배치가 전부다. 여기서 암호 해독의 첫 단추가 보인다. 쓰인 글자가 Q W E A S D Z X C 아홉 종류뿐이다. 치환 암호라면 복원된 평문이 알파벳 전역에 고르게 퍼졌을 텐데, 하필 자판 왼쪽 아래 3×3 블록에만 모여 있다. 게다가 늘 세 글자씩 묶인다. 글자 자체가 뜻을 담은 게 아니라 자판 위 위치가 의미라는 신호다.

🧩 핵심 아이디어 — 키 세 개가 긋는 획

자판을 격자로 보자. 왼쪽 세 열, 위 세 행을 떼면 이렇게 된다.

Q W E
A S D
Z X C

이 블록 위에서 QWE를 손가락으로 이으면 맨 윗줄을 가로지르는 가로획이 된다. WSX는 가운데 열을 타고 내려오는 세로획, QSC는 왼쪽 위에서 오른쪽 아래로 내려긋는 대각획이다. 키 세 개짜리 묶음 하나가 곧 직선 획 하나다. 공백으로 나뉜 묶음이 여러 개면 그 획들을 같은 격자에 겹쳐 하나의 그림을 만든다.

자판 제스처 개념도. 위쪽 QWERTY 자판에서 왼쪽 아홉 칸 Q W E, A S D, Z X C 가 파란 테두리로 강조되고, 아래쪽 여덟 카드가 가로획 세 개, 세로획 세 개, 대각획 두 개를 각각 주황 선으로 보여 준다
자판 제스처 개념도. 위쪽 QWERTY 자판에서 왼쪽 아홉 칸 Q W E, A S D, Z X C 가 파란 테두리로 강조되고, 아래쪽 여덟 카드가 가로획 세 개, 세로획 세 개, 대각획 두 개를 각각 주황 선으로 보여 준다

읽는 방향은 상관없다. QWE든 EWQ든 같은 맨 윗줄 가로획이고, 격자를 채우는 결과는 똑같다. 그래서 두 번째 줄의 EWQ는 첫 줄의 QWE와 같은 획으로 취급한다. 세로획도 마찬가지라 QAZ와 ZAQ는 같고, 대각선 QSC와 CSQ도 같다. 암호를 만든 사람이 손가락으로 어느 쪽에서 긋기 시작했느냐의 차이일 뿐, 자판에 남는 선은 동일하다.

왜 하필 자판 왼쪽 블록일까. 오른손잡이 기준으로 왼손 세 손가락이 자연스럽게 닿는 영역이 Q~Z 세 열이다. QWEASDZXC 아홉 키는 3×3으로 정확히 맞아떨어지는 유일한 모서리 블록이기도 하다. 숫자 키패드(전화기·계산기)로 글자를 그리는 변형도 같은 발상인데, 그쪽은 3×4라 글자를 더 다양하게 만들 수 있는 대신 "어느 아홉 칸을 쓰는가"가 덜 자명하다. 이 문제는 자판 모서리라는 제약 덕분에 블록을 찾기가 비교적 쉽다.

▶🐛 삽질 — 처음엔 치환이나 키 시프트 암호로 의심했다

QWE WSX를 처음 봤을 때 가장 먼저 떠오른 건 자판 기반 치환이었다. 키를 한 칸씩 오른쪽으로 민다거나(Q→W), 자판 열을 숫자로 바꾸는 식의 매핑을 몇 가지 머릿속으로 돌려 봤다. 흔한 자판 암호가 "손가락을 한 칸 밀어 친 오타"를 되돌리는 방식이라, 그 가능성부터 점검한 것이다. 하지만 두 가지가 맞지 않았다.

  • 글자 집합이 너무 좁다. 치환이든 시프트든 평문이 영어라면 결과 문자가 Q W E A S D Z X C 아홉 종류 안에만 떨어질 이유가 없다. 자판 한 칸 밀기를 역으로 풀어도 평문은 자판 전역으로 퍼져야 한다. 아홉 칸에 갇혀 있다는 건 평문이 그 안에 있는 게 아니라, 그 아홉 칸 자체가 도화지라는 뜻이다.
  • 세 글자 묶음이 규칙적이다. 치환은 글자 하나하나가 독립적인데, 여기선 늘 정확히 세 개가 한 덩어리다. 세 개를 이어야 의미가 생긴다는 뜻이고, 이건 글자 값이 아니라 **배치(모양)**를 가리킨다. 3×3 블록에서 길이 3짜리 직선은 가로 3개, 세로 3개, 대각 2개로 딱 여덟 개뿐인데, 등장한 유니크 묶음도 정확히 그 범위 안이었다.

그래서 치환 계열 가설은 접고, "아홉 칸 블록 위에 무언가를 그린다"는 쪽으로 방향을 틀었다. 세 글자가 자판에서 일직선이라는 걸 확인한 순간 획이라는 해석이 굳어졌다.

💣 한 글자는 어떻게 만들어지나 — O 조립

획 여러 개가 한 글자가 되는 과정을 둘째 줄로 짚어 보자. QAZ ZXC CDE EWQ는 네 개의 묶음이다. 각각 왼쪽 세로획, 아래 가로획, 오른쪽 세로획, 위 가로획이다. 이 넷을 같은 격자에 차례로 얹으면 네 변이 모두 채워져 사각 테두리, 즉 가운데가 빈 O가 된다.

둘째 줄 O 조립 단계도. QAZ 왼쪽 세로에 ZXC 아래 가로, CDE 오른쪽 세로, EWQ 위 가로를 화살표를 따라 차례로 더하면 가운데 한 칸만 빈 사각 테두리가 되어 네 변이 다 차면 O 가 된다는 걸 보여 준다
둘째 줄 O 조립 단계도. QAZ 왼쪽 세로에 ZXC 아래 가로, CDE 오른쪽 세로, EWQ 위 가로를 화살표를 따라 차례로 더하면 가운데 한 칸만 빈 사각 테두리가 되어 네 변이 다 차면 O 가 된다는 걸 보여 준다

나머지 글자도 같은 방식이다. 다섯 줄이 각각 어떤 획으로 이뤄지는지 정리하면 이렇다.

글자줄 (묶음)획 구성
TQWE WSX위 가로 + 가운데 세로
OQAZ ZXC CDE EWQ왼·아래·오른·위 네 변
XQSC ESZ두 대각선 (↘, ↙)
IQWE WSX ZXC위 가로 + 가운데 세로 + 아래 가로
CQWE QAZ ZXC위 가로 + 왼쪽 세로 + 아래 가로

T는 윗변과 가운데 기둥, I는 거기에 아랫변을 더한 모양이라 둘이 헷갈리기 쉽다. 차이는 아래 가로획(ZXC)의 유무 하나다. C는 O에서 오른쪽 세로획을 뺀 것으로 보면 외우기 쉽다. 이렇게 직선 획의 조합만으로 표현되는 글자는 세그먼트 디스플레이(7-세그먼트)로 그릴 수 있는 글자들과 겹친다.

획을 겹칠 때 중요한 성질 하나는 같은 칸을 두 획이 지나도 한 번만 칠해진다는 점이다. 집합의 합집합(OR)처럼 동작한다. 예를 들어 O를 만드는 네 획에서 왼쪽 세로획 QAZ와 아래 가로획 ZXC는 모서리 Z 칸을 공유하는데, 그 칸이 두 번 칠해진다고 달라지는 건 없다. 그래서 묶음의 순서도, 겹침도 결과에 영향을 주지 않는다. 한 줄 안의 묶음을 어떤 순서로 읽든 최종 격자는 똑같다. 이 성질 덕분에 "획들을 모아 칠한 뒤 모양을 읽는다"는 단순한 규칙이 모호함 없이 성립한다.

🔬 획을 좌표로 — analyze.py

머릿속 해석을 기계적으로 검증했다. 블록의 각 키에 좌표 (열, 행)을 매기고, help.txt에 등장하는 모든 묶음을 모아 세 점이 등간격 직선인지, 가로·세로·대각 중 무엇인지 분류하는 짧은 스크립트를 썼다. 만약 하나라도 "직선 아님"이 나오면 획 해석이 틀린 것이다.

#!/usr/bin/env python3
"""help.txt 에 등장하는 키 토큰이 QWERTY 왼쪽 3x3 블록에서 각각 어떤 '획'인지 분류한다.
 
블록(열 x, 행 y):
    Q(0,0) W(1,0) E(2,0)
    A(0,1) S(1,1) D(2,1)
    Z(0,2) X(1,2) C(2,2)
"""
import pathlib, collections
 
POS = {
    'Q': (0, 0), 'W': (1, 0), 'E': (2, 0),
    'A': (0, 1), 'S': (1, 1), 'D': (2, 1),
    'Z': (0, 2), 'X': (1, 2), 'C': (2, 2),
}
 
def classify(tok):
    p = [POS[c] for c in tok]
    dx = p[1][0] - p[0][0]
    dy = p[1][1] - p[0][1]
    # 세 점이 등간격 직선인지
    straight = (p[2][0] - p[1][0], p[2][1] - p[1][1]) == (dx, dy)
    if not straight:
        return "직선 아님"
    if dy == 0:
        row = {0: "맨위", 1: "가운데", 2: "맨아래"}[p[0][1]]
        return f"가로획({row} 행)"
    if dx == 0:
        col = {0: "왼쪽", 1: "가운데", 2: "오른쪽"}[p[0][0]]
        return f"세로획({col} 열)"
    if dx == dy:
        return "대각획(↘ 좌상→우하)"
    return "대각획(↙ 우상→좌하)"
 
def main():
    lines = [l.strip() for l in pathlib.Path("extracted/help.txt").read_text().splitlines() if l.strip()]
    toks = []
    for ln in lines:
        toks += ln.split()
    uniq = sorted(set(toks))
    print(f"줄 수 = {len(lines)}, 전체 토큰 = {len(toks)}, 유니크 토큰 = {len(uniq)}")
    print("-" * 44)
    for t in uniq:
        coords = " ".join(f"{c}{POS[c]}" for c in t)
        print(f"{t} : {classify(t):22s} | {coords}")
 
if __name__ == "__main__":
    main()
python3 analyze.py
analyze.py 실행 결과. 다섯 줄에서 전체 토큰 열네 개, 유니크 여덟 개가 나오고 여덟 묶음이 전부 가로·세로·대각 직선으로 분류되며, 각 줄 오른쪽에 키별 좌표가 찍혀 세 점이 실제로 일직선임을 확인해 준다
analyze.py 실행 결과. 다섯 줄에서 전체 토큰 열네 개, 유니크 여덟 개가 나오고 여덟 묶음이 전부 가로·세로·대각 직선으로 분류되며, 각 줄 오른쪽에 키별 좌표가 찍혀 세 점이 실제로 일직선임을 확인해 준다

classify 함수는 세 점의 좌표 차이를 본다. 첫 점에서 둘째 점으로 가는 변위 (dx, dy)가 둘째에서 셋째로 갈 때도 똑같으면 세 점이 등간격 직선이다. 그 다음 dy == 0이면 가로, dx == 0이면 세로, dx == dy면 좌상에서 우하로 내려가는 대각, 아니면 반대 대각으로 가른다. 좌표계만 제대로 잡으면 분류 자체는 조건문 몇 줄이다. 핵심은 이 함수가 모든 묶음에 대해 "직선 아님"을 한 번도 내지 않는지를 보는 것이었다.

유니크 묶음은 여덟 개, 전부 "직선 아님" 없이 가로·세로·대각으로 떨어졌다. 3×3 블록에서 가능한 길이 3짜리 직선이 정확히 여덟 개(가로 3 + 세로 3 + 대각 2)인데, 등장한 유니크 묶음 수와 일치한다. 자판 위에서 손가락으로 쭉 긋는 획이라는 해석이 데이터로 확인된 셈이다. 이제 한 줄에 든 획들을 격자에 겹쳐 그림을 완성하면 된다.

🎯 격자에 겹쳐 글자 복원

각 획이 지나는 칸을 3×3 격자에 칠한다. 둘째 줄처럼 묶음이 네 개면 네 획을 모두 얹고, 첫째 줄처럼 두 개면 두 획만 얹는다. 다섯 줄을 차례로 렌더하면 글자가 하나씩 떠오른다.

다섯 줄을 3×3 녹색 격자로 렌더한 개념도. QWE WSX 는 T, QAZ ZXC CDE EWQ 는 사각형 O, QSC ESZ 는 X, QWE WSX ZXC 는 I, QWE QAZ ZXC 는 C 가 되고 아래에 password 는 TOXIC 이라 적힌다
다섯 줄을 3×3 녹색 격자로 렌더한 개념도. QWE WSX 는 T, QAZ ZXC CDE EWQ 는 사각형 O, QSC ESZ 는 X, QWE WSX ZXC 는 I, QWE QAZ ZXC 는 C 가 되고 아래에 password 는 TOXIC 이라 적힌다

위에서부터 T O X I C. 문제 설명이 요구한 "쓰러진 원인"은 중독(toxic)이고, 제목 keyboard toxicosis의 toxicosis(중독증)와 정확히 이어진다. 서사와 정답 단어, 그리고 복원된 글자가 모두 한 곳을 가리킨다. 25/01/26 공지대로 대문자로 제출해야 하므로 플래그는 cause{TOXIC}다.

🚀 Full Exploit

격자 렌더를 그대로 코드로 옮겼다. 각 줄의 묶음에서 키를 좌표로 바꿔 격자를 칠하고 출력한다. 글자 판독은 사람이 눈으로 하지만, 아스키 격자가 또렷해서 바로 읽힌다. 손으로 격자를 그리다 I와 T를 헷갈리는 실수를 없애려는 목적이다.

#!/usr/bin/env python3
"""keyboard toxicosis (DreamHack, Bronze 2, misc) solver.
 
help.txt 의 각 줄은 QWERTY 자판 왼쪽 3x3 블록 위에서 그린 '획(stroke)'들의 모음이다.
3x3 노드:
    Q W E      (0,0)(1,0)(2,0)
    A S D      (0,1)(1,1)(2,1)
    Z X C      (0,2)(1,2)(2,2)
각 3글자 토큰은 그 세 키를 잇는 직선 한 획이다. 한 줄의 획들을 격자에 겹쳐 글자를 읽는다.
"""
import sys, pathlib
 
POS = {
    'Q': (0, 0), 'W': (1, 0), 'E': (2, 0),
    'A': (0, 1), 'S': (1, 1), 'D': (2, 1),
    'Z': (0, 2), 'X': (1, 2), 'C': (2, 2),
}
 
def render(line):
    grid = [[' '] * 3 for _ in range(3)]
    for token in line.split():
        for ch in token:
            x, y = POS[ch]
            grid[y][x] = '#'
    return grid
 
def main():
    p = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else 'extracted/help.txt')
    letters = []
    for raw in p.read_text().splitlines():
        line = raw.strip()
        if not line:
            continue
        grid = render(line)
        print(f"[{line}]")
        for row in grid:
            print('  ' + ''.join(row))
        print()
        letters.append(grid)
    # 각 격자가 어떤 글자인지는 사람이 눈으로 읽는다(아래 출력 참조)
    print("=> 격자를 위에서부터 읽으면: T O X I C")
    print("=> password = TOXIC")
    print("=> FLAG = cause{TOXIC}")
 
if __name__ == '__main__':
    main()
python3 solve.py
solve.py 실행 결과. 다섯 줄이 차례로 아스키 격자로 그려져 T, O, X, I, C 모양이 나오고, 마지막에 격자를 위에서부터 읽으면 T O X I C, password 는 TOXIC, FLAG 는 cause 중괄호 TOXIC 이라는 세 줄이 출력된다
solve.py 실행 결과. 다섯 줄이 차례로 아스키 격자로 그려져 T, O, X, I, C 모양이 나오고, 마지막에 격자를 위에서부터 읽으면 T O X I C, password 는 TOXIC, FLAG 는 cause 중괄호 TOXIC 이라는 세 줄이 출력된다

출력 격자를 위에서부터 읽으면 T, O, X, I, C. 이 다섯 글자를 모아 cause{TOXIC}를 제출하면 정답으로 처리된다. 서버가 필요 없는 오프라인 문제라, 파일을 받고 격자를 그리는 것으로 풀이가 끝난다.

📝 정리

브론즈 난이도답게 알고리즘은 없다. 이 문제의 전부는 관찰 하나였다. 암호문에 쓰인 글자가 자판 왼쪽 아홉 칸에만 갇혀 있고 늘 세 개씩 묶인다는 사실. 거기서 "글자 값이 아니라 자판 위 모양"이라는 해석으로 넘어가면 나머지는 격자에 칠하는 단순 작업이다. 문제 제목과 서사가 toxic이라는 답을 넌지시 알려 주지만, 그걸 자판 위에서 실제로 복원해 보는 과정이 핵심이다.

  • 문자 집합을 먼저 세어 본다. 치환·비즈네르 같은 고전 암호를 가정하기 전에, 쓰인 글자가 어디에 모여 있는지부터 본다. 알파벳 전역이 아니라 자판 한 귀퉁이에 몰려 있으면 값이 아니라 배치가 의미인 경우가 많다. help.txt가 아홉 글자만 쓴다는 걸 셈한 게 모든 것의 출발점이었다.
  • 묶음 단위를 의심한다. 일정하게 N개씩 묶이면 그 N개가 함께 하나의 단위를 이룬다. 여기선 세 키가 직선 한 획이었고, 3×3 블록에서 길이 3짜리 직선이 여덟 가지뿐이라는 제약이 해석을 뒷받침했다.
  • 눈으로 읽는 단계는 코드로 또렷하게. 격자를 머릿속에서 그리면 I와 T, O와 C를 헷갈리기 쉽다. 아스키 격자로 렌더해 두면 판독 실수가 사라지고, 글에 싣기에도 근거가 분명하다.

이 유형은 CTF misc에서 꾸준히 변주된다. 숫자 키패드로 글자를 그리는 판, 휴대폰 T9 자판을 쓰는 판, 혹은 아예 게임 패드 방향키로 획을 긋는 판까지 형태만 바꿔 나온다. 공통점은 "입력 장치의 물리적 배치 위에 선을 그어 모양을 만든다"는 발상이다. 암호문이 특정 키 집합에만 갇혀 있고 규칙적으로 묶인다면, 그 입력 장치를 격자로 펼쳐 놓고 선을 그어 보는 걸 한 번쯤 떠올릴 만하다.

마지막으로, 이번 풀이에서 코드가 한 일은 사실 "확인"이지 "해독"이 아니었다는 점을 짚고 싶다. 답인 TOXIC은 자판 위에서 손으로 획을 그어도 충분히 보인다. 그럼에도 analyze.py로 모든 묶음이 직선인지 검증하고 solve.py로 격자를 또렷하게 렌더한 이유는, 손으로 읽은 결과가 정말 맞는지를 기계로 한 번 더 못 박기 위해서다. 브론즈 문제라 손풀이로 끝내도 되지만, 작은 문제에서 "눈으로 본 것을 코드로 재확인하는" 습관을 들여 두면 더 복잡한 문제에서 추측과 사실을 가르는 힘이 된다. misc는 종종 번뜩이는 관찰로 풀리지만, 그 관찰을 검증 가능한 절차로 바꿔 두는 게 풀이를 남과 공유할 때의 신뢰가 된다.

이 글이 도움이 됐나요?

Comments

댓글

0개

댓글을 남기려면 로그인이 필요해요. (네이버 · 구글 계정)

댓글 불러오는 중…

Related

관련 글

3개
[🥉 Bronze 2] private 는 비밀이 아니다 — DreamHack Where is gift? 풀이
blog

[🥉 Bronze 2] private 는 비밀이 아니다 — DreamHack Where is gift? 풀이

Solidity 컨트랙트가 플래그를 `string private` 로 숨겼지만, EVM 스토리지는 온체인에 평문으로 남는다. Sepolia 테스트넷에 eth_getStorageAt 을 날려 slot 0 을 읽고, 긴 문자열 저장 규칙(keccak256 베이스 슬롯)으로 데이터를 복원해 플래그를 꺼낸다.
#dreamhack#ctf#misc+6
2026-10-12#dreamhack +3
[🥉 Bronze 2] 400개 난수 파일 더미에서 플래그 한 줄 찾기 — DreamHack Random String 풀이
blog

[🥉 Bronze 2] 400개 난수 파일 더미에서 플래그 한 줄 찾기 — DreamHack Random String 풀이

1024글자짜리 base62 난수 문자열 파일이 400개 쏟아진다. "이 많은 파일 속에서 플래그를 찾을 수 있겠냐"는 도발이 곧 풀이다. 통계로 파일 더미가 균등 난수임을 확인하고 쪼개져 있지 않다는 걸 검증한 뒤, 플래그 포맷을 재귀 grep 한 방으로 뽑는다. 포맷을 몰라도 base62에 없는 글자가 박힌 파일을 역추적하면 같은 파일이 나온다.
#dreamhack#ctf#misc+4
2026-10-11#dreamhack +3
[🥉 Bronze 4] 반투명 덧칠은 지우개가 아니다 — DreamHack QR Code 풀이
blog

[🥉 Bronze 4] 반투명 덧칠은 지우개가 아니다 — DreamHack QR Code 풀이

QR 코드 위에 검은 낙서가 덮여 있다. 그런데 픽셀값이 열 종류밖에 없고 255, 63, 16, 4, 1 이라는 등비 사슬을 이룬다. 알파 75% 검정을 겹칠 때마다 밑의 밝기가 1/4 씩만 남은 흔적이다. "픽셀값이 0보다 크면 흰 모듈" 한 줄로 29x29 격자를 되살리고, 재인코딩본과 841칸 전수 대조까지 했다.
#dreamhack#ctf#misc+5
2026-08-22#dreamhack +3