Step 178. CTF 맛보기 3: Reversing — crackme 2개 + Ghidra — 소스 없이 읽는 법

Step 178. CTF 맛보기 3: Reversing — crackme 2개 + Ghidra — 소스 없이 읽는 법

Level 3 — CTF 실전과 공격 스킬 심화 | 난이도 ★★★★☆ | 예상 소요 시간 6시간

전제: Step 177(Pwn 입문), Step 62~65(C와 메모리). WSL 우분투의 gcc와 gdb를 쓸 수 있습니다.

⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다. crackme 연습 바이너리는 풀라고 만들어진 합법 학습 자료입니다.

  • 준비물: WSL 우분투 (실측: Ubuntu 24.04, gcc 13.3.0, gdb 15.1). Ghidra는 별도 설치가 필요한 도구라 이 챕터에서는 화면 예시로 안내합니다.
  • 주의: 오늘 만드는 crackme는 여러분이 직접 컴파일한 연습용입니다. 리버싱 기법을 남의 소프트웨어에 함부로 적용하면 라이선스·법률 문제가 될 수 있습니다.

Reversing(리버싱)은 소스 코드 없이 실행 파일만 받아 "이게 뭘 하는 프로그램인지" 해부하는 종목입니다. 어제 Pwn이 "허점을 찾아 공격"이라면, 리버싱은 "동작을 이해하는 것" 자체가 목표입니다. 오늘의 연습 과제는 crackme — 비밀번호를 맞히면 "correct"를 출력하는 연습용 바이너리입니다. 소스를 보지 않고 비밀번호를 찾아내는 것이 게임의 규칙입니다.


1. 학습 목표

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

  • 정적 분석(실행하지 않고 읽기)과 동적 분석(실행하며 관찰)의 차이를 설명한다
  • strings로 바이너리 안의 문자열 단서를 찾는다
  • objdump의 디스어셈블리에서 수상한 연산(비교·XOR)을 찾는다
  • gdb 브레이크포인트로 "비교되는 두 문자열"을 실행 중에 관찰한다
  • 디컴파일러(Ghidra)가 무엇을 보여 주는 도구인지 안다

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

오늘의 도구 한눈에 보기

구분 내용
언어·환경 C(crackme 제작용) + WSL 우분투 bash (실측: gcc 13.3.0, gdb 15.1, x86-64)
오늘의 명령어 strings, objdump -d, gdb -batch -x 명령파일, gdb의 break / x/s / continue
필요한 개념 정적 vs 동적 분석, 디컴파일러, XOR 감춤, 함수 인자 레지스터(rdi·rsi)
오늘의 산출물 crackme 2개의 풀이 기록 + "무엇으로 찾았나" 정리

2-1. 정적 분석 vs 동적 분석

리버싱의 도구는 두 갈래입니다. 정적 분석(static analysis)은 프로그램을 실행하지 않고 파일 자체를 읽습니다 — 문자열 목록, 디스어셈블리, 디컴파일 결과. 동적 분석(dynamic analysis)은 실행해 가며 관찰합니다 — 디버거로 멈추고, 메모리와 레지스터를 들여다봅니다.

실전에서는 둘을 번갈아 씁니다. 정적으로 "수상한 자리"를 찾고, 동적으로 그 자리에서 실제 값을 확인하는 식입니다. 오늘 두 번째 crackme가 바로 그 순서를 요구합니다.

2-2. strings — 가장 싼 첫 도구

컴파일된 바이너리 안에는 문자열 상수(프롬프트, 에러 메시지, 그리고 때때로 비밀번호)가 그대로 들어 있습니다. strings는 바이너리에서 "사람이 읽을 수 있는 글자열"만 건져 줍니다. 리버싱의 첫 동작은 언제나 이것입니다 — 안 나오면 그때 무거운 도구를 꺼냅니다.

2-3. 디컴파일러와 Ghidra

디컴파일러(decompiler)는 기계어를 거슬러 의사 C 코드로 복원해 보여 주는 도구입니다. Ghidra는 NSA가 공개한 무료 디컴파일러로, CTF 리버싱의 표준 도구입니다. 바이너리를 가져오면(Import → 분석) CodeBrowser 창에서 함수 목록(Symbol Tree)과 의사 코드(Decompile 창)를 나란히 보여 줍니다.

이 환경에는 설치돼 있지 않으므로 화면은 예시로 안내하고(3-5), 같은 일을 하는 기본 도구(objdump, gdb)로 원리를 실측합니다. 어셈블리가 나오더라도 겁내지 마세요 — 오늘 필요한 것은 cmp(비교), call(호출), xor(배타적 OR) 세 글자뿐입니다 (Step 177 벽 5의 약속).

2-4. XOR 감춤 — 한 바이트짜리 자물쇠

비밀번호를 평문으로 저장하면 strings에 바로 걸립니다. 그래서 출제자는 비밀번호를 XOR로 감춥니다 — 각 바이트에 같은 값(키)을 XOR해 저장하고, 프로그램이 실행될 때 다시 XOR해 복원합니다. XOR는 같은 키로 두 번 하면 원래대로 돌아오는 성질(A ⊕ K ⊕ K = A)이 있어 "감추기와 복원"에 딱입니다 (Step 104의 Natas에서 만났던 바로 그 연산입니다).

strings로는 안 보이지만, 실행 중 복원된 순간은 보입니다 — 정적 분석의 한계를 동적 분석이 메우는 전형입니다.

2-5. 함수 인자는 레지스터로 간다

x86-64 리눅스에서 함수를 호출할 때 인자는 정해진 레지스터에 실립니다. 첫 번째 인자는 rdi, 두 번째는 rsi. strcmp(a, b)가 호출되는 순간 rdi에는 a의 주소, rsi에는 b의 주소가 있습니다. gdb의 x/s $rdi는 "rdi가 가리키는 곳을 문자열로(x/s) 보여 달라"는 뜻 — 이 두 줄이 오늘의 핵심 관찰 도구입니다.


3. 따라 하기

3-1. 실험실 — crackme 두 개 만들기

출제자가 되어 봅니다. 아는 구조를 만들고, 그다음 플레이어가 되어 부숩니다. ~/lab178 폴더를 만들어 진행하세요.

입력 (crackme1.c — 평문 비밀번호)

#include <stdio.h>
#include <string.h>
int main(void){
    char pw[64];
    printf("password: ");
    scanf("%63s", pw);
    if(strcmp(pw, "sup3r-s3cr3t-pw") == 0)
        puts("correct! FLAG{str1ngs_t0ld_m3_th3_pw}");
    else
        puts("wrong.");
    return 0;
}

입력 (crackme2.c — XOR로 감춘 비밀번호)

#include <stdio.h>
#include <string.h>
int main(void){
    char pw[64];
    /* "원래 비밀번호"를 0x5A로 XOR해 감춘 배열 — 평문은 어디에도 없다 */
    char secret[] = {0x37, 0x6b, 0x3e, 0x34, 0x6b, 0x3d, 0x32, 0x2e,
                     0x05, 0x28, 0x6e, 0x38, 0x38, 0x6b, 0x2e, 0x00};
    char decoded[64];
    int i;
    for(i = 0; secret[i]; i++) decoded[i] = secret[i] ^ 0x5A;
    decoded[i] = 0;
    printf("password: ");
    scanf("%63s", pw);
    if(strcmp(pw, decoded) == 0)
        puts("correct! FLAG{x0r_c4nnot_h1de_fr0m_gdb}");
    else
        puts("wrong.");
    return 0;
}

컴파일

mkdir -p ~/lab178 && cd ~/lab178
gcc -O1 -o crackme1 crackme1.c
gcc -O1 -o crackme2 crackme2.c

(2026-09-09 실측. scanf의 반환값 경고가 뜰 수 있지만 실습에는 지장이 없습니다.)

이제부터는 소스를 잊으세요. 여러분은 방금 받은 낯선 바이너리 두 개를 푸는 플레이어입니다.

3-2. crackme 1 풀이 — strings의 승리

첫 도구는 가장 싼 것, strings입니다:

strings ./crackme1
password: 
sup3r-s3cr3t-pw
wrong.
correct! FLAG{str1ngs_t0ld_m3_th3_pw}

(2026-09-09 실측. 실제로는 다른 시스템 문자열들 사이에 이 줄들이 섞여 나옵니다 — 수상한 것만 골라 적었습니다. strings ./crackme1 | grep -iE "pw|secret|flag"처럼 걸러 보세요.)

출력 읽는 법: password:는 프롬프트, wrong.correct!는 판정 메시지. 그리고 그 사이에 어색하게 끼어 있는 sup3r-s3cr3t-pw — 프롬프트도 메시지도 아닌 문자열은 "비교 대상"일 가능성이 높습니다. 확인해 봅니다:

echo sup3r-s3cr3t-pw | ./crackme1
password: correct! FLAG{str1ngs_t0ld_m3_th3_pw}

(2026-09-09 실측.)

정리: 소스도, 어셈블리도 필요 없었습니다. 비밀번호가 평문으로 박힌 바이너리는 strings 한 줄에 열립니다. 실제 crackme 입문 문제의 절반이 이 수준이고, 그래서 리버싱의 첫 동작은 언제나 strings입니다.

3-3. crackme 2 풀이 1단계 — strings가 침묵하다

같은 도구를 두 번째 바이너리에 들이댑니다:

strings ./crackme2
password: 
wrong.
correct! FLAG{x0r_c4nnot_h1de_fr0m_gdb}

(2026-09-09 실측. 비밀번호 후보가 보이지 않습니다 — strings ./crackme2 | grep -iE "m1dn1ght|r4bb1t"은 아무것도 반환하지 않음을 실측으로 확인했습니다.)

출력 읽는 법: 프롬프트와 판정 메시지는 있는데 비교 대상이 없습니다. 비밀번호가 파일 안에 평문으로 존재하지 않는다는 뜻입니다 — 감춰져 있습니다. 정적 분석의 첫 도구가 여기서 한계를 만났습니다.

그래도 포기하기 전에 한 가지 더, 디스어셈블리에서 "감춤의 방식"을 찾습니다:

objdump -d ./crackme2 | grep "0x5a"
    1219:	83 f2 5a             	xor    $0x5a,%edx

(2026-09-09 실측.)

출력 읽는 법: xor $0x5a — 뭔가에 0x5A를 XOR하고 있습니다. XOR는 "감추기와 복원"의 연산(2-4)입니다. 즉 이 프로그램은 실행 중에 무언가를 복원합니다. 복원된 값이 보고 싶다면, 복원이 끝난 순간에 실행을 멈추면 됩니다. 동적 분석의 출발입니다.

3-4. crackme 2 풀이 2단계 — gdb로 비교의 순간을 포착

복원된 비밀번호는 strcmp를 호출하는 바로 그 순간에 메모리에 있습니다. 그 순간에 멈춰 봅시다. 먼저 main 안에서 strcmp 호출 위치를 찾습니다:

gdb -batch -ex "disassemble main" ./crackme2 | grep strcmp
   0x000000000000126d <+164>:	call   0x10b0 <strcmp@plt>

(2026-09-09 실측.)

<+164> — main 시작점에서 164바이트 지점에 호출이 있습니다. 거기에 브레이크포인트를 겁니다. gdb 명령을 파일로 준비합니다 (cmds.gdb):

break *main+164
run < in.txt
x/s $rdi
x/s $rsi

$rdi/$rsi는 비교되는 두 문자열의 주소(2-5)입니다. 아무 입력 파일을 만들고 실행합니다:

echo AAAA > in.txt
gdb -batch -x cmds.gdb ./crackme2
Breakpoint 1 at 0x126d

Breakpoint 1, 0x000055555555526d in main ()
0x7fffffffe5e0:	"AAAA"
0x7fffffffe620:	"m1dn1ght_r4bb1t"

(2026-09-09 실측. 주소값은 실행마다 다릅니다.)

출력 읽는 법: 결정적 장면입니다. rdi에는 우리가 넣은 "AAAA", rsi에는 프로그램이 방금 복원한 "m1dn1ght_r4bb1t"가 나란히 서 있습니다. strings가 못 찾은 비밀번호를, 실행 중의 한순간을 얼려서 꺼냈습니다. 확인:

echo m1dn1ght_r4bb1t | ./crackme2
password: correct! FLAG{x0r_c4nnot_h1de_fr0m_gdb}

(2026-09-09 실측.)

: XOR 감춤은 "파일 안"에서는 안전하지만 "실행 중"에는 무방비입니다 — 비교하려면 어쨌든 복원해야 하니까요. 이것이 정적 분석과 동적 분석의 분업입니다. 파일이 말하지 않으면, 실행에게 묻는다.

참고: break strcmp처럼 함수 이름에 바로 거는 방법도 있습니다. 다만 프로그램 시작 준비 단계(동적 로더)에서도 strcmp가 여러 번 호출돼, 실측에서 연속 네 번이 로더의 호출이었습니다. continue로 몇 번 넘기거나, 오늘처럼 main 안의 호출 주소에 정확히 거는 방법이 빠릅니다.

