[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)의 시작 주소 파악 가능
- 섹션 헤더들을 살펴보면 .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을 분석해보겠다
* 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 있다
[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 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 |