PE Header

안상준·2025년 5월 9일

Reversing

목록 보기
2/16

PE에 대해서 알아보자

PE(Portable Executable)란?

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

Build


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

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

  • static : 모든 라이브러리에 있는 함수가 실행파일에 복사가 되며, 실행파일의 크기가 매우 커지게 된다.
  • dynamic : 함수의 주소나 참조 정보만 포함하고, 런타임에 함수들이 실행파일에 로드가 된다.

PE 파일 구조

PE파일의 구조이다. 각 구조에 대해서 자세히 살펴보자

IMAGE_DOS_HEADER

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

  • e_magic : Magic Number(Dos 방식을 따른다는 서명값)
  • e_lfanew : File addresss of new exe heaer, IMAGE_DOS_HEADER의 다음인 IMAGE_NT_HEADER의 위치를 가르킨다.

Signature

e_magic 이 무엇인지 자세히 설명하면

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

IMAGE_NT_HEADER


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

Signature


winnt.h 에 정의돼 있는 정의

PEView에서 캡쳐
IMAGE_DOS_HEADER 와 마찬가지로 signature가 존재한다.
0x00004550이고, PEView에 리틀엔디안으로 값이 있는 것을 볼 수 있다.

IMAGE_FILE_HEADER

파일이 실행하기 위한 가장 기본적인 데이터가 담겨 있다.
IMAGE_OPTIONAL_HEADER의 각 필드에 대한 설명을 정리한 표이다.

IMAGE_OPTIONAL_HEADER


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

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

IMAGE_DATA_DIRECTORY


IMAGE_DATA_DIRECTORY는 특정 데이터 디렉토리(Import Table, Export Table)의 위치와 크기를 정의힌다.

  • VirtualAddress : 상대주소로 실제 주소에서 베이스 주소를 뺀 값이다. 가상주소는 메모리 재배치를 유연하게 하고, PE파일의 크기와 구조츨 최적화하기 위해 사용한다.

IAT

Address

Import Address Table로, DLL 함수의 실제 메모리 주소를 저장하는 테이블이다. PEView를 이용해 IAT의 위치를 찾을 수 있다.
IMAGE_OPTIONAL_HEADER를 보면

IAT의 주소를 찾을 수 있다.

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

00408180을 보면 IAT시작주소 임을 알 수 있다.

call

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

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

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

IMAGE_SECTION_HEADER


IMAGE_SECTION_HEADER는 각 섹션에 대한 이름을 미롯해 시작 주소와 사이즈 등의 정보를 관리하는 구조체이다.

SECTION

업로드중..각 섹션에 대해서 설명을 정리한 표이다.

Header Size

각 헤더의 위치를 구하는 방법에 대해서 설명하고자 한다.
앞서 살펴본 헤더의 멤버변수들을 보면 헤더의 크기를 정의하는 변수들이 있던 것을 볼 수 있는데 이를 이용하여 각 헤더의 위치를 구한다.
상위 헤더의 주소부터 순서대로 구해야 한다.

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));

코드를 살펴보면

  • IMAGE_NT_HEADER = IMAGE_DOS_HEADER + e_lfanew
  • IMAGE_FILE_HEADER = IMAGE_NT_HEADER -> FileHeader
  • IMAGE_OPTIONAL_HEADER = IMAGE_NT_HEADER -> OptionalHeader
  • IMAGE_SECTION_HEADER = IMAGE_OPTIONAL_HEADER + sizeof(IMAGE_OPTIONAL_HEADER)

이러한 방법으로 각 헤더에 접근이 가능하다.

출처 : 리버스 엔지니어링 바이블(지은이 강병탁, 2012)

0개의 댓글