트렌드브리프
x86 어셈블리 리버싱 입문, 도구 선택과 호출 규약 읽기

x86 어셈블리 리버싱 입문, 도구 선택과 호출 규약 읽기

게시 · 약 6분 분량

AI 요약

x86 어셈블리 리버싱을 처음 접하는 개발자를 위한 입문 안내. NSA가 무료 공개한 Ghidra 등 도구 고르기, x86-64 레지스터와 System V 호출 규약, objdump로 C 코드의 어셈블리를 직접 확인하는 실습까지 순서대로 정리했다.

목차
  1. 리버싱과 x86, 용어부터 정리하기
  2. 어떤 도구부터 설치해야 하나
  3. 레지스터와 호출 규약, 함수를 알아보는 법
  4. C 코드를 어셈블리로 직접 확인하기
  5. AT&T 문법과 인텔 문법은 뭐가 다른가
  6. 다음 단계로 나아가기

x86 어셈블리 리버싱을 시작하려면 세 가지만 갖추면 된다. 컴파일된 바이너리를 어셈블리로 풀어 주는 디스어셈블러 하나, x86-64 레지스터와 함수 호출 규약의 기본 지식, 그리고 짧은 C 코드를 직접 컴파일해 결과를 눈으로 확인하는 연습이다. 도구는 유료 제품 없이도 충분하다. 2026년 7월 기준 미국 국가안보국(NSA)이 오픈소스로 공개한 Ghidra, Hex-Rays가 비상업용으로 무료 배포하는 IDA Free만으로 입문 과정을 전부 소화할 수 있다.

이 글은 리버싱을 처음 접하는 개발자를 대상으로, 리눅스 환경의 64비트(x86-64) 사용자 프로그램을 무료 도구로 읽는 첫 단계까지만 다룬다. hex 값과 C 기초만 갖춘 초보자라면 0x44.cc의 입문 튜토리얼 Reversing for dummies - x86 assembly and C code(2021)가 같은 흐름을 초보자 눈높이로 정리해 두었으니 출발점으로 삼을 만하다. 악성코드 분석이나 안티디버깅 우회, 윈도우 커널 같은 심화 주제는 범위 밖이다. 그리고 리버싱은 라이선스와 법이 허용하는 대상에만 적용해야 한다 — 남의 상용 소프트웨어를 무단으로 뜯어보는 것은 별개의 문제다.

리버싱과 x86, 용어부터 정리하기

리버스 엔지니어링(리버싱)은 소스 코드 없이 컴파일된 실행 파일이나 라이브러리를 분석해 그 동작과 내부 논리를 되짚는 작업이다. 소스가 없으니 기계어를 사람이 읽을 수 있는 어셈블리로 되돌려(디스어셈블) 한 줄씩 해석한다.

x86은 인텔이 설계한 명령어 집합(CPU가 이해하는 기계어 규칙의 모음) 계열을 가리킨다. 요즘 데스크톱과 서버에서 쓰는 64비트 확장판은 x86-64 또는 AMD64라고 부르는데, ‘x86 리버싱’이라고 하면 실무에서는 대개 이 64비트를 가리킨다.

비슷해 보이는 두 용어는 처음부터 구분해 두면 편하다. 디스어셈블은 기계어를 그에 대응하는 어셈블리 명령으로 되돌리는 것이고, 디컴파일은 거기서 한 발 더 나아가 C에 가까운 고수준 코드로 복원해 주는 것이다. 도구를 고를 때도 이 차이가 기준이 된다.

어떤 도구부터 설치해야 하나

무료로 시작한다면 Ghidra 하나로 충분하다. NSA가 2019년 오픈소스로 공개한 리버싱 도구로, 디스어셈블과 디컴파일을 한 화면에서 제공하고 GitHub 릴리스 페이지에서 내려받을 수 있다. 여기에 리눅스에 기본으로 깔려 있는 objdump와 GDB를 곁들이면 별도 설치 없이 명령줄에서 바로 실습할 수 있다.

대표적인 입문 도구를 성격별로 정리하면 다음과 같다.

도구제공 주체비용·라이선스성격
GhidraNSA무료·오픈소스디스어셈블·디컴파일 통합 GUI
IDA FreeHex-Rays무료(비상업용)x86-64 클라우드 디컴파일러 포함
Cutter·rizinrizin 프로젝트무료·오픈소스radare2 계열, Ghidra 디컴파일러 연동
objdump·GDBGNU무료·오픈소스명령줄 디스어셈블·디버깅

Hex-Rays가 배포하는 IDA Free는 x86과 x86-64를 지원하고 x64 바이너리용 클라우드 디컴파일러를 포함하지만, 라이선스가 비상업용으로 한정된다고 Hex-Rays 공식 안내가 밝히고 있다. 명령줄 도구를 선호한다면 radare2에서 갈라져 나온 rizin과 그 GUI인 Cutter도 있다. 도구별 최신 버전과 배포 조건은 각 공식 사이트에서 확인하는 편이 안전한데, Ghidra는 NSA 공식 저장소의 릴리스 페이지가 기준이다.

레지스터와 호출 규약, 함수를 알아보는 법

어셈블리를 읽을 때 가장 먼저 익힐 것은 레지스터(CPU 안의 작은 임시 저장 공간)와 함수 호출 규약이다. 호출 규약은 함수를 부를 때 인자를 어디에 담고 반환값을 어디서 받는지 정해 둔 약속이다. 이걸 알면 낯선 어셈블리에서도 어디가 함수 호출이고 어떤 값이 첫 번째 인자인지 바로 짚어 낼 수 있다.

리눅스·macOS·FreeBSD 같은 유닉스 계열은 System V AMD64 ABI라는 규약을 따른다. 이 규약에서 정수·포인터 인자는 앞에서부터 다음 순서의 레지스터에 담긴다.

일곱 번째부터는 스택에 쌓이고, 함수의 반환값은 RAX에 담긴다. 실수(부동소수점) 인자는 XMM0부터 순서대로 쓴다. 주의할 점은 윈도우가 다른 규약을 쓴다는 것이다. 마이크로소프트 x64 규약에서는 앞 네 개 인자가 RCX, RDX, R8, R9 순서로 들어간다. 그래서 같은 x86-64라도 대상 운영체제에 따라 인자 레지스터를 다르게 읽어야 한다.

64비트 규약을 살펴봤으니 32비트도 짚어 두자. 32비트 x86에서 C 함수가 기본으로 쓰는 __cdecl 규약은 인자를 레지스터가 아니라 스택으로 넘긴다. 인자는 오른쪽부터 역순으로 스택에 push하고, 반환값은 EAX에 담는다. 함수 진입부의 프롤로그가 EBP를 기준으로 스택 프레임을 세우고, 종료부의 에필로그가 그 프레임을 정리한 뒤 호출한 자리로 되돌아간다. 이 규약을 알아 두면 디스어셈블 결과에서 프롤로그·에필로그로 함수의 시작과 끝을 구분하고, 스택에 push된 인자를 세어 함수가 인자를 몇 개 받는지까지 읽어 낼 수 있다.

왼쪽 C 함수 창에서 점선 대응선과 화살표가 오른쪽 디스어셈블 창으로 이어지고, 그 오른쪽에는 인자·기준 슬롯·지역변수 구간으로 나뉜 스택 프레임 기둥이 아래 방향 화살표와 함께 그려진 선화
호출 규약을 알면 디스어셈블에서 함수 경계가 보인다: C 한 줄이 어셈블리 여러 줄로, 인자는 스택 프레임에 쌓인다.

C 코드를 어셈블리로 직접 확인하기

규약을 외우기보다 직접 만든 C 코드가 어떤 어셈블리로 바뀌는지 눈으로 보는 편이 빠르다. 짧은 함수 하나를 컴파일한 뒤 objdump로 디스어셈블해 보자. objdump는 GNU Binutils에 들어 있는 도구이고, -M intel 옵션을 주면 더 읽기 쉬운 인텔 문법으로 출력한다.

// add.c
int add(int a, int b) { return a + b; }

$ gcc -O0 -c add.c -o add.o
$ objdump -d -M intel add.o

최적화를 끈(-O0) 상태로 컴파일하면 add 함수는 대략 이런 형태로 나온다.

<add>:
 push rbp
 mov rbp,rsp
 mov DWORD PTR [rbp-0x4],edi
 mov DWORD PTR [rbp-0x8],esi
 mov edx,DWORD PTR [rbp-0x4]
 mov eax,DWORD PTR [rbp-0x8]
 add eax,edx
 pop rbp
 ret

앞 절에서 본 호출 규약이 그대로 드러난다. 첫 번째 인자 a는 edi(RDI의 하위 32비트), 두 번째 인자 b는 esi(RSI의 하위 32비트)로 들어와 스택에 저장되고, 두 값을 더한 결과가 eax(RAX의 하위 32비트)에 담긴 채 ret로 반환된다. 컴파일러를 설치하지 않고 브라우저에서 같은 실험을 해 보고 싶다면 Compiler Explorer에서 왼쪽에 C 코드를 넣고 오른쪽 어셈블리 출력을 실시간으로 비교할 수 있다.

AT&T 문법과 인텔 문법은 뭐가 다른가

같은 명령을 두 가지 문법으로 표기한다는 점이 초보자를 자주 헷갈리게 한다. objdump와 GDB는 기본값이 AT&T 문법이고, 앞서 쓴 -M intel처럼 옵션을 줘야 인텔 문법으로 바뀐다.

가장 큰 차이는 피연산자(명령이 다루는 값) 순서다. 인텔 문법은 목적지를 먼저 적고(예: mov eax, 1 — eax에 1을 넣어라), AT&T 문법은 출발지를 먼저 적으며 레지스터에 %, 상수에 $를 붙인다(예: mov $1, %eax). 둘은 같은 동작을 다르게 적은 것뿐이라 팀 관례나 취향에 맞춰 고르면 된다. GDB에서 인텔 문법으로 보고 싶다면 아래 명령을 쓰고, ~/.gdbinit에 넣어 두면 매번 자동으로 적용된다.

set disassembly-flavor intel

어느 쪽이든 명령 이름(mov, add, call, ret 등)과 동작은 같으니, 처음에는 한 문법을 정해 익숙해진 뒤 나머지를 눈에 익히는 순서를 권한다.

다음 단계로 나아가기

기본기가 잡히면 실제 바이너리로 넘어가면 된다. 리버싱 연습용으로 일부러 만든 작은 crackme를 풀어 보거나, 보안 대회(CTF)의 리버싱·pwn 문제로 난도를 올리는 경로가 흔하다. Ghidra로 함수 그래프를 보며 전체 흐름을 잡고, 헷갈리는 구간은 GDB로 한 줄씩 실행하며 레지스터 값을 확인하는 식으로 두 도구를 병행하면 이해가 빨라진다. 무엇을 분석하든 그 대상이 리버싱을 허용하는지부터 확인하는 습관을 먼저 들이는 것이 좋다.

더 많은 글 보기 RSS 구독