Step 218. crackme 10개 (난이도 중) — 관계식을 읽고, 문을 하나씩 연다

Step 218. crackme 10개 (난이도 중) — 관계식을 읽고, 문을 하나씩 연다

Level 3 — Reversing 트랙 | 난이도 ★★★★☆ | 예상 소요 시간 8시간

전제: Step 217(crackme 난이도 하)을 마쳤다. 세 유형 간파가 자동으로 나오고, gdb로 비교 순간을 포착할 수 있다.

⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. 키젠 제작은 합법 crackme에 한정된 분석 훈련입니다.

  • 준비물: WSL 우분투 (실측: Ubuntu 24.04, gcc 13.3.0, gdb 15.1), 파이썬 3 (역산기·키젠 작성용). 외부 플랫폼 문제 수집은 화면 예시로 안내합니다.
  • 주의: 난이도 중부터는 "한 번에 정답"이 안 나옵니다. 못 푸는 문제가 나오는 것이 정상이며, 규칙(뒤에서 정합니다)에 따라 풀이를 참고하는 것도 이번 챕터의 학습법입니다.

어제의 난이도 하가 "정답이 어디 숨었나"였다면, 오늘의 난이도 중은 "검증의 구조를 읽는가"입니다. 이름과 시리얼의 관계식을 풀어 키젠을 만들거나, 세 겹의 검증문을 하나씩 열거나, 실행할 때마다 바뀌는 검증 앞에서 전략을 바꾸거나. 정적 분석만으로 안 되면 동적 분석과 섞는 판단력 — 그것이 오늘의 핵심 기술입니다.


1. 학습 목표

이 챕터를 끝내면 다음을 할 수 있습니다:

  • 난이도 중의 세 특징(관계식 검증, 다단계 체크, 실행 의존 검증)을 구분한다
  • 디스어셈블리에서 관계식을 읽어 파이썬 키젠으로 옮긴다
  • 컴파일러 최적화(shl+sub = 곱셈)를 원래 연산으로 복원해 읽는다
  • 다단계 검증을 체크포인트별 관찰 전술로 분해한다
  • 정적/동적 중 어느 쪽으로 풀지 전략을 먼저 세우는 습관을 가진다

2. 배경 지식 — 오늘의 도구와 개념

오늘의 도구 한눈에 보기

구분 내용
언어·환경 C(문제 제작용) + Python(키젠·솔버) + WSL 우분투 bash (실측: gcc 13.3.0, gdb 15.1)
오늘의 명령어 objdump -dshl/sub/imul/xor 읽기, gdb 체크포인트 BP, 파이썬 itertools.product(브루트포스)
필요한 개념 해시식 관계(이름→시리얼), 키젠, 다단계 검증, 컴파일러의 곱셈 최적화, 대칭 변환
오늘의 산출물 키젠 1개 + 다단계 솔버 1개 + 10개 전략 노트

2-1. 난이도 중의 세 특징

난이도 하와의 차이는 "정답이 파일에 있느냐"입니다. 중급 문제의 정답은 대부분 파일에 없습니다 — 계산되기 때문입니다.

  1. 관계식 검증(키젠형) — 정답이 고정돼 있지 않고, 이름(또는 아이디)과 시리얼의 관계로 정해집니다. 시리얼 == f(이름)을 만족하는 쌍을 만드는 것이 목표라, 정답 하나를 찾는 게 아니라 f를 복원해야 합니다. 이 f를 재구현한 프로그램이 키젠입니다.
  2. 다단계 체크 — 검증이 문 여러 개로 나뉘어 있습니다. 각 문을 지나야 다음이 보이는 구조라, "한 번의 포착"이 아니라 "문마다 관찰"이 필요합니다.
  3. 실행 의존 검증 — 시간, PID, 랜덤값처럼 실행할 때마다 바뀌는 재료를 씁니다. 정답 문자열이 존재하지 않을 수도 있어, 동적 분석이 필수가 됩니다.

