PE 파일은 몇십 파이트의 MS-DOS 스텁(Stub)으로 시작한다.
MS-DOS 스텁은 예전의 MS-DOS MZ 포맷으로 PE에서는 크게 의미가 없다.
덤프에서 "This program cannot be run in DOS mode."라는 식별 가능한 문자열을 볼 수 있을 뿐이다.
PE 프로그램을 예전 MS-DOS나 윈도우 3.1에서 실행하면 바로 이 문자열이 출력된다.
사실 MS-DOS 스텁은 위의 문자열을 출력하기 위한 조그만한 16비트 도스용 애플리케이션에 지나지 않는다.
DOS 스텁은 64바이트의 IMAGE_DOS_HEADER 구조체와 해당 스텁 코드로 이루어진다.
WinNT.h에는 IMAGE_DOS_HEADER 구조체가 다음과 같이 정의되어 있다.
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 초기 (상대) SS 값
WORD e_sp; // Initial SP value 초기 SP 값
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value 초기 IP 값
WORD e_cs; // Initial (relative) CS value 초기 (상대적) CS 값
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) OEM 식별자 (e_oeminfo 용)
WORD e_oeminfo; // OEM information; e_oemid specific OEM 정보; e_oemid 특정
WORD e_res2[10]; // Reserved words 예약어
LONG e_lfanew; // File address of new exe header 새 exe 헤더의 파일 주소
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
덤프에서 0x0~0x3F까지 IMAGE_DOS_HEADER 구조체 64Byte를 나타낸 것이다.
이 구조체 중 실제로 쓸모가 있는 것은 첫 바이트 필드인 e_magic과 마지막 필드인 e_lfanew다.
이외 나머지 필드는 무시해도 좋다.
1) e_magic
모든 윈도우 PE 파일(EXE와 DLL, 그리고 사실은 DLL이지만 확장자만 바꾼 OCX나 CPL과 커널 모드에서 구동되는 디바이스 드라이버 파일인 SYS, 닷넷(.NET) 애플리케이션까지 포함)은 IMAGE_DOS_HEADER로 시작한다.
이때 e_magic은 아스키 코드이니 "MZ"로 고정되어 있다.
앞의 구조체에서는 e_magic이 WORD 타입으로 되어 있는데, "MZ"를 워드 단위로 읽으면 0x5A4D (0x5A='Z', 0x4D='M')가 된다. 물론 리틀 엔디언(Little Endian) 방식이기 때문에 워드의 상/하위 바이트가 바뀐다!
이 "MZ"는 도스를 설계한 사람 중의 한 명인 Mark Zbikowski의 이니셜이다.
이 값은 WinNT.h에 매크로로 정의되어 있다.
#define IMAGE_DOS_SIGNATURE 0x5A4D // MZ
PE 파일을 열었을 때 처음으로 체크할 항목이 이 매직 넘버다.
PIMAGE_DOS_HEADER pdh = (PIMAGE_DOS_HEADER)m_pImgView;
if (pdh->e_magic != IMAGE_DOS_SIGNATURE)
throw _T("윈도우 실행 파일 포맷이 아닙니다.");
PEPlus 클래스는 인라인 함수 정의를 통해 다음과 같이 도스 시그니처 체크 함수를 제공한다.
inline bool PEPlus::IsDosSigniture(PBYTE pImgBase)
{
return (*PWORD(pImgBase) == IMAGE_DOS_SIGNATURE);
}
2) e_lfanew
이제 일련의 워드 필드들은 모두 무시하고, 마지막 LONG 타입의 e_lfanew 필드에 주목하기 바란다.
이 필드는 실제 PE 파일의 시작이라고 할 수 있는 IMAGE_NT_HEADERS 구조체의 시작 오프셋 값을 가진다.
헥사 값이 "F8 00 00 00"이며, DWORD 값으로는 0x000000F8이 된다.
오프셋 0x000000F8 위치의 값을 확인해보면 헥사 "50 45"로 시작함을 알 수 있다.
이 값은 아스키 코드로 "PE"며, 이것은 실제 PE 포맷의 시작을 의미하는 시그니처다.
따라서 PE 포맷의 e_lfanew 필드를 통해서 실제 시작 오프셋을 획득할 수 있다.
PE 파일을 열어서 파일의 선두에서 IMAGE_DOS_HEADER를 읽어들인 다음 e_magic이 "MZ"인지 확인 후,
e_lfanew 필드 값을 읽어들여 이 값만큼 파일 포인터를 이동시키면 본격적인 PE 파일 포맷을 분석할 수 있게 된다.
#define GET_NT_OFFSET(ib) (PIMAGE_DOS_HEADER(ib)-> e_lfanew)
#define GET_NT_HDRPTR(ib) ((PBYTE)(ib) + PIMAGE_DOS_HEADER(ib)->e_lfanew)
IMAGE_DOS_HEADER 구조체를 제외한 나머지 부분을 살펴보면, 16비트 프로그램에 해당하는 도스 스텁 부분은 정확히 오프셋 0x000000F7에서 끝남을 알 수 있다. 0x4E 부터 0x75까지는 "This program cannot be run in DOS mode."라는 아스키 문자열이다. 이 도스 스텁은 블록의 문자열을 출력하는 코드를 의미한다. 도스나 윈도우 3.1에서 윈도우 PE를 실행하면 16비트 운영체제에서 32비트나 64비트 애플리케이션을 실행할 수 없음을 의미하는 이 문자열이 출력되고 프로그램은 종료된다. PE의 세계에서는 의미가 없기 때문에 이 문자열은 눈으로 확인만하고 무시하면 된다.