Little Endian & Big Endian
바이트 오더링은 데이터를 저장하는 방식을 말한다. 크게 빅 엔디언(Bin Endian)과 리틀 엔디언(Little Endian)으로 나눌 수 있다. 리틀 엔디언은 데이터를 뒤부터 순차적으로 기록하고, 빅 엔디언은 앞에부터 순차적으로 기록한다. 아래의 예를 보자.
BYTE b = 0x12;
WORD w = 0x1234;
DWORD dw = 0x12345678;
char str[] = “abcde”;
TYPE |
NAME |
SIZE |
Big Endian |
Little Endian |
BYTE |
b |
1 |
12 |
12 |
WORD |
w |
2 |
12 34 |
34 12 |
DWORD |
dw |
4 |
12 34 56 78 |
78 56 34 12 |
char [] |
str |
6 |
61 62 63 64 65 00 |
65 64 63 62 61 00 |
|
|
|
|
|
|
위의 표에서 알 수 있듯이, 빅 엔디언은 데이터를 앞부터 순차적으로 저장한다. 그에 반해, 리틀 엔디언은 데이터를 뒤부터 순차적으로 저장한다.
빅 엔디언은 데이터를 순서대로 저장하기 때문에 사람이 보기에 직관적이라는 장점이 있다. 대형 UNIX 서버에 사용되는 RISC 계열의 CPU에서 많이 사용되며, 네트워크 프로토콜에도 사용된다.
리틀 엔디언은 Intel x86 Processor에 사용된다. 데이터를 역순으로 저장시키는 것은 산술 연산과 데이터의 타입이 확장/축소될 때 더 효율적이라는 장점을 가지고 있다.
IA-32 Register Basic
레지스터(Register)란 CPU 내부에 존재하는 다목적 저장 공간이다. CPU 내부에 존재하기 때문에 물리적으로 가까이 위치하고 있어 데이터 처리를 고속으로 할 수 있다.
우리가 레지스터를 알아야 하는 이유는 디버거가 디스어셈블 해준 결과물인 어셈블리어의 대부분이 레지스터를 조작하고 확인하는 명령어들이기 때문이다. 레지스터를 모르면 명령어 자체도 이해하기 힘들다.
Intel Architecture 32bit(IA-32) Register
IA-32는 지원하는 기능도 많고 그만큼 레지스터 수도 많다. 많은 레지스터들 중에서 우리가 이번에 알아볼 레지스터는 ‘Basic Program Execution Registers’이다.
Basic Program Execution Registers는 다시 4개의 그룹으로 나누어져 있다.
General Purpose Registers (32bit, 8개)
Segment Registers (16bit, 6개)
Program Status and Control Register (32bit, 1개)
Instruction Pointer (32bit, 1개)
General Purpose Registers (범용 레지스터)
범용 레지스터는 이름처럼 범용적으로 사용되는 레지스터들이다. 각 레지스터의 크기는 32비트이고, 보통은 상수/주소 등을 저장할 때 주로 사용되며, 특정 어셈블리 명령어에서는 특정 레지스터를 조작하기도 한다. 또한, 어떤 레지스터들은 특수한 용도로 사용되기도 한다.
위 사진은 범용 레지스터이다. 각 레지스터들은 16비트 하위 호환을 위하여 몇 개의 구획으로 나누어진다. EAX를 기준으로 설명하면 다음과 같다.
EAX: 0~31까지 구간의 32비트
AX: 0~15까지 구간의 EAX 하위 16비트
AH: 8~15까지 구간의 AX 상위 8비트
AL: 0~7까지 구간의 AX 하위 8비트
즉, 4바이트를 다 사용하고 싶을 때는 EAX를 사용하고, 2바이트만 사용할 때는 AX를 사용하면 된다. 또 1바이트만 사용하고 싶을 때는 AH 혹은 AL을 사용한다. 이렇게 상황에 따라 8비트, 16비트, 32비트로 유연하게 사용할 수 있다.
각 레지스터의 이름은 다음과 같다.
EAX: Accumulator for operands and results data
EBX: Pointer to data in the DS segment
ECX: Counter for string and loop operations
EDX: I/O pointer
위의 레지스터들을 주로 산술연산(ADD, SUB, XOR, OR 등) 명령어에서 상수/변수 값의 저장 용도로 많이 사용된다. 어떤 어셈블리 명령어(MUL, DIV, LODS 등)들은 특정 레지스터를 직접 조작하기도 한다. 이런 명령어가 실행된 이후에 특정 레지스터들의 값이 변경된다.
그리고 추가적으로, ECX와 EAX는 특수한 용도로 사용된다. ECX는 반복문 명령어(LOOP)에서 반복 카운트(loop count)로 사용된다. 루프를 돌 때마다 ECX의 값을 1씩 감소시키는 형태로 동작한다. EAX는 일반적으로 함수의 리턴 값에 사용된다. 모든 Win32 API 함수들은 리턴 값을 EAX에 저장한 후 리턴한다.
참고: Win32 API 함수들은 내부적으로 ECX와 EDX를 사용하기 때문에 호출된 직후 ECX와 EDX의 값이 바뀐다. 따라서 ECX와 EDX에 중요한 값이 저장되어 있다면 API를 호출하기 전에 다른 레지스터나 스택에 백업해두어야 한다.
Other General Purpose Register
나머지 범용 레지스터들의 이름은 다음과 같다.
EBP: Pointer to data on the stack (in the SS segment)
ESI: source pointer for string operations
EDI: Destination pointer for string operations
ESP: Stack pointer (in the SS segment)
위 4개의 레지스터들은 주로 메모리 주소를 저장하는 포인터로 사용된다.
ESP는 스택 메모리 주소를 가리킨다. 어떤 명령어들(PUSH, POP, CALL, RET)은 ESP를 직접 조작하기도 한다. 스택 메모리 관리는 프로그램에서 매우 중요하기 때문에 ESP를 다른 용도로 사용하지 말아야 한다.
EBP는 함수가 호출되었을 때 그 순간의 ESP를 저장하고 있다가, 함수가 리턴하기 직전에 다시 ESP에 값을 되돌려줘서 스택이 깨지지 않도록 한다. (이런 방법을 Stack Frame 기법이라고 하며, 리버싱에서 중요한 개념이다.) ESI와 EDI는 특정 명령어들 (LODS, STOS, REP MOVS 등)과 함께 주로 메모리 복사에 사용된다.
Segment Register(세그먼트 레지스터)
세그먼트(Segment)란 IA-32의 메모리 관리 모델에서 나오는 용어이다.
IA-32 보호 모드에서 세그먼트란 메모리를 조각내어 각 조각마다 시작 주소, 범위, 접근 권한 등을 부여해서 메모리를 보호하는 기법을 말한다. 또한 세그먼트는 페이징(Paging) 기법과 함께 가상 메모리를 실제 물리 메모리로 변경할 때 사용된다. 세그먼트 메모리는 Segment Descriptor Table(SDT)이라고 하는 곳에 기술되어 있는데, 세그먼트 레지스터는 바로 이 SDT의 index를 가지고 있다.
각 세그먼트 레지스터의 이름은 아래와 같다.
CS: Code Segment
SS: Stack Segment
DS: Data Segment
ES: Extra(Data) Segment
FS: Data Segment
GS: Data Segment
이름 그대로 CS는 프로그램의 코드 세그먼트를 나타내며, SS는 스택 세그먼트, DS는 데이터 세그먼트를 나타낸다. ES, FS, GS 세그먼트는 추가적인 데이터 세그먼트이다.
FS 레지스터는 애플리케이션 디버깅에도 자주 등장하는데 SHE(Structured Exception Handling), TEB(Thread Environment Block), PEB(Process Environment Block) 등의 주소를 계산할 때 사용되며 고급 리버싱을 할 때 주제이다.
Program Status & Control Register
EFLAGS: Flag Register
플래그(Flag) 레지스터의 이름은 EFLAGS이며 32비트(4바이트) 크기이다. 다른 레지스터와 마찬가지로 예상할 수 있듯이, FLAGS 레지스터(16비트, 즉 2바이트)의 확장 형태이다.
EFLAGS 레지스터는 각각의 비트마다 의미를 가지고 있다. 각 비트는 1 또는 0의 값을 가지는데, 이는 On/Off 혹은 True/False를 의미한다. 일부 비트는 시스템에서 직접 세팅하고, 일부 비트는 프로그램에서 사용된 명령의 수행 결과에 따라 세팅된다.
EFLAGS 레지스터의 32개의 각 비트 의미를 전부 이해한다는 것은 상당히 어려운 일이다. 리버싱 입문 단계에서는 Application Debuging에 필요한 3개의 flag(ZF, OF, CF)만 잘 이해하면 된다.
Zero Flag(ZF): 연산 명령 후에 결과 값이 0이 되면 ZF가 1(True)로 세팅된다.
Overflow Flag(OF): 부호 있는 수(signed integer)의 오버플로우가 발생했을 때 1로 세팅된다. 그리고 MSB(Most Significant Bit)가 변경되었을 때도 1로 세팅된다.
Carry Flag(CF): 부호 없는 수(unsigned integer)의 오버플로우가 발생했을 때 1로 세팅된다.
Instruction Pointer
Instruction pointer는 CPU가 처리할 명령어의 주소를 나타내는 레지스터이다. EIP의 크기는 32비트이고, 마찬가지로 16비트인 IP 레지스터의 확장된 형태이다. CPU는 EIP에 저장된 메모리 주소의 명령어(instruction)를 하나 처리하고 자동으로 그 명령어의 길이만큼 EIP의 길이를 증가시킨다. 이런 식으로 계속 명령어를 처리한다.
범용 레지스터와는 다르게 EIP는 그 값을 직접 변경할 수 없도록 되어 있어서 다른 명령어를 통하여 간접적으로 변경해야 한다. EIP를 변경하고 싶을 때는 특정 명령어(JMP, JCC, CALL, RET)를 사용하거나 인터럽트, 예외를 발생시켜야 한다.