3-5. 같은 일을 Ghidra로 — 화면 예시

이 환경에는 Ghidra가 없어 화면 예시로 안내합니다. 실제로 해볼 분은 ghidra-sre.org에서 받아 설치하세요 (Java가 필요합니다). 절차는 이렇습니다:

# 화면 예시 — Ghidra 사용 흐름
1. Ghidra 실행 → File > New Project → 프로젝트 생성
2. crackme1 파일을 드래그해 Import → 더블클릭 → 분석(Analyze) Yes
3. CodeBrowser 왼쪽 Symbol Tree > Functions > main 더블클릭
4. 오른쪽 Decompile 창에 의사 C 코드가 표시됨:

undefined8 main(void)
{
  char local_48 [64];
  printf("password: ");
  __isoc99_scanf("%63s", local_48);
  iVar1 = strcmp(local_48, "sup3r-s3cr3t-pw");
  if (iVar1 == 0) { puts("correct! ..."); }
  else { puts("wrong."); }
  return 0;
}

읽는 법: 어셈블리를 몰라도 흐름이 읽힙니다 — 입력을 받아 strcmp로 비교하고, 비교 대상 문자열이 디컴파일 결과에 그대로 보입니다. 변수명이 local_48, iVar1처럼 난해한 것은 정상입니다. 기계가 지은 이름을 읽으며 "아, 이건 비밀번호 버퍼구나" 추론하는 것이 리버싱의 일상입니다.

crackme2를 Ghidra에 넣으면 XOR 복원 루프가 의사 코드로 보이고, 키(0x5A)가 눈에 들어옵니다 — 그러면 3-3의 objdump 발견과 같은 결론에 디컴파일러로 도달하는 셈입니다. 도구가 달라도 사고는 같습니다: 비교 대상을 찾아라.

3-6. 풀이 기록 정리

문제 strings objdump gdb 열쇠
crackme1 비밀번호 평문 노출 (안 봄) (필요 없음) strings 한 줄
crackme2 침묵 (XOR 감춤) xor $0x5a 발견 strcmp 순간에 평문 관찰 정적→동적 연계

이 표의 마지막 칸이 리버싱의 문법입니다: 가벼운 도구부터, 안 되면 한 단계 무거운 도구로.


4. 미션과 연습문제

미션 — 나만의 crackme 만들고 친구(또는 미래의 나)에게 내주기

  1. crackme2를 변형한 crackme3.c를 만듭니다 — XOR 키를 0x5A가 아닌 다른 한 바이트(예: 0x37)로 바꾸고, 비밀번호도 바꿉니다 (감춘 배열은 파이썬으로 계산: bytes(b ^ 0x37 for b in pw.encode()))
  2. 컴파일한 뒤, 소스를 보지 않고 오늘의 세 도구 순서(strings → objdump → gdb)로 다시 풉니다
  3. 풀이 기록을 남깁니다: "어느 도구에서 무엇이 보였는가"

연습문제

문제 1. 정적 분석과 동적 분석의 차이를, 오늘 crackme2 풀이의 어느 장면이 보여 주는지 들어 설명하세요.

문제 2. crackme1의 비밀번호는 왜 strings에 그대로 걸렸나요? C 소스의 문자열 상수가 컴파일 후 어디에 가는지 관점에서 답하세요.

문제 3. crackme2에서 xor $0x5a,%edx 한 줄을 발견했을 때 "이 프로그램이 실행 중에 뭔가를 복원한다"고 추론할 수 있는 이유를 XOR의 성질로 설명하세요.

문제 4. gdb에서 x/s $rsi가 비밀번호를 보여 줬습니다. 왜 하필 rdirsi였나요?


5. 모범 답안과 완료 기준

미션 모범 답안

키 0x37, 비밀번호 blu3-m00n으로 만들었다면:

[제작] enc = bytes(b ^ 0x37 for b in "blu3-m00n".encode())
       → secret[] = {0x55, 0x5b, 0x42, 0x04, 0x5a, ...}

[풀이 1: strings] ./crackme3 → 비밀번호 안 보임 (감춤 성공 확인)
[풀이 2: objdump] grep "0x37" → xor $0x37 발견 → "XOR 감춤이구나"
[풀이 3: gdb] break *main+164 (디스어셈블리로 strcmp 위치 먼저 확인)
       → rsi에 "blu3-m00n" 관찰 → echo로 확인 → correct!

