컴퓨터 속에는 하나의 거대한 세계가 있다. 복잡한 논리적 인과관계가 존재하고, 여러 개체가 상호작용하며, 그 세계에서 통용되는 **기계어(Machine Code)**라는 언어가 있다.
바이너리를 분석하는 분야인 리버스 엔지니어링은 그 거대한 세계의 동작 자체를 이해하는 작업을 수행한다. 이를 위해 리버스 엔지니어가 갖춰야 하는 기본 소양 중 하나는 컴퓨터의 언어를 이해하는 것이다.
컴퓨터 과학자 중 한 명인 David Wheeler는 EDSAC을 개발하면서 **어셈블리 언어(Assembly Language)**와 **어셈블러(Assembler)**라는 것을 고안해냈다.
어셈블러는 일종의 통역사인데, 개발자들이 어셈블리어로 코드를 작성하면 컴퓨터가 이해할 수 있는 기계어로 코드를 치환해줬다. 어셈블리어가 기계어보다는 인간이 이해하기 훨씬 쉬웠으므로, 개발자들은 더욱 편리하게 개발을 진행할 수 있게 되었다.
그런데 소프트웨어를 역분석하는 사람들은 여기에 역발상을 더해, 기계어를 어셈블리 언어로 번역하는 **역어셈블러(Disassembler)**를 개발했다. 기계어로 구성된 소프트웨어를 역어셈블러에 넣으면, 어셈블리 코드로 번역된다. 이로 인해 소프트웨어 분석가들은 소프트웨어를 분석하려고 기계어를 읽을 필요가 없어졌다.
어셈블리어(Assembly Language)는 앞서 소개했듯, 컴퓨터의 기계어와 치환되는 언어이다. 이는 기계어가 여러 종류라면 어셈블리어도 여러 종류여야 함을 의미한다. 그리고 앞에서 **명령어 집합구조(Instruction Set Architecture, ISA)**를 설명할 때 이야기했듯, CPU에 사용되는 ISA는 IA-32, x86-64, ARM, MIPS 등 종류가 굉장히 다양하다.
따라서 이들의 종류만큼 많은 수의 어셈블리어가 존재한다. 그 중에서 x64 아키텍처를 대상으로 만들어진 어셈블리어에 대해 알아보자.
어셈블리어는 컴퓨터 아키텍처별로 다른 문법을 가지고 있다. 하지만 같은 아키텍처에서도 어셈블러에 따라 다른 문법을 가질 수 있는데, 대표적인 두 문법이 바로 Intel 문법과 AT&T 문법이다.
Intel 문법은 **NASM (Netwide Assembler)**에서 사용하고, AT&T 문법은 **GAS (Gnu Assembler)**에서 사용한다.
여기서는 Intel 문법을 자세히 살펴볼 예정이지만, AT&T도 리눅스 환경에서 어셈블리어, 혹은 시스템 해킹과 리버스 엔지니어링을 공부할 때 많이 사용되는 문법이므로 Intel 문법에 대한 규칙을 배운 후 Intel 문법과 AT&T 문법의 차이점을 비교해 학습해보며 AT&T 문법으로도 코드를 작성하고 분석할 수 있도록 하는 것이 좋다.
모든 언어는 문법 구조를 가다. 영어는 주어, 동사, 목적어 등으로 이루어져 있고 한국어는 주어, 목적어, 서술어 등으로 이루어져 있습니다. 이런 구조를 가지는 이유는 의미를 명확히 전달하고, 문장을 해석하는 규칙을 정하기 위함이다.
프로그래밍 언어도 마찬가지인데, 문법 구조가 존재해야 컴퓨터가 해당 언어를 원활하게 해석하고 정확한 동작을 수행할 수 있다. 문법이 없다면 코드의 의미가 불명확해지고, 컴퓨터가 이를 해석하는 과정에서 혼란이 발생할 것이다.