可以看出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
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
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)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
按照它的定义,我们分别完成各个成员。
第一个成员(e_magic)是个WORD类型,占2个字节,它被用于表示一个MS-DOS兼容的文件类型,他的值是固定的0x5A4D,所以在十六进制编辑器中输入“4D5A”。 (注意:
因为我们是在十六进制编辑器下写数据,所以所有的数据格式都是十六进制式的。但是我们在开发环境中通常在数据前添加“0x”用来表示十六进制数 ,即:0x5A4D。而在十六进制编辑器中,直接写成“4D5A”即可。后面内容都照此规定书写。有一点需要说明,为什么十六进制值0x5A4D输入到十六进制编辑器中是4D5A呢?这是因为一个内存值,无论是占两个字节的WORD类型,还是占四个字节的DWORD类型等,如同我们学习数学中的十进制数值一样,都是有高低位之分的,从右向左位越来越高。然而在十六进制编辑器中,十六进制位是自左向右依次增高。因此按照高低位对齐的原则,值0x5A4D中,低位0x4D应该应该放到左边,0x5A应该放到右边。也就得到了编辑器中的4D5A。)
第2个成员到第18个成员总共58个字节,是对DOS程序环境的初始化等操作,对于我们这个程序来说,没什么影响,我们通通用“00”来填充。(如果您想对其进行详细了解,请查阅相关书籍。) (提示:
我们在此不可能把PE结构所有的知识点都面面俱到,因为他十分的庞大。当然也没有必要对他作完全彻底的掌握,只需掌握关键的地方就可以了。以后我们都将把不影响程序执行的成员填充为零,这样做,一方面使程序看起来简洁,另一方面可以使您快速定位PE结构中要重点掌握的地方。)
第19(e_lfanew)个成员非常重要,他是一个LONG类型,占4个字节,用来表示“PE文件标志”在文件中的偏移,单位是byte。而从图5-1中可以看到“PE文件标志”紧随“MS-DOS 实模式残余程序”其后。知道这一点,我们就可以计算一下,我们的“DOS MZ header”总共64 byte,后面的“MS-DOS 实模式残余程序”占112 byte, 64 + 112 = 176 byte。但是要注意,我们这里的176是十进制的,转化成十六进制是0xB0。因为是4个字节,其余三位字节应该以00补齐,所以最终的值为0x000000B0。所以在我们的十六进制编辑器中按照高低对齐的原则应该填写“B0000000”。
接下来完成“MS-DOS 实模式残余程序”,笔者已经介绍,他是用在DOS下执行的,而我们所完成的HelloWorld程序是在win32下执行的。所以这里的内容并不影响我们程序的执行。因此这里直接用“00”来填充,注意总共112 byte。 这两部分完成之后代码如图6所示: