|
继续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
|