[Rev] 리버싱 · 작성: 2026-07-19 19:32:50 · 수정: 2026-07-21 23:26:47 · 조회 11
x86-64 어셈블리를 "명령어를 외우는 것"으로 접근하면 금방 지친다. 실제로 리버싱에서 필요한 건 몇 안 되는 패턴 — 함수 프롤로그/에필로그, 분기 조건, 메모리 접근 방식 — 을 눈에 익혀서 어셈블리 덩어리를 "덩어리(청크) 단위"로 읽어내는 능력이다.
리눅스 도구(objdump, gdb)는 기본이 AT&T 문법(mov %rax, %rbx, 소스가 왼쪽), Windows 도구·IDA·Ghidra는 Intel 문법(mov rbx, rax, 목적지가 왼쪽)을 쓴다. 같은 명령어가 정반대 순서로 보인다는 걸 모르면 완전히 헷갈린다 — gdb에서 set disassembly-flavor intel로 통일해두는 걸 권장한다.
; 프롤로그
push rbp
mov rbp, rsp
sub rsp, 0x20 ; 지역 변수용 스택 공간 확보
; ... 함수 본체 ...
; 에필로그
leave ; mov rsp, rbp; pop rbp 와 동일
ret ; 스택의 리턴 주소로 점프
이 패턴이 보이면 "새 함수의 시작/끝"이라는 걸 바로 알아챌 수 있다 — 스택 버퍼 오버플로우 글에서 다룬 리턴 주소 하이재킹이 정확히 이 ret 명령어를 겨냥한다는 걸 여기서 다시 확인할 수 있다.
mov(값 복사), lea(주소 계산 — 실제로 메모리를 읽지 않고 "이 주소가 뭔지"만 계산해서 레지스터에 넣는다는 점이 mov [addr]과 다르다)add, sub, xor(같은 레지스터끼리 xor하면 관용적으로 "0으로 초기화"라는 뜻), and, or, shl/shr(비트 시프트 — 곱셈/나눗셈을 컴파일러가 최적화한 결과인 경우가 많다)cmp a, b(내부적으로 a - b를 계산해 플래그 레지스터만 갱신, 결과는 버림) → 바로 뒤에 오는 je/jne/jg/jl 같은 조건부 점프가 그 플래그를 보고 분기를 결정한다. cmp와 jcc는 항상 짝을 이뤄서 읽어야 그 분기가 뭘 비교하는지 이해된다call addr(현재 RIP를 스택에 push하고 addr로 점프 — 이 push된 값이 나중에 ret이 읽는 "리턴 주소"다), RDI/RSI/RDX/RCX/R8/R9에 순서대로 담기는 인자cmp eax, 0x1337 ; 사용자 입력(eax)과 정답을 비교
jne fail ; 다르면 fail로 점프
; 여기 도달하면 "성공" 분기
크랙미나 라이선스 체크 로직을 리버싱할 때는 결국 이런 cmp+조건부 점프 패턴을 찾아서, 그 직전에 비교 대상이 어떻게 계산됐는지(입력값을 어떤 알고리즘으로 변환한 결과인지)를 거슬러 올라가는 작업이 핵심이다 — 크랙미 실습 방법론 글에서 이 접근을 실전 단계로 정리한다.
어셈블리를 "명령어 하나하나의 의미"가 아니라 "프롤로그/에필로그로 함수 경계를 찾고, cmp+jcc 짝으로 분기 로직을 찾는" 패턴 인식으로 접근하면, 처음 보는 바이너리도 훨씬 빠르게 구조가 잡힌다.