2-2. 컴파일러는 곱셈을 싫어한다

중급 분석의 첫 벽은 어셈블리가 소스와 다르게 보이는 것입니다. 대표적으로 곱셈 — CPU에서 곱셈은 느린 명령이라, 컴파일러는 상수 곱셈을 시프트와 뺄셈으로 바꿉니다:

s * 31  →  (s << 5) - s        # shl $0x5 후 sub
s * 33  →  (s << 5) + s

오늘 실측에서 정확히 이것을 만납니다. shl $0x5 다음 sub가 나오면 "×31"로 읽는 연습 — 이 치환표가 중급 디스어셈블리 읽기의 시작입니다.

2-3. 키젠의 사고법 — 검증 함수를 통째로 이식한다

관계식 문제의 정석 풀이는 검증 함수의 파이썬 이식입니다. 디스어셈블리에서 계산 순서를 읽어 그대로 옮기고, 이름을 넣어 시리얼을 계산합니다. 포인트는 "이해하고 다시 짜는" 것이 아니라 "그대로 옮기는" 것 — 이식이 정확하면 결과는 자동으로 맞습니다. 정수 오버플로우(32비트 마스크) 같은 기계어의 디테일까지 옮겨야 하는 이유가 여기 있습니다.

2-4. 다단계 검증의 분해 — 문마다 멈춘다

세 겹의 검증을 한 번에 이해하려 하지 마세요. 각 검사 지점(체크포인트)에 BP를 걸고, 문을 하나씩 관찰합니다. "1단계가 길이 검사구나 — 그럼 길이를 맞춘 입력으로 2단계를 본다" 식의 점진 공략입니다. Step 216의 조건부 BP가 여기서 빛납니다 — 수백 번 도는 루프에서 원하는 회차에만 멈출 수 있으니까요.

2-5. "같은 입력 두 번" 실험

검증이 산수인지 암호학인지 헷갈릴 때의 첫 실험입니다. 같은 입력을 두 번 넣어 결과(내부 값, 실패 지점)가 같으면 결정적 변환 — 역산 가능성이 높습니다. 실행마다 다르면 시간·랜덤 의존 — 동적 분석으로 그 재료가 만들어지는 자리를 봐야 합니다. 판단이 서야 도구를 고를 수 있습니다.

2-6. 풀이 참고의 룰

난이도 중부터는 못 푸는 문제가 생깁니다. 오늘의 규칙: 10개 중 2개까지 풀이(write-up) 참고를 허용합니다. 단, 조건이 있습니다 — 읽고 끝이 아니라, ① 내 환경에서 재현하고, ② "내가 놓친 단서"를 한 줄로 기록합니다. 놓친 단서의 목록이 곧 실력의 지도입니다.


3. 따라 하기

3-1. 훈련장 — 중급 표본 2종

관계식과 다단계, 두 특징의 교과서 표본을 만듭니다. 소스를 알고 만든 뒤, 덮어 두고 분석가가 되는 것은 어제와 같습니다.

입력 (m1_keygen.c — 관계식 검증)

#include <stdio.h>
#include <string.h>

/* 이름으로부터 시리얼을 계산하는 검증기 — 이름과 시리얼의 "관계식"을 풀어야 한다 */
unsigned int calc_serial(const char *name) {
    unsigned int s = 0x1357;
    int i;
    for (i = 0; name[i]; i++)
        s = s * 31 + (unsigned char)name[i];
    return s ^ 0x2468ace;
}

int main(void) {
    char name[64];
    unsigned long serial;
    printf("name: ");
    scanf("%63s", name);
    printf("serial: ");
    scanf("%lu", &serial);
    if (serial == (unsigned long)calc_serial(name))
        puts("correct! FLAG{m1_r3l4t10n_br0k3n}");
    else
        puts("wrong.");
    return 0;
}

입력 (m2_stages.c — 다단계 체크)

