PE에 대해서 알아보자

Portable Executable File Format : 파일이 실행할 수 있는, 이식 가능한 다른 곳에 옮겨져도 실행이 가능하도록 만들어 놓은 포맷 ex) .exe, .dll 실행파일 시행과 라이브러리 로드를 위한 포맷이다.

gcc를 이용하여 컴파일 해본 사람은 쉽게 알 수 있다. gcc로 -c옵션으로 컴파일 하면 .o파일이 생성되고, -o옵션으로 컴파일 하면 .exe파일이 생성된다.(-o옵션은 .c파일도 가능)

이런식으로 컴파일 과정이 이루어진다. Link 과정에서 c에서 사용하는 헤더파일(<math.h>, <window.h>, ...) 라이브러리들이 실행파일에 복사가 된다.
라이브러리는 Static Link Library, Import Library(dll) 이렇게 두 가지로 나누어 지는데
PE파일의 구조이다. 각 구조에 대해서 자세히 살펴보자

winnt.h 에 정의돼 있는 IMAGE_DOS_HEAER 의 구조체 이다.
IMAGE_DOS_HEADER는 PE의 첫 부분으로, 파일이 DOS-HEADER임을 나타낸다.
구조체 안에 많은 멤버변수들이 있지만 두 가지만 살펴보도록 하자.
e_magic 이 무엇인지 자세히 설명하면

PEView로 실행파일을 열면 바로 보이는 화면이다. 빨간색 박스 부분이 IMAGE_DOS_HEADER 부분이고, 앞서 본 구조체에 따르면 e_magic은 word, 2byte이다. 다시 바로 위 사진을 보면 앞에 2byte는 4D 5A 우측에 MZ 라고 쓰여 있는 것을 볼 수 있다.
이 MZ는 MS-DOS의 창시자 MarkZbikowski의 이니셜을 딴 것이다.

IMAGE_NT_HEADER 는 PE 파일의 핵심 정보를 저장한다. 파일 속성(아키텍처, 섹션, ...) 을 정의, 실행 파일의 상세 정보를 저장한다.

winnt.h 에 정의돼 있는 정의

PEView에서 캡쳐
IMAGE_DOS_HEADER 와 마찬가지로 signature가 존재한다.
0x00004550이고, PEView에 리틀엔디안으로 값이 있는 것을 볼 수 있다.
파일이 실행하기 위한 가장 기본적인 데이터가 담겨 있다.

IMAGE_OPTIONAL_HEADER의 각 필드에 대한 설명을 정리한 표이다.

IMAGE_NT_HEADER 구조체를 보면 IMAGE_OPTIONAL_HEADER 가 포함돼 있는 것을 볼 수 있다. 실행 파일을 로딩하고 실행하는 데 필요한 대부분의 정보를 담고 있는 영역이다. "Optional"이라 적혀있지만 실제로는 거의 모든 실행 가능한 PE 파일에서 필수이다.

IMAGE_OPTIONAL_HEADER의 각 필드에 대해서 설명한 표이다.

IMAGE_DATA_DIRECTORY는 특정 데이터 디렉토리(Import Table, Export Table)의 위치와 크기를 정의힌다.
Import Address Table로, DLL 함수의 실제 메모리 주소를 저장하는 테이블이다. PEView를 이용해 IAT의 위치를 찾을 수 있다.
IMAGE_OPTIONAL_HEADER를 보면

IAT의 주소를 찾을 수 있다.

IAT시작주소 = Image Base + RVA = 00400000 + 00008180 = 00408180 이다. 실제 IDA를 이용해 확인해 보면

00408180을 보면 IAT시작주소 임을 알 수 있다.
그러면 함수 호출시 IAT를 사용하는 이유에 대해서 살펴보자.
일반적으로 함수를 호출하면 함수의 시작주소로 점프를 하게 된다.

위의 명령은 함수를 호출하는 call 명령어이다.
FCD59077은 call 명령어가 실행된 위치로부터의 상대적인 거리인 오퍼랜드 값이다.
만약 LoadIconA를 호출하는 위치가 변경이 된다면 해당 오퍼랜드 값을 변경해 줘야 한다. 이를 해결하기 위해 IAT를 이용한다.

IAT에 접근하여 간접적으로 함수를 호출한다.

IMAGE_SECTION_HEADER는 각 섹션에 대한 이름을 미롯해 시작 주소와 사이즈 등의 정보를 관리하는 구조체이다.
각 섹션에 대해서 설명을 정리한 표이다.
각 헤더의 위치를 구하는 방법에 대해서 설명하고자 한다.
앞서 살펴본 헤더의 멤버변수들을 보면 헤더의 크기를 정의하는 변수들이 있던 것을 볼 수 있는데 이를 이용하여 각 헤더의 위치를 구한다.
상위 헤더의 주소부터 순서대로 구해야 한다.
PIMAGE_DOS_HEADER pSehIDH = (PIMAGE_DOS_HEADER) GetModuleHandle(NULL);
PIMAGE_NT_HEADERS pSehINH = (PIMAGE_NT_HEADERS)((DWORD)pSehIDH + pSehIDH->e_lfanew);
PIMAGE_FILE_HEADER pIFH = &pSehINH->FileHeader;
PIMAGE_OPTIONAL_HEADER pIOH = (PIMAGE_OPTINAL_HEADER)&pSehINH->OptionalHeader;
PIMAGE_SECTION_HEADER pISH = (PIMAGE_SECTION)HEADER)((PBYTE)PIOH + sizeof(IMAGE_OPTIONAL_HEADER));
코드를 살펴보면
이러한 방법으로 각 헤더에 접근이 가능하다.
출처 : 리버스 엔지니어링 바이블(지은이 강병탁, 2012)