본문 바로가기
게임해킹/리버싱 핵심원리(나뭇잎책)

4장 IA-32 Register 기본 설명

by HHack 2024. 8. 17.
반응형

4.1 CPU 레지스터란?

레지스터란 CPU 내부에 존재하는 다목적 저장 공간입니다.

4.1.1 레지스터에 대해 알아야 하는 이유

디버깅을 잘 하려면 디버거가 해석(디스어셈)해주는 명령어를 공부해야 합니다. 어셈블리 명령어의 대부분은 레지스터를 조작하고 그 내용을 검사하는 것들인데 정작 레지스터를 모르면 명령어 자체도 이해하기 힘들기 때문입니다.

4.2 IA-32의 레지스터

IA-32는 지원하는 기능도 많고 그만큼 레지스터의 수도 많습니다. 아래 목록은 IA-32에 존재하는 레지스터들의 종류(그룹)들 입니다.

 레지스터 종류
 Basic program execution registers
 x87 FPU registers
 MMX registers
 XMM registers
 Control registers
 Memory management registers
 Debug registers
 Memory type range registers
 Machine specific registers
 Machine check registers
 ...

4.2.1 Basic program execution registers

Basic program execution registers는 다시 4개의 그룹으로 나눌 수 있습니다.

① General Purpose Registers (32비트 - 8개)

② Segment Registers (16비트 - 6개)

③ Program Status and Control Register (32비트 - 1개)

④ Instruction Pointer (32비트 - 1개)

(1) 범용 레지스터(General Purpose Registers)

범용 레지스터 이름처럼 범용적으로 사용되는 레지스터들입니다. IA-32에서 각각의 범용 레지스터들의 크기는 32비트(4바이트)입니다. 보통은 상수/주소 등을 저장할 때 주로 사용되며, 특정 어셈블리 명령어에서는 특정 레지스터를 조작하기도 합니다. 또한 어떤 레지스터들은 특수한 용도로 사용되기도 합니다.

※ 참고
각 레지스터들은 16비트 하위 호환을 위하여 몇 개의 구획으로 나뉘어집니다. (EAX 기준)

- EAX : (0 ~ 31) 32비트
- AX : (0 ~ 15) EAX의 하위 16비트
- AH : (8 ~ 15) AX의 상위 8비트
- AL : (0 ~ 7) AX의 하위 8비트

즉 4바이트(32비트)를 다 사용하고 싶을 때는 EAX를 사용하고, 2바이트(16비트)만 사용할 때는 EAX의 하위 16비트 부분인 AX를 사용하면 됩니다. AX는 다시 상위 1바이트(8비트)인 AH와 하위 1바이트(8비트)인 AL로 나뉩니다. 이런 식으로 하나의 32비트 레지스터를 상황에 맞게 8비트, 16비트, 32비트로 사용할 수 있습니다.

 

각 레지스터의 이름은 아래와 같습니다.

  • EAX : Accumulator for operands and results data
  • EBX : Pointer to data in DS segment
  • ECX : Counter for string and loop operations
  • EDX : I/O pointer

  위 4개의 레지스터들은 주로 산술연산(ADD, SUB, XOR, OR 등) 명령어에서 상수 / 변수 값의 저장 용도로 많이 사용됩니다. 어떤 어셈블리 명령어(MUL, DIV, LODS 등)들은 특정 레지스터를 직접 조작하기도 합니다.(이런 명령어가 실행된 이후 특정 레지스터들의 값이 변경됩니다.)

 

그리고 추가적으로 ECX와 EAX는 특수한 용도로도 사용됩니다.

- ECX는 반복문 명령어(LOOP)에서 반복 카운터(loop count)로 사용되며, 루프를 돌떄마다 ECX를 1씩 감소시킵니다.

- EAX는 일반적으로 함수 리턴 값에 사용됩니다. 모든 Win32 API 함수들은 리턴 값을 EAX에 저장 후 리턴합니다.

※ 참고 - Windows 어셈블리 프로그래밍에서 주의할 점!
Win32 API 함수들은 내부에서 ECX와 EDX를 사용합니다. 따라서 이런 API가 호출되면 ECX와 EDX의 값은 변경되어 버립니다. 따라서 ECX와 EDX에 중요한 값이 저장되어 있다면 API 호출 전에 다른 레지스터나 스택에 백업해야 합니다.

 

나머지 범용 레지스터들의 이름은 아래와 같습니다.

  • 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 등)과 함께 주로 메모리 복사에 사용됩니다.

★ 정리

1. EAX (Accumulator for operands and results data)

기본 역할: EAX는 주로 산술 및 논리 연산에서 누산기로 사용됩니다. 대부분의 연산 결과는 EAX에 저장됩니다.

용도: 덧셈, 뺄셈, 곱셈, 나눗셈 같은 연산의 결과를 저장합니다.

함수 호출 시 반환 값이 EAX 레지스터를 통해 전달됩니다.

예시: “ADD EAX, EBX” 명령어는 EAXEBX를 더한 값을 EAX에 저장합니다.

 

2. EBX (Pointer to data in DS segment)

기본 역할: EBX는 주로 데이터 세그먼트(DS)에 있는 데이터를 가리키는 베이스 레지스터(포인터)로 사용됩니다.

용도: 데이터 세그먼트 내에서 오프셋을 가리켜 데이터를 접근하는 데 사용됩니다. 예를 들어, 배열이나 구조체에 대한 오프셋 계산에 사용될 수 있습니다.

일반적인 베이스 레지스터로도 사용되며, 특정 용도로 예약되어 있지는 않지만, 메모리 주소 계산 등에서 널리 사용됩니다.

또한, EBX는 함수 호출에서 비-파괴적인 레지스터로 간주되어, 호출된 함수가 값을 변경하지 않도록 보장합니다.

예시: “MOV AL, [EBX]” 명령어는 EBX가 가리키는 메모리 위치의 값을 AL 레지스터로 로드합니다.

 

3. ECX (Counter for string and loop operations)

기본 역할: ECX는 루프 및 문자열 처리에서 카운터 역할을 합니다.

용도: 루프 명령어(LOOP, LOOPZ, LOOPNZ)에서 반복 횟수를 저장합니다.(카운터로 사용)

문자열 처리 명령어(MOVSB, LODSB )에서 문자열 길이를 저장하여 반복적으로 문자열을 처리합니다.(반복 횟수를 지정하는 역할)

예시: “LOOP label” 명령어는 ECX 값을 감소시키고, 0이 아닌 경우 지정된 레이블로 점프합니다.

REP 접두사가 있는 명령어(REP MOVSB, REP STOSB )에서 ECX는 반복 횟수를 결정합니다.

 

4. EDX (I/O pointer)

기본 역할: EDX는 입출력 작업 및 곱셈, 나눗셈 같은 확장된 산술 연산에서 보조적인 역할을 합니다.

용도: EAX와 함께 곱셈 및 나눗셈 연산에서의 확장된 결과를 저장하거나, 나눗셈에서 몫 또는 나머지를 저장합니다.

일부 입출력 명령어(IN, OUT)에서 입출력 포트 접근 시 포트 번호를 가리키는 데 사용됩니다.

예시: “MUL EBX” 명령어는 EAXEBX를 곱하고, 결과의 하위 32비트는 EAX, 상위 32비트는 EDX에 저장됩니다.

 

5. EBP (Pointer to data on the stack in the SS segment)

기본 역할: EBP는 스택 프레임의 베이스 포인터로, 함수 호출 시 로컬 변수와 매개변수 접근에 사용됩니다.

용도: 함수 호출 시 스택 프레임을 설정하고, 함수 내에서 로컬 변수 및 매개변수에 접근합니다.

함수 시작 시, 호출 전 EBP 값을 스택에 저장하고, ESPEBP에 복사하여 새 스택 프레임을 설정합니다.

스택 기반의 메모리 접근을 용이하게 합니다.

지역 변수 및 함수 인자에 접근할 때, EBP를 기준으로 오프셋을 사용합니다 ([EBP-4], [EBP+8] ).

예시: 함수 내에서 “MOV EAX, [EBP+8]”은 스택에서 첫 번째 매개변수의 값을 EAX에 로드합니다.

 

6. ESI (Source pointer for string operations)

기본 역할: ESI는 문자열 처리나 배열등의 메모리에서 데이터를 읽어오기 위한 소스 포인터 역할을 합니다.

용도: 문자열 복사, 문자열 비교 등에서 소스 문자열의 시작 주소를 가리킵니다.

MOVSB, LODSB, STOSB 등과 같은 문자열 조작 명령어에서 사용됩니다.

예시: “MOVSB” 명령어는 ESI가 가리키는 소스 메모리 주소의 값을 EDI가 가리키는 목적지 메모리 주소로 복사하고, 각각 ESIEDI를 증가시킵니다.

REP 접두사와 함께 사용될 때, ESI는 연산이 끝난 후 자동으로 증가하거나 감소합니다.

 

7. EDI (Destination pointer for string operations)

기본 역할: EDI는 문자열 처리나 배열 등에서 목적지 주소를 가리키는 포인터 역할을 합니다.

용도: ESI와 쌍을 이루어 동작하며 문자열 복사, 문자열 비교 등에서 데이터를 기록할 목적지 문자열의 시작 주소를 가리킵니다.

MOVSB, LODSB, STOSB 등과 같은 문자열 조작 명령어에서 사용됩니다.

예시: “STOSB” 명령어는 EAX의 하위 8비트(AL)에 있는 데이터를 EDI가 가리키는 메모리 위치에 저장하고, EDI를 증가시킵니다.

REP 접두사와 함께 사용될 때, EDI는 연산이 끝난 후 자동으로 증가하거나 감소합니다.

 

8. ESP (Stack pointer in the SS segment)

기본 역할: ESP는 현재 스택의 최상위 요소를 가리키며, 스택의 입출력 작업을 담당합니다.

용도: 함수 호출 시, 호출된 함수의 반환 주소 및 함수 인자들을 스택에 푸시합니다.

로컬 변수를 스택에 할당할 때 사용됩니다.

스택에서 데이터를 푸시하거나 팝할 때, ESP는 자동으로 증가하거나 감소하여 스택의 포인터를 조정합니다.

PUSH, POP 명령어를 통해 스택에 값을 저장하거나 꺼낼 때 사용됩니다.

스택 프레임 관리에 중요한 역할을 하며, 특히 함수 호출과 리턴에서 사용됩니다.

예시: “PUSH EAX” 명령어는 EAX의 값을 스택에 저장하고, ESP4바이트 감소합니다.

(2) 세그먼트 레지스터

세그먼트 레지스터란 IA-32의 메모리 관리 모델에서 나오는 용어입니다.

◆ 개념

- IA-32 보호모드에서 세그먼트란 메모리를 조각내어 각 조각마다 시작 주소, 범위, 접근 권한 등을 부여해서 메모리를 보호하는 기법을 말합니다. 또한 세그먼트는 페이징(Paging) 기법과 함께 가상 메모리를 실제 물리 메모리로 변경할 때 사용됩니다.

- 세그먼트 메모리는 Segment Descriptor Table(SDT)이라고 하는 곳에 기술되어 있는데, 세그먼트 레지스터는 바로 이 SDT의 index를 가지고 있습니다.

그림 4.3 세그먼트 메모리 모델

- 세그먼트 레지스터가 가리키는 세그먼트 디스크립터(Segment Descriptor)와 가상 메모리가 조합되어 선형주소(Linear Address)가 되며, 페이징 기법에 의해서 선형주소가 최종적으로 물리주소(Physical Address)로 변환됩니다.

- 세그먼트 레지스터는 총 6개(CS, SS, DS, ES, FS, GS)이며 각각의 크기는 16비트(2바이트)이며, 각 세그먼트 레지스터의 이름은 아래와 같습니다.

  • CS : Code Segment
  • SS : Stack Segment
  • DS : Data Segment
  • ES : Extra(Data) Segment
  • FS : Data Segment
  • GS : Data Segment

★ 정리

세그먼트 레지스터는 x86 아키텍처에서 메모리 세그먼트를 관리하는 데 사용됩니다. 이들 레지스터는 메모리의 특정 섹션에 대한 세그먼트 베이스 주소를 저장하여 프로세서가 메모리 내의 특정 위치에 접근할 수 있도록 합니다. 각각의 세그먼트 레지스터는 특정한 역할을 가지며, 프로그램 실행 시 특정한 메모리 영역에 접근하는 데 사용됩니다.

 

1. CS (Code Segment)

역할: CS 레지스터는 현재 실행 중인 코드(명령어)가 위치한 메모리 영역을 가리킵니다. 프로그램이 실행될 때 CPU는 CS 레지스터에 있는 값과 명령어 포인터(EIP/RIP) 값을 결합하여 현재 실행 중인 명령어의 물리적 주소를 결정합니다.
주요 기능: 이 레지스터는 주로 코드의 실행 흐름을 제어하며, CPU가 명령어를 가져올 때 참조하는 세그먼트입니다. 코드의 실행은 보통 이 세그먼트 내에서만 이루어집니다.
사용 방식: CS는 현재 실행 중인 코드 세그먼트의 시작 주소를 저장합니다. CS와 IP(Instruction Pointer)가 결합되어 다음에 실행할 명령어의 정확한 주소를 결정합니다.
예시: CS가 0x1000이고, IP가 0x200이라면, CPU는 물리적 주소 0x1200에서 명령어를 가져옵니다.

 

2. SS (Stack Segment)

역할: SS 레지스터는 현재 프로그램의 스택이 위치한 메모리 영역을 가리킵니다.(스택의 시작 주소를 지정합니다.) 스택은 함수 호출 시 매개변수, 반환 주소, 로컬 변수를 저장하는 데 사용됩니다.
주요 기능: 스택은 LIFO(Last In, First Out) 구조로 작동하며, 함수 호출 및 복귀, 중단점 및 예외 처리 시 중요한 역할을 합니다.
사용 방식: ESP(Stack Pointer)와 결합되어 현재 스택의 위치를 가리킵니다. SS와 ESP를 조합하여 스택 내에서의 데이터 접근이 이루어집니다.
예시: 함수 호출 시 PUSH 명령어는 SS
로 가리키는 위치에 데이터를 저장하고, ESP 값을 감소시킵니다.

 

3. DS (Data Segment)

역할: DS 레지스터는 기본 데이터 세그먼트를 가리킵니다. 일반적으로 전역 변수, 구조체, 배열, 정적 데이터 등의 데이터가 저장된 메모리 영역입니다.
주요 기능: DS는 메모리에서 데이터를 읽거나 쓸 때 기본 세그먼트로 사용됩니다. DS는 기본적인 데이터 접근을 위한 기본 세그먼트 레지스터로 사용되며, 데이터를 참조할 때 대부분의 경우 DS가 사용됩니다.
사용 방식: 기본적인 데이터 조작 명령어들이 DS:에 의해 지정된 메모리 위치를 사용합니다.
예시: "MOV EAX, [DS:EBX]"는 DS 레지스터에 의해 가리켜진 세그먼트에서 EBX의 오프셋에 있는 데이터를 EAX에 로드합니다.

 

4. ES (Extra Segment)

역할: ES 레지스터는 추가 데이터 세그먼트를 가리키며, 주로 문자열 작업이나 메모리 복사 작업에 사용됩니다.
주요 기능: CPU 명령어에서 추가적인 메모리 영역을 참조할 때 사용됩니다.
사용 방식: ES는 MOVS, LODS, STOS와 같은 문자열 처리 명령어에서 목적지 주소를 가리키는 데 사용됩니다. DS와 함께 문자열 복사나 비교 작업에서 소스와 목적지 세그먼트를 설정하는 데 사용됩니다.
예시: "MOVSB" 명령어는 DS에서 ES로 데이터를 이동합니다.

 

5. FS (Data Segment)

역할: FS 레지스터는 보통 스레드별 데이터, 예외 핸들러 등 특정 목적의 데이터를 저장하는 메모리 세그먼트를 가리킵니다. 운영 체제 또는 특정 응용 프로그램에서 특별한 데이터 구조에 접근하기 위해 사용됩니다.
주요 기능: FS는 고정된 용도로 특정 데이터를 참조하는 데 사용되며, 운영체제에 따라 다양한 용도로 활용될 수 있습니다. 
사용 방식: FS는 일반적으로 특정한 데이터 구조나 스레드 로컬 저장소(Thread Local Storage, TLS)를 가리키는 데 사용됩니다. 운영 체제는 FS를 이용해 각 스레드의 고유 데이터를 관리할 수 있습니다.
예시: Windows 운영 체제에서는 FS 레지스터가 TEB(Thread Environment Block)를 가리키는 데 사용됩니다.
Windows 운영체제에서는 스레드 로컬 저장소(TLS)에 접근할 때 FS를 사용합니다.

 

※ 참고

(1) Thread Environment Block (TEB)
TEB의 역할: TEB는 각 스레드에 대해 운영 체제에서 관리하는 데이터 구조체입니다. 여기에는 스레드와 관련된 다양한 정보를 포함하는 데이터 구조체이며, 예를 들어, 스택의 시작과 끝, 예외 처리 정보, 스레드 로컬 스토리지(TLS) 슬롯 등을 포함합니다.
FS 레지스터와의 관계: Windows 운영 체제에서 FS 레지스터는 항상 현재 실행 중인 스레드의 TEB를 가리킵니다. FS:[0x18]는 TEB의 시작 주소이며, 여기서 다양한 스레드 정보를 참조할 수 있습니다.
예시: FS:[0x18]는 TEB의 시작 부분을 가리키며, TEB 내에서 다른 필드를 접근할 때 사용됩니다.


(2) Thread Local Storage (TLS)
TLS의 역할: TLS는 각 스레드가 독립적으로 사용하는 데이터 저장 공간입니다. 이는 스레드 간에 공유되지 않으며, 이를 통해 각 스레드는 전역 변수처럼 접근할 수 있는 데이터 영역을 가지지만, 실제로는 각 스레드마다 서로 다른 값을 유지할 수 있습니다. 이는 스레드가 다른 스레드와 독립적으로 실행될 때 매우 유용합니다.
TEB와의 관계: TEB 내에는 TLS 슬롯이 위치해 있으며, 이 슬롯들은 각 스레드에 대해 고유한 데이터를 저장하는 데 사용됩니다. TEB의 특정 오프셋에 TLS 슬롯들이 배치되어 있으며, 각 슬롯은 TLS 데이터의 포인터를 가리킵니다.
FS 레지스터와의 관계: FS 레지스터를 통해 TEB 내의 TLS 슬롯들을 참조할 수 있습니다. 예를 들어, 특정 TLS 슬롯은 "FS:[0x2C]와 같은 주소에서 찾을 수 있으며, 이를 통해 TLS 데이터에 접근하게 됩니다.

 

6. GS (Data Segment)

역할: GS 레지스터도 FS와 유사하게 특별한 데이터 세그먼트를 가리킵니다. 주로 운영 체제에서 커널 모드 또는 특정 응용 프로그램에서 사용되며 운영체제에 따라 그 용도가 다를 수 있습니다.
주요 기능: GS는 주로 시스템 수준의 데이터를 참조하거나 스레드별로 관리되는 데이터를 처리하는 데 사용됩니다. 예를 들어, Linux 운영체제에서는 GS가 TLS를 관리하는 데 사용됩니다.
사용 방식: GS는 다양한 목적으로 사용될 수 있지만, 특히 TEB와 유사한 역할을 하거나 운영 체제의 특정 데이터 구조를 가리킵니다.
예시: Linux 운영 체제에서는 GS가 스레드 로컬 데이터에 접근하는 데 사용됩니다.

 

◆ 요약
CS: 현재 실행 중인 코드 세그먼트를 가리키며, 명령어의 위치를 결정합니다.
SS: 스택 세그먼트를 가리키며, 함수 호출 시 스택에 데이터를 저장하고 꺼낼 때 사용됩니다.
DS: 일반적인 데이터 세그먼트를 가리키며, 전역 변수와 정적 데이터에 접근합니다.
ES: 추가적인 데이터 세그먼트를 가리키며, 문자열 조작에서 주로 사용됩니다.
FS: 특별한 데이터 세그먼트를 가리키며, 스레드 로컬 저장소 등 특정 데이터 구조에 접근합니다.
GS: GS도 FS와 유사한 역할을 하지만, 특정 운영 체제나 응용 프로그램의 데이터 구조에 접근하기 위해 사용됩니다.


이러한 세그먼트 레지스터들은 x86 아키텍처에서 메모리를 세그먼트 단위로 나누어 효율적으로 관리하고 접근하기 위한 중요한 역할을 합니다.
- 세그먼트의 사용: 이들 세그먼트 레지스터는 보호 모드에서 다양한 메모리 세그먼트를 관리하는 데 사용되며, 운영체제와 프로그램이 메모리에 효율적으로 접근할 수 있도록 돕습니다.
- 실제 적용: 현대의 64비트 운영체제에서는 페이징 메모리 관리가 주로 사용되므로, 세그먼트 레지스터의 역할이 축소되었지만, 여전히 하위 호환성 및 특정 시스템 작업에서 중요한 역할을 합니다.
이 레지스터들은 시스템의 안정성과 데이터 보안을 유지하면서 다양한 메모리 작업을 효율적으로 수행할 수 있게 합니다.

(3) 프로그램 상태와 컨트롤 레지스터

플래그(Flag) 레지스터의 이름은 EFLAGS이며 32비트(4바이트) 크기입니다. (EFLAGS 레지스터 역시 16비트의 FLAGS 레지스터의 32비트 확장 형태입니다.) EFLAGS 레지스터는 각각의 비트마다 의미를 가지고 있습니다. 각 비트는 1 또는 0의 값을 가지는데, 이는 On / Off 혹은 True / False를 의미합니다. 일부 비트는 시스템에서 직접 세팅하고, 일부 비트는 프로그램에서 사용된 명령의 수행 결과에 따라 세팅됩니다.

그림 4.4 EFLAGS Register

  • Zero Flag (ZF)
    연산 명령 후에 결과 값이 0이 되면 ZF가 1(True)로 세팅된다.
  • Overflow Flag (OF)
    부호 있는 수(signed integer)의 오버플로가 발생했을 때 1로 세팅된다.
    그리고 MSB(Most Significant Bit)가 변경되었을 때 1로 세팅된다.
  • Carry Flag (CF)
    부호 없는 수(unsigned integer)의 오버플로가 발생했을 때 1로 세팅된다.

(4) Instruction Pointer

Instruction Pointer( = EIP)는 CPU가 처리할 명령어의 주소를 나타내는 레지스터이며, 크기는 32비트(4바이트)입니다. (EIP 또한 16비트의 IP 레지스터의 확장 형태이다.) CPU는 EIP에 저장된 메모리 주소의 명령어(instruction)을 하나 처리하고 난 후 자동으로 그 명령어 길이만큼 EIP를 증가시킵니다.

범용 레지스터들과는 다르게 EIP는 그 값을 직접 변경할 수 없도록 되어 있어서 다른 명령어를 통해 간접적으로 변경해야 합니다. EIP를 변경하고 싶을 때는 특정 명령어(JMP, Jcc, CALL, RET)를 사용하거나 인터럽트(interrupt), 예외(exception)을 발생시켜야 합니다.

 

반응형

'게임해킹 > 리버싱 핵심원리(나뭇잎책)' 카테고리의 다른 글

7장 스택 프레임  (0) 2024.09.04
6장 abex' crackme #1 분석  (0) 2024.08.30
5장 스택  (1) 2024.08.29
3장 리틀 엔디언 표기법  (0) 2024.08.16
1장 리버싱 스토리 / 2장 HelloWorld 리버싱  (1) 2024.08.08