typedef struct _IMAGE_DOS_HEADER { WORD e_magic; // 5A4D WORD e_cblp; // 0090 WORD e_cp; // 0003 WORD e_crlc; // 0000 WORD e_cparhdr; // 0004 WORD e_minalloc; // 0000 WORD e_maxalloc; // FFFF WORD e_ss; // 0000 WORD e_sp; // 00B8 WORD e_csum; // 0000 WORD e_ip; // 0000 WORD e_cs; // 0000 WORD e_lfarlc; // 0040 WORD e_ovno; // 0000 WORD e_res[4]; // 0000000000000000 WORD e_oemid; // 0000 WORD e_oeminfo; // 0000 WORD e_res2[10]; // 0000000000000000000000000000000000000000 DWORD e_lfanew; // 000000E0 }IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
上面是打开Windows XP环境下的notepad.exe文件显示的对应IMAGE_DOS_HEADER -----> DOS头 下面是winhex非运行情况下在硬盘打开查看对应的位置
上面选中的部分就是DOS头,对应工具的显示
后面就是DOS Stub数据,这里的数据在现在的常用系统来看是没用的,有的人成为垃圾数据,因为这是微软在设计的时候给16位系统设计使用的,知道就可以,下面选中的部分是对应winhex展示
从上面来看到PE Signature之前是站16进制的D0 ---> 十进制208 --> 208/4=52 这个52表示在winhex里面要以每4个字节进行移动,因为每行winhex是4x4=16个字节,所以再除以4 ---> 52/4=13 ,那么就可以在文件的起始位置向下数13行即可
即:00000000 ----> 000000D0 刚好13行,然后下面就是PE Signature ;
PE Signature和PE标准文件头代码
typedef struct _IMAGE_NT_HEADERS { DWORD Signature; IMAGE_FILE_HEADER FileHeader; IMAGE_OPTIONAL_HEADER32 OptionalHeader; }IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32; DWORD Signature; 4个字节 0x00004550 typedef struct _IMAGE_FILE_HEADER { WORD Machine; WORD NumberOfSections; DWORD TimeDateStamp; DWORD PointerToSymbolTable; DWORD NumberOfSymbols; WORD SizeOfOptionalHeader; WORD Characteristics; }IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
上面是选中的PE Signature 0x00004550 ;根据上述代码 _IMAGE_FILE_HEADER 结构体里面含有3个DWORD 4个WORD 总共算起来理解为5个DWORD,也就是5个4字节的宽度,4x5=20个字节,那么算的按照行来算好找些,向后数5个4字节内容即可
上面选中的就是_IMAGE_FILE_HEADER说站的宽度,下面是对应工具的展示
到了这里就要开始找_IMAGE_OPTIONAL_HEADER 就是可选PE头数据,因为可选PE头数据每个不同的应用程序都不一样,所以是不确定的,但是在上面的IMAGE_FILE_HEADER的倒数第二个字段SizeOfOptionHeader已经给出了可选PE头对齐前的实际大小宽度 ---> 00E0 --> 224 --> 224/4 = 56 --> 56/4=14 这里跟上面计算到PE Signature是一样的方法 ,所以得出结论就是从000000F8位置向后移动224字节,也就是按照行来算移动14行
上图选中的部分就是可选PE头数据,后面就是节表了,至此我们就找到了节表的起始位置
上图是对应的工具解析的可选PE头数据,不过我们发现后面还有一段数据,过来之后再到节表的起始位置,对应上图有字段的数据部分
没有字段的部分
节表数据结构说明
下面带是在WINNT.H的5345行代码获取
#define IMAGE_SIZEOF_SHORT_NAME 8 typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //8x1=8 站8个字节 union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc; DWORD VirtualAddress; DWORD SizeOfRawData; DWORD PointerToRawData; DWORD PointerToRelocations; DWORD PointerToLinenumbers; WORD NumberOfRelocations; WORD NumberOfLinenumbers; DWORD Characteristics; } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
总结:
1.Name 8个字节 一般情况下是以"\0"结尾的ASCII码字符串来标识的名称,内容可以自定义. ---> 正常情况下编译器生成的名字是.text 名称是可以修改 注意:该名称并不遵守必须以"\0"结尾的规律,如果不是以"\0"结尾,系统会截取8个字节的长度进行处理. 2.Misc --> VirtualSize 双字,该节在没有对齐前的真实尺寸,该值可以不准确。 0x00007748 --> 在内存中也可以在文件看,为实际测试观察打开的notepad.exe和未打开的notepad.exe他们大小相距7746, 而实际显示的是7748,所以这个值可能不准确,有偏差 3.VirtualAddress 节区在内存中的偏移地址。加上ImageBase才是在内存中的真正地址. ---> 这个是在内存中看偏移,是在内存中的起始地址 上述VirtualAddress的理解: (1)(在内存中)首先确定ImageBase的地址:0x01000000 这个位置就是在内存中打开之后,最开始的那个位置,是文件头4D5A--MZ那里计算开始 (2)(在内存中)VirtualAddress的地址: 0x00001000 到此就可以加上这两个地址就是节表中表示的第一个节的内容,0x01000000+0x00001000=0x01001000 (3) 得到第一个节内容的内存地址:0x01001000 (4) 节表我们可以理解为:就是一本书的目录,然后目录告诉你某一具体的内容在哪一页的位置 4.SizeOfRawData 节在文件中对齐后的尺寸.0x00007800 5.PointerToRawData 节区在文件中的偏移. 0x00000400 ---> 这个是在文件中偏移 上述PointerToRawData的理解: (1)(在文件中)这个很好理解,因为是在文件中,所以不需要考虑ImageBase,直接基于PointerToRawData的值从文件的起始位置向后加0x00000400即可 (2) 所以在文件中对应的节表位置就是0x00000400 6.PointerToRelocations 在obj文件中使用 对exe无意义 7.PointerToLinenumbers 行号表的位置 调试的时候使用 8.NumberOfRelocations 在obj文件中使用 对exe无意义 9.NumberOfLinenumbers 行号表中行号的数量 调试的时候使用 10.Characteristics 节的属性 ---> 可执行,可读,里面有代码
下面依然按照上面打开notepad.exe的实例来看看
上面标识的就是节表中Name的8个字节标识,默认情况下,编译器生成的名称就是.text 但是这个是可以更改的,相关具体说明,我已经在上面解释过了,我们继续看
union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc;
上述显示:0x00007748 ----> (在内存中)是指向Misc这个变量,而这个变量是被定义为联合体,里面有两个4字节的值,分别为PhysicalAddress和 VirtualSize 我们只需要看VirtualSize,而看这个值就可以直接理解为看Misc这个变量的值就可以;
Misc ---> VirtualSize 简单的理解就是表示在没有对齐的情况下其实际的尺寸(也可以理解为大小),看看具体实例
在文件中查看:
起始位置:0x00000400 ,但是在计算的时候从前一位算起,就是0x000003FF
上面显示的未对齐的结束位置是0x00007B45 ----> 我们计算下 7B45-03FF=7746 ---> 显示7746个字节,而这里实际显示是7748 有可能不准确,我们看看内存中是什么样子的
起始位置:0x01001000
未对齐的结束位置:0x01008745 ---> 我们计算下 ---> 8745-0FFF=7746个字节
0x00001000 --- > VirtualAddress 上图中的值表示节区在内存中的偏移地址。加上ImageBase就是在内存中的真正地址,具体解释,为在上面的总结中已经描述了,下面我们直接看实例结果:
0x01000000+0x00001000=0x01001000
PE工具解析出来的ImageBase地址是:0x01000000
得知ImageBase地址,就是文件被打开在内存中的起始地址,我们在winhex看看
此时的偏移地址就是我上面说的,VirtualAddress ---> 0x00001000 将ImageBase和VirtualAddress地址相加便是节表中第一个指向内容的位置 0x01000000+0x00001000=0x01001000 我们找到地址为:0x01001000
上述指向的值 ---> SizeOfRawData (在文件中,没被打开)她表示节在文件中对齐后的尺寸或者理解为对齐后的大小; ----> .0x00007800 下面我们看看具体的实例
上面显示了对齐前后的起始位置和结束位置,我们需要找到对齐后的结束位置然后减去对齐后的起始位置的前一个位置即可
上面是第一个节表结束的位置0x00007BFF ;到此可以计算出来 SizeOfRawData的值 ---> 0x000007BFF - 0x000003FF = 0x00007800
上述指向的值 ----> PointerToRawData 节区在文件中的偏移. 0x00000400 (在文件中,没被打开);相关的具体解释我在上面的总结中解释过了,为这里直接给出实例,所以从文件的起始位置直接找到地址为0x00000400即可
后面的4个值无意义,是针对obj文件的,咱不看了,直接看最后一个值---> Characteristics 节的属性
总结下来的结果是:Characteristics 节的属性 ---> 可执行,可读,里面有代码 ,我们具体看看,她对应的值是:0x60000020
我们看看PE工具总结出来的属性特征
标志(属性块) 常用特征值对照表: [值:00000020h] [IMAGE_SCN_CNT_CODE // Section contains code.(包含可执行代码)] [值:00000040h] [IMAGE_SCN_CNT_INITIALIZED_DATA // Section contains initialized data.(该块包含已初始化的数据)] [值:00000080h] [IMAGE_SCN_CNT_UNINITIALIZED_DATA // Section contains uninitialized data.(该块包含未初始化的数据)] [值:00000200h] [IMAGE_SCN_LNK_INFO // Section contains comments or some other type of information.] [值:00000800h] [IMAGE_SCN_LNK_REMOVE // Section contents will not become part of image.] [值:00001000h] [IMAGE_SCN_LNK_COMDAT // Section contents comdat.] [值:00004000h] [IMAGE_SCN_NO_DEFER_SPEC_EXC // Reset speculative exceptions handling bits in the TLB entries for this section.] [值:00008000h] [IMAGE_SCN_GPREL // Section content can be accessed relative to GP.] [值:00500000h] [IMAGE_SCN_ALIGN_16BYTES // Default alignment if no others are specified.] [值:01000000h] [IMAGE_SCN_LNK_NRELOC_OVFL // Section contains extended relocations.] [值:02000000h] [IMAGE_SCN_MEM_DISCARDABLE // Section can be discarded.] [值:04000000h] [IMAGE_SCN_MEM_NOT_CACHED // Section is not cachable.] [值:08000000h] [IMAGE_SCN_MEM_NOT_PAGED // Section is not pageable.] [值:10000000h] [IMAGE_SCN_MEM_SHARED // Section is shareable(该块为共享块).] [值:20000000h] [IMAGE_SCN_MEM_EXECUTE // Section is executable.(该块可执行)] [值:40000000h] [IMAGE_SCN_MEM_READ // Section is readable.(该块可读)] [值:80000000h] [IMAGE_SCN_MEM_WRITE // Section is writeable.(该块可写)]
上述给的结果是:0x60000020
Characteristics: 0x000001fc 60000020 [标志(块属性):20000000h 40000000h 00000020h ]
4个字节,我们先理解低位的0020 --- > 转换为二进制 --> 0000 0000 0010 0000 ----> 这个二进制中,从右往左边数,实际根本上就是将其第六位置为“1” 才得到的属性 ---> 包含可执行代码
[值:00000020h] [IMAGE_SCN_CNT_CODE // Section contains code.(包含可执行代码)]
那么前面的6000该怎么理解呢?特征中是没6的,实际上是可以通过特征中给的4和2进行确认,也就是下面的
[值:20000000h] [IMAGE_SCN_MEM_EXECUTE // Section is executable.(该块可执行)] [值:40000000h] [IMAGE_SCN_MEM_READ // Section is readable.(该块可读)]
由此我们看到Characteristics ---> 60000020 ---> 总结出来就是:可读,可执行,里面有代码