Step 226. ★ 중간 점검: 난이도 중 crackme 독립 해부 — 풀이 없이, 끝까지
Level 3 — CTF 실전과 공격 스킬 심화 | 난이도 ★★★★☆ | 예상 소요 시간 8~12시간(며칠에 나눠 진행)
전제: Step 215~225의 Reversing 마무리 구간 — Ghidra 정적 분석, x64dbg 동적 분석, crackme 누적 풀이(하·중), 안티디버깅 우회, 언패킹, 키젠, 난독화 대응 전략을 경험했다.
- 준비물: crackmes.one 계정, Ghidra, x64dbg, 기록용 문서, 타이머. 플랫폼 화면은 "화면 예시"로 표기하며 실제 목록은 직접 확인하세요.
- ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. crackmes.one은 풀라고 올라온 crackme를 합법적으로 연습하는 공개 플랫폼입니다.
- 성격 안내: 점검 챕터입니다 — 새 기법은 없고, 선정 기준·진행 절차·막힘 대응·산출물 규격이 전부입니다. 로컬 실측이 없으므로 화면과 출력은 예시로 표기합니다.
Reversing 트랙의 졸업 시험입니다. 규칙은 단순합니다 — 안 풀어 본 난이도 중 crackme 하나를 골라, 다른 사람의 풀이를 보지 않고, 전체 로직을 문서화할 수준까지 해부한다. 정답을 맞히는 것으로 끝이 아니라, 검증 알고리즘 전체를 의사 코드로 복원하고 키젠까지 완성하는 "완전 해부"가 목표입니다. 정적(Ghidra)으로 지도를 그리고, 동적(x64dbg)으로 값을 확인하는, 지금까지 배운 모든 것의 총동원입니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- 공정한 점검이 되도록 crackme를 선정한다(난이도·아키텍처·풀이 수 기준)
- 정적 → 동적 → 로직 복원의 4단계 절차와 시간 상한으로 독립 해부를 진행한다
- "검증 함수 하나로 좁히기" 기법으로 장시간 막힘을 돌파한다
- 전체 로직 문서 + 정답/키젠 + 도구·기법 정리의 3종 산출물을 완성한다
- 막혔을 때 중단 판정과 재훈련 주소를 스스로 정한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | Ghidra(정적), x64dbg(동적), 파이썬(키젠 작성), 기록용 문서 |
| 오늘의 절차 | 선정 → 정적 1차(지도) → 동적(값 확인) → 로직 복원 → 키젠 → 문서화 |
| 필요한 개념 | 독립 해부의 정의, 완전 해부 vs 정답 맞히기, 막힘의 세 유형, 시간 상한 |
| 오늘의 산출물 | ① 전체 로직 문서 ② 정답 도출 또는 키젠 ③ 사용 도구/기법 정리 |
2-1. "독립 해부"의 정의 — 무엇이 허용이고 무엇이 반칙인가
허용: 공식 문서, 어셈블리 매뉴얼, 이 책의 이전 챕터들, 도구 사용법 검색. "지식"을 찾는 것은 언제나 허용입니다.
반칙: 해당 crackme의 Write-up·풀이 영상·댓글의 힌트를 보는 것. "이 문제의 답"을 찾는 것은 한 번 보면 점검이 무효입니다. crackmes.one은 문제마다 풀이가 올라오니, 문제 페이지에서 "Solutions" 탭을 열지 않는 것이 규칙의 전부입니다.
왜 이렇게까지 하나요? 실전에는 풀이가 없기 때문입니다. 악성코드 샘플에 Write-up이 달려 있지 않듯이, "아무도 풀어주지 않는 바이너리와 마주하는 힘"을 재는 날입니다. 풀이를 본 문제는 아깝지만 버리고 새 문제를 고르세요 — 이 점검의 가치는 통과 여부가 아니라 측정의 정직함에 있습니다.
2-2. 선정 기준 — 공정한 시험 문제 고르기
crackmes.one에서 아래 조건으로 고릅니다 (화면 예시):
# 화면 예시 — crackmes.one 검색 필터의 전형적 구성
Platform: Windows (또는 자신이 연습한 플랫폼)
Quality: 3.0 이상 ← 조잡한 문제는 변수라서 제외
Difficulty: 2.0~3.0 ← '난이도 중'에 해당
선정 세 규칙:
- 안 풀어 본 것 — 당연하지만, 목록을 훑다 "본 적 있는 화면"이면 제외
- 풀이가 3개 이상 달린 것 — 막혀도 점검 후 검증할 풀이가 존재해야 합니다
- 언어·플랫폼이 익숙한 것 — 네이티브 Windows PE 또는 연습한 리눅스 ELF. 점검 날에 새 언어를 배우지 않습니다
필터를 적용하면 후보 목록이 이런 모양으로 나옵니다 (화면 예시 — 실제 목록은 수시로 바뀌니 직접 확인하세요):
# 화면 예시 — 필터 적용 후 문제 목록의 행 형태
이름 난이도 품질 플랫폼 풀이 수
some-crackme-01 2.1 3.2 Windows x64 7
serial-checker-x 2.6 4.0 Windows x64 5
keygenme-lite 2.3 3.5 Linux ELF 4
각 행에서 보는 것은 세 개입니다 — 난이도(범위 안인가), 풀이 수(3개 이상인가), 플랫폼(내 도구가 있는 곳인가). 셋을 통과한 것만 후보로 모아 랜덤 확정하면, 선정의 공정성은 확보됩니다.
2-3. 4단계 절차와 시간 상한
| 단계 | 하는 일 | 상한 |
|---|---|---|
| 1. 정적 1차 | Ghidra 로드 → 함수 목록, 문자열 목록, 검증 지점 특정 | 90분 |
| 2. 동적 | 검증 지점에 BP → 입력-기대값 관계 관찰, 레지스터/메모리 추적 | 2시간 |
| 3. 로직 복원 | 검증 알고리즘 전체를 의사 코드로 작성 | 90분 |
| 4. 완성 | 정답 도출 또는 키젠 작성 → 실행으로 검증 → 문서 정리 | 2시간 |
합계 약 6시간 + 여유. 하루에 몰아도, 나눠도 됩니다. 상한은 "멈추라"가 아니라 "전략을 바꾸라"는 신호입니다 — 2-4를 보세요. 한 가지 더: 각 단계를 시작할 때 시작 시각을 문서에 적는 습관을 들이세요. 시간을 재는 것 자체가 조급함을 줄여 줍니다 — "아직 상한이 한 시간 남았다"는 사실은 침착함의 근거가 됩니다.
2-4. 막힘의 세 유형과 돌파
4시간 이상 진전이 없으면, 기술 문제가 아니라 분석 단위 문제일 가능성이 큽니다.
- 유형 A — 바다를 읽는 중: 전체 코드를 다 이해하려 하고 있습니다. 돌파: "검증 함수 하나"로 좁히세요. 문자열("correct", "wrong")에서 역참조(xref)로 비교 지점을 찾고, 그 함수만 읽습니다. 바다는 함수 하나씩 육지가 됩니다.
- 유형 B — 정적으로 안 읽히는 중: 난독화(Step 224)나 패킹(Step 220)일 수 있습니다. 돌파: 읽기를 포기하고 동적으로 — 복호화 직후, 비교 직전에 BP를 걸어 값을 봅니다.
- 유형 C — 값은 보이는데 규칙이 안 보이는 중: 입력-출력 쌍이 부족합니다. 돌파: 입력을 한 글자씩 바꿔 가며 기대값 변화를 표로 기록하세요. 규칙은 데이터에서 나옵니다.
세 유형에 공통인 원리가 있습니다 — "같은 방법을 더 오래"는 선택지에 없습니다. 막힘은 언제나 "방법을 바꿔라"는 신호이지 "더 버텨라"는 신호가 아닙니다. 그리고 방법을 바꿔도 두 번 연속 막히면, 그 문제는 지금의 여러분보다 한 단계 위입니다 — 중단하고 어느 유형이었는지 기록한 뒤, 해당 Step으로 돌아가 보충하고 새 문제로 재도전하세요. 이것은 실패가 아니라 측정의 결과입니다.
3. 따라 하기 — 점검 운영 매뉴얼
오늘의 "따라 하기"는 기법이 아니라 절차 자체입니다. 순서대로 진행하며 각 단계의 산출물을 문서에 남기세요.
3-1. 선정과 서약
- crackmes.one에서 2-2의 기준으로 후보 3개를 고릅니다
- 그중 하나를 무작위로(주사위, 랜덤 숫자) 확정합니다 — 골라잡는 순간 난이도 편향이 생깁니다
- 기록 문서 첫 줄에 서약을 씁니다: "이 문서를 닫을 때까지 Solutions 탭을 열지 않는다. 막히면 2-4의 체크리스트를 실행한다."
- 타이머를 켭니다. 총 시간과 단계별 시간을 기록합니다
서약까지 쓰고 나면 브라우저 정리를 권합니다 — Solutions 탭이 열려 있거나 자동완성에 뜨는 환경에서는 "안 보겠다"는 의지만으로 부족합니다. 문제 파일만 내려받고 브라우저의 해당 탭은 닫으세요. 환경이 의지를 돕게 만드는 것이 이 단계의 실제 내용입니다.
3-2. 정적 1차 — 지도 그리기
Ghidra에 로드하고 세 가지만 챙깁니다 (출력 예시 — 문제마다 다릅니다):
# 출력 예시 — 정적 1차 정리 노트의 형태
함수 수: 23 (분석 완료)
문자열: "Enter serial:", "Correct!", "Nope.", "usage: ..."
검증 지점 후보: FUN_00401240 — "Nope."를 참조하는 유일한 함수
읽는 법: 문자열에서 "Correct!/Nope."를 찾고, 그것을 참조하는 함수를 xref로 찾으면 검증 지점이 잡힙니다. 이것이 지도의 전부입니다 — 나머지 함수는 지금 읽지 않습니다. 90분 안에 여기까지 오면 정상입니다.
3-3. 동적 — 값 확인
x64dbg로 검증 지점에 브레이크포인트를 걸고, 의도적으로 틀린 입력(AAAA...)을 넣습니다.
# 출력 예시 — 동적 관찰 노트의 형태
입력: AAAA0000
검증 함수 진입 시: EAX = 입력 포인터, ECX = 상수 0x1F35
비교 직전: 계산된 값 0x1F35 vs 내 입력에서 유도된 값 0x089A → 불일치 → "Nope."
관찰: 입력 4번째 글자를 바꾸면 0x089A가 변함 → 4번째 글자가 해시에 들어감
읽는 법: "입력을 바꿨을 때 무엇이 바뀌는가"가 알고리즘 역추적의 실타래입니다. 한 번에 다 이해하려 하지 말고, 입력-결과 쌍을 표로 쌓으세요 (2-4 유형 C).
동적 단계에서 자주 쓰는 동작을 미리 정리해 두면 시간이 절약됩니다 — 검증 함수 진입에 BP 걸기, 비교 명령(cmp, test) 직전까지 실행, 레지스터·스택 값을 노트에 옮기기, 입력을 바꿔 반복하기. Step 216에서 익힌 메모리 브레이크포인트는 "문자열이 어디서 변형되는가"를 쫓을 때 특히 강력합니다.
3-4. 로직 복원과 완성
관찰 데이터로 검증 알고리즘을 의사 코드로 씁니다. 의사 코드의 기준은 하나 — 이 문서만 보고 제3자가 키젠을 짤 수 있는가입니다. "입력을 변환해서 비교한다"는 요약이 아니라, 변환의 순서·상수·비교값까지 적어야 통과입니다.
그리고 역연산으로 정답을 만들거나, 임의 시리얼을 생성하는 키젠을 파이썬으로 작성합니다. 키젠은 "조건을 거꾸로 푸는 프로그램"입니다 — 검증이 sum == 728이라면, 키젠은 합이 728이 되는 문자열을 생성합니다. 마지막 검증은 언제나 원본 바이너리입니다 — 키젠이 만든 시리얼을 넣어 "Correct!"가 뜨는 것을 눈으로 확인해야 완료입니다. 의사 코드가 맞아도 원본 통과가 없으면 미완성입니다.
# 출력 예시 — 완성 판정 화면
C:\lab> crackme.exe
Enter serial: KIMI-7F35-Q2
Correct!
3-5. 점검 후 — 검증과 복기
완성했든 시간을 다 썼든, 이제 Solutions 탭을 열어 다른 풀이와 비교합니다. 내가 놓친 지름길, 내가 더 잘한 부분을 문서에 추가합니다. 이 비교가 점검의 절반입니다 — 혼자 푸는 것이 입력이라면, 비교가 정답 확인입니다.
비교에서 특히 볼 것은 세 가지입니다. ① 다른 풀이의 검증 지점 접근법 — 나는 문자열 xref로 갔는데, 누군가는 entry point 역추적으로 왔다면 그 경로도 익힙니다. ② 내가 고생한 지점을 남은 어떻게 넘었는가 — 그 차이가 다음 문제의 시간을 줄입니다. ③ 내 의사 코드가 정답이었는가 — 틀렸다면 틀린 부분이 곧 복습 주소입니다.
3-6. 기록표 — 점검이 끝난 뒤 남는 것
점검의 결과물은 "풀었다"는 기억이 아니라 문서입니다. 아래 형태의 기록표를 채우세요 (출력 예시):
# 출력 예시 — 독립 해부 기록표
문제: crackmes.one / (문제명) / 난이도 2.3 / Windows x64
선정 사유: 필터 조건 통과, 후보 3개 중 랜덤 확정
타임라인: 정적 1차 70분 → 동적 110분 → 로직 복원 80분 → 키젠+검증 60분 (총 5시간 20분)
막힘 기록: 2회 — ① 함수 전체를 읽기 시작함(유형 A, 25분 손실) → 검증 함수로 좁혀 돌파
② XOR 키 역산 실수 → 입력-출력 표로 재확인
사용 기법: 문자열 xref, 소프트웨어 BP, 입력 변화 관찰 표, 파이썬 키젠
결과: 완전 해부 성공 — 키젠 3회 연속 "Correct!" 통과
복기: 다른 풀이 2편 열람, 내가 놓친 OEP 단축 경로 1개 메모
읽는 법: "막힘 기록"이 이 표의 핵심입니다. 막힘을 기록해 두면 다음 점검에서 같은 함정을 피하는 속도가 빨라지고, 유형별 손실 시간이 줄어드는 것이 실력 향상의 가장 정직한 지표입니다. 총 소요 시간 자체보다 "막힘에서 회복하는 시간"을 보세요.
4. 미션과 연습문제
미션 — 독립 해부 3종 산출물
- 전체 로직 문서: 검증 알고리즘의 의사 코드 + 함수 지도 + 문자열 목록. "누가 읽어도 이 crackme의 검증 방식을 아는" 수준으로 씁니다
- 정답 또는 키젠: 파이썬 키젠 스크립트 + 원본 바이너리에서의 "Correct!" 검증 기록(어떤 입력을 넣어 어떤 출력을 봤는지)
- 도구/기법 정리: 이번에 실제로 쓴 기법 목록(예: xref 역참조, 하드웨어 BP, XOR 키 역산)과 각각 어느 단계에서 썼는지
연습문제
문제 1. 점검에서 Write-up 열람이 "반칙"인 이유를, 이 점검의 측정 대상이 무엇인지로 설명해 보세요.
문제 2. 선정 세 규칙 중 "풀이가 3개 이상"인 이유는 무엇인가요? 반칙 금지와 모순되지 않나요?
문제 3. 4시간 동안 진전이 없을 때의 첫 행동은 무엇인가요? 유형별로 어떻게 달라지나요?
문제 4. "정답 맞히기"와 "완전 해부"의 차이를 산출물 관점에서 설명해 보세요. 왜 이 점검은 후자를 요구하나요?
5. 모범 답안과 완료 기준
미션 모범 답안
산출물은 문제마다 다르므로 형식만 제시합니다.
# 전체 로직 문서 (형태 예시)
검증 흐름: 입력 → 길이 검사(8) → 문자별 변환(테이블 참조) → 합산 → 상수 비교
검증 함수: 0x00401240 (별칭 check_serial)
핵심 상수: 변환 테이블 0x00403000, 기대 합계 0x1F35
의사 코드: (여기에 5~15줄)
검증하는 법: ① 의사 코드만으로 제3자가 키젠을 짤 수 있는가. ② 키젠 출력이 원본 바이너리에서 통과했는가(검증 기록). ③ 도구/기법 목록이 이름 나열이 아니라 "어느 단계에 무엇을 위해"인가. 셋 다 예이면 완전 해부입니다.
연습문제 해답
문제 1 해답. 이 점검이 재는 것은 "문제를 풀었다"가 아니라 "풀이 없이 풀어내는 루틴이 서 있는가"입니다. Write-up을 보면 결과는 나오지만 측정 대상인 루틴은 사용되지 않으므로, 점검 자체가 무의미해집니다. 실전(미지의 악성코드, 신규 바이너리)에는 풀이가 없다는 것이 이 규칙의 근거입니다.
문제 2 해답. 모순이 아니라 시간 분리입니다. 점검 중에는 보지 않고(반칙), 점검이 끝난 뒤 비교·검증하는 데 씁니다(3-5). 풀이가 없는 문제를 골라 버리면 완성 후에도 내 해부가 맞는지 교차 검증할 길이 없습니다.
문제 3 해답. 첫 행동은 "분석 단위를 좁히는 것"입니다 — 전체를 읽고 있었다면(유형 A) 검증 함수 하나로, 정적으로 막혔다면(유형 B) 동적 관찰로, 규칙이 안 보이면(유형 C) 입력-출력 표 작성으로. 공통점은 "같은 방법을 더 오래"가 아니라 "방법을 바꾸는 것"입니다.
문제 4 해답. 정답 맞히기의 산출물은 시리얼 문자열 하나입니다 — 우연히 패치 한 방으로도 가능합니다. 완전 해부의 산출물은 의사 코드 + 키젠 + 기법 정리로, 알고리즘을 이해했다는 증거입니다. 실전 분석(악성코드, 취약점)은 이해가 요구사항이므로 점검도 이해를 요구합니다.
완료 기준 체크리스트
- [ ] 선정 세 규칙으로 안 풀어 본 난이도 중 crackme를 골랐다
- [ ] 점검 중 Solutions/Write-up을 보지 않았다
- [ ] 정적 1차(함수 지도 + 문자열 + 검증 지점)를 90분 상한으로 완료했다
- [ ] 동적 분석으로 입력-기대값 관계를 관찰·기록했다
- [ ] 검증 알고리즘 전체를 의사 코드로 복원했다
- [ ] 정답 또는 키젠을 만들어 원본 바이너리에서 통과를 눈으로 확인했다
- [ ] 3종 산출물(로직 문서, 키젠, 기법 정리)을 완성했다
- [ ] 점검 후 다른 풀이와 비교·복기했다
6. 흔한 실수와 해결
벽 1. "조금만" 힌트를 본다
증상: 댓글의 첫 줄만, 제목만 봤습니다.
원인: 막힘의 고통은 실재합니다. 하지만 한 줄의 힌트도 측정을 무효로 만듭니다 — "힌트를 받은 뒤의 진전"은 내 루틴의 실력이 아닙니다.
해결: 봤다면 그 문제는 기록에 "힌트 사용"으로 표기하고 다른 문제로 다시 시작하세요. 정직한 실패 기록이 거짓 성공보다 훨씬 가치 있습니다.
벽 2. 선정에 두 시간을 쓴다
증상: 목록을 내리며 "더 적당한 것"을 찾습니다.
원인: 시험 불안이 선정 회피로 나타나는 것입니다.
해결: 2-2 기준으로 후보 3개를 20분 안에 뽑고, 랜덤으로 확정하세요. 고르는 실력은 점검 대상이 아닙니다.
벽 3. 전체를 다 읽으려다 지친다
증상: Ghidra의 함수를 처음부터 순서대로 디컴파일해 읽고 있습니다.
원인: 유형 A의 막힘입니다. 23개 함수 중 검증에 관련된 것은 보통 1~3개입니다.
해결: 문자열 → xref → 검증 함수. 이 세 동작만 하세요. 나머지 함수는 "필요해지면" 읽습니다.
벽 4. 키젠은 됐는데 원본에서 통과하지 않는다
증상: 내 의사 코드로는 맞는데 원본 바이너리가 "Nope."를 뱉습니다.
원인: 관찰에서 놓친 조건이 있습니다 — 대표적으로 길이 검사, 대소문자 구분, 마지막 글자 체크섬.
해결: 복원한 의사 코드와 원본의 비교 지점을 다시 대조하세요. 특히 "내가 생략한 분기"를 찾습니다. 이 불일치가야말로 최고의 학습 재료입니다.
벽 5. 시간 상한을 "포기 신호"로 읽는다
증상: 상한에 걸리자마자 풀이를 찾습니다.
원인: 상한의 의미 오해입니다. 상한은 "멈춰라"가 아니라 "전략을 바꿔라"입니다.
해결: 2-4의 유형을 진단하고 방법을 바꿔 한 번 더 갑니다. 그래도 안 되면 그때 중단하고, 어느 유형에서 막혔는지를 기록해 재훈련 주소(해당 Step으로 복귀)를 정합니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| 독립 해부 | 풀이 없이 전체 로직까지 복원하는 것 — 지식 검색은 허용, 답 검색은 반칙 |
| 완전 해부 | 의사 코드 + 키젠 + 검증까지 — "이해"의 증거 3종 |
| 선정 세 규칙 | 안 풀어 본 것 / 풀이 3개 이상 / 익숙한 플랫폼 |
| 시간 상한 | 멈춤 신호가 아니라 전략 전환 신호 |
| 막힘 3유형 | 바다 읽기(범위) / 안 읽힘(난독화→동적) / 규칙 불명(데이터 부족) |
| 점검 후 비교 | 다른 풀이와의 대조 — 점검의 절반은 끝나고 일어난다 |
오늘의 절차
| 단계 | 산출물 |
|---|---|
| 선정 + 서약 | 후보 3개 → 랜덤 확정, 서약 문장 |
| 정적 1차 | 함수 지도, 문자열 목록, 검증 지점 |
| 동적 | 입력-기대값 관찰 표 |
| 로직 복원 | 의사 코드 |
| 완성 | 키젠 + 원본 통과 기록 |
| 복기 | 타인 풀이와의 비교 노트 |
명령어보다 중요한 감각
이 점검이 재는 것은 기억력이 아니라 루틴입니다 — 막혔을 때 다음에 무엇을 하는가. 답을 모르는 바이너리 앞에서 "문자열 → xref → 검증 함수 → BP → 관찰 표 → 의사 코드"가 몸에서 나오면, Reversing 트랙은 졸업입니다. 그리고 오늘의 이 루틴이 곧 악성코드 분석 현장의 그것과 같은 것이라는 것도 — 도구가 같고, 순서가 같고, "풀이가 없다"는 것까지 같습니다.
결과가 어땠든 이 점검은 다음 트랙의 출발점이기도 합니다. Reversing에서 기른 "구조를 읽고 값을 확인하는 눈"은 Crypto 트랙에서 "수식을 읽고 약한 수치를 찾는 눈"으로 이어집니다. 완전 해부를 해냈다면 자신감을 가지세요. 중단 판정을 했다면 그것 역시 결과입니다 — 기록표의 막힘 유형이 다음 두 주의 복습 계획입니다.
전부 체크되면 Step 226 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.