#include <stdio.h>
#include <string.h>

int main(void) {
    char pw[64];
    int i, s = 0;
    printf("key: ");
    scanf("%63s", pw);
    if (strlen(pw) != 8) goto fail;                /* 1단계: 길이 */
    for (i = 0; i < 8; i++)
        s += (pw[i] ^ 0x20) + i;                   /* 2단계: 변환 합 */
    if (s != 462) goto fail;
    if (pw[0] != 0x6b || pw[7] != 0x79) goto fail; /* 3단계: 앵커 k??????y */
    puts("correct! FLAG{m2_thr33_g4t3s}");
    return 0;
fail:
    puts("wrong.");
    return 0;
}
cd ~/lab214_218
gcc -O1 -o m1_keygen m1_keygen.c
gcc -O1 -o m2_stages m2_stages.c
printf "test\n123\n" | ./m1_keygen
echo abcd | ./m2_stages
name: serial: wrong.
key: wrong.

(2026-09-09 실측. 둘 다 오답에 대해 조용히 거부합니다 — 여기서부터가 분석입니다.)

3-2. m1 분석 — 관계식을 디스어셈블리에서 읽기

검증 함수를 엽니다:

objdump -d m1_keygen | sed -n "/<calc_serial>:/,/ret/p"
00000000000011a9 <calc_serial>:
    11ad:	0f b6 17             	movzbl (%rdi),%edx          ; 글자 하나 읽기
    11b0:	84 d2                	test   %dl,%dl
    11b2:	74 28                	je     11dc                 ; 널이면 종료
    11b8:	b8 57 13 00 00       	mov    $0x1357,%eax         ; 초기값 0x1357
    11bd:	89 c1                	mov    %eax,%ecx
    11bf:	c1 e1 05             	shl    $0x5,%ecx            ; ecx = s << 5
    11c2:	29 c1                	sub    %eax,%ecx            ; ecx = (s<<5) - s = s*31
    11c4:	0f b6 d2             	movzbl %dl,%edx
    11c7:	8d 04 0a             	lea    (%rdx,%rcx,1),%eax   ; s = 글자 + s*31
    11d2:	84 d2                	test   %dl,%dl
    11d4:	75 e7                	jne    11bd                 ; 루프
    11d6:	35 ce 8a 46 02       	xor    $0x2468ace,%eax      ; 마지막에 XOR
    11db:	c3                   	ret

(2026-09-09 실측. 주석은 제가 단 것이며, 일부 행을 생략했습니다.)

출력 읽는 법: 여기가 오늘의 첫 벽입니다 — 소스의 s * 31어디에도 없습니다. 대신 shl $0x5(32배) 뒤 sub(한 번 빼기). 32s − s = 31s — 컴파일러의 곱셈 최적화입니다 (2-2). 이것을 읽어 내면 관계식이 복원됩니다:

s = 0x1357 시작 → 각 글자마다 s = s*31 + 글자 → 마지막에 s XOR 0x2468ace

main 쪽도 확인하면 call calc_serialcmp 0x8(%rsp),%rax + je — 계산 결과와 내 시리얼을 비교하는 구조가 보입니다 (2026-09-09 실측).

3-3. m1 풀이 — 파이썬 키젠

관계식을 그대로 이식합니다:

입력 (keygen_m1.py)

import sys
name = sys.argv[1]
s = 0x1357
for ch in name.encode():
    s = (s * 31 + ch) & 0xFFFFFFFF   # 32비트 unsigned 흉내 — 오버플로우까지 이식
print(s ^ 0x2468ace)
python3 keygen_m1.py daimon
3264632025

(2026-09-09 실측.)

읽는 법: & 0xFFFFFFFF가 핵심 디테일입니다. C의 unsigned int는 32비트에서 넘침이 버려지는데, 파이썬 정수는 무한히 커지므로 마스크를 씌워 같은 기계처럼 만들어야 합니다. 이 한 줄을 빼면 긴 이름에서 키젠이 틀어집니다.

검증:

printf "daimon\n3264632025\n" | ./m1_keygen
name: serial: correct! FLAG{m1_r3l4t10n_br0k3n}

(2026-09-09 실측.)

정리: 정답을 "찾은" 것이 아니라 관계식을 "가진" 것입니다. 이 키젠은 어떤 이름에도 유효한 시리얼을 냅니다 — crackme 풀이의 상급 형태가 이것입니다.

3-4. m2 분석 — 문 세 개의 지도

objdump -d m2_stages | sed -n "/<main>:/,/ret/p" | grep -E "cmp|call|jne|xor"
    120d:	call   10d0 <strlen@plt>
    1212:	cmp    $0x8,%rax              ; 1단계: 길이 == 8
    1216:	jne    124c <fail>
    1226:	xor    $0x20,%eax             ; 2단계 루프: (글자 ^ 0x20) + 인덱스 누적
    1231:	cmp    $0x8,%rdx
    1235:	jne    1222 <루프>
    1237:	cmp    $0x1ce,%ecx            ; 2단계: 합 == 0x1ce (=462)
    123d:	jne    124c <fail>
    123f:	cmpb   $0x6b,(%rsp)           ; 3단계: 첫 글자 == 'k'
    1243:	jne    124c <fail>
    1245:	cmpb   $0x79,0x7(%rsp)        ; 3단계: 끝 글자 == 'y'

(2026-09-09 실측. 주석은 제가 단 것입니다.)

출력 읽는 법: jne가 셋 — 실패로 가는 문이 세 개입니다. 각 문의 조건을 나열하면:

  1. 길이 정확히 8
  2. 각 글자를 0x20과 XOR하고 인덱스를 더한 총합이 462(0x1ce)
  3. 첫 글자 ‘k'(0x6b), 마지막 글자 ‘y'(0x79)

정답은 하나가 아닙니다 — 조건을 만족하는 아무 키나 됩니다. 이런 문제는 역산이 아니라 조건 충족 탐색입니다.

3-5. m2 풀이 — 조건 솔버

세 조건을 수식으로 옮겨 파이썬에 탐색시킵니다:

입력 (solve_m2.py)

from itertools import product

# 디스어셈블리에서 읽은 조건:
# 1단계 len==8 / 2단계 sum((pw[i]^0x20)+i)==462 / 3단계 pw[0]=='k', pw[7]=='y'
target = 462
fixed = (ord("k") ^ 0x20) + 0 + (ord("y") ^ 0x20) + 7
need = target - fixed          # i=1..6이 채워야 할 합
print("중간 6글자가 채워야 할 합:", need)

found = None
for mid in product("abcde3fg", repeat=6):
    pw = "k" + "".join(mid) + "y"
    s = sum((ord(c) ^ 0x20) + i for i, c in enumerate(pw))
    if s == target:
        found = pw
        break
print("찾은 키:", found)
중간 6글자가 채워야 할 합: 291
찾은 키: k333gggy

(2026-09-09 실측.)

echo k333gggy | ./m2_stages
key: correct! FLAG{m2_thr33_g4t3s}

(2026-09-09 실측.)

정리: 문 세 개를 한꺼번에 풀려 했다면 어려웠을 것입니다. 문별로 조건을 적고(3-4), 고정된 문(1, 3단계)부터 채우고, 남은 자유도(중간 6글자)를 탐색 — 이 분해가 다단계 검증의 정석입니다.

3-6. 전략 노트 — 정적/동적 선택 기준

오늘 두 문제를 결산하면 도구 선택의 기준이 보입니다:

상황 선택 이유
관계식이 디스어셈블리에서 완전히 읽힘 정적 → 파이썬 이식 한 번 읽으면 영구적 키젠
조건이 여러 문으로 나뉨 정적으로 지도 → 솔버 탐색 조건 나열이 반은 푼 것
실행마다 재료가 바뀜 동적 필수 파일에 정답이 아예 없음
변환이 복잡해 읽기 어려움 동적 포착으로 입력→출력 표 확보 블랙박스라도 입출력은 관찰됨

본 훈련(외부 문제 10개)에서는 각 문제를 열기 전에 이 표의 어느 행인지 먼저 적고 시작하세요. 전략을 적는 버릇이 헤매는 시간을 줄입니다.


4. 미션과 연습문제

미션 — 난이도 중 10개 + 전략 노트

  1. 난이도 3 안팎의 crackme 10개를 고릅니다 (crackmes.one 외에 다른 플랫폼도 섞어 보세요. 외부 접속이 안 되면 m1/m2를 변형한 자작으로 대체 — 초기값·XOR 상수·문 개수를 바꾸면 됩니다)
  2. 각 문제를 시작하기 전에 한 줄 적습니다: "이 문제는 ___형이라 ___로 푼다"
  3. 관계식형은 키젠(파이썬) 작성까지, 다단계형은 문별 조건 나열까지를 목표로 합니다
  4. 8개 이상 해결을 목표로 합니다. 못 푼 문제(최대 2개)는 풀이 참고 후 재현하고, "내가 놓친 단서"를 기록합니다
  5. 끝나면 전략 노트를 결산합니다: 전에 적은 예측과 실제 풀이가 어긋난 문제가 있었는가

연습문제

문제 1. m1의 디스어셈블리에서 shl $0x5sub가 나왔습니다. 이것이 왜 ×31이며, ×33이라면 어떤 모습이었을까요?

문제 2. 키젠의 & 0xFFFFFFFF가 왜 필요한가요? 빼면 어떤 경우에 틀어지나요?

문제 3. m2처럼 "정답이 여러 개인 검증"과 m1처럼 "정답이 관계식인 검증"의 차이를 설명하고, 각각에 맞는 풀이 형태(솔버/키젠)를 연결하세요.

문제 4. 어떤 crackme의 검증 결과가 실행할 때마다 달라진다면 어떤 재료를 의심해야 하고, 왜 정적 분석만으로 부족한가요?


5. 모범 답안과 완료 기준

미션 모범 답안

전략 노트의 예:

#01 serial_me     | 예측: 관계식 → 실제: 관계식 ✓ | 키젠 작성, 25분
#02 triple_gate   | 예측: 다단계 → 실제: 다단계 ✓   | 문별 조건 나열 후 솔버, 31분
#03 time_bomb     | 예측: 다단계 → 실제: 실행 의존 ✗ | 시간 기반 — gdb로 재료 생성 지점 관찰, 40분
#04 hashy         | 예측: 관계식 → 실제: 관계식 ✓   | shl+sub 치환 읽기에 10분 소모, 35분
...
결산: 10개 중 8개 해결, 2개 풀이 참고
놓친 단서 1: #06은 검증 함수가 두 개였는데 하나만 읽음 — "call을 전부 세는" 습관 필요
놓친 단서 2: #09는 shl $0x5 + add였는데 sub로 읽음 — 부호 확인 습관 필요

검증하는 법: ① 8개 이상의 성공 근거(플래그 또는 correct 출력)가 있는가. ② 풀이를 참고한 문제에 재현과 "놓친 단서"가 있는가. ③ 키젠형에서 임의의 새 이름으로 시리얼을 만들어 통과했는가 — 하나만 맞춘 것은 키젠이 아니라 우연이므로, 다른 이름으로 한 번 더 검증하는 것이 기준입니다.

연습문제 해답

문제 1 해답. shl $0x5는 2⁵ = 32배이고, 뒤의 sub %eax는 원래 값을 한 번 빼므로 32s − s = 31s가 됩니다. 같은 이치로 ×33은 32s + s이므로 shl $0x5add가 나왔을 것입니다. 시프트량과 덧뺄셈의 조합으로 상수 곱셈을 읽는 것이 핵심이며, 뺄셈과 덧셈을 착각하면 관계식 전체가 틀어집니다 — 오늘 실측의 11bf·11c2 두 줄이 바로 그 지점입니다.

