3406 단어
17 분
Reversing 정적 분석부터 동적 분석까지

리버싱(Reverse Engineering)은 소스 코드가 없는 프로그램을 분석하여 내부 동작 방식과 구조를 파악하는 기술이다. 보안 연구, 악성코드 분석, CTF, 취약점 분석, 호환성 연구, 디버깅 등 다양한 분야에서 사용된다. 하지만 리버싱은 반드시 허가된 대상과 합법적인 목적 안에서 수행해야 한다. 따라서 필자는 이번 글에서 리버싱의 기본 개념부터 정적 분석, 동적 분석, 어셈블리 해석, 패치 원리, 분석자가 가져야 할 방어적 관점까지 설명하도록 하겠다.


1. 리버싱의 기본 개념#

리버싱의 핵심은 실행 파일을 관찰하여 다음 질문에 답하는 것이다.

  • 이 프로그램은 어떤 입력을 받는가?
  • 입력값은 어디에서 검증되는가?
  • 중요한 분기문은 어디에 존재하는가?
  • 어떤 라이브러리 함수나 시스템 호출을 사용하는가?
  • 메모리와 레지스터 값은 실행 중 어떻게 변하는가?

일반적인 리버싱 분석 흐름은 다음과 같다.

단계목적
파일 식별파일 형식, 아키텍처, 컴파일러 정보 확인
정적 분석실행하지 않고 문자열, 함수, 분기 구조 분석
동적 분석디버거로 실행 흐름과 메모리 변화 관찰
핵심 로직 파악검증 루틴, 암호화 루틴, 조건 분기 확인
문서화함수 이름, 구조체, 흐름도, 분석 결과 정리

리버싱은 단순히 어셈블리를 읽는 작업이 아니라 프로그램이 가진 전체 흐름을 복원하는 과정에 가깝다.

2. 실행 파일과 메모리 구조#

리버싱을 하기 위해서는 실행 파일이 메모리에 어떻게 올라가는지 이해해야 한다.

2.1 ELF 기준 메모리 구조#

Linux ELF 실행 파일 기준으로 주요 메모리 영역은 다음과 같다.

영역설명
.text실행 가능한 코드가 저장되는 영역
.rodata읽기 전용 문자열, 상수 데이터 저장
.data초기화된 전역 변수 저장
.bss초기화되지 않은 전역 변수 저장
heapmalloc 등으로 동적 할당되는 영역
stack지역 변수, 함수 호출 정보, 리턴 주소 저장

예를 들어 문자열 비교 로직을 분석한다면 .rodata에 저장된 문자열과 .text에 존재하는 비교 루틴을 함께 확인해야 한다.

2.2 x86_64 주요 레지스터#

x86_64 환경에서 자주 보는 레지스터는 다음과 같다.

레지스터역할
RIP다음에 실행할 명령어 주소
RSP현재 스택의 최상단 주소
RBP스택 프레임 기준 주소
RAX함수 반환값 또는 계산 결과
RDI첫 번째 함수 인자
RSI두 번째 함수 인자
RDX세 번째 함수 인자
RCX네 번째 함수 인자

System V AMD64 ABI 기준으로 Linux x86_64 함수 호출 시 인자는 보통 RDI, RSI, RDX, RCX, R8, R9 순서로 전달된다.

3. 정적 분석#

정적 분석은 프로그램을 실행하지 않고 파일 자체를 분석하는 방식이다. 실행 위험이 있는 파일을 다룰 때 가장 먼저 수행하는 분석 단계이기도 하다.

3.1 파일 정보 확인#

먼저 파일의 형식과 아키텍처를 확인한다.

Terminal window
file ./target
checksec ./target
readelf -h ./target

file 명령어는 실행 파일 형식과 아키텍처를 확인하는 데 사용된다. checksec은 NX, Canary, PIE, RELRO 같은 보호 기법 적용 여부를 확인할 때 유용하다. readelf는 ELF 헤더, 섹션, 심볼 정보를 분석할 수 있다.

3.2 문자열 분석#

실행 파일 안에 포함된 문자열은 프로그램의 기능을 추측하는 단서가 된다.

Terminal window
strings -a ./target
strings -a ./target | grep -i pass

예를 들어 다음과 같은 문자열이 발견되었다고 가정하자.

Input password:
Correct!
Wrong password!

이 경우 프로그램 내부에 패스워드 검증 루틴이 존재할 가능성이 높다. 분석자는 해당 문자열이 참조되는 위치를 찾아 검증 로직으로 이동할 수 있다.

3.3 디스어셈블과 디컴파일#

정적 분석 도구는 바이너리 코드를 사람이 읽을 수 있는 형태로 변환한다.

도구용도
Ghidra무료 디컴파일러, 함수 구조 복원
IDA Free디스어셈블 및 함수 분석
radare2CLI 기반 바이너리 분석
objdump간단한 디스어셈블 확인
Binary Ninja상용 리버싱 분석 도구

