리버스 엔지니어링 기드라 실전가이드 Chapter1 리버스 엔지니어링 입문을 정리해봅시다.
디스어셈블과 디컴파일
디스어셈블 : 헥스코드 상태에서 어셈블리 언어(저수준 언어) 로 변환 하는 것 디컴파일 : 헥스코드 상태에서 고수준 언어로 변환 하는 것
프로그램 실행파일과 하드웨어
CPU : 제어부, 연산부, 레지스터로 구성됨
- 제어부 : 메인 메모리에서 실행하는 명령을 취득
- 연산부 : 제어부에서 받은 명령을 실행하고 그 결과를 레지스터나 메인메모리에 저장함
- 레지스터 : CPU 내 존재하는 데이터 저장 영역
메인 메모리 : 프로그램을 실행하기 위한 메인 공간, 메모리 공간은 프로세스 별 분리되어 있으며, 독립적으로 사용함 실행 파일 : 실행되는 프로그램의 본체이며, 보통 디스크에 저장되어 있음
레지스터
주요 레지스터에는 범용 레지스터, 상태 레지스터, 명령 포인터, 세그먼트 레지스터가 있음
- 범용 레지스터 : 연산이나 포인터 저장 등 범용적으로 사용할 수 있는 레지스터
- 상태 레지스터 : x86프로세서에서는 EFLAGS 레지스터라고 불림, 32비트 레지스터에 각 비트가 상태를 나타내는 플래그가 되고, 연산 결과 상태를 나타내는데 사용함
- 명령 포인터 : 다음으로 실행될 명령용 오프셋을 저장함, x86 프로세서에서는 EIP 레지스터가 명령 포인트, 디버거를 사용한 디버깅 시에는 EIP의 값을 변경하는 것으로 지정한 주소로부터 실행가능함
- 세그먼트 레지스터 : 레지스터는 메모리 내 특정 세그먼트를 나타내는 16비트 레지스터
메모리 영역
모든 실행 파일은 메인 메모리(RAM)에서 읽히고 CPU에서 실행됨, 메모리상의 데이터 위치를 나타내는 값을 주소라고 하며, 통상 16진수로 표기함
- 코드 영역 : 실행할 코드가 저장되는 메모리 영역
- 데이터 영역 : 코드 실행시 읽고 쓰는 데이터, 글로벌 변수가 저장되는 메모리 영역
- 힙 영역 : 프로그램 실행 시 동적으로 확보되는 메모리 영역
- 스택 영역 : 프로그램 실행 시 함수의 인수, 로컬 변수, 리턴 주소 등을 저장하기 위해 사용되는 영역
스택
PUSH, POP 명령을 사용해 데이터를 저장하고 꺼내는 LIFO 데이터 구조를 가진 영역 프로그램 실행 중 스택 상태는 계속 변환하며 스택의 톱 주소는 ESP 레지스터, 베이스 주소 EBP 레지스터에 저장 스택 이용 방법은 함수의 호출 규약에 따라 변화함
- 스택 프레임 확보를 위한 처리를 프롤로그 라고함
- 해제를 위한 처리를 에필로그 라고함
호출 규약
호출 규약이란 함수 호출 시 인수 전달 방법과 반환값의 수취 방법을 정의한 것
- cdecl : 1. x86의 C/C++에서 가장 일반적인 호출 규약
- 함수의 인수는 역순으로 쌓이고 반환 값은 EAX 레지스터에 저장되며 함수 호출원이 스택을 POP함
- stdcall :
- 윈도우 API에서 이용되는 호출 규약
- 함수의 인수는 역순으로 쌓이고 반환 값은 EAX에 저장되며 함수가 종료될 때 그 함수 자체가 스택을 PUSH함
- 인수를 위해 확보한 스택 영역은 RET 명령 오퍼랜드에 의해 POP되는 크기가 지정됨
- fastcall :
- 레지스터를 사용하여 인수 전달, 레지스터는 컴파일러에 의존함
- 처음 2개의 인수를 ECX, EDX 레지스터에 저장 하고, 3개이상의 경우 cdecl과 마찬가지로 나머지 인수는 역순으로 스택에 저장
- 반환 값은 EAX에 저장되며, stdcall과 마찬가지로 함수가 종료될 때 그 함수 자체가 스택을 Free함
- thiscall : 1. C++ 클래스의 멤버 함수로 이용되는 호출 규약
- 윈도우에서는 함수의 인수는 역순으로 스택에 쌓이고 반환 값은 EAX에 저장
- this 포인터를 ECX 레지스터에 저장
PE 포맷
PE 포맷 실행 파일은 윈도우에서 이용되는 형식
헤더
DOS 헤더
IMAGE_DOS_HEADER 구조체로 정의됨 선두의 e_magic은 PE 포맷 실행 파일의 매직 넘버인 MZ(0x4D, 0x5A) 마지막 멤버 오프셋 0x3c 의 e_lfanew는 PE 헤더의 오프셋
DOS Stub
MS-DOS용 코드로 “This program cannot be run in DOS mode.” 라는 문자열을 포함 리버스 엔지니어링에서는 PE포맷의 실행 파일인지 확인하는 요소
PE헤더
IMAGE_NT_HEADER 구조체로 정의되며 PE(0x50, 0x45, 0x00, 0x00)이라는 값의 서명과 IMAGE_FILE_HEADER 구조체의 FileHeader, IMAGE_OPTIONAL_HEADER32 구조체의 OptionalHeader로 구성
File Header 중 섹션 수를 나타내는 Number Of Section, 컴파일한 일시를 나타내는 Number Of Sections가 있음
Optional Header 중 엔트리 포인트의 주소(RVA)를 나태내는 Address Of Entry Point나 프로그램이 메모리에 로드되는 주소를 나타내는 ImageBase, IMAGE_DATA_DIRECTORY 구조체의 배열인 Data Directory가 있음
RVA = Relative Virtual Address의 약자로 실제 주소는 RVA+ImageBase 값을 더해서 계산함
ImageBase는 기본적으로 아래와 같음
- EXE = 0x400000
- DLL = 0x10000000
DataDirectory에는 내보내기 테이블 엔트리, 임포트 테이블 엔트리, IAT 엔트리가 존재하며, 프로그램이 메모리에 로딩되면 IAT에는 임포트할 API의 주소가 입력됨
섹션 테이블(섹션 헤더)
IMAGE_SECTION_HEADER 구조체로 정의, 섹션 테이블에는 각 세션 이름과 주소 정보가 저장되어 있음
섹션
라이브러리
파일 조작이나, 메모리 조작, 통신 등 대부분의 프로그램 동작에는 운영체제가 제공하는 라이브러리를 이용함 PE 포맷의 경우 윈도우 API가 라이브러리로 제공됨 링크 방법에는 정적링크, 동적링크 2개가 있음
- 정적링크 : 라이브러리를 결합하여 하나의 실행 파일을 생성
- 동적링크 : Dynamic Link Library(DLL) 형식으로 다른 프로그램에 라이브러리 함수 제공
임포트
PE 포맷에서는 보통 윈도우 API를 이용하기 위해 윈도우가 제공하는 DLL이 내보내는 함수를 임포트하여 호출함
익스포트
PE 포맷에서는 다른 프로그램에서 사용 가능한 함수를 내보낼 수 있음
x64 아키텍처
최근 운영체제는 64비트이기 때문에 x64아키텍처 프로그램이 많음
x64 아키텍처 변환 변경 사항
- 모든 주소, 포인터가 64비트로
- 레지스터가 64비트가 되며, 수도 증가
- 64비트 대응 및 명령 추가
- 함수 호출 규약이 x64 호출 규약으로 변경
x64 레지스터
범용 레지스터 64비트로 확장 , r8~r15 8개의 레지스터가 추가됨
x64 호출 규약
윈도우
- 제1인수부터 제4인수까지 RCX, RDX, R8, R9 레지스터에 저장
- 인수가 부동소수점일 경우 SSE2 레지스터인 XMM0~XMM3으로 넘어감
- 인수가 4개 이상이면 4개까지는 레지스터, 그 이후는 스택을 이용하여 전달
- 반환값이 정수거나 포인터면 RAX 레지스터에, 부동소수점의 경우 XMM0에 저장
- RBP 레지스터는 베이스 포인터로 사용되지 않으며 변수 등의 스택 참조는 RSP 레지스터를 사용
- x64 호출 규약 함수의 프롤로그 처리 후 스택에는 다음 요소가 포함되어 있음 -> 리턴 주소 -> 함수의 프롤로그에서 PUSH된 비휘발 레지스터 값 -> 함수가 사용하는 로컬 변수, 스택을 경유하여 건네받은 인수 -> 레지스터 경유로 건네받은 인수의 홈영역
유닉스 계열 운영체제
리눅스나 FreeBSD등의 호출 규약은 System V AMD64 ABI 호출 규약
- 제1인수에서 제6인수까지를 각각 RDI, RSI, RDX, RCX, R8, R9에 저장
- 시스템 호출에는 RCX 레지스터가 아닌 R10 레지스터가 사용
- 사용인수가 6개 이상일 경우 스택을 통해 전달
- 인수가 부동소수점이면 SSE2 레지스터인 XMM0~XMM7 로 넘어감
- 반환값이 64비트 사이즈까지의 정수 또는 포인터면 RAX, 128비트 값이면 상위 64비트는 RDX 레지스터에, 하위 64비트는 RAX 레지스터에 저장
- 부동소수점의 반환값 역시 64비트 사이즈는 XMM0 레지스터에, 128비트까지의 값은 XMM0~XMM1 레지스터에 분할하여 저장됨
- 레드존 개념이 존재 -> 레드존 : 스택 포인터 아래에 배치되는 128바이트 최적화를 위한 영역 -> 128바이트까지의 일시 데이터를 스택 포인터 변경 없이 스택에 배치 가능