[리버싱] : PE Header - ①
#REVERSINGIMAGE_DOS_HEADER, IMAGE_NT_HEADER, IMAGE_FILE_HEADER 구조체 알아보기
우선 PE(Portable Executable File Format)
는 무엇인가 단어의 뜻을 풀어서 해석하면 우리의 파일(File)이 이식 가능한 다른 곳(Portable)에 가도 실행이 가능 하도록(Executable)
만들어 놓은 포맷(Format)이란 뜻이다.
PE Header
안에는 실행 파일을 실행하기 위한 각종 정보가 기록돼 있다. EXE나 DLL을 실행하면 항상 개발자가 만든 코드가 실행되기 전에 PE의 정보부터 읽어와서
바이너리를 메모리에 올리기 위한 각종 데이터를 설정하는 작업을 하게 된다.
PE
는 여러 구조체로 구성되어 있으며 중요한 구조체는 다음과 같다.
- IMAGE_DOS_HEADER
- IMAGE_NT_HEADER
- IMAGE_FILE_HEADER
- IMAGE_OPTIONAL_HEADER
- IMAGE_SECTION_HEADER
- IMAGE_IMPORT_DESCRIPTOR
- IMAGE_EXPORT_DIRECTORY
- IMAGE_IMPORT_BY_NAME
- IMAGE_THUNK_DATA32
IMAGE_DOS_HEADER
IMAGE_DOS_HEADER
구조체는 WinNT.h
헤더 파일에 정의되어 있다.
멤버 수가 많지만 쓸모 있는 멤버는 딱 두 개로 첫 번째 필드인 e_magic
과 마지막 필드인 e_lfanew
이다.
e_magic
e_magic
필드는 현재 파일이 PE 파일인지 체크하는 것 말고는 쓸 일이 없다. PE 파일에서 가장 먼저 MZ 스트링(4D 5A)
가 등장한다면 이는 PE 파일이라는 것을 나타내는 것이다.
MZ 헤더
를 통해 MS-DOS 헤더의 시작을 알리고, 이 바이너리가 PE 파일인지 아닌지를 체크한다.
직접 확인해 보자. 내 컴퓨터에 있는 exe 파일 한 개를 PEview
라는 프로그램으로 열어보자.
PE 파일의 시작이 MZ 스트링(4D 5A)
로 시작하는 것을 직접 확인할 수 있다.
e_lfanew
다음으로 확인해볼 필드는 e_lfanew
이다. 이는 IMAGE_NT_HEADER
의 구조체 위치를 알아 내는 데 사용되는 값이다. 실질적인 PE의 오프셋이 어디인지 이 필드를 통해 지정하게 된다.
참고로 다음과 같은 코드로 사용하며, PIMAGE_NT_HEADERS 사용을 위한 준비 작업 정도이다.
IMAGE_NT_HEADER
다음으로 살펴볼 구조체는 IMAGE_NT_HEADER
이다.
첫 번째 멤버인 Signature
는 PE\0\0
을 표시하는 4바이트 값으로 50 45 00 00
이 된다. 이도 직접 확인해 보자.
PEview
에서 IMAGE_NT_HEADERS
를 클릭하면 제일 먼저 시작하는 부분이 50 45 00 00
이다.
옛날에는 이 signature 부분에 해커들이 자신의 표식을 남기두는 용도로도 사용했지만 현재 출시되는 운영체제에서는 이 부분이 50 45 00 00이 아니면 실행이 안되도록 설계했다고 한다.
IMAGE_FILE_HEADER
다음은 IMAGE_FILE_HEADER
이다. 파일을 실행하기 위한 가장 기본적인 데이터가 담겨 있는 구조체라 할 수 있다.
멤버들 하나씩 알아보자.
Machine
어떤 CPU
에서 이 파일이 실행될 수 있는지를 알려준다. 만약 일반 데스크톱이나 노트북에서만 리버싱을 한다면 별로 눈여겨볼 필요는 없다.
이 멤버는 다음 값 중 하나를 가질 수 있다.
- IMAGE_FILE_MACHINE_I386(0x014c) : x86를 의미
- IMAGE_FILE_MACHINE_IA64(0x0200) : Intel Itanium를 의미
- IMAGE_FILE_MACHINE_AMD64(0x8664) : X64를 의미
NumberOfSections
섹션이 몇 개 있는지 알려주는 멤버이다. 섹션이란 .text나 .data 같은 코드 섹션이나 데이터 섹션 등을 일컫는다.
MFC로 별다른 옵션 변경 없이 빌드한 파일의 경우에는 .text, .rdata, .data, .rsrc
4개의 섹션이 존재하며 NumberOfSections
의 값도 당연히 4로 표시된다.
마이크로소프트 공식 문서에 따르면 Windows 로더는 섹션 수를 96개로 제한한다고 한다.
TimeDateStamp
현재 파일이 빌드된 날짜
를 표시하는 멤버이다. 정확히는 obj
파일이 컴파일러를 통해 EXE
로 생성된 시간이라고 할 수 있는데
obj 파일만 따로 만들고 나중에 EXE로 컴파일하는 경우는 거의 없으므로 뭉뚱그려서 개발자가 EXE/DLL를 만든 시간이라고 보면 된다.
변조가 쉬우므로 항상 신뢰할 수 있는 값은 아니다. 또한 델파이
의 경우 이 멤버 값을 항상 1992년으로 표시하므로 델파이로 만든 파일은 이 필드를 통해서는 빌드 날짜를 확인할 수 없다.
즉, TimeDateStamp
의 값이 1992
로 작성된 파일은 델파이
로 만들어졌구나로 생각하면 된다. 1992년 프로그램이 지금 내 컴퓨터에 있을 확률은 거의 없으므로..
SizeOfOptionalHeader
SizeOfOptionalHeader
멤버는 IMAGE_OPTIONAL_HEADER32
구조체의 크기를 나타낸다. IMAGE_OPTIONAL_HEADER32
는 PE를 로딩하기 위한 굉장히 중요한 구조체를 담고 있는데
이 구조체는 OS마다 다를 수도 있기 때문에 PE 로더는 SizeOfOptionalHeader 값을 먼저 확인한 뒤 IMAGE_OPTIONAL_HEADER32 구조체의 크기를 처리한다.
Characteristics
이 필드는 현재 파일이 어떤 형식인지 알려준다. DLL
인지 EXE
인지 구분
하는 용도 정도로 생각하면 된다.
그러나 DLL인지 EXE인지 구분하는 방법은 이것 이외에도 많이 있으므로(파일 확장자만 봐도 구별 가능..) 눈 여겨볼 가치가 큰 멤버는 아닌 것 같다.
이번장에서는 PE 헤더의 IMAGE_DOS_HEADER, IMAGE_NT_HEADER, IMAGE_FILE_HEADER 까지만 살펴보고 다음장에서 나머지 IMAGE_OPTIONAL_HEADER, IMAGE_SECTION_HEADER를 다뤄보자.