리버싱, 포너블 전에 알아야 할 기본 지식들을 정리 [일부 Dreamhack ]

Architecture

설명

CPU의 내부 구성 요소, 레지스터, 명령어 등을 포함하는 개념

아키텍처를 알아야 분석 시 어셈블리어 분석 및 POC 작성을 잘 할 수 있다. 그 이유는 비트 별로 레지스터의 명과 크기가 다르며 종류 별로 작동 가능한 OS가 다르기 때문이다. [그 외 다양한 이유가 있을 수 있음]

아키텍처의 비트는 CPU가 한번에 처리할 수 있는 데이터의 크기WORD라고 부른다.

x86-64 > WORD > 8byte > 64bit

종류

x86

  • Intel 기반 32bit CPU
  • x86은 32bit CPU의 대표명사처럼 불림
  • Windows, Linux, Mac OS (BigSur까지) 지원

x86_64 (amd64)

  • Intel 기반 64bit CPU, x86과 호환됨.
  • Windows, Linux, Mac OS (BigSur까지) 지원

arm

  • RISC(Reduced Instruction Set Computer) 아키텍처
  • 저전력 소비, 높은 효율성
  • arm 기반 32bit CPU
  • x86과 아예 달라서 호환 안 됨.
  • Linux, Mac OS (Monterey부 터), Android, iOS

arm64

  • arm 기반 64bit CPU
  • 32bit arm과 호환됨
  • Linux, Mac OS (Monterey부터), Android, iOS

Register

여기선 64비트 위주로 정리를 해놓을 예정

x86-64는 x86의 64비트 확장 아키텍처이며 호환이 가능함 [레지스터의 호환이 가능하다는 것임 -> 32비트 프로그램을 64비트 환경에서 돌리는게 가능하다는 건 아니다. -> 물론 Windows 환경에선 일부분 지원을 해주기 때문에 돌아가긴 하는데 이게 레지스터 호환 때문만이라곤 보기 힘듦]

General Register

주용도는 있으나, 다양한 용도로 사용될 수 있음.

이름 용도
rax (accumulator register) 함수의 반환 값
rbx (base register) x64에서는 주된 용도 없음
rcx (counter register) 반복문의 반복 횟수, 각종 연산의 시행 횟수
rdx (data register) x64에서는 주된 용도 없음
rsi (source index) 데이터를 옮길 때 원본을 가리키는 포인터
rdi (destination index) 데이터를 옮길 때 목적지를 가리키는 포인터
rsp (stack pointer) 사용중인 스택의 위치를 가리키는 포인터
rbp (stack base pointer) 스택의 바닥을 가리키는 포인터

Segment Register

Code, Stack, Data 메모리 영역을 가리킬 때 사용되는 CS, SS, DS 레지스터와 운영체제 별로 용도를 결정할 수 있는 ES, FS, GS 레지스터

Flag Register

가장 많이 접할 플래그들은 아래 표와 같음 [다른게 보인다면 추후에 검색해서 찾아볼 것]

플래그 의미
CF(Carry Flag) 부호 없는 수의 연산 결과가 비트의 범위를 넘을 경우 설정 됩니다.
ZF(Zero Flag) 연산의 결과가 0일 경우 설정 됩니다.
SF(Sign Flag) 연산의 결과가 음수일 경우 설정 됩니다.
OF(Overflow Flag) 부호 있는 수의 연산 결과가 비트 범위를 넘을 경우 설정 됩니다.

Instruction Pointer Register

CPU가 처리할 명령어의 주소를 나타내는 레지스터이다. JMP, CALL, RET 등으로 변경된다.

Segment

데이터 용도별로 메모리의 구획을 나눈 것이다. 각 용도에 맞게 적절한 권한(읽기, 쓰기, 실행)을 부여할 수 있음.

세그먼트 역할 일반적인 권한 사용 예
코드 세그먼트 실행 가능한 코드가 저장된 영역 읽기, 실행 main() 등의 함수 코드
데이터 세그먼트 초기화된 전역 변수 또는 상수가 위치하는 영역 읽기와 쓰기 또는 읽기 전용 초기화된 전역 변수, 전역 상수
BSS 세그먼트 초기화되지 않은 데이터가 위치하는 영역 읽기, 쓰기 초기화되지 않은 전역 변수
스택 세그먼트 임시 변수가 저장되는 영역 읽기, 쓰기 지역 변수, 함수의 인자 등
힙 세그먼트 실행중에 동적으로 사용되는 영역 읽기, 쓰기 malloc(), calloc() 등으로 할당 받은 메모리

Code Segment

실행 가능한 코드가 위치하는 영역으로 Text Segment라고도 부른다. 아래와 같은 함수가 해당 부분에 위치함.

int main() {
	...
}

Data Segment

컴파일 시점에 값이 정해진 전역 변수 및 전역 상수들이 위치하는 영역이다. 상수는 rodata, 가변하는 변수는 data에 위치한다.

아래와 같이 변수들이 해당 부분에 위치하며, 포인터의 경우 문자열은 rodata에 변수는 data에 위치하게 된다.

int data1 = 1234;                  // data
char data2[] = "writable";         // data
const char data3[] = "readable";   // rodata
char *ptr_data4 = "readable";      // data/rodata

int main() {
	...
}

BSS Segment

컴파일 시점에 값이 정해지지 않은 전역 변수가 위치하는 영역이다. 선언만하고 초기화하지 않은 변수도 포함되며 해당 변수들은 프로그램 시작 시 0으로 초기화됨.

int BSS;

int main() {
	printf("%d\n", BSS); // 0
	return 0;
}

Stack Segment

함수의 인자나 지역 변수와 같은 임시 변수들이 위치하는 영역이다. 스택 프레임이라는 단위를 사용하며 함수 시작과 끝에 프롤로그, 에필로그 과정을 통해 생성 및 반환된다.

int stack() {
	int stack_data = 0;
	scanf("%d", &stack_data);

	return 0;
}

Heap Segment

C언어에서 malloc(), calloc() 등을 호출하여 할당받은 메모리가 위치하는 영역이다. 아래에서 heap_data_ptr은 스택에 위치하며, 해당 값은 heap 주소를 가리킨다.

int heap() {
	int *heap_data_ptr = malloc(sizeof(*heap_data_ptr));
	*heap_data_ptr = 1234

	return 0;
}

Assembly

opcode operand operand와 같은 문법 구조를 지닌다.

Opcode

다양한 명령 코드들 중 일부 정리

각 명령 코드별 자세한 움직임은 분석 시에 찾아보면서 익히면 됨.

명령 코드  
데이터 이동(Data Transfer) movlea
산술 연산(Arithmetic) incdecaddsub
논리 연산(Logical) andorxornot
비교(Comparison) cmptest
분기(Branch) jmpjejg
스택(Stack) pushpop
프로시져(Procedure) callretleave
시스템 콜(System call) syscall

Operand

피연산자는 []로 둘러싸인 것으로 DWORD PTR[0x1234567]와 같이 표기됨.

WORD가 2바이트 - 16비트인 이유는 제일 초기 16비트 아키텍처를 개발하였기 때문이다.

피연산자 타입 크기
BYTE 1byte
WORD 2byte
DWORD 4byte
QWORD 8byte

호출 규약

호출 규약을 적용하는 것은 컴파일러의 몫이며, 따로 명시하지 않으면 CPU 아키텍처에 적합한 호출 규약을 알아서 적용시킴.

x86

|**함수호출규약**|**사용 컴파일러**|**인자 전달 방식**|**스택 정리**|**적용**| |—|—|—|—|—| |stdcall|MSVC|Stack|Callee|WINAPI| |cdecl|GCC, MSVC|Stack|Caller|일반 함수| |fastcall|MSVC|ECX, EDX|Callee|최적화된 함수| |thiscall|MSVC|ECX(인스턴스), Stack(인자)|Callee|클래스의 함수| x86-64

함수호출규약 사용 컴파일러 인자 전달 방식 스택 정리 적용
MS ABI MSVC RCX, RDX, R8, R9 Caller 일반 함수, Windows Syscall
System ABI GCC RDI, RSI, RDX, RCX, R8, R9, XMM0–7 Caller 일반 함수

x86-64 : SYSV

SYSV에서 정의한 함수 호출 규약은 다음의 특징을 갖는다.

  1. 6개의 인자를 RDI, RSI, RDX, RCX, R8, R9에 순서대로 저장하여 전달합니다. 더 많은 인자를 사용해야 할 때는 스택을 추가로 이용합니다.
  2. Caller에서 인자 전달에 사용된 스택을 정리합니다.
  3. 함수의 반환 값은 RAX로 전달합니다.

x86 : cdecl

x86아키텍처는 레지스터의 수가 적기에 스택을 통해 인자를 전달함. 또한 Caller(호출자)가 스택을 정리함.