|
继续PE系列笔记的更新
PE别的笔记索引可前往:
PE文件笔记一 PE先容
前面学习了PE的DOS部首和PE文件头,这次学习的结构为PE节表
PS:关于PE文件头中扩展PE头的数据目次项,其中包含了导入表、导出表、重定位表等等,临时留作之后
PE节表
PE节表作用
表示Image的section头格式
PE节表结构
PE节表结构对应C中的结构体说明多个IMAGE_SECTION_HEADER多个_IMAGE_SECTION_HEADER每个_IMAGE_SECTION_HEADER描述背面的一个节结构体截图
在winnt.h中找到_IMAGE_SECTION_HEADER,得到以下截图(具体查找对应C结构体方法在PE文件笔记一 PE先容中已经说明了,这里不再赘述)
结构体代码
#define IMAGE_SIZEOF_SHORT_NAME 8typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; 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;#define IMAGE_SIZEOF_SECTION_HEADER 40结构体成员分析
相比于扩展PE头,节表的成员并不算太多,但有部分成员仅针对.obj文件
下表为结构成员对应数据宽度和说明,加黑的成员为重点
成员数据宽度说明NameBYTE[8]=8字节节名称Misc.PhysicalAddressDWORD(4字节)节的文件地址Misc.VirtualSizeDWORD(4字节)节的虚拟大小VirtualAddressDWORD(4字节)节在内存中的偏移地址SizeOfRawDataDWORD(4字节)节在文件中对齐后的尺寸PointerToRawDataDWORD(4字节)节区在文件中的偏移PointerToRelocationsDWORD(4字节).obj文件有效PointerToLinenumbersDWORD(4字节)调试相关NumberOfRelocationsWORD(2字节).obj文件有效NumberOfLinenumbersWORD(2字节)行号表中行号的数目CharacteristicsDWORD(4字节)节的属性Name
官方翻译
一个8字节、用空填充的UTF-8字符串。如果字符串长度正好是8个字符,则没有结束空字符。对于较长的名称,该成员包含一个正斜杠(/),背面是十进制数字的ASCII表示情势,该数字是字符串表中的偏移量。可执行映像不利用字符串表,也不支持超过8个字符的节名
通俗版
ASCII字符串 可自界说 只截取8个 可以8个字节都是名字
Misc
官方翻译
Misc 双字,该字段是一个union型的数据,这是该节在没有对齐前的真实尺寸,该值可以不正确
通俗版
这是一个团结结构,可以利用下面两个值其中的任何一个,一样平常是取Misc.VirtualSize
Misc.PhysicalAddress
文件地址
Misc.VirtualSize
节加载到内存时的总大小,以字节为单位。如果该值大于SizeOfRawData成员,则该section将被0填充。此字段仅对可执行Image有效,对于object files应设置为0
VirtualAddress
官方翻译
section载入内存时的第一个字节的地址,相对于image base。对于object files,这是应用重定位之前的第一个字节的地址
通俗版
在内存中的偏移地址,加上ImageBase才是在内存中的真正地址(VA)
VA:Full Name Virtual Address(全名虚拟地址), is the in-memory virtual address(是内存中的虚拟地址)
VirtualAddress又被称为节区的RVA地址,RVA:Relative Virtual Offset (相对虚拟偏移)
VA = RVA(VirtualAddress) + ImageBase ,即 内存中的虚拟地址 = 虚拟地址 + 镜像基地址
SizeOfRawData
官方翻译
磁盘上初始化数据的大小,以字节为单位。这个值必须是IMAGE_OPTIONAL_HEADER结构文件对齐FileAlignment成员的倍数。如果该值小于VirtualSize成员,则节的其余部分将被填充为0。如果该节只包含未初始化的数据,则该成员为零
通俗版
节在文件中对齐后的尺寸
PointerToRawData
官方翻译
指向COFF文件中的第一页的文件指针。这个值必须是IMAGE_OPTIONAL_HEADER结构文件对齐FileAlignment成员的倍数。如果一个section只包含未初始化的数据,则将该成员设为0
通俗版
节区在文件中的偏移,又被称为FOA:File Offset Address 文件偏移地址
PointerToRelocations
官方翻译
指向该节重定位项开始的文件指针。如果没有重新定位,则此值为零
通俗版
在".obj"文件中利用,指向重定位表的指针
PointerToLinenumbers
官方翻译
指向section行号表开头的文件指针。如果没有COFF line numbers,该值为0
通俗版
行号表的位置(供调试用)
NumberOfRelocations
官方翻译
section重定位表项的数目。对于可执行映像,此值为0
通俗版
重定位表的个数(在OBJ文件中利用)
NumberOfLinenumbers
官方翻译
section的行号条目的数目
通俗版
行号表中行号的数目
Characteristics
官方翻译
节的特征。界说了以下值
宏界说值寄义无0x00000000保留无0x00000001保留无0x00000002保留无0x00000004保留IMAGE_SCN_TYPE_NO_PAD0x00000008该节不得填塞至下一界限线。这个标记过时了,被IMAGE_SCN_ALIGN_1BYTES取代无0x00000010保留IMAGE_SCN_CNT_CODE0x00000020该节包含可执行代码IMAGE_SCN_CNT_INITIALIZED_DATA0x00000040该节包含初始化的数据IMAGE_SCN_CNT_UNINITIALIZED_DATA0x00000080该节包含未初始化的数据IMAGE_SCN_LNK_OTHER0x00000100保留IMAGE_SCN_LNK_INFO0x00000200该节包含解释或其他信息。这只对object files有效无0x00000400保留IMAGE_SCN_LNK_REMOVE0x00000800该节将不会成为image的一部分。这只对object files有效。IMAGE_SCN_LNK_COMDAT0x00001000该节包含COMDAT数据。这只对object files有效。无0x00002000保留IMAGE_SCN_NO_DEFER_SPEC_EXC0x00004000该节包含重置TLB项中的speculative异常处理位IMAGE_SCN_GPREL0x00008000该节包含通过全局指针引用的数据无0x00010000保留IMAGE_SCN_MEM_PURGEABLE0x00020000保留IMAGE_SCN_MEM_LOCKED0x00040000保留IMAGE_SCN_MEM_PRELOAD0x00080000保留IMAGE_SCN_ALIGN_1BYTES0x00100000在1字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_2BYTES0x00200000在2字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_4BYTES0x00300000在4字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_8BYTES0x00400000在8字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_16BYTES0x00500000在16字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_32BYTES0x00600000在32字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_64BYTES0x00700000在64字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_128BYTES0x00800000在128字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_256BYTES0x00900000在256字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_512BYTES0x00A00000在512字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_1024BYTES0x00B00000在1024字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_2048BYTES0x00C00000在2048字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_4096BYTES0x00D00000在4096字节的界限上对齐数据。这只对object files有效IMAGE_SCN_ALIGN_8192BYTES0x00E00000在8192字节的界限上对齐数据。这只对object files有效IMAGE_SCN_LNK_NRELOC_OVFL0x01000000该节包含扩展的重新定位。该节的重定位计数超过了节头中为其保留的16位。如果节头中的NumberOfRelocations字段为0xffff,则实际的重定位计数存储在第一次重定位的VirtualAddress字段中。如果设置了IMAGE_SCN_LNK_NRELOC_OVFL,而且该section中的重定位值小于0xffff,则会产生错误IMAGE_SCN_MEM_DISCARDABLE0x02000000该节可以根据需要抛弃IMAGE_SCN_MEM_NOT_CACHED0x04000000不能缓存该节IMAGE_SCN_MEM_NOT_PAGED0x08000000该节不能分页IMAGE_SCN_MEM_SHARED0x10000000该节可以在内存中共享IMAGE_SCN_MEM_EXECUTE0x20000000该节可以作为代码执行IMAGE_SCN_MEM_READ0x40000000该节可以读IMAGE_SCN_MEM_WRITE0x80000000该节可以写通俗版
实战分析
从先前分析的扩展PE头的末端开始看起,选中部分为节表
因为有多个节,这里取第一个节进行分析,按顺序依次将数据填入对应的成员得到:
成员值说明Name2E 74 65 78 74 00 00 00对应ASCII为 .text,即节名Misc0x001990A9VirtualAddress0x00001000RVA=0x1000SizeOfRawData0x00199200节在文件中对齐后的尺寸为0x00199200PointerToRawData0x00000400FOA=0x400PointerToRelocations0x00000000PointerToLinenumbers0x00000000NumberOfRelocations0x0000NumberOfLinenumbers0x0000Characteristics0x60000020详见下方Name
该节的节名为 .text
VirtualAddress
节的RVA为0x1000
通过RVA可以得到VA=RVA+ImageBase(在内存中虚拟的地址 = 虚拟地址 + 镜像基地址)
ImageBase在前面的扩展PE头中已经得知是0x400000,不清晰的可以回顾PE文件笔记五 PE文件头之扩展PE头,这里不再赘述
所以得到VA=0x1000+0x400000=0x401000
为了验证这一点,将步伐启动,使其加载到内存后再用Winhex查看其状态(具体流程在PE文件笔记二 PE文件的两种状态中已经说明)
得到:
可以清晰地看到,第一个节的位置对应VA,验证完毕
Misc和SizeOfRawData
Misc:该节在没有对齐前的真实尺寸为0x001990A9
团结前面得到的VA,可以算出,该节在内存中的末尾位置为:VA+Misc=0x401000+0x001990A9=0x59A0A9
于是要转到相应的位置进行查看:
在WinHex的底部找到偏移量:XXXX,单击
在弹出的窗口中修改要跳转的VA(虚拟地址)
跳转后得到:
可以看到,已经跳转到了第一个节的末尾
SizeOfRawData:该节在文件中对齐后的尺寸为0x00199200
团结前面得到的VA,可以算出,该节在内存中的末尾位置为:VA+SizeOfRawData=0x401000+0x00199200=0x59A200
于是要转到相应的位置进行查看:
发现附近都是00,因为该地址为节在内存中的首地址+文件对齐后的地址,在内存中(运行态)无效
而且此时会发现SizeOfRawData=0x00199200=(Misc ÷ FileAlignment)向上取整×FileAlignment
即SizeOfRawData=0x00199200=(0x001990A9整除0x200+1)×0x200=(0xCC8+1)×0x200 = 0xCC9 × 0x200 = 0x00199200
满足SizeOfRawData的界说
其实SizeOfRawData在PE文件笔记二 PE文件的两种状态中也已经说明了,这里主要是夸大SizeOfRawData在运行态时无效,且验证了SizeOfRawData在运行态时的来源,有关文件对齐和内存对齐等的知识可以前往回顾
在内存中(运行态时),实际上 该节在内存中的末尾位置应该为:VA+内存对齐后的大小
但是在节的属性中并没有表示内存对齐后大小的成员,内存对齐后的大小是如何得来的?
内存对齐后的大小
决定内存对齐后的大小的因素有2个:
- 内存对齐:即SectionAlignment
- Max{Misc,SizeOfRawData}:Misc和SizeOfRawData的最大值
内存对齐后的大小 = (Max{Misc,SizeOfRawData} ÷ SectionAlignment) 向上取整 × SectionAlignment
取SizeOfRawData很容易理解,但为什么还和Misc有关?
Misc表示的是实际大小难道不是一定小于文件对齐后的大小吗?
并不是,实际大小也可能要比文件对其后的大小要大,就拿全局变量为例
全局变量可以分为两种:有初始值的和没有初始值的
有初始值的全局变量在文件中就已经为其分配了空间,而没有初始值的全局变量只有到步伐加载到内存中(运行态)后才会为其分配空间
假设当前存储的全局变量都是没有有初始值的,即在文件中没有为其分配空间,也就是在文件中大小为0,这也就导致SizeOfRawData为0,因此,此时的Misc > SizeOfRawData
所以内存对齐后的大小要综合Misc和SizeOfRawData决定
于是按照上面的公式计算:
内存对齐后的大小 = (Max{0x001990A9,0x00199200} ÷ 0x1000) 向上取整 × 0x1000 = (0x199200 ÷ 0x1000)向上取整 × 0x1000 = 0x19A000
该节在内存中的末尾位置应该为:VA+内存对齐后的大小 = 0x401000+0x19A000=0x59B000
再转到相应位置去查看:
发现已经到达下一个节,和下一个节的起始位置根据VirtualAddress + ImageBase = 0x400000+0x0019b000=0x59B000相匹配
PointerToRawData
节区在文件中的偏移为0x400,这里引入了一个概念:FOA,即文件偏移地址
所以PointerToRawData=FOA=文件偏移地址,同样在运行态无效,在PE文件笔记二 PE文件的两种状态也可以回顾
Characteristics
此时Characteristics=0x60000020=0x40000000+0x20000000+0x00000020
对照前面的表格:
宏界说值寄义IMAGE_SCN_CNT_CODE0x00000020该节包含可执行代码IMAGE_SCN_MEM_EXECUTE0x20000000该节可以作为代码执行IMAGE_SCN_MEM_READ0x40000000该节可以读所以得到了该节的属性为:包含可执行代码、可以作为代码执行、可以读
自写代码剖析节表
在先前代码的基础上,进一步改进
// PE.cpp : Defines the entry point for the console application.//#include #include #include #include //在VC6这个比较旧的环境里,没有界说64位的这个宏,需要本身界说,在VS2019中无需本身界说#define IMAGE_FILE_MACHINE_AMD64 0x8664int main(int argc, char* argv[]){ //创建DOS对应的结构体指针 _IMAGE_DOS_HEADER* dos; //读取文件,返回文件句柄 HANDLE hFile = CreateFileA("C:\\Users\\lyl610abc\\Desktop\\dbgview64.exe", GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0); //根据文件句柄创建映射 HANDLE hMap = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, 0); //映射内容 LPVOID pFile = MapViewOfFile(hMap, FILE_MAP_READ, 0, 0, 0); //类型转换,用结构体的方式来读取 dos = (_IMAGE_DOS_HEADER*)pFile; //输出dos->e_magic,以十六进制输出 printf("dos->e_magic:%X\n", dos->e_magic); //创建指向PE文件头标记的指针 DWORD* peId; //让PE文件头标记指针指向其对应的地址=DOS首地址+偏移 peId = (DWORD*)((UINT)dos + dos->e_lfanew); //输出PE文件头标记,其值应为4550,否则不是PE文件 printf("peId:%X\n", *peId); //创建指向可选PE头的第一个成员magic的指针 WORD* magic; //让magic指针指向其对应的地址=PE文件头标记地址+PE文件头标记大小+尺度PE头大小 magic = (WORD*)((UINT)peId + sizeof(DWORD) + sizeof(_IMAGE_FILE_HEADER)); //输出magic,其值为0x10b代表32位步伐,其值为0x20b代表64位步伐 printf("magic:%X\n", *magic); //根据magic判断为32位步伐还是64位步伐 switch (*magic) { case IMAGE_NT_OPTIONAL_HDR32_MAGIC: { printf("32位步伐\n"); //确定为32位步伐后,就可以利用_IMAGE_NT_HEADERS来接收数据了 //创建指向PE文件头的指针 _IMAGE_NT_HEADERS* nt; //让PE文件头指针指向其对应的地址 nt = (_IMAGE_NT_HEADERS*)peId; printf("Machine:%X\n", nt->FileHeader.Machine); printf("Magic:%X\n", nt->OptionalHeader.Magic); //创建一个指针数组,该指针数组用来存储所有的节表指针 //这里相当于_IMAGE_SECTION_HEADER* sectionArr[nt->FileHeader.NumberOfSections],声明了一个动态数组 _IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**) malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections); //创建指向块表的指针 _IMAGE_SECTION_HEADER* sectionHeader; //让块表的指针指向其对应的地址 sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS)); //计数,用来计算块表地址 int cnt = 0; //比较 计数 和 块表的个数,即遍历所有块表 while(cnt< nt->FileHeader.NumberOfSections){ //创建指向块表的指针 _IMAGE_SECTION_HEADER* section; //让块表的指针指向其对应的地址=第一个块表地址+计数*块表的大小 section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER)*cnt); //将得到的块表指针存入数组 sectionArr[cnt++] = section; //输出块表名称 printf("%s\n", section->Name); } break; } case IMAGE_NT_OPTIONAL_HDR64_MAGIC: { printf("64位步伐\n"); //确定为64位步伐后,就可以利用_IMAGE_NT_HEADERS64来接收数据了 //创建指向PE文件头的指针 _IMAGE_NT_HEADERS64* nt; nt = (_IMAGE_NT_HEADERS64*)peId; printf("Machine:%X\n", nt->FileHeader.Machine); printf("Magic:%X\n", nt->OptionalHeader.Magic); //创建一个指针数组,该指针数组用来存储所有的节表指针 //这里相当于_IMAGE_SECTION_HEADER* sectionArr[nt->FileHeader.NumberOfSections],声明了一个动态数组 _IMAGE_SECTION_HEADER** sectionArr = (_IMAGE_SECTION_HEADER**)malloc(sizeof(_IMAGE_SECTION_HEADER*) * nt->FileHeader.NumberOfSections); //创建指向块表的指针 _IMAGE_SECTION_HEADER* sectionHeader; //让块表的指针指向其对应的地址,区别在于这里加上的偏移为_IMAGE_NT_HEADERS64 sectionHeader = (_IMAGE_SECTION_HEADER*)((UINT)nt + sizeof(_IMAGE_NT_HEADERS64)); //计数,用来计算块表地址 int cnt = 0; //比较 计数 和 块表的个数,即遍历所有块表 while (cnt < nt->FileHeader.NumberOfSections) { //创建指向块表的指针 _IMAGE_SECTION_HEADER* section; //让块表的指针指向其对应的地址=第一个块表地址+计数*块表的大小 section = (_IMAGE_SECTION_HEADER*)((UINT)sectionHeader + sizeof(_IMAGE_SECTION_HEADER) * cnt); //将得到的块表指针存入数组 sectionArr[cnt++] = section; //输出块表名称 printf("%s\n", section->Name); } break; } default: { printf("error!\n"); break; } } return 0;}运行结果
32位运行结果
64位运行结果
代码说明
代码基于上一次的笔记PE文件笔记五 PE文件头之扩展PE头增长了对块表的剖析
此次代码中用到了动态声明数组,只不过声明的数组为指针数组
关于指针数组可以回顾逆向基础笔记二十三 汇编 指针(四)
这里增补一下动态数组的声明:
#include #include int main() { //普通的数组声明 int arr[5]; //动态的数组声明 int num = 5; int* arr2 =(int*) malloc(sizeof(int) * 5); return 0;}普通的数组声明转动态的数组声明
#include #include int main() { //普通的数组声明 类型 arr[5]; //动态的数组声明 int num = 5; 类型* arr2 =(类型*) malloc(sizeof(类型) * 5); return 0;}对于前面的代码无非就是类型为_IMAGE_SECTION_HEADER*的情况
代入可得
#include #include int main() { //普通的数组声明 _IMAGE_SECTION_HEADER* arr[5]; //动态的数组声明 int num = 5; _IMAGE_SECTION_HEADER** arr2 =(_IMAGE_SECTION_HEADER**) malloc(sizeof(_IMAGE_SECTION_HEADER*) * 5); return 0;}说明
大抵将PE文件的各个结构都说明了一遍,数据目次项之后也会增补,掌握了结构后,就要开始操作这些结构了
在节表中比较重要的成员就是和内存对齐或文件对齐有关的那几个成员,这次的笔记也稍微提到了FOA、RVA、VA的概念,为后续作个铺垫
PE文件的学习可能有些枯燥,但只有在相识了其结构以后才能更好地搞事情~( ̄▽ ̄)~*
如今看似只是做个类似弱化的PEID,只能读取PE文件的相关数据貌似没有什么意思
后续有了PE的知识,就可以团结知识,来修改步伐使其出现我们想要的内容以及本身写个保护壳等等,尽请期待
附件
附上本笔记中分析的EverEdit文件:点我下载
来源:http://www.12558.net
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有帐号?立即注册
x
|