예시 C 코드는 다음과 같다.

#include <stdio.h>
#include <string.h>
int main() {
char input[32];
printf("Input password: ");
scanf("%31s", input);
if (strcmp(input, "rev_basic_2026") == 0) {
puts("Correct!");
} else {
puts("Wrong password!");
}
return 0;
}

컴파일된 바이너리에서는 위 코드가 다음과 유사한 어셈블리 흐름으로 보일 수 있다.

lea rdi, [rbp-0x20]
lea rsi, [rip+0x2004] ; "rev_basic_2026"
call strcmp
test eax, eax
jne wrong
lea rdi, [rip+0x2015] ; "Correct!"
call puts
jmp end
wrong:
lea rdi, [rip+0x2020] ; "Wrong password!"
call puts

strcmp()는 두 문자열이 같으면 0을 반환한다. 따라서 test eax, eax 이후 jne wrong 분기가 존재한다면, eax가 0이 아닐 때 실패 루틴으로 이동한다는 의미이다.

4. 동적 분석#

동적 분석은 프로그램을 직접 실행하면서 레지스터, 메모리, 분기 흐름을 관찰하는 방식이다.

4.1 디버거 실행#

Linux 환경에서는 gdbpwndbg, gef 같은 플러그인을 자주 사용한다.

Terminal window
gdb ./target

기본적인 디버깅 명령어는 다음과 같다.

명령어설명
break mainmain 함수에 브레이크포인트 설정
run프로그램 실행
ni다음 명령어 실행, 함수 내부로 진입하지 않음
si다음 명령어 실행, 함수 내부로 진입
info registers레지스터 값 확인
x/s 주소해당 주소의 문자열 확인
x/gx 주소해당 주소의 8바이트 값 확인
disassemble mainmain 함수 디스어셈블

4.2 브레이크포인트 설정#

문자열 비교 함수에 브레이크포인트를 걸면 입력값과 정답 문자열의 위치를 확인할 수 있다.

break strcmp
run
info registers
x/s $rdi
x/s $rsi

x86_64 Linux 환경에서 strcmp(a, b)가 호출될 때 첫 번째 인자 aRDI, 두 번째 인자 bRSI에 들어간다. 따라서 x/s $rdi, x/s $rsi를 사용하면 비교 대상 문자열을 확인할 수 있다.

4.3 실행 흐름 추적#

조건 분기는 리버싱에서 매우 중요한 단서이다.

명령어의미
cmp a, b두 값을 비교
test a, a값이 0인지 확인할 때 자주 사용
je / jz같으면 점프
jne / jnz같지 않으면 점프
jg / ja크거나 초과하면 점프
jl / jb작거나 미만이면 점프
call함수 호출
ret함수 복귀

특히 cmp, test 이후 나오는 조건 점프를 보면 성공 루틴과 실패 루틴을 구분할 수 있다.

5. 패턴 기반 분석#

리버싱에서는 자주 반복되는 코드 패턴을 빠르게 식별하는 능력이 중요하다.

5.1 비밀번호 검증 패턴#

비밀번호 검증 로직은 보통 다음과 같은 형태를 가진다.

call strcmp
test eax, eax
jne fail

이 패턴은 문자열 비교 결과가 0이 아닐 경우 실패 루틴으로 이동한다는 의미이다. 따라서 분석자는 fail 라벨의 반대쪽 흐름을 따라가며 성공 조건을 확인한다.

5.2 길이 검증 패턴#

입력 길이를 확인하는 코드에서는 strlen() 호출 이후 비교 명령어가 등장하는 경우가 많다.

call strlen
cmp rax, 0x10
jne fail

위 코드는 입력 문자열의 길이가 0x10, 즉 16바이트가 아니면 실패한다는 뜻이다.

5.3 반복문 패턴#

문자 단위 검증에서는 반복문과 인덱스 증가가 함께 나타난다.

mov eax, 0
loop_start:
movzx edx, byte ptr [rbp+rax-0x30]
xor edx, 0x23
cmp dl, byte ptr [rip+rax+table]
jne fail
add rax, 1
cmp rax, 0x10
jne loop_start

이러한 코드는 입력값을 한 글자씩 가져와 연산한 뒤 테이블 값과 비교하는 구조이다. 이 경우 분석자는 반복문의 종료 조건, 연산 방식, 비교 테이블을 함께 복원해야 한다.

6. 패치와 후킹의 원리#

패치는 바이너리의 일부 명령어를 수정하여 실행 흐름을 바꾸는 것이다. 단, 상용 소프트웨어의 인증 우회나 무단 변조는 불법이 될 수 있으므로 반드시 자신이 만든 프로그램, CTF 문제, 허가된 테스트 환경에서만 수행해야 한다.

6.1 조건 분기 패치#

예를 들어 다음과 같은 코드가 있다고 가정한다.

test eax, eax
jne fail

jne fail은 비교 결과가 0이 아닐 때 실패 루틴으로 이동한다. 실습용 바이너리에서는 이 조건 분기를 반대로 바꾸거나 NOP 처리하여 실행 흐름이 어떻게 변하는지 관찰할 수 있다.

패치 방식의미
JNE -> JE점프 조건을 반대로 변경
JNE -> JMP항상 점프하도록 변경
JNE -> NOP조건 분기를 제거
CALL 제거특정 함수 호출을 건너뜀

이 과정의 목적은 무단 우회가 아니라 어셈블리 명령어와 프로그램 흐름이 어떻게 연결되는지 이해하는 것이다.

6.2 후킹의 개념#

후킹은 특정 함수 호출을 가로채서 인자나 반환값을 관찰하거나 바꾸는 기법이다. 보안 분석에서는 프로그램이 어떤 API를 호출하는지 추적하기 위해 사용된다.

예를 들어 분석자는 다음과 같은 질문을 던질 수 있다.

  • 어떤 파일을 열려고 하는가?
  • 어떤 네트워크 주소에 접근하려 하는가?
  • 어떤 입력값을 비교 함수에 전달하는가?
  • 암호화 함수에 들어가는 평문과 키는 무엇인가?

후킹은 매우 강력한 분석 방법이지만 실제 서비스나 타인의 프로그램에 무단으로 적용해서는 안 된다.

7. 안티 리버싱과 분석자 관점#

프로그램은 분석을 어렵게 만들기 위해 여러 기법을 사용할 수 있다.

7.1 대표적인 안티 리버싱 기법#

기법설명
심볼 제거함수 이름과 변수 이름 제거
난독화제어 흐름과 문자열을 복잡하게 변형
패킹실행 시점에 실제 코드를 복원
안티 디버깅디버거 실행 여부를 탐지
무결성 검사코드 영역이 변조되었는지 확인

이러한 기법은 분석 난이도를 높이지만 분석을 불가능하게 만들지는 않는다. 분석자는 먼저 표면적인 실행 흐름을 파악하고, 점진적으로 복원 범위를 넓혀야 한다.

7.2 안전한 분석 환경#

알 수 없는 바이너리를 분석할 때는 격리된 환경을 사용하는 것이 좋다.

  • 가상 머신 사용
  • 네트워크 격리
  • 스냅샷 생성
  • 분석 로그 기록
  • 해시값 저장
  • 원본 파일 보존

특히 출처가 불분명한 파일은 절대 메인 환경에서 실행하지 않아야 한다.

8. 리버싱 실습 체크리스트#

리버싱을 처음 시작할 때는 다음 순서로 분석하면 좋다.

8.1 기본 정보 확인#

Terminal window
file ./target
sha256sum ./target
checksec ./target

8.2 문자열 확인#

Terminal window
strings -a ./target | less

8.3 함수 구조 확인#

Terminal window
objdump -d ./target | less

또는 Ghidra, IDA Free 같은 도구로 함수 목록과 참조 관계를 확인한다.

8.4 디버깅#

gdb ./target
break main
run
disassemble main

8.5 핵심 로직 정리#

분석 중에는 다음 내용을 꾸준히 기록해야 한다.

항목기록 내용
입력 위치사용자 입력이 저장되는 버퍼 또는 변수
검증 함수strcmp, memcmp, custom check 함수
성공 조건성공 루틴으로 이동하는 조건
실패 조건실패 루틴으로 이동하는 조건
중요 문자열에러 메시지, 성공 메시지, 힌트 문자열
중요 주소분기문, 함수 시작 주소, 테이블 주소

9. 리버싱 학습 방향#

리버싱 실력을 키우려면 어셈블리, 운영체제, 컴파일러, 디버거 사용법을 함께 공부해야 한다.

9.1 추천 학습 순서#

  1. C언어 포인터와 메모리 구조 이해
  2. x86_64 어셈블리 기본 명령어 학습
  3. gdb로 간단한 C 프로그램 디버깅
  4. Ghidra로 함수와 문자열 참조 분석
  5. CTF Reversing 문제 풀이
  6. ELF, PE 파일 구조 학습
  7. 난독화와 패킹 개념 이해

리버싱은 처음에는 어셈블리 때문에 어렵게 느껴지지만, 반복되는 패턴을 익히면 점점 프로그램의 구조가 눈에 들어오기 시작한다.


오늘은 리버싱의 기본 개념과 정적 분석, 동적 분석, 어셈블리 해석, 패치와 후킹의 원리, 안전한 분석 환경까지 알아보았다.

Reversing 정적 분석부터 동적 분석까지
https://blog.paisl.cloud/posts/004/
저자
PAISL
발행일
2026-05-12
콘텐츠 라이선스
CC BY-NC-SA 4.0