검증하는 법: ① 감춘 비밀번호가 strings에 안 걸리는가(걸리면 감춤 실패 — 배열에 평문이 섞인 것), ② objdump에서 새 키가 발견되는가, ③ gdb 없이 키와 배열만으로 수동 복원도 할 수 있는가(파이썬 한 줄: bytes(b ^ 0x37 for b in enc)) — 셋이 다 되면 "만드는 쪽과 부수는 쪽"을 둘 다 이해한 것입니다.

연습문제 해답

문제 1 해답. 정적 분석은 파일을 실행하지 않고 읽는 것이고, 동적 분석은 실행하며 관찰하는 것입니다. crackme2에서 strings와 objdump(정적)는 "감춰져 있다, XOR이다"까지는 알려 줬지만 비밀번호 자체는 몰랐습니다. gdb로 strcmp 호출 순간에 멈춰(동적) 복원된 평문 m1dn1ght_r4bb1t를 관찰해 풀었습니다. 파일이 침묵할 때 실행에게 묻는 것 — 이 연계가 리버싱의 기본 리듬입니다.

문제 2 해답. C 소스의 문자열 상수(리터럴)는 컴파일 후 바이너리의 데이터 영역에 평문 그대로 저장되기 때문입니다. strcmp(pw, "sup3r-s3cr3t-pw")의 비교 대상은 코드가 아니라 데이터라 기계어로 변환되지 않습니다. strings는 이 데이터 영역에서 읽히는 글자열을 건져 내므로, 평문 상수는 반드시 걸립니다.

문제 3 해답. XOR는 같은 키로 두 번 적용하면 원래 값으로 돌아오는 성질(A ⊕ K ⊕ K = A)이 있습니다. 그래서 "키로 XOR된 데이터 + 실행 중 다시 XOR하는 코드"의 조합은 곧 "감춰 둔 값을 실행 중에 복원한다"는 뜻입니다. 반대로 XOR가 아니라 단순 덧셈 루프나 상수 비교만 보였다면 다른 추론을 했을 것입니다 — 디스어셈블리에서 연산의 종류를 읽는 이유입니다.

문제 4 해답. x86-64 리눅스의 함수 호출 규약에서 인자는 정해진 레지스터로 전달되기 때문입니다 — 첫 번째 인자는 rdi, 두 번째는 rsi. strcmp(입력, 비밀번호)의 두 인자가 그 두 레지스터에 실리므로, 호출 직전에 멈춰 x/s로 읽으면 비교되는 두 문자열이 보입니다. 규약을 알면 "비교의 순간"이 어디에 실려 있는지 예측할 수 있습니다.

완료 기준 체크리스트

  • [ ] 정적 분석과 동적 분석의 차이를 한 문장씩 말할 수 있다
  • [ ] strings로 crackme1의 비밀번호를 찾아 풀었다
  • [ ] crackme2에서 strings가 침묵하는 것을 확인하고 이유를 안다
  • [ ] objdump -d에서 xor $0x5a를 찾아 "감춤"을 추론했다
  • [ ] gdb 브레이크포인트로 strcmp 순간의 두 문자열을 관찰해 crackme2를 풀었다
  • [ ] Ghidra의 화면 구성(Symbol Tree, Decompile 창)과 절차를 안다
  • [ ] 미션: crackme3를 만들고 세 도구 순서로 다시 풀었다

6. 흔한 실수와 해결

벽 1. strings 출력이 너무 많아 뭐가 단서인지 모르겠다

증상: 수백 줄이 나와서 눈이 둥둥 뜁니다.
원인: 시스템 라이브러리 문자열까지 다 섞여 나옵니다. 정상입니다.
해결: 걸러서 보세요 — strings ./crackme1 | grep -iE "pass|flag|secret|wrong|correct". 판정 메시지("wrong", "correct") 주변의 문자열이 비교 대상일 확률이 높습니다. 또 길이 필터 strings -n 6도 잡음을 줄여 줍니다.

벽 2. gdb에서 $rdi가 변수 확장으로 사라진다

증상: x/s $rdi를 치면 Argument required (starting display address). 오류가 납니다 — $rdi가 통째로 사라집니다. 이 챕터를 실측하는 동안 실제로 겪은 문제입니다 (쉘을 거쳐 gdb에 전달할 때 $가 쉘 변수 확장으로 먹힘).
원인: 쉘이 $rdi를 환경 변수로 해석해 빈 문자열로 바꿔 버립니다.
해결: 대화형 gdb 안에서 직접 치면 문제없고, 스크립트로 넘길 때는 명령 파일(-x cmds.gdb)을 쓰되 파일을 만들 때 $가 확장되지 않게 주의하세요 (작성자는 파이썬의 chr(36)로 우회했습니다). 같은 이유로 bash에서 echo $rdi도 빈 줄이 나옵니다.