문제 2 해답. C의 unsigned int는 32비트라 곱셈·덧셈의 결과가 넘치면 상위 비트가 버려지지만, 파이썬 정수는 무한히 커지기 때문입니다. 짧은 이름에서는 넘침이 없어 우연히 맞지만, 이름이 길어지면 s가 2³²를 넘는 시점부터 파이썬의 값과 실제 바이너리의 값이 갈라집니다. 마스크는 "기계의 넘침"을 흉내 내는 장치 — 이식은 연산뿐 아니라 넘침까지 옮겨야 정확합니다.

문제 3 해답. m2는 조건(길이·합·앵커)을 만족하면 되는 제약 충족 문제라 정답이 무수히 많고, 그중 아무것이나 찾는 솔버(탐색기)가 맞습니다. m1은 이름마다 정답이 달라지는 함수 관계라, 특정 정답이 아니라 함수 자체를 복원한 키젠이 필요합니다. 구분법: 검증에 외부 입력(이름)이 계산 재료로 들어가는가 — 들어가면 키젠형, 아니면 조건 충족형일 가능성이 높습니다.

문제 4 해답. 시간(time), 프로세스 ID, 랜덤(/dev/urandom) 같은 실행 시점 재료를 의심합니다. 이런 검증은 정답이 파일 어디에도 존재하지 않고 실행마다 새로 계산되므로, 파일을 아무리 읽어도 정답은 없습니다. 필요한 것은 동적 분석 — 재료가 만들어지는 호출(time 등)에 BP를 걸어 그 실행의 재료 값을 읽고, 같은 실행 안에서 검증을 통과하거나 검증을 우회하는 쪽으로 전략을 바꿔야 합니다.

완료 기준 체크리스트

  • [ ] 난이도 중의 세 특징(관계식/다단계/실행 의존)을 구분해 말할 수 있다
  • [ ] shl+sub 조합을 곱셈으로 복원해 읽을 수 있다
  • [ ] m1의 관계식을 파이썬 키젠으로 이식하고 다른 이름으로도 검증했다
  • [ ] m2의 문 세 개를 조건으로 나열하고 솔버로 키를 찾았다
  • [ ] 문제를 열기 전에 "유형 예측과 도구 선택"을 적는 습관을 들였다
  • [ ] 10개 중 8개 이상 해결(또는 풀이 참고 후 재현)했다
  • [ ] 못 푼 문제의 "놓친 단서"를 기록했다

6. 흔한 실수와 해결

벽 1. 키젠을 만들었는데 어떤 이름에서는 틀린다

증상: 짧은 이름은 통과하는데 긴 이름의 시리얼이 거부됩니다.
원인: 32비트 오버플로우 마스크 누락 — 파이썬 정수가 2³²를 넘어 계속 커진 것입니다 (문제 2 참조).
해결: 루프마다 & 0xFFFFFFFF를 씌우세요. 그래도 틀리면 부호 확장(movzbl = 0 확장, movsbl = 부호 확장)을 디스어셈블리에서 다시 확인하세요 — 문자의 상위 비트 처리가 다르면 결과가 갈라집니다.

벽 2. shl+sub를 ×31이 아니라 다른 수로 읽었다

증상: 키젠이 전부 오답을 냅니다.
원인: 시프트량 착각(shl $0x5 = ×32 기준) 또는 add/sub 혼동.
해결: 작은 입력으로 대조 실험하세요 — 한 글자 이름 "A"(0x41)의 기대값은 ((0x1357 * 곱수) + 0x41) ^ XOR상수. 곱수를 31/33 등으로 바꿔 가며 gdb로 읽은 실제 중간값과 비교하면 어느 쪽인지 즉시 확정됩니다. 추측이 아니라 한 글자 실험이 기준입니다.

