실행파일 (PE) 의 구조
PE(Portable Executable) 정의 : PE 구조로 된 PE 파일들은 플랫폼에 관계없이 Win32 운영 체제가 돌아가는 시스템이면 어디서든 실행 가능하다는 의미 EXE 와 DLL 등의 파일구조를 PE 파일 포맷이라고 명명함 COFF 라는 포맷을 계승한 파일 포맷 PE(Portable Executable) 정의 : PE 구조로 된 PE 파일들은 플랫폼에 관계없이 Win32 운영 체제가 돌아가는 시스템이면 어디서든 실행 가능하다는 의미 EXE 와 DLL 등의 파일구조를 PE 파일 포맷이라고 명명함 COFF 라는 포맷을 계승한 파일 포맷 정의정의
IMAGE_DOS_HEADER 64byte DOS STUB CODE 의 Size 는 가변 DOS STUB CODE PE Signature IMAGE_NT_HEADER 224byte or 96byte IMAGE_FILE_HEADER IMAGE_OPTIONAL_HEADER IMAGE_SECTION_HEADER Section 당 40byte SECTION 1Ex).text SECTION …Ex).rdata PE 파일의 전체구조
DOS HEADER 는 항상 64byte 의 크기를 가지며 윈도우 내에서 IMAGE_DOS_HEADER 라는 구조체로 정의되어 있다 (WinNT.h) DOS HEADER 구조체의 처음 e_magic 값은 항상 MZ 로 고정되며, 마지막 e_lfanew 값은 PE 헤더의 시작위치를 가 르키는 offset 값이다 DOS STUB CODE 는 없어도 되는 부분으로서 프로그램 을 도스 모드에서 실행시켰을 때 실행되는 코드이며, 일반 적으로 “This program must be run under Microsoft Windows” 라는 메시지를 출력하고 종료하는 코드가 삽 입되어 있으며 사이즈는 가변이다. DOS HEADER 는 항상 64byte 의 크기를 가지며 윈도우 내에서 IMAGE_DOS_HEADER 라는 구조체로 정의되어 있다 (WinNT.h) DOS HEADER 구조체의 처음 e_magic 값은 항상 MZ 로 고정되며, 마지막 e_lfanew 값은 PE 헤더의 시작위치를 가 르키는 offset 값이다 DOS STUB CODE 는 없어도 되는 부분으로서 프로그램 을 도스 모드에서 실행시켰을 때 실행되는 코드이며, 일반 적으로 “This program must be run under Microsoft Windows” 라는 메시지를 출력하고 종료하는 코드가 삽 입되어 있으며 사이즈는 가변이다. IMAGE_DOS_HEADER
RVA : PE 파일 내의 파일 offset 과는 무관하며, PE 파일이 가상주소 공간에 로드되었을 때의 그 시작주소에 대한 상대주소를 나타냄. 메모리상에서의 PE 파일의 시작주소에 대한 offset PE 파일에서의 절대주소 : Imagebase PE 파일에서의 상대주소 : 모든 RVA 값 RVA 를 통해서 파일상의 offset 구하기 RVA 값이 속한 섹션을 찾는다.( 섹션테이블 ) 그 섹션의 RVA 값과 PointerToRawData 값의 차를 구한다.(converter) 찾고자 하는 RVA 값에서 converter 값을 뺀다. RVA : PE 파일 내의 파일 offset 과는 무관하며, PE 파일이 가상주소 공간에 로드되었을 때의 그 시작주소에 대한 상대주소를 나타냄. 메모리상에서의 PE 파일의 시작주소에 대한 offset PE 파일에서의 절대주소 : Imagebase PE 파일에서의 상대주소 : 모든 RVA 값 RVA 를 통해서 파일상의 offset 구하기 RVA 값이 속한 섹션을 찾는다.( 섹션테이블 ) 그 섹션의 RVA 값과 PointerToRawData 값의 차를 구한다.(converter) 찾고자 하는 RVA 값에서 converter 값을 뺀다.
Signature(4byte) : PE( ) 의 값을 갖는다. IMAGE_FILE_HEADER(20byte) Machine : CPU 의 ID 를 나타낸다. 0x14c(IA32) 또는 0x200(IA64) NumberOfSections : 섹션의 개수를 의미한다. TimeDateStamp : 링커가 해당 파일을 만들어낸 시간을 의미한다. SizeOfOptionalHeader : 파일헤더 뒤에오는 OPTIONAL_HEADER 의 사이즈를 나타낸다.( 데이터 디렉토리가 존재할 경우 224byte, 존 재하지 않을경우 96byte) Characteristics : PE 파일의 속성을 의미한다. 일반적으로 실행파 일의 경우 0x10f 값을 가진다. Signature(4byte) : PE( ) 의 값을 갖는다. IMAGE_FILE_HEADER(20byte) Machine : CPU 의 ID 를 나타낸다. 0x14c(IA32) 또는 0x200(IA64) NumberOfSections : 섹션의 개수를 의미한다. TimeDateStamp : 링커가 해당 파일을 만들어낸 시간을 의미한다. SizeOfOptionalHeader : 파일헤더 뒤에오는 OPTIONAL_HEADER 의 사이즈를 나타낸다.( 데이터 디렉토리가 존재할 경우 224byte, 존 재하지 않을경우 96byte) Characteristics : PE 파일의 속성을 의미한다. 일반적으로 실행파 일의 경우 0x10f 값을 가진다. IMAGE_NT_HEADERS
IMAGE_OPTIONAL_HEADER Magic : IMAGE_OPTIONAL_HEADER 를 나타내는 시그너처로서 32bit PE 의 경우 0x010b, 64bit 의 경우 0x020b 값을 가진다. AddressOfEntryPoint : 로더가 실행을 개시할 ( 최초로 실행될 코드 ) 주소. 이 주소는 RVA 로서 보통.text 섹션의 시작점이다. IMAGE_OPTIONAL_HEADER Magic : IMAGE_OPTIONAL_HEADER 를 나타내는 시그너처로서 32bit PE 의 경우 0x010b, 64bit 의 경우 0x020b 값을 가진다. AddressOfEntryPoint : 로더가 실행을 개시할 ( 최초로 실행될 코드 ) 주소. 이 주소는 RVA 로서 보통.text 섹션의 시작점이다. IMAGE_NT_HEADERS 디버거로 실행 (calc.exe) Peview 로 calc.exe open
ImageBase : 해당 PE 가 가상주소 공간에 로드될때의 그 시작주소. 기본적으로 EXE 파일은 0x , DLL 파일은 0x ImageBase : 해당 PE 가 가상주소 공간에 로드될때의 그 시작주소. 기본적으로 EXE 파일은 0x , DLL 파일은 0x IMAGE_NT_HEADERS DOS HEADER PE Signature FILE_HEADER OPTIONAL_HEADER SECTION HEADER Section …. DOS HEADER PE Signature FILE_HEADER OPTIONAL_HEADER SECTION HEADER Section …. 파일상태 메모리에 로드된 상태 ImageBase
SectionAlignment : 메모리상에서 섹션의 각 섹션이 차지해야 하는 최소의 단위. 각 섹션은 SectionAlignment X n 의 주소에 서 시작해야 한다. 기본값으로 4K(0x1000) FileAlignment : PE 파일 내에서의 섹션들의 정렬단위. SectionAlignment 와 같은 개념. 보통 0x200 또는 0x100 SectionAlignment : 메모리상에서 섹션의 각 섹션이 차지해야 하는 최소의 단위. 각 섹션은 SectionAlignment X n 의 주소에 서 시작해야 한다. 기본값으로 4K(0x1000) FileAlignment : PE 파일 내에서의 섹션들의 정렬단위. SectionAlignment 와 같은 개념. 보통 0x200 또는 0x100 IMAGE_NT_HEADERS OPTIONAL_HEADER SECTION HEADER Section …. OPTIONAL_HEADER SECTION HEADER Section …. 파일상태 메모리에 로드된 상태 FileAlignment 를 맞춰주기 위한 padding SectionAlignment 를 맞춰주기 위 한 padding 파일상태 메모리에 로드된 상태
SizeOfImage : 메모리에 로드된 PE Image 전체의 크기를 나타내며, 이 값은 ImageBase 필드가 가리키는 주소값으로 부터 해당 PE 의 마지막 섹션까지의 크기이다. SectionAlignment 의 배수가 되어야 한다. SizeOfHeaders : DOS HEADER 에서부터 패딩을 포함한 section table 까지의 크기의 총합. FileAlignment 의 배수가 되어야 한다. NumberOfRvaAndSizes : IMAGE_DATA_DIRECTORY 구조체 배 열 의 원소의 개수를 나타낸다. DATA_DIRECTORY 를 가지고 있 다면 16(0x10) 값을 가진다. IMAGE_DATA_DIRECTORY : Virtualaddress 와 Size 로 구성된 구 조 체로서 마지막 구조체의 값은 0x00 으로 채워져 있다. SizeOfImage : 메모리에 로드된 PE Image 전체의 크기를 나타내며, 이 값은 ImageBase 필드가 가리키는 주소값으로 부터 해당 PE 의 마지막 섹션까지의 크기이다. SectionAlignment 의 배수가 되어야 한다. SizeOfHeaders : DOS HEADER 에서부터 패딩을 포함한 section table 까지의 크기의 총합. FileAlignment 의 배수가 되어야 한다. NumberOfRvaAndSizes : IMAGE_DATA_DIRECTORY 구조체 배 열 의 원소의 개수를 나타낸다. DATA_DIRECTORY 를 가지고 있 다면 16(0x10) 값을 가진다. IMAGE_DATA_DIRECTORY : Virtualaddress 와 Size 로 구성된 구 조 체로서 마지막 구조체의 값은 0x00 으로 채워져 있다. IMAGE_NT_HEADERS
DOS HEADER PE Signature FILE_HEADER OPTIONAL_HEADE R SECTION HEADER Section …. SizeOfHeaders 파일상태 DOS HEADER PE Signature FILE_HEADER OPTIONAL_HEADE R SECTION HEADER Section …. SizeOfImage 메모리에 로드된 상태 IMAGE_NT_HEADERS
IMAGE_DATA_DIRECTORY IMAGE_DIRECTORY_ENTRY_EXPORT [00] IMAGE_DIRECTORY_ENTRY_IMPORT [01] … … … IMAGE_DIRECTORY_ENTRY_TLS [09] … IMAGE_DIRECTORY_ENTRY_IAT [12] … IMAGE_DIRECTORY_ENTRY_NULL [15] IMAGE_NT_HEADERS EXPORT TABLE VirtualAddress Size IMPORT TABLE VirtualAddress Size ……… 임포트 테이블의 메모리상에서 의 시작점과 크기에 대한 정보를 가지고 있다.
임포트 섹션이 존재하지 않을수도 있으며, 다른 섹션에 포함되어 있을 수 있다. 임포트 섹션이 존재하지 않을 경우 IMAGE_DATA_DIRECTORY 내의 IMAGE_DIRECTORY_ENTRY_IMPORT 를 통하여 임포트 테이블의 위치를 알아낼수 있다. 임포트 테이블은 IMAGE_IMPORT_DESCRIPTOR 라는 구조체의 배열로 구 성 되며, 이 구조체는 임포트한 DLL 의 정보를 담고 있다. 임포트 섹션이 존재하지 않을수도 있으며, 다른 섹션에 포함되어 있을 수 있다. 임포트 섹션이 존재하지 않을 경우 IMAGE_DATA_DIRECTORY 내의 IMAGE_DIRECTORY_ENTRY_IMPORT 를 통하여 임포트 테이블의 위치를 알아낼수 있다. 임포트 테이블은 IMAGE_IMPORT_DESCRIPTOR 라는 구조체의 배열로 구 성 되며, 이 구조체는 임포트한 DLL 의 정보를 담고 있다. Import_Section Typedef struct _IMAGE_IMPORT_DESCRIPTOR{ union{ DWORD Characteristics; DWORD OriginalFirstThunk; }; DWORD TimeDateStamp; DWORD ForwarderChain; DWORD Name; DWORD FirstThunk; } IMAGE_IMPORT_DESCRIPTOR;
IMAGE_IMPORT_DESCRIPTOR OriginalFirstThunk –INT(Import Name Table)( 또는 ILT(Import Lookup Table)) 을 가르키는 RVA 값으로서 INT 는 IMAGE_THUNK_DATA 라는 구조체 의 배열로 구성된다. – 이때 IMAGE_THUNK_DATA 배열의 각 원소는 IMAGE_IMPORT_BY_NAME 이라는 구조체를 가리키는 RVA 값 을 가진다. TimeDateStamp – 시간과 날짜를 나타내는 시간 스탬프이다. – 바인딩 전에는 항상 0 이다. ForwarderChain – 일반적으로 바인딩되지 않은 경우 0 이다. IMAGE_IMPORT_DESCRIPTOR OriginalFirstThunk –INT(Import Name Table)( 또는 ILT(Import Lookup Table)) 을 가르키는 RVA 값으로서 INT 는 IMAGE_THUNK_DATA 라는 구조체 의 배열로 구성된다. – 이때 IMAGE_THUNK_DATA 배열의 각 원소는 IMAGE_IMPORT_BY_NAME 이라는 구조체를 가리키는 RVA 값 을 가진다. TimeDateStamp – 시간과 날짜를 나타내는 시간 스탬프이다. – 바인딩 전에는 항상 0 이다. ForwarderChain – 일반적으로 바인딩되지 않은 경우 0 이다. Import_Section
IMAGE_IMPORT_DESCRIPTOR Name – 임포트된 DLL 의 이름을 담과 있는 NULL 로 끝나는 아스키 문자 열에 대한 RVA 값을 가진다. FirstThunk –OriginalFirstThunk 와 마찬가지로 IMAGE_THUNK_DATA 의 배 열 을 가리키며, IMAGE_THUNK_DATA 의 배열의 각 원소 역시 IMAGE_IMPORT_BY_NAME 을 가리킨다. –PE 가 가상주소 공간에 매핑되면 IMAGE_THUNK_DATA 는 임포 트한 DLL 내의 사용할 실제 함수의 주소를 담게 된다. – 실제 함수의 주소를 담고 있는 IMAGE_THUNK_DATA 의 이 배 열 을 IAT(Import Address Table) 이라고 하며, 실제 함수의 주소가 IAT 에 설정된 것을 바인딩 되었다고 한다. IMAGE_IMPORT_DESCRIPTOR Name – 임포트된 DLL 의 이름을 담과 있는 NULL 로 끝나는 아스키 문자 열에 대한 RVA 값을 가진다. FirstThunk –OriginalFirstThunk 와 마찬가지로 IMAGE_THUNK_DATA 의 배 열 을 가리키며, IMAGE_THUNK_DATA 의 배열의 각 원소 역시 IMAGE_IMPORT_BY_NAME 을 가리킨다. –PE 가 가상주소 공간에 매핑되면 IMAGE_THUNK_DATA 는 임포 트한 DLL 내의 사용할 실제 함수의 주소를 담게 된다. – 실제 함수의 주소를 담고 있는 IMAGE_THUNK_DATA 의 이 배 열 을 IAT(Import Address Table) 이라고 하며, 실제 함수의 주소가 IAT 에 설정된 것을 바인딩 되었다고 한다. Import_Section
IMAGE_THUNK_DATA AddressOfData –INT 와 IAT 가 바인딩 전에 모두 IMAGE_IMPORT_BY_NAME 구 조 체를 가리키고 있을 때, IMAGE_THUNK_DATA 는 AddressOfData 의 의미로 사용되었다고 볼수 있다. Ordinal – 모듈정의 파일을 통하여 서수 파일을 지정하게 되면 링커는 DLL 링크시 함수명을 통하지 않고, 서수를 통해서 사용된 함수를 링크하게 된다. Function – 바인딩 후에 IAT 테이블이 실제 함수의 주소로 변경되었을 경우 Function 의 의미로 사용된 것이며, INT 는 Function 의 의미로 사 용될 일이 없다. –IAT 는 바인딩전에는 AddressOfData, 또는 Ordinal 의 의미로 사 용되고, 바인딩 후에는 Function 의 의미로 사용된다. IMAGE_THUNK_DATA AddressOfData –INT 와 IAT 가 바인딩 전에 모두 IMAGE_IMPORT_BY_NAME 구 조 체를 가리키고 있을 때, IMAGE_THUNK_DATA 는 AddressOfData 의 의미로 사용되었다고 볼수 있다. Ordinal – 모듈정의 파일을 통하여 서수 파일을 지정하게 되면 링커는 DLL 링크시 함수명을 통하지 않고, 서수를 통해서 사용된 함수를 링크하게 된다. Function – 바인딩 후에 IAT 테이블이 실제 함수의 주소로 변경되었을 경우 Function 의 의미로 사용된 것이며, INT 는 Function 의 의미로 사 용될 일이 없다. –IAT 는 바인딩전에는 AddressOfData, 또는 Ordinal 의 의미로 사 용되고, 바인딩 후에는 Function 의 의미로 사용된다. Import_Section
OriginalFirstThunk TimeDateStamp ForwarderChain Name FirstThunk Import_Section ….. IMAGE_THUNK_DAT A IMAGE_IMPORT_BY_NAM E 바인딩 전 임포트된 DLL 내의 실제 함수코드 바인딩 후
Import_Section 임포트 테이블의 내용 OriginalFirstThunk FirstThunk INT IAT 바인딩 후의 IAT 변경모습
임포트 과정 PE 파일을 메모리에 로드한 후 데이터 디렉토리의 두번째 엔트리 인 IMAGE_DIRECTORY_ENTRY_IMPORT 로 부터 임포트 테이 블의 주소를 구한다. 임포트 테이블의 Name 필드를 통해 임포트 할 DLL 의 이름을 알아 낸 후, 공간 확보후 DLL 을 가상주소공간에 맵핑 시킨다. INT 를 통해서 임포트 할 함수의 이름 또는 서수 (Ordinal) 값을 알아 낸다. 위의 정보를 이용하여 해당 DLL 의 익스포트 테이블을 통해 필요 한 함수의 실제 주소를 알아내서 IAT 를 수정한다. 임포트 과정 PE 파일을 메모리에 로드한 후 데이터 디렉토리의 두번째 엔트리 인 IMAGE_DIRECTORY_ENTRY_IMPORT 로 부터 임포트 테이 블의 주소를 구한다. 임포트 테이블의 Name 필드를 통해 임포트 할 DLL 의 이름을 알아 낸 후, 공간 확보후 DLL 을 가상주소공간에 맵핑 시킨다. INT 를 통해서 임포트 할 함수의 이름 또는 서수 (Ordinal) 값을 알아 낸다. 위의 정보를 이용하여 해당 DLL 의 익스포트 테이블을 통해 필요 한 함수의 실제 주소를 알아내서 IAT 를 수정한다. Import_Section
IMAGE_SECTION_HEADER 는 40byte 로 구성되며 다음과 같이 정의된다. IMAGE_SECTION_HEADER 는 40byte 로 구성되며 다음과 같이 정의된다. IMAGE_SECTION_HEADER Typedef struct _IMAGE_SECTION_HEADER{ BYTEName[8]; union{ DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORDVirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORDNumberOfRelocations; WORD NumberOfLinenumbers; DWORDCharacteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
IMAGE_SECTION_HEADER DWORD Name 섹션의 아스키 이름을 나타낸다. 8 바이트까지 지정가능하며, NULL 문자 는 제외된다. 로딩과정과 상관이 없다. DWORD VirtualAddress PE 에서 해당 섹션을 매핑시켜야 할 가상 주소 공간 상의 RVA 값이다. 메모리에서 본 해당 섹션의 시작주소를 의미한다. DWORD SizeOfRawData 파일 상태에서의 섹션의 사이즈 값 ( 패딩된 상태 ) FileAlignment 값의 배수이다. DWORD PointerToRawData 파일 상태에서의 섹션이 시작하는 offset 값이다. DWORD Characteristics 해당 섹션의 속성을 나타내는 플래그의 집합이다. DWORD Name 섹션의 아스키 이름을 나타낸다. 8 바이트까지 지정가능하며, NULL 문자 는 제외된다. 로딩과정과 상관이 없다. DWORD VirtualAddress PE 에서 해당 섹션을 매핑시켜야 할 가상 주소 공간 상의 RVA 값이다. 메모리에서 본 해당 섹션의 시작주소를 의미한다. DWORD SizeOfRawData 파일 상태에서의 섹션의 사이즈 값 ( 패딩된 상태 ) FileAlignment 값의 배수이다. DWORD PointerToRawData 파일 상태에서의 섹션이 시작하는 offset 값이다. DWORD Characteristics 해당 섹션의 속성을 나타내는 플래그의 집합이다.
… Name :.text VirtualAddress SizeOfRawData PointerToRawData Charcteristics ….text … IMAGE_SECTION_HEADER 파일상태메모리에 로드된 상태 … Name :.text VirtualAddress SizeOfRawData PointerToRawData Charcteristics ….text … PointerToRawData 가 지정한 곳에서 부 터 SizeOfRawData 만큼의 데이터를 읽 어들여 VirtualAddress 가 가르키는 곳에 맵핑한 후에 Characteristics 에 설정된 속성 정보적용
섹션은 같은 성질의 데이터들을 로더가 구분할 수 있도록 저정해둔 영역을 말한다. 섹션은 같은 성질의 데이터들을 로더가 구분할 수 있도록 저정해둔 영역을 말한다. SECTION 코드.text 프로그램을 실행하기 위한 코드를 담고 있는 섹션이 다. 데이터.data 초기화된 전역변수나 정적변수들을 담고 있는 읽기, 쓰기가 가능한 섹션이다..rdata 읽기 전용 데이터 섹션으로 소스상에서 상수로 정의 한 것들이 배치된다. 임포트 API 정보.idata 임포트할 DLL 과 그 API 들에 대한 정보를 담고 있는 섹션이다..rdata 에 병합되는 추세이다. 재배치 정보.reloc 실행파일에 대한 기본 재배치 정보를 담고 있는 섹션 이다. TLS.tls 스레드 지역 저장소를 위한 섹션이다.
참고 자료 Zesrever 의 지식펌프 조립하면서 배우는 PE Null2root PE 포멧 분석 (PDF) Windows 시스템 실행파일의 구조와 원리 – 이호동 저 참고 자료 Zesrever 의 지식펌프 조립하면서 배우는 PE Null2root PE 포멧 분석 (PDF) Windows 시스템 실행파일의 구조와 원리 – 이호동 저