리버싱

IMAGE_DOS_HEADER 구조와 DOS 스텁

cyanhe_wh 2021. 1. 25. 21:26
728x90
반응형

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의 세계에서는 의미가 없기 때문에 이 문자열은 눈으로 확인만하고 무시하면 된다.

728x90
반응형