벽 3. break strcmp에 걸었는데 이상한 문자열들만 나온다

증상: /lib64/ld-linux-x86-64.so.2, __vdso_clock_gettime 같은 문자열이 보입니다.
원인: 프로그램 시작 준비를 하는 동적 로더도 strcmp를 씁니다. main이 실행되기 전에 여러 번 호출됩니다 (2026-09-09 실측에서 연속 네 번).
해결: continue로 몇 번 넘기거나, 3-4처럼 disassemble main으로 main 안의 호출 위치를 찾아 break *main+오프셋에 정확히 거세요. 후자가 재현 가능한 방법입니다.

벽 4. 주소가 책과 다르다

증상: 책은 <+164>인데 내 환경은 다른 오프셋입니다.
원인: 컴파일러 버전과 옵션(-O1 여부)에 따라 기계어 배치가 달라집니다. 정상입니다.
해결: 오프셋을 외우지 말고 절차를 외우세요 — disassemble maincall ... strcmp 줄 찾기 → 그 줄의 오프셋에 break. 절차는 어떤 바이너리에서도 같습니다.

벽 5. Ghidra 분석이 끝나지 않는다 / 변수명이 이상하다

증상: Analyze가 오래 걸리고, 의사 코드에 iVar1, local_48 같은 이름만 보입니다.
원인: 둘 다 정상입니다. 분석은 파일 크기에 따라 수 분 걸릴 수 있고, 디컴파일러의 변수명은 기계가 임시로 지은 것입니다.
해결: 분석은 기다리면 됩니다. 변수명은 읽다 보면 역할이 보입니다 — 비교에 쓰이면 "비밀번호 버퍼", 반환값이면 "판정 결과". 추론하며 읽는 것 자체가 리버싱 실력입니다. 나중에는 L키로 이름을 바꿔 가며 읽게 됩니다.


7. 정리

오늘의 개념

개념 한 줄 설명
Reversing 소스 없이 실행 파일을 해부해 동작을 이해하는 종목
crackme 비밀번호를 맞히는 연습용 바이너리 — 리버싱의 입문 문제
정적 분석 실행하지 않고 파일을 읽기 (strings, objdump, 디컴파일)
동적 분석 실행하며 관찰하기 (gdb 브레이크포인트, 레지스터 관찰)
XOR 감춤 같은 키로 두 번 XOR하면 원래대로 — 감추기와 복원에 사용
디컴파일러(Ghidra) 기계어를 의사 C 코드로 복원해 보여 주는 도구
호출 규약 함수 인자는 rdi, rsi, … 순서의 레지스터로 전달된다

오늘의 명령어

명령 하는 일
strings ./바이너리 바이너리에서 읽을 수 있는 문자열 건지기 — 리버싱의 첫 동작
strings -n 6 ./바이너리 | grep -iE "패턴" 길이 필터 + 패턴으로 잡음 줄이기
objdump -d ./바이너리 | grep xor 디스어셈블리에서 수상한 연산 찾기
gdb -batch -ex "disassemble main" ./바이너리 main의 기계어 배치 보기 (호출 위치 찾기)
break *main+오프셋x/s $rdi / x/s $rsi 비교 순간에 멈춰 두 인자 문자열 관찰

명령어보다 중요한 감각

오늘의 두 crackme가 보여 준 것은 리버싱의 문법 전체입니다. 가벼운 도구부터 — strings가 안 되면 objdump, 파일이 침묵하면 실행에게 묻는다. 그리고 거꾸로, "감추고 싶은 것은 실행 중에는 반드시 드러난다"는 것도 봤습니다. 비교하려면 복원해야 하고, 복원된 순간은 관찰 가능하니까요.

이 감각은 crackme를 넘어 갑니다. 악성코드 분석도, 취약점 분석도, "이 프로그램이 진짜 하는 일이 뭔가"라는 모든 질문도 결국 같은 도구 상자에서 시작합니다. 웹, Pwn에 이어 리버싱까지 — 세 종목의 맛을 봤습니다. 어느 도구를 쥘 때 손이 편했는지, 그 기억이 Step 181에서 주력 분야를 고르는 재료가 됩니다.


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