security/리버싱핵심원리

PE File Format 정리

민사민서 2023. 3. 9. 11:44

[PE Header 구조]

- IMAGE_DOS_HEADER [크기 0x40]

0x0~0x1 : DOS Signature "MZ(4D5A)"

0x3C~0x3F : NT header의 offset

- DOS Stub [DOS_HEADER 이후부터 NT header offset 전까지]

0x0~0xD : 16bit DOS용 어셈블리

0xE ~ : 문자열

- IMAGE_NT_HEADERS

1) PE Signature ("PE\x00\x00") [크기 0x4]

2) IMAGE_FILE_HEADER [크기 0x14]

0x0~0x1 : Machine (0x014c: Intel 386, 0x0200: Intel 64)

0x2~0x3 : Number of sections (>0)

0x10~0x11 : Size of Optional Header // PE 로더는 SizeOfOptionalHeader 값을 보고 IMAGE_OPTIONAL_HEADER32 구조체 크기 인식한다!!!!

0x12~0x13 : Characteristics (0x2: executable, 0x2000: DLL file)

3) IMAGE_OPTIONAL_HEADER32 [크기 0xE0]

0x0~0x1 : Magic number (0x10B: HEADER32, 0x20B: HEADER64)

0x10~0x14 : Entry Point의 RVA

0x1C~0x1F : Image Base (EXE/DLL 파일은 user 영역 0~7FFFFFFF, SYS 파일은 kernel 영역 80000000~FFFFFFFF)

0x20~0x23 : 메모리에서 섹션의 최소단위 (Section Alignment)

0x24~0x27 : 파일에서 섹션의 최소단위 (File Alignment)

0x38~0x3B : 메모리에 로딩된 PE Image의 크기

0x3C~0x3F : PE Header 크기 = 첫번째 섹션 시작지점 (File Alignment의 배수)

0x45~0x46 : 1=시스템드라이버파일, 2=GUI파일, 3=CUI파일

0x5C~0x5F : DataDirectory 배열의 개수 // PE 로더는 NumberOfRvaAndSizes 값을 보고 배열 크기를 인식한다!!!

0x60~0xDF : IMAGE_DATA_DIRECTORY(RVA + Size) 구조체 배열 // 0: EXPORT Directory 1: IMPORT Directory, 2: RESOURCE Directory, 9: TLS Directory

- IMAGE_SECTION_HEADER [크기 각각 0x28]

0x0~0x7 : 섹션 이름

0x8~0xF : VirtualSize+VirtualAddress, 메모리에서 섹션이 차지하는 크기 + 섹션의 시작주소(RVA)

0x10~0x17 : SizeOfRawData+PointerToRawData, 파일에서 섹션이 차지하는 크기 + 섹션의 시작 위치(offset)

0x24~0x27 : Characteristics, (MSB에서 2:executable, 4:readable, 8:writable)

 

[변환공식]

RAW = RVA - VirtualAddress(섹션) + PointerToRawData(섹션)

 

[IAT - notepad.exe]

typedef struct _IMAGE_IMPORT_DESCRIPTOR { // 구조체 배열 마지막은 NULL 구조체
    DWORD OriginalFirstThunk; // INT address (RVA)
    DWORD TimeDateStamp;
    DWORD ForwarderChain;
    DWORD Name; // library name address (RVA)
    DWORD FirstThunk; // IAT address (RVA)
} IMAGE_IMPORT_DESCRIPTOR;
typedef struct _IMAGE_IMPORT_BY_NAME {
    WORD Hint; // ordinal
    BYTE Name[n]; // function name string
} IMAGE_IMPORT_BY_NAME;

1) IMAGE_OPTIONAL_HEADER32의 DataDirectory[1].VirtualAddress 값을 통해 IMAGE_IMPORT DESCRIPTOR 구조체 배열(=IMPORT Directory Table)의 시작 주소 파악 가능

IMAGE_OPTIONAL_HEADER32.DataDirectory[1]
.text 섹션 헤더

- 섹션 헤더들을 살펴보면 .text RVA=1000, .data RVA=C000이므로 IDT는 .text 섹션에 속한다

