리버싱을 위한 기초 지식(구조)
Reversing/Reverse Engineering

리버싱을 위한 기초 지식(구조)

프로그램 실행 구조

컴퓨터 기본 구조

컴퓨터의 기본 구조: CPU(processor) + Memory(메모리) + HDD(하드디스크)

 

PE 포맷으로 구성된 실행파일을 클릭하면 운영체제에 있던 로더(loader)가 PE 헤더에 있는 정보를 분석해 PE 보디에 있는 코드와 데이터를 메모리에 배치하면서 프로그램이 실행된다.


CPU (Central Processing Unit)

중앙 처리 장치; 기계어로 쓰여진 컴퓨터 프로그램의 명령어를 해석하고 실행하는 컴퓨터의 부분(하드웨어/칩)이다.

  • 레지스터 (Register) : 컴퓨터의 프로세서(CPU) 내에서 자료를 보관하는 저장공간으로, 보통 현재 계산을 수행 중인 값을 저장하는 데 사용한다
  • 산술 논리 연산 장치 (ALU; Arithmetic Logic Unit) : 산술 연산과 논리 연산을 계산하는 디지털 회로이다.
  • 제어 장치 (CU; Control Unit) : 프로그램 실행 중 메모리에서 CPU로 가져와 레지스터에 저장된 값을 ALU에 전달하여 연산을 하도록 하는 중간 장치이다.

이 중 제어장치와 연산장치에서 프로그램 안에 있는 명령어를 실행한다. 이 때 사용되는 모든 데이터들은 레지스터에 있어야한다. 따라서, 메모리에 있는 데이터를 레지스터로 복사하는 과정이 필요하다.

 

 ◻  context switching : 여러 프로그램을 동시에 실행하면서 CPU 사용 권한을 다른 프로그램에게 넘겨주게 되는데, 이 때 자신이 사용하고 있던 모든 레지스터를 메모리 영역에 복사한다.


Instruction Cycle

[출처] Computer Organization and Architecture (William Stallings)

CPU는 기본적으로

  • Fetch: 다음 실행할 명령어를 읽어온다
  • Decode: 읽어온 명령어를 해석한다
  • Execute: 해석한 결과를 실행한다

이 과정을 반복하는 장치이다. 이렇게 한 개의 기계 코드(명령어)를 fetch→decode→execute 하는 과정을 "instruction cycle"이라고 한다.


Memory

[출처] https://d41jung0d.tistory.com/16

  • Code 영역 : 프로그램의 코드가 저장되어 있는 영역
  • Data 영역 : 정적 변수와 전역 변수가 들어가는 영역
  • Heap 영역 : 동적 메모리 할당에 사용되는 영역
  • Stack 영역 : 함수를 호출할 때 사용되는 매개 변수, 지역 변수가 저장되는 영역

Stack

  1. 함수 내의 로컬 변수 임시 저장
  2. 함수 호출 시 파라미터 전달
  3. 리턴 주소(return address) 저장

의 역할을 하는 FILO/LIFO(First In Last Out/Last In First Out) 구조의 메모리 공간이다.

  • 스택은 "POP"과 "PUSH"의 동작만을 지원한다.

   -  POP : 데이터를 스택에서 꺼내는 동작을 하는 명령어

   -  PUSH : 데이터를 스택에 집어넣는 명령어

  • 스택은 한 방향으로만 데이터가 쌓인다.

PUSH 명령을 통해 데이터가 stack에 추가되면 ESP 레지스터(스택 포인터)는 스택의 top (낮은 주소)을 향해 움직이고, 

POP 명령을 통해 데이터가 stack에서 제거되면 ESP 레지스터는 스택의 bottom(높은 주소)를 향해 움직인다. 


Stack Frame

서브루틴(함수)이 가지는 자신만의 스택 영역; 서브 루틴 내에서 사용하는 데이터(파라미터, 지역 변수 등)가 저장된다.

  • 함수가 호출될 때(서브루틴이 시작될 때) 스택 프레임이 생성되고, 함수가 동작을 종료하고 리턴 어드레스로 돌아갈 때 스택 프레임은 소멸한다.

서브루틴을 호출할 때 필요한 인자들을 스택에 입력하고, 실행 바로 직전에 운영체제에서는 리턴 주소(복귀 주소)를 스택에 집어넣는다.

  • EBP(base pointer) 레지스터(프레임 포인터)를 사용해 스택 내의 로컬 변수, 파라미터, 복귀 주소에 접근한다.

EBP는 스택 프레임의 시작점을 가리킨다. 

ESP는 프로그램 실행 중 계속 그 값이 달라지기 때문에 EBP에 함수의 시작(스택프레임의 시작점)의 ESP 값을 EBP에 저장하고 이를 기준으로 하여 해당 함수의 변수, 파라미터, 리턴 주소에 접근할 수 있도록 한다.

PUSH EBP
MOV EBP, ESP
......
......
......
MOV ESP, EBP
POP EBP
RETN

△ 스택 프레임의 어셈블리 코드 구조


PE 파일 (Portable Executable; EXE)

윈도우 운영체제에서 사용하는 실행 파일; 윈도우에서 프로그램을 개발하고 실행 파일로 컴파일하면 PE 형식으로 만들어진다.

컴파일러를 이용해 고급언어를 운영체제가 이해할 수 있는 기계어로 번역한다. 이 때 번역된 기계어는 파일로 만들어지고, 이것이 실행파일(PE 파일)이다.

프로그램을 실행할 때 필요한 기본 정보와 파일을 메모리의 어디에 저장할 지에 대한 배치 정보가 포함되어 있다.

  • PE 파일은 PE 포맷에 따라 만들어야한다.
  • PE 파일의 종류: 실행 계열(EXE, SCR), 라이브러리 계열(DLL, OCX, CPL, DRV), 드라이버 계열(SYS, VXD), 오브젝트 파일 계열(DBJ)(오브젝트 파일은 실행은 부라능하지만 컴파일의 결과물이기 때문에 PE 파일로 인정)

PE 포맷은 윈도우에서 만든 프로그램이 실행되기 위해 준수해야 하는 파일 형식, 일종의 규칙으로, 지켜지지 않은 프로그램은 올바르게 실행되지 않는다.

  • PE 파일은 헤더(Header)보디(Body)로 구성된다.
  • PE 파일은 PE Header + Section Header + Section Data 로 구성된다.
IMAGE_DOS_HEADER PE HEADER PE HEADER
MS-DOS Stub Program
IMAGE_NT_HEADERS
└ Signature
 IMAGE_FILE_HEADER
 IMAGE_OPTIONAL_HEADER
     IMAGE_DATA_DIRECTORY
SECTION HEADER 1
SECTION HEADER 2 
SECTION HEADER
SECTION DATA1
SECTION DATA 2
SECTION DATA DATA

△ PE 파일 구조

① 헤더 / 보디(데이터)

PE파일의 Header에는 파일 구성에 관련된 정보(포맷)(파일이 어떻게 동작해야하는지에 대한 규칙, 어디서부터 실행되어야 하는지, 어떻게 메모리에 적재되어야하는지, 필요한 stack, heap 메모리의 크기를 얼마로 할 지 등등)를 구조체의 형식으로 저장되어 있다.

PE파일의 Body에는 프로그램에서 사용되는 코드(헤더에 적혀있는 규칙에 따라 실행되는 기계어들)와 프로그램이 사용하는 데이터가 들어간다.

 

② 헤더 (PE header + Section Header) / 섹션 데이터

PE Header의 IMAGE_DOS_HEADER, MS-DOS Stub Program, IMAGE_NT_HEADERS 이 세 가지 헤더는 모든 윈도우 파일에 공통으로 있는 헤더다.

Section Header는 pe 파일 마다 가지는 내용과 형태가 다르고, 최소 1개 이상의 섹션 헤더를 가진다.

Section Data도 pe 파일 마다 가지는 내용과 형태가 다르고, 최소 1개 이상의 섹션 데이터 영역을 가진다.

 

  • IMAGE_DOS_HEADER: DOS 운영체제가 윈도우용 PE파일을 실행했을 때 적절한 오류 메시지를 보여주고, 실제 윈도우용 PE 헤더 위치를 가리키는 역할
  • MS-DOS Stub Program: DOS 운영체제에서 윈도우용 PE 파일을 실행했을 때 보여줄 오류메시지를 저장

PE 파일 내부에 이처럼 오류 처리를 위한 작은 DOS 프로그램이 내장되어 있다.

typedef struct IMAGE_DOS_HEADER { 
WORD e_magic; // 005 signature : 4D5A (“MZ") 
WORD e_cblp; 
WORD e_cp; 
WORD e crlc; 
WORD e_cparhdr; 
WORD e minalloc; 
WORD e maxalloc; 
WORD e ss; 
WORD e_sp; 
WORD e csum; 
WORD e_lp; 
WORD e_cs; 
WORD e lfarlc; 
WORD e_ovno; 
WORD e res[4] ; 
WORD e oemid; 
WORD e oeminfo; 
WORD e_res2[lÐ] ; 
LONG e_lfanew; // offset to NT header 
} lMAGE_DOS_ ADER , PlMAGE_DOS_H DER; 
  • IMAGE_NT_HEADERS = 4바이트의 Signature + 2개의 IMAGE_FILE_HEADER, IMAGE_OPTIONAL_HEADER 구조체IMAGE_OPTIONAL_HEADER 구조체 뒤, 16개의 IMAGE_DATA_DIRECTORY 구조체가 따라온다.
typedef struct _IMAGE_NT_HEADERS { 
DWORD Signature; // PE Signature : 50450000 ("PE"00) 
IMAGE F1LE HEADER FileHeader; 
IMAGE_OPT10NAL_HEADER32 OptionalHeader; 
} IMAGE NT HEADERS32 , *P1MAGE NT HEADERS32; 

typedef struct IMAGE_FILE_HEADER { 
WORD Machine; 
WORD NumberOfSections; 
DWORD TimeDateStamp; 
DWORD Poi nte rToSymbolTable; 
DWORD NumberOfSymbols ; 
WORD SizeOfOptionalHeade r; 
WORD Characteristi cs; 
} 1MAGE F1LE HEADER , *P1MAGE F1LE HEADER;


typedef struct _IMAGE_DATA_DIRECTORY { 
DWORD VirtualAddress; 
DWORD Size; 
} lMAGE_DATA_DIRECTORY, * PlMAGE_DATA_DIRECTORY; 
#define lMAGE NUMBEROF DIRECTORY ENTRIES 16 
typedef struct _IMAGE_OPTIONAL_HEADER { 
WORD Magic; 
BYTE ajorLinkerVersion; 
BYTE MinorLinkerVersion; 
DWORD SizeOfCode; 
DWORD SizeOflnitializedData; 
DWORD SizeOfUninitializedData; 
DWORD AddressOfEntryPoint; 
DWORD BaseOfCode; 
DWORD BaseOfData ; 
DWORD ImageBase; 
DWORD SectionAlignment; 
DWORD FileAlignment; 
WORD MajorOperatingSystemVersion; 
WORD MinorOperatingSystemVersion ; 
WORD MajorlmageVersion; 
WORD MinorlmageVersion; 
WORD MajorSubsystemVersion; 
WORD MinorSubsystemVersion; 
DWORD Win32VersionValue; 
DWORD SizeOflmage ; 
DWORD SizeOfHeaders; 
DWORD CheckSum; 
WORD Subsystem; 
WORD DllCharacteristics; 
DWORD SizeOfStackReserve; 
DWORD SizeOfStackCommit; 
DWORD SizeOfHeapReserve; 
DWORD SizeOfHeapCommit; 
DWORD LoaderFlags; 
DWORD NumberOfRvaAndSizes; 
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; 
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32; 


특히, IMAGE_OPTIONAL_HEADER 구조체는 PE 구조에서 핵심적인 역할을 한다.

  • 주소 지정 방법: 
  • pFile: PE 파일 내부에서의 오프셋(파일)
  • RVA(Relative Virtual Address): 어느 기준 위치(ImageBase)에서부터의 상대 주소(메모리); PE 헤더 내의 정보는 RVA 형태가 많다.
  • VA(Virtual Address): 프로세스 가상 메모리의 절대 주소(메모리) => RVA + ImageBase = VA

 

pFile은 PE 파일이 물리적으로 HDD에 저장되었을 때 의미있고,

VA와 RVA는 PE 파일이 메모리에 로드됐을 때 의미있는 값이다.

 

 

  • DLL(Dynamic Linking Library): 동적 연결 라이브러리; 프로그램 실행 중 필요한 라이브러리를 동적으로 연결해 사용한다.
  • IAT(Import Address Table): PE 파일 안에 어떤 라이브러리의 어떤 함수를 가져다 쓰는지 기록해놓은 정보(테이블)

프로그램 내부 라이브러리가 아닌 별도의 파일(DLL)을 구성하고 필요할 때 가져다가 사용한다.

한 번 로딩된 DLL 코드 및 리소스는 Memory mapping 기술로 여러 프로세스에서 공유해서 사용되고, 라이브러리가 업데이트되어도 해당 DLL 파일만 교체하면 된다.

이런 장점이 많은.. DLL을 사용할 수 있도록 PE 파일에는 DLL을 지원하는 IAT 정보가 포함되어 있다.

 

로더가 PE 파일을 메모리에 로딩할 때,

IAT에 기록된 API 이름을 참조해 실제 주소를 찾는다 → IAT 안에 API를 가리키는 주소를 적는다

 

코드에서 라이브러리를 참조하는 부분은 IAT 내부에 있는 함수의 주소를 사용한다.


스택과 스택 프레임, PE 파일은 따로 더 공부를 하고 정리해야할 것 같다!!

 

[출처]

리버싱 입문(조성문/프리렉)

리버싱 핵심원리(이승원/인사이트)

dreamhack.io/learn/20#1

SMALL

'Reversing > Reverse Engineering' 카테고리의 다른 글

패킹과 언패킹  (0) 2021.05.18
어셈블리 코드 변환  (0) 2021.04.06
HelloWorld.exe  (2) 2021.04.01
Instruction (명령어)  (0) 2021.03.30
[dreamhack] 리버싱 엔지니어링이란  (0) 2021.03.27