PE 파일
PE는 Portable Executable은 이식 가능한 실행 파일이라는 뜻으로 하나의 실행 파일을 여러 운영체제에서 실행 가능하다는 의미다. 유닉스 실행 파일 형식인 COFF(Common Object File Format)을 기반으로 exe, dll, obj, sys 등의 확장자를 가진 파일들이 이에 해당한다고 볼 수 있다.
PE 파일 종류
1. 실행 계열 : exe, scr
2. 드라이버 계열 : sys, vxd
3. 라이브러리 계열 : dll, ocx, cpl, drv
4. 오브젝트 계열 : obj
VA와 RVA
VA
> 프로세스 가상 메모리의 절대주소
RVA
> ImageBase로부터의 상대주소 (메모리에 로딩된 상태)
> PE 메모리에 적재되기 전 기본 ImageBase는 0이다. 그리고 PE안에서 상대주소로 주소가 적히게 된다. 이는 메모리 적재 시 절대주소에 비해 상대주소가 ImageBase에서 얼만큼 떨어진 곳으로 이동하기 때문에 재배치가 더 쉽게 이루어진다.
* RVA + ImageBase = VA
PE 파일 기본 구조
* 위 사진에서 address는 메모리에서의 절대 주소, 즉 위치를 표현한 것으로 VA라고도 한다
PE Header
1. DOS Header (64 byte, 0x40 고정)
PE 헤더의 제일 앞 부분에 기존 DOS EXE Header를 확장해 만든 것이 MAGE_DOS_HEADER 구조체이다.
아래 구조체들 중 우리가 볼 부분은 e_magic과 e_lfanew다.
typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
▶ WORD e_magic : DOS signature로 항상 45 5A(ASCII 값으로 “MZ”)다
▶ WORD e_lfanew : NT Heaer의 offset으로 파일에 따라 가변적인 값을 갖는다
이 값이 가리키고 있는 위치에 NT Header 구조체가 있어야 한다
2. DOS Stub
DSO Header 밑에 있으며 옵션에 의해 존재 여부를 경정하기 때문에 크기가 일정하지 않다. 위 DOS Header와는 다르게 없어도 파일 실행에는 문제가 없다.
DOS Header에서 e_lfanew가 가변적인 이유가 바로 이 DOS Stub이 가변적이기 때문이다. 그렇기에 DOS Stub 밑에 있는 DOS Header의 e_lfanew의 필드 값이 변하는 것이다.
아래 사진을 보면 ASCII코드 영역에 “This Program cannot be run in DOS mode”라는 문자열을 볼 수 있다. 이 뜻은 DOS 모드에서 해당 파일이 실행되는 것을 막기 위한 것이다. 해당 부분은 16비트 환경에서만 실행되어 해당 문구를 확인할 수 있지만 32비트 환경에서는 실행되지 않는다고 한다.
3. NT Header
NT Header의 구조체인 IMAGE_NT_HEADER는 32 bit와 64 bit에서 사용하는 구조체가 다르다. 두 구조체의 차이점은 OptionalHeader를 32 bit에서는 IMAGE_IPTIONAL_HEADER32를 64 bit에서는 IMAGE_OPTIONAL_HEADER64를 사용한다는 점이다. 32bit 구조체의 크기는 248 byte이고 64 bit의 구조체의 크기는 264 byte다.
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
#ifdef _WIN64
typedef IMAGE_NT_HEADERS64 IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS64 PIMAGE_NT_HEADERS;
#else
typedef IMAGE_NT_HEADERS32 IMAGE_NT_HEADERS;
typedef PIMAGE_NT_HEADERS32 PIMAGE_NT_HEADERS;
#endif
▶ DWORD Signature : 이 시그니처로 해당 파일이 PE 파일 구조인지 아닌지 알 수 있다. 시그니처는 PE00으로 50 45 00 00으로 저장된다.
▶ IMAGE_FILE_HEADER
아래 구조체에서 중요한 멤버만 보도록 하겠다.
typedef struct _IMAGE_FILE_HEADER {
WORD Machine;
WORD NumberOfSections;
DWORD TimeDateStamp;
DWORD PointerToSymbolTable;
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader;
WORD Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
> WORD Machine : CPU에 따른 값으로 32 bit는 0x014, 64 bit는 0x8664이다. 이 외에도 어느 회사의 CPU인지도 알 수 있다.
2447 #define IMAGE_FILE_MACHINE_UNKNOWN 0 2448 #define IMAGE_FILE_MACHINE_I860 0x014d 2449 #define IMAGE_FILE_MACHINE_I386 0x014c 2450 #define IMAGE_FILE_MACHINE_R3000 0x0162 2451 #define IMAGE_FILE_MACHINE_R4000 0x0166 2452 #define IMAGE_FILE_MACHINE_R10000 0x0168 2453 #define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 2454 #define IMAGE_FILE_MACHINE_ALPHA 0x0184 2455 #define IMAGE_FILE_MACHINE_SH3 0x01a2 2456 #define IMAGE_FILE_MACHINE_SH3DSP 0x01a3 2457 #define IMAGE_FILE_MACHINE_SH3E 0x01a4 2458 #define IMAGE_FILE_MACHINE_SH4 0x01a6 2459 #define IMAGE_FILE_MACHINE_SH5 0x01a8 2460 #define IMAGE_FILE_MACHINE_ARM 0x01c0 2461 #define IMAGE_FILE_MACHINE_THUMB 0x01c2 2462 #define IMAGE_FILE_MACHINE_ARMNT 0x01c4 2463 #define IMAGE_FILE_MACHINE_AM33 0x01d3 2464 #define IMAGE_FILE_MACHINE_POWERPC 0x01f0 2465 #define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1 2466 #define IMAGE_FILE_MACHINE_IA64 0x0200 2467 #define IMAGE_FILE_MACHINE_MIPS16 0x0266 2468 #define IMAGE_FILE_MACHINE_ALPHA64 0x0284 2469 #define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 2470 #define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 2471 #define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64 2472 #define IMAGE_FILE_MACHINE_TRICORE 0x0520 2473 #define IMAGE_FILE_MACHINE_CEF 0x0cef 2474 #define IMAGE_FILE_MACHINE_EBC 0x0ebc 2475 #define IMAGE_FILE_MACHINE_AMD64 0x8664 2476 #define IMAGE_FILE_MACHINE_M32R 0x9041 2477 #define IMAGE_FILE_MACHINE_CEE 0xc0ee |
> WORD NubmerOfSection : 섹션의 개수를 나타낸다. 항상 0보다 커야 하고 정의된 섹션의 개수와 동일해야 한다. 우리는 이 값으로 테이블 크기를 알 수 있다.
> WORD SizeOfOptionalHeader : IMAGE_OPTIONAL_HEADER 구조체의 크기를 나타내며 32 bit는 0xE0(224 byte)이고 64 bit는 0xF0(240 byte)을 값으로 갖게 된다.
* 해당 말은 즉, IMAGE_OPTIONAL_HEADER의 크기는 변경이 가능하다는 것이다.
> WORD Characteristics : 파일의 정보를 나타내는 플래그고 bit OR 형식으로 조합되어 있다.
2429 #define IMAGE_FILE_RELOCS_STRIPPED 0x0001 /* No relocation info */ 2430 #define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 2431 #define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 2432 #define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 2433 #define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 2434 #define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 2435 #define IMAGE_FILE_16BIT_MACHINE 0x0040 2436 #define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 2437 #define IMAGE_FILE_32BIT_MACHINE 0x0100 2438 #define IMAGE_FILE_DEBUG_STRIPPED 0x0200 2439 #define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 2440 #define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 2441 #define IMAGE_FILE_SYSTEM 0x1000 2442 #define IMAGE_FILE_DLL 0x2000 2443 #define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 2444 #define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 |
▶ IMAGE_OPTIONAL_HEADER64
< 32 bit >
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;
DWORD ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
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;
< 64 bit >
typedef struct _IMAGE_OPTIONAL_HEADER64 {
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
ULONGLONG ImageBase;
DWORD SectionAlignment;
DWORD FileAlignment;
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion;
WORD MinorSubsystemVersion;
DWORD Win32VersionValue;
DWORD SizeOfImage;
DWORD SizeOfHeaders;
DWORD CheckSum;
WORD Subsystem;
WORD DllCharacteristics;
ULONGLONG SizeOfStackReserve;
ULONGLONG SizeOfStackCommit;
ULONGLONG SizeOfHeapReserve;
ULONGLONG SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes;
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
Optional Header 역시 중요한 멤버 10개만 보도록 하겠다.
> WORD Magic : 32bit 구조체라면 0x10B이고 64bit 구조체라면 0x20B0이다
> DWORD AddressOfEntryPoint : EP의 RVA 주소 값이 들어있어 프로그램의 시작 주소를 나타낸다
> ImageBase : 가상 메모리에서 PE 파일이 시작되는 주소로 PE 파일이 맵핑되는 시작 주소를 가리킨다
> DWORD SectionAlignment/FileAlignment : 메모리와 파일 섹션의 최소 단위로 섹션의 크기는 이 둘의 배수여야 한다
> DWORD SizeOfImage : 가상 메모리에서 PE Image가 차지하는 크기로 보통 파일과 메모리의 로딩 크기는 다르다
> DWORD SizeOfHeader : PE Header의 전체 크기로 FileAlignment의 배수여야 한다
> DWORD Suabsystem : 파일을 구분해준다 (1 : 드라이버 파일, 2 : GUI 파일, 3 : CUI 파일)
> DWORD NumberOfRvaAndSizes : DataDirectory의 배열 개수를 나타낸다
> IMAGE_DATA_DIRECTORY DataDirectory : IMAGE_DATA_DIRECTORY 구조체 배열로 각 항목마다 정의된 값을 갖는다.
Offset (PE/PE32+) | Description |
96/112 | Export table address and size |
104/120 | Import table address and size |
120/136 | Exception table address and size |
128/144 | Certificate table address and size |
136/152 | Base relocation table address and size |
144/160 | Debugging information starting address and size |
152/168 | Architecture-specific data address and size |
160/176 | Global pointer register relative virtual address |
168/184 | Thread local storage (TLS) table address and size |
176/192 | Load configuration table address and size |
184/200 | Bound import table address and size |
192/208 | Import address table address and size |
200/216 | Delay import descriptor address and size |
208/224 | The CLR header address and size |
216/232 | Reserved |
4. Section Header
각 섹션의 속성을 정의한 것으로 프로그램의 안정성을 위해 섹션마다 엑세스 권한이 달라야 한다. 따라서 각각의 섹션의 속성을 기술할 섹션 헤더가 필요하다. 이 속성을 통해 .text(code) 섹션, .data 섹션, .rdata 섹션 등에 대한 정보를 알 수 있다.
섹션명 | 용도 |
.text (code) | 코드, 실행, 읽기 속성으로 컴파일 후 이곳에 저장 |
.data | 초기화, 읽기, 쓰기 속성으로 초기화된 전역 변수를 저장 |
.rdata | 초기화, 읽기 속성으로 const로 선언된 변수같이 읽기만 가능한 데이터 저장 |
.bss | 비초기화, 읽기, 쓰기 속성으로 초기화되지 않은 전역 변수 저장 |
.edata | 초기화, 읽기 속성으로 EAT과 관련된 정보 저장 |
.idata | 초기화, 읽기, 쓰기 속성으로 IAT 관련 정보 저장 |
.rsrc | 초기화, 읽기 속성으로 리소스 저장 |
▶ DWORD VirtualSize : 메모리에 섹션이 차지하는 크기로 SectionAlignment의 배수다
▶ DWORD VirtualAddress : 메모리에서 섹션이 시작되는 주소로 RVA를 의미한다
▶ DWORD SizeOfRawData : 파일에서 섹션이 차지하는 크기로 해당 섹션의 빈 공간이 얼마나 있는지 알기 위해서 필요하다. 빈공간이 생기는 이유는 FileAlignment의 배수로 공간을 할당하기 때문이다.
▶ DWORD PointerToRawData : 파일에서 섹션의 시작 주소로 FileAlignment의 배수 값을 갖는다
▶ DWORD Charcteristics : 섹션의 속성으로 쓰기와 읽기 등을 변경할 수 있다
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
wow64 fs redirection
wow64는 Windows on Windows 64 bit라는 뜻으로 윈도우는 32 bit 환경에서 만들어진 프로그램을 64 bit 환경에서 실행시킬 때 system32 폴더로 파일을 복사하면 제대로 실행시키기 위해 자동으로 syswow64로 리다이렉트 되어 복사하게 된다. 그래서 만약 32 bit 프로그램의 PE 헤더에 대해서 볼 때 이미 64 bit 환경에 맞춰 리다이렉트 되어 HxD로 보게 된다면 원래 PE 헤더와 조금 다르게 나온다.
하지만 PE Viewer로 확인하면 원래 32 bit 환경과 똑같은 결과로 확인할 수 있다. 이 이유는 PE Viewer는 실행되는 파일에 따라 어떤 환경의 서브시스템에서 실행해야 하는지 판단한 후 해당 플랫폼에서 실행할 수 있도록 환경을 구성하기 때문이다. 그래서 32 bit 프로그램을 64 bit 환경에서 본다 하더라도 PE Viewer가 해당 프로그램은 32 bit 환경의 서브시스템에서 실행되어야 판단하여 wow64를 이용해 32 bit 환경을 구성해준다.
만약 이를 처음부터 피하고 싶다면 아래와 같은 코드로 리다이렉션을 피할 수 있다.
PVOID OldValue;
Wow64DisableWow64FsRedirection(&OldValue);
CopyFile(szSourcePath, szTargetPath, false);
Wow64RevertWow64FsRedirection(OldValue);
< 참고 >
http://egloos.zum.com/paulownia/v/4594905
https://noenemy.tistory.com/372
'Study > Reversing' 카테고리의 다른 글
[Reversing] IAT와 EAT 로딩 과정 (0) | 2020.10.13 |
---|---|
[Reversing] 어셈블리어 C로 변환하기 #2 (0) | 2020.09.22 |
[Reversing] 어셈블리어 C언어로 변환하기 #1 (0) | 2020.09.14 |
[Reversing] abex' Crackme #1 (0) | 2020.09.14 |
[Reversing] x86(32 bit) / x64(64 bit) 호출규약 (0) | 2020.09.14 |
댓글