상세 컨텐츠

본문 제목

Reverse Core 13장 (★★★★★)

Computer Science/Reverse Core

by RACC8N 2020. 1. 22. 23:12

본문

◆ PE File Format

 

PE(Portable Executable) File : Windows OS의 실행파일 형식.

 

PE 파일 종류

종류 주요 확장자
실행 계열 EXE, SCR
라이브러리 계열 DLL, OCX, CPL, DRV
드라이버 계열 SYS, VXD
오브젝트 파일 계열 OBJ

※ OBJ 파일을 제외한 모든 것은 실행 가능한 파일. (리버싱에서 관심 가질 필요 없음)

 

기본 구조

Dos Header ~ Section Header = PE Header  //  Section = PE Body

 

파일에서는 Offset  //  메모리에서는 VA (Virtual Address, 절대주소)

 

Section Header : 각 Section에 대한 파일/메모리에서의 크기, 위치, 속성 등이 정의되어 있음.

 

파일/메모리에서 Section의 시작 위치는 각 파일/메모리의 최소 기본 단위의 배수에 해당하는 위치여야 하고,

빈 공간은 NULL로 채워버림. (NULL padding)

 

VA & RVA

 

VA(Virtual Address) : 프로세스 가상 메모리의 절대주소.

 

RVA(Relative Virtual Address) : 어느 기준 위치(ImageBase)에서부터의 상대주소

 

VA = RVA + ImageBase

 

※ PE Header 대부분의 정보가 RVA 형태인 이유 : PE파일이 프로세스 가상 메모리의 특정 위치에 로딩될 때, 이미 해당 위치에 다른 PE파일이 로딩되어있을 수 있다. 이런 경우 재배치(Relocation)과정을 통해서 비어있는 다른 위치에 로딩되어야 한다. 만약 PE Header 정보들이 VA(절대주소)로 되어 있다면 정상적인 엑세스가 이루어지지 않을 것이다. 따라서 정보를 RVA(상대주소)로 하여 Relocation이 발생해도 기준위치에 대한 상대위치가 변하지 않기 때문에 문제없이 엑세스가 가능하다.

 

◆ PE Header

 

DOS Header : DOS 파일에 대한 하위 호환성을 고려해서 만든 부분. IMAGE_DOS_HEADER 구조체가 존재함.

size of Structure 40 (64byte)
e_magic DOS Signature (4D5A → ASCII값 "MZ")
e_lfanew NT Header의 Offset (위치에 NT Header 구조체가 존재)

DOS Stub : 코드와 데이터의 혼합으로 이루어져 있음. (DOS Stub의 존재 여부는 옵션이며 없어도 실행 가능함.)

 

32비트 Windows OS는 이쪽 명령어를 실행 시키지 않음. 

하나의 실행 파일에 DOS와 Windows에서 모두 실행 가능한 파일을 만들 수 있음.

(DOS 환경에서는 16비트 DOS용 코드가, Windows 환경에서는 32비트 Windows 코드가 각각 실행됨) 

 

NT Header : DOS Header의 e_lfanew값이 가리키고 있는 위치에 NT Header 구조체 IMAGE_NT_HEADERS가 존재.

size of Structure F8 (248byte)
Signature PE Signature (50450000h → ASCII값 "PE")
FileHeader 파일의 개략적인 속성을 나타냄
OptionalHeader PE Header 구조체 중에서 가장 크기가 큼

NT Header - File Header : IMAGE_FILE_HEADER 구조체.

다음 4가지 멤버 값이 정확히 세팅되어있지 않으면 파일은 정상적으로 실행되지 않는다.

Machine CPU별로 고유한 값. (32bit Intel x86 = 0x014C)
NumberOfSection 섹션의 개수 (0보다 크고, 정의된 섹션 개수 = 실제 섹션)
SizeOfOptionalHeader IMAGE_NT_HEAER의 마지막 멤버 OptionalHeader의 크기
Characteristics 파일의 속성을 나타냄. (속성들을 bit OR 형식으로 조합한 값)

※ IMAGE_DOS_HEADER의 e_lfanew 멤버와 IMAGE_FILE_HEADER의 SizeOfOptionalHeader 멤버 때문에 일반적인 PE파일 형식을 벗어나는 "꽈배기 PE 파일"(PE Patch)을 만들 수 있다.

 

/Example/

NT Header - Optional Header : IMAGE_OPTIONAL_HEADER32 구조체.

다음 9가지 멤버 값이 정확히 세팅되어있지 않으면 파일은 정상적으로 실행되지 않는다.

Magic HEADER32 = 10B  //  HEADER64 = 20B
AddressOfEntryPoint EP의 RVA 값 (최초로 실행되는 코드의 시작 주소)
ImageBase 기준 위치
SectionAlignment / FileAlignment 섹션의 최소 단위 ( 메모리 : SectionAlign / 파일 : FileAlign )
SizeOfImage 가상 메모리에서 PE Image가 차지하는 크기
SizeOfHeader PE Header의 전체 크기 (값 만큼 떨어진 위치에 첫번째 섹션)
 Subsystem 1 : Driver File // 2 : GUI File // 3: CUI File
NumberOfRvaAndSizes DataDirectory 배열의 개수
DataDirectory  배열의 각 항목마다 정의된 값을 가짐

※ DataDirectory : IMAGE_DATA_DIRECTORY 구조체의 배열

※ EXPORT, IMPORT, RESOURCE, TLS Directory

 

/Example/

Section Header : Section의 속성을 정의한 것. ( 속성 : File/Memory에서의 시작 위치, 크기, 엑세스 권한 ... )

 

메모리 속성별 엑세스 권한

종류 엑세스 권한
code 실행, 읽기
data 비실행, 읽기, 쓰기
resource 비실행, 읽기

※ Section Header는 각 Section별 IMAGE_SECTION_HEADER 구조체의 배열로 되어있음.

항목 의미
VirtualSize 메모리에서 섹션이 차지하는 크기
VirtualAddress 메모리에서 섹션의 시작 주소 (RVA)
SizeOfRawData 파일에서 섹션이 차지하는 크기
PointerToRawData 파일에서 섹션의 시작 위치
Characteristics 섹션의 속성 (bit OR)

※ VirtualAddress와 PointerToRawData는 각각 (IMAGE_OPTIONAL_HEADER32에 정의된) SectionAlignment / FileAlignment에 맞게 결정됨.

 

/Example/

※ Image : 파일에서의 PE와 메모리에서의 PE는 서로 다른 모양을 가지므로, 메모리에 로딩된 상태를 Image라는 용어를 사용하여 구별함.

 

RVA TO RAW

 

1. RVA가 속해 있는 Section을 찾는다.

2. 간단한 비례식을 사용하여 File offset (RAW)을 계산한다.

 

RAW - PointerToRawData = RVA - VirtualAddress

RAW = RVA - VirtualAddress + PointerToRawData

 

// ImageBase = 01000000 

Q1. RVA = 5000(.text)일 때 File Offset = ?

  A1. RAW = RVA - VA + PointerToRawData ( RAW = 5000(.text) - 1000(.text) + 400(.text) = 4400(text) )

Q2. RVA = 13314(.rsrc)일 때 File Offset = ?

  A2. RAW = RVA - VA + PointerToRawData ( RAW = 13314(.rsrc) - B000(.rsrc) + 8400(.rsrc) = 10714(.rsrc) )

Q3. RVA = ABA8(.data)일 때 File Offset = ?

  A3. RAW = RVA - VA + PointerToRawData ( RAW = ABA8(.data) - 9000(.data) + 7C00(.data) = 97A8(.rsrc) ) 

RVA는 data Section이고, RAW는 resource Section 이라면 말이 안됨. 이 경우에 "해당 RVA(ABA8)에 대한 RAW값은 정의할수 없다" 고 해야함. 이런 경우는 VirtualSize와 SizeOfRawData 값이 서로 달라서 벌어짐. 

 

◆ IAT (Import Address Table)

 

IAT : 프로그램이 어떤 라이브러리에서 어떤 함수를 사용하고 있는지를 기술한 Table.

 

DLL (Dynamic Linked Library)의 로딩 방식

 

1.Explicit Linking : 프로그램에서 사용되는 순간에 로딩하고 사용이 끝나면 메모리에서 해제되는 방법.

 

2.Implicit Linking : 프로그램 시작할 때 같이 로딩되어 프로그램 종료할 때 메모리에서 해제되는 방법.

 

IAT는 Implicit Linking에 대한 매커니즘을 제공하는 역할을 함.

 

IMAGE_IMPORT_DESCRIPTOR : PE 파일이 자신이 어떤 라이브러리를 Import하고 있는지를 명시해 놓은 구조체.

일반적인 프로그램에서는 보통 여러 개의 라이브러리를 Import하기 때문에 라이브러리 개수만큼 위 구조체의 배열 형식으로 존재하며, 구조체 배열의 마지막은 NULL 구조체로 끝남.

 

* Import : Library에게 서비스(함수)를 제공받는 일

* Export : Library에서 다른 PE파일에게 서비스(함수)를 제공하는 일

항목 의미
OriginalFirstThunk INT (Import Name Table)의 주소 (RVA)
Name Library 이름 문자열의 주소 (RVA)
FirstThunk IAT (Import Address Table)의 주소 (RVA)

※ PE 헤더에서 'Table'이라고 하면 '배열'을 뜻함.

   INT에서 각 원소의 값은 IMAGE_IMPORT_BY_NAME 구조체 포인터이다.

   INT와 IAT의 크기는 같아야 함.

※ IAT 입력 순서

1. IMAGE_IMPORT_DESCRIPTOR(IID)의 Name 멤버를 읽어서 라이브러리의 이름 문자열("Kernel32.dll")을 얻는다.

2. 해당 라이브러리를 로딩한다.

   → LoadLibrary("Kernel32.dll")

3. IID의 OriginalFirstThunk 멤버를 읽어서 INT 주소를 얻는다.

4. INT에서 배열의 값을 하나씩 읽어 해당 IMAGE_IMPORT_BY_NAME 주소(RVA)를 얻는다.

5. IMAGE_IMPORT_BY_NAME의 Hint(ordinal) 또는 Name 항목을 이용하여 해당 함수의 시작 주소를 얻는다.

   → GetProcAddress("GetCurrentThreadId")

6. IID의 FirstThunk(IAT) 멤버를 읽어서 IAT 주소를 얻는다.

7. 해당 IAT 배열 값에 위에서 구한 함수 주소를 입력한다. (GetProcAddress)

8. INT가 끝날 때까지(NULL을 만날 때까지) 위 4~7 과정을 반복한다.

 

Q1. 실제 IMAGE_IMPORT_DESCRIPTOR 구조체 배열은 PE 파일 어느 곳에 존재할까?

  A1. PE Body에 위치. 

IMAGE_OPTIONAL_HEADER32.DataDirectory[1].VirtualAddress 값이 실제 IMAGE_IMPORT_DESCRIPTOR 구조체 배열의 시작 주소이다.

 

 

'Computer Science > Reverse Core' 카테고리의 다른 글

Reverse Core 10장  (0) 2020.01.15
Reverse Core 7장  (0) 2020.01.15
Reverse Core 5장  (0) 2020.01.14
Reverse Core 4장  (0) 2020.01.14
Reverse Core 3장  (0) 2020.01.14

관련글 더보기

댓글 영역