벽 3. 다단계 검증에서 첫 문도 못 넘겨 두 번째 문이 안 보인다

증상: 1단계(길이)에서 계속 실패해 2단계 조건을 관찰할 기회가 없습니다.
원인: 순차 검증 구조의 본질입니다 — 앞 문을 통과해야 뒤 문이 실행됩니다.
해결: 두 가지 길이 있습니다. 정적으로는 디스어셈블리가 문을 가리지 않으니(3-4처럼) 전부 읽으면 됩니다. 동적으로는 1단계를 통과하는 임시 입력(아무 8글자)을 만들어 2단계에 진입하세요. 문을 여는 것과 조건을 아는 것은 별개의 일입니다.

벽 4. 브루트포스 솔버가 영원히 안 끝난다

증상: product의 탐색 공간이 너무 큽니다 (예: 26글자⁶).
원인: 자유도 전체를 탐색하고 있습니다.
해결: 조건으로 공간을 줄이는 것이 솔버의 기술입니다. m2에서는 3단계(앵커)가 2글자를 고정하고 2단계(합)가 나머지의 합을 고정하므로, 실제 자유도는 훨씬 작습니다. 문자 집합을 추리고("abcde3fg"), 조건을 먼저 대수로 정리한 뒤 탐색하세요 — 수식이 먼저, 브루트포스는 나중입니다.

벽 5. 풀이를 읽었는데 "왜 그렇게 생각했지"가 안 남는다

증상: 참고한 풀이대로 재현은 됐는데 다음 문제에 응용이 안 됩니다.
원인: 재현만 하고 "놓친 단서"를 기록하지 않은 것입니다. 재현은 손의 학습이고, 단서 기록은 눈의 학습입니다.
해결: 풀이를 덮고 이렇게 자문하세요 — "이 풀이의 첫 단서는 파일의 어디에 있었나? 내 첫 30분은 어디를 보고 있었나?" 그 차이 한 줄이 2-6의 규칙이 노리는 것입니다.


7. 정리

오늘의 개념

개념 한 줄 설명
관계식 검증(키젠형) 정답 = f(이름) — f를 복원한 프로그램이 키젠
다단계 체크 문별 조건 나열 → 고정된 문부터 채우기 → 나머지 탐색
곱셈 최적화 shl+sub = ×31, shl+add = ×33 — 컴파일러의 상수 곱셈 치환
32비트 마스크 & 0xFFFFFFFF — 기계의 넘침을 파이썬에 이식하는 장치
실행 의존 검증 시간·랜덤 재료 — 파일에 정답 없음, 동적 분석 필수
전략 노트 열기 전에 "유형 예측 + 도구 선택"을 적는 훈련

오늘의 명령어

명령 하는 일
objdump -d | grep -E "shl|sub|imul|xor" 관계식의 재료(시프트·연산·상수) 수집
python3 키젠.py 이름 관계식 이식물로 시리얼 계산
itertools.product (파이썬) 조건 충족 탐색 — 문자 집합은 추려서
gdb 체크포인트 BP + 조건식 다단계 검증의 각 문에서 관찰

명령어보다 중요한 감각

난이도 중에서 실력은 "읽기"가 아니라 번역에 있습니다. 어셈블리를 수식으로, 수식을 파이썬으로 — 두 번의 번역이 정확하면 정답은 부산물입니다. 그리고 번역의 검증은 언제나 작은 실험(한 글자, 임시 8글자)입니다. 큰 문제를 안 풀리는 이유는 대부분 작은 실험을 건너뛰었기 때문입니다.

마지막으로, 못 푼 두 문제를 아끼세요. 오늘의 "놓친 단서" 목록은 이번 트랙 전체에서 가장 정보密度가 높은 문서입니다 — 그것은 문제집이 아니라, 여러분의 눈의 지도이니까요.


전부 체크되면 Step 218 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.