- RAW = RVA - VirtualAddress + PointerToRawData = A048 - 1000 + 400 = 9448

- 0x12C(Size) / 0x14(구조체 Size) = 15 이므로 총 14개의 DLL이 로딩되었음을 알 수 있다

 

2) IMPORT Directory Table에서 KERNEL32.dll을 분석해보겠다

File Offset 0x9448에 실제로 구조체 배열 존재

* INT, Name, IAT RVA 모두 .text 섹션에 속해 있음

- DLL 이름 문자열 주소(RAW) = 0xA214 - 0x1000 + 0x400 = 0x9614

- IAT 주소(RAW) = 0x102C - 0x1000 + 0x400 = 0x42C

- 4Byte 크기 API 함수들의 VA가 담겨있다

- INT 주소(RAW) = 0xA260 - 0x1000 + 0x400 = 0x9660

- Hint/Name 담겨있는 구조체인 IMAGE_IMPORT_BY_NAME 구조체의 포인터(RVA)가 담겨있다

- GetLocalTime 예시로 들면 RAW=0xA746-0x1000+0x400 = 0x9B46에 Hint/Name 있다

Hint: 0x203, Name: "GetLocalTime"

 

[EAT - kernel32.dll]

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD Characteristics;
    DWORD TimeDatesTamp;
    WORD MajorVersion;
    WORD MinorVersion;
    DWORD Name; // library file name (RVA)
    DWORD Base;
    DWORD NumberOfFunctions; // 실제 export 함수 개수
    DWORD NumberOfNmaes; // Export 함수 중 이름을 가지는 함수 개수
    DWORD AddressOfFunctions; // EAT, Export functions 주소 배열
    DWORD AddressOfNames; // 함수 주소 배열
    DWORD AddressOfNameOrdinals; // Ordinal 배열
} IMAGE_EXPORT_DIRECTORY;

1) IMAGE_OPTIONAL_HEADER32의 DataDirectory[0].VirtualAddress 값을 통해 IMAGE_EXPORT_DIRECTORY의 시작 주소 파악 가능

.text 섹션 헤더

- 섹션 헤더들을 살펴보면 .text RVA=1000, .data RVA=C6000이므로 EXPORT 구조체는 .text 섹션에 속한다

- RAW = RVA - VirtualAddress + PointerToRawData = 0xB51C0 - 0x1000 + 0x1000 = 0xB51C0

 

2) IMAGE_EXPORT_DIRECTORY 분석

- 0x554개의 Export functions 존재, 0x552개의 Export 함수가 이름을 가짐 (0x554 - > 0x552 수정필요!)

- RAW = 0xB8730 - 0x1000 + 0x1000 = 0xB8730에 해당 파일 이름이 있다

- NamePointerTable RAW = 0xB6738이고, 함수의 이름 문자열의 주소(RVA)가 담겨있다

- 배열의 원소 개수 = NumberOfNames = 0x552

- Ordinal Table RAW = 0xB7C88이고, ordinal 값 들어있다 (export하는 모든 함수에 이름이 존재한다면 index = ordinal)

- 배열의 원소 개수 = NumberOfNames = 0x552

- Export Address Table RAW = 0xB51E8이고, export 함수들의 주소가 담겨있다. (index 0,1은 이름 안가지는 함수)

- 배열의 원소 개수 = NumberOfFunctions = 0x554

cf) GetProcAddress() 동작원리

1. Name Pointer Table로 가서 문자열 비교를 통하여 원하는 함수 이름을 찾는다, 이 때 위치를 name_index

2. Ordinal Table로 가서 name_index번째 원솟값을 통해 원하는 함수의 ordinal 값을 얻는다.

3. EAT에서 ordinal번째 원솟값을 통해 원하는 함수의 시작 주소를 얻는다

 

'security > 리버싱핵심원리' 카테고리의 다른 글

UPack 파일 분석 - HXD, Ollydbg  (0) 2023.03.12
PE 재배치  (0) 2023.03.11
UPX 압축  (0) 2023.03.11
tiny PE 분석  (0) 2023.03.11
Ollydbg 단축키 정리  (0) 2023.03.08