PE文件-节表--转自iczelion,附vc示范
2011-07-16 22:56
519 查看
我们已经学了许多关于 DOS header 和 PE header 的知识。接下来就该轮到 section table(节表)了。节表其实就是紧挨着 PE header 的一结构数组。该数组成员的数目由 file header (IMAGE_FILE_HEADER) 结构中 NumberOfSections 域的域值来决定。节表结构又命名为 IMAGE_SECTION_HEADER。
IMAGE_SIZEOF_SHORT_NAME equ 8
IMAGE_SECTION_HEADER STRUCT
Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?)
union Misc
PhysicalAddress dd ?
VirtualSize dd ?
ends
VirtualAddress dd ?
SizeOfRawData dd ?
PointerToRawData dd ?
PointerToRelocations dd ?
PointerToLinenumbers dd ? 哦
NumberOfRelocations dw ?
NumberOfLinenumbers dw ?
Characteristics dd ?
IMAGE_SECTION_HEADER ENDS
同样,不是所有成员都是很有用的,我们只关心那些真正重要的。
现在我们已知晓 IMAGE_SECTION_HEADER 结构,再来模拟一下 PE装载器的工作吧:
读取 IMAGE_FILE_HEADER 的 NumberOfSections域,知道文件的节数目。
SizeOfHeaders 域值作为节表的文件偏移量,并以此定位节表。
遍历整个结构数组检查各成员值。
对于每个结构,我们读取PointerToRawData域值并定位到该文件偏移量。然后再读取SizeOfRawData域值来决定映射内存的字节数。将VirtualAddress域值加上ImageBase域值等于节起始的虚拟地址。然后就准备把节映射进内存,并根据Characteristics域值设置属性。
遍历整个数组,直至所有节都已处理完毕。
注意我们并没有使用节名: 这其实并不重要。
vc代码:
遍历节表的步骤:
PE文件有效性校验。
定位到 PE header 的起始地址。
从 file header 的NumberOfSections域获取节数。
通过两种方法定位节表: ImageBase+SizeOfHeaders 或者 PE header的起始地址+ PE header结构大小。 (节表紧随 PE header)。如果不是使用文件映射的方法,可以用SetFilePointer 直接将文件指针定位到节表。节表的文件偏移量存放在 SizeOfHeaders域里。(SizeOfHeaders 是 IMAGE_OPTIONAL_HEADER 的结构成员)
处理每个 IMAGE_SECTION_HEADER 结构。
IMAGE_SIZEOF_SHORT_NAME equ 8
IMAGE_SECTION_HEADER STRUCT
Name1 db IMAGE_SIZEOF_SHORT_NAME dup(?)
union Misc
PhysicalAddress dd ?
VirtualSize dd ?
ends
VirtualAddress dd ?
SizeOfRawData dd ?
PointerToRawData dd ?
PointerToRelocations dd ?
PointerToLinenumbers dd ? 哦
NumberOfRelocations dw ?
NumberOfLinenumbers dw ?
Characteristics dd ?
IMAGE_SECTION_HEADER ENDS
同样,不是所有成员都是很有用的,我们只关心那些真正重要的。
Field | Meanings |
---|---|
Name1 | 事实上本域的名称是"name",只是"name"已被MASM用作关键字,所以我们只能用"Name1"代替。这儿的节名长不超过8字节。记住节名仅仅是个标记而已,我们选择任何名字甚至空着也行,注意这里不用null结束。命名不是一个ASCIIZ字符串,所以不用null结尾。 |
VirtualAddress | 本节的RVA(相对虚拟地址)。PE装载器将节映射至内存时会读取本值,因此如果域值是1000h,而PE文件装在地址400000h处,那么本节就被载到401000h。 |
SizeOfRawData | 经过文件对齐处理后节尺寸,PE装载器提取本域值了解需映射入内存的节字节数。(译者注: 假设一个文件的文件对齐尺寸是0x200,如果前面的 VirtualSize域指示本节长度是0x388字节,则本域值为0x400,表示本节是0x400字节长)。 |
PointerToRawData | 这是节基于文件的偏移量,PE装载器通过本域值找到节数据在文件中的位置。 |
Characteristics | 包含标记以指示节属性,比如节是否含有可执行代码、初始化数据、未初始数据,是否可写、可读等。 |
读取 IMAGE_FILE_HEADER 的 NumberOfSections域,知道文件的节数目。
SizeOfHeaders 域值作为节表的文件偏移量,并以此定位节表。
遍历整个结构数组检查各成员值。
对于每个结构,我们读取PointerToRawData域值并定位到该文件偏移量。然后再读取SizeOfRawData域值来决定映射内存的字节数。将VirtualAddress域值加上ImageBase域值等于节起始的虚拟地址。然后就准备把节映射进内存,并根据Characteristics域值设置属性。
遍历整个数组,直至所有节都已处理完毕。
注意我们并没有使用节名: 这其实并不重要。
vc代码:
void main() { //将文件映射到内存 PVOID pMapping = MapFileToView(PE_FILE_NAME); //获取文件头 IMAGE_NT_HEADERS* lpImageNtHeader = GetPeHeader(pMapping); if (lpImageNtHeader) { //显示文件头信息 PARSE_NT_HEADER_CALLBACK(lpImageNtHeader); //显示文件节头信息 PARSE_SECTION_HEADER_CALLBACK(GET_IMAGE_SECTION_HEADER(lpImageNtHeader),GET_IMAGE_NUMBER_OF_SECTIONS(lpImageNtHeader)); //分析导入表 PARSE_IMPORT_TABLE_CALLBACK(&(lpImageNtHeader->OptionalHeader.DataDirectory[1]),pMapping); } //取消映射 UnmapViewOfFile(pMapping); pMapping = NULL; } /* 映射文件到内存映像 */ PVOID MapFileToView(LPCSTR filename) { HANDLE fHandle = ::CreateFile(filename, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); if (fHandle==INVALID_HANDLE_VALUE) { PROCESS_FAILURE(CREATE_FILE_FAILURE); } HANDLE hMapping = ::CreateFileMapping( fHandle, NULL, PAGE_READONLY, NULL, NULL, NULL ); if (hMapping==NULL) { CloseHandle(fHandle); PROCESS_FAILURE(CREATE_MAPPING_FILE); } LPVOID pMapping = ::MapViewOfFile(hMapping,FILE_MAP_READ,NULL,NULL,NULL); if (pMapping==NULL) { PROCESS_FAILURE(MAP_VIEW_FAILURE); } CloseHandle(hMapping); CloseHandle(fHandle); hMapping = NULL; fHandle = NULL; return pMapping; } /* 验证PE入口函数 */ BOOL Validate(LPVOID pMapping) { //1.validate IMAGE_DOS_HEADER IMAGE_DOS_HEADER * dosHeader = (IMAGE_DOS_HEADER*)pMapping; // if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE) // { // WRITE_LINE(VALID_DOS_SIGNATURE); // } IMAGE_NT_HEADERS * nt_header=(IMAGE_NT_HEADERS*)((byte*)pMapping+dosHeader->e_lfanew); // if (nt_header->Signature == IMAGE_NT_SIGNATURE) // { // WRITE_LINE(VALID_PE_SIGNATURE); // } // WRITE_LINE( // ( // dosHeader->e_magic == IMAGE_DOS_SIGNATURE && nt_header->Signature == IMAGE_NT_SIGNATURE? // TEXT(VALID_PE_FILE): // TEXT(INVALID_PE_FILE) // ) // ); return dosHeader->e_magic == IMAGE_DOS_SIGNATURE && nt_header->Signature == IMAGE_NT_SIGNATURE; } IMAGE_NT_HEADERS* GetPeHeader(LPVOID pMapping) { IMAGE_DOS_HEADER * dosHeader = (IMAGE_DOS_HEADER*)pMapping; IMAGE_NT_HEADERS * nt_header=(IMAGE_NT_HEADERS*)((byte*)pMapping+dosHeader->e_lfanew); //检查PE有效性 if (dosHeader->e_magic == IMAGE_DOS_SIGNATURE && nt_header->Signature == IMAGE_NT_SIGNATURE)//如果PE头有效则返回PE头 { return nt_header; } //PE头无效则取消内存映射释放资源并返回NULL UnmapViewOfFile(pMapping); pMapping = NULL; return NULL; } /* 分析NT_HEADER回调函数 */ void PARSE_NT_HEADER_CALLBACK(IMAGE_NT_HEADERS* lpNtHeader) { WRITE_LINE(TEXT("----------------------------------FILE HEADER------------------------------------------------")); WRITE_LINE((lpNtHeader->FileHeader.Machine==IMAGE_FILE_MACHINE_I386?TEXT("该程序运行所在机器:Intel 386"):TEXT("该程序运行所在机器:非Intel 386"))); WRITE(TEXT("该程序的块数量:")); WRITE_LINE(lpNtHeader->FileHeader.NumberOfSections); WRITE_LINE(TEXT("----------------------------------FILE OPTIONAL HEADER--------------------------------------")); std::cout.setf(std::ios::hex,std::ios::basefield);//设置输出格式为16进制 std::cout << TEXT("PE装载器准备运行的PE文件的第一个指令的RVA:") << lpNtHeader->OptionalHeader.AddressOfEntryPoint << std::endl; std::cout << TEXT("PE文件的优先装载地址:") << lpNtHeader->OptionalHeader.ImageBase << std::endl; std::cout << TEXT("内存中节对齐的粒度:") << lpNtHeader->OptionalHeader.SectionAlignment << std::endl; std::cout << TEXT("文件中节对齐的粒度:") << lpNtHeader->OptionalHeader.FileAlignment << std::endl; std::cout << TEXT("win32子系统版本[若PE文件是专门为Win32设计的,该子系统版本必定是4.0否则对话框不会有3维立体感]:") << lpNtHeader->OptionalHeader.MajorOperatingSystemVersion << "." << lpNtHeader->OptionalHeader.MinorOperatingSystemVersion << std::endl; std::cout << TEXT("内存中整个PE映像体的尺寸[它是所有头和节经过节对齐处理后的大小]:") << lpNtHeader->OptionalHeader.SizeOfImage << std::endl; std::cout << TEXT("所有头+节表的大小[也就等于文件尺寸减去文件中所有节的尺寸。可以以此值作为PE文件第一节的文件偏移量]:") << lpNtHeader->OptionalHeader.SizeOfHeaders << std::endl; std::cout << TEXT("PE文件属于子系统:") << (lpNtHeader->OptionalHeader.Subsystem==IMAGE_SUBSYSTEM_WINDOWS_GUI?TEXT("图形用户界面"):TEXT("字符界面")) << std::endl; WRITE_LINE(TEXT("----------------------------------FILE SECTION TABLE--------------------------------------")); //定位节表位置 IMAGE_SECTION_HEADER *lpSectionHeader = (IMAGE_SECTION_HEADER *)((byte*)lpNtHeader+sizeof(*lpNtHeader)); // PARSE_SECTION_HEADER_CALLBACK(lpSectionHeader,lpNtHeader->FileHeader.NumberOfSections); // for (int i=0;i<lpNtHeader->FileHeader.NumberOfSections;i++) // { // std::cout << lpSectionHeader->Name << "\0" << std::endl; // std::cout << TEXT("\t本节的RVA(相对虚拟地址):") << lpSectionHeader->VirtualAddress << std::endl; // std::cout << TEXT("\t经过文件对齐处理后节尺寸:") << lpSectionHeader->SizeOfRawData << std::endl; // std::cout << TEXT("\t本节基于文件的偏移量:") << lpSectionHeader->PointerToRawData << std::endl; // lpSectionHeader++; // } } /* 分析IMAGE_SECTION_HEADER回调函数 */ void PARSE_SECTION_HEADER_CALLBACK(IMAGE_SECTION_HEADER* lpSectionHeader,INT numberOfSections) { if (lpSectionHeader && numberOfSections) { for (int i=0;i<numberOfSections;i++) { std::cout << lpSectionHeader->Name << "\0" << std::endl; std::cout << TEXT("\t本节的RVA(相对虚拟地址):") << lpSectionHeader->VirtualAddress << std::endl; std::cout << TEXT("\t经过文件对齐处理后节尺寸:") << lpSectionHeader->SizeOfRawData << std::endl; std::cout << TEXT("\t本节基于文件的偏移量:") << lpSectionHeader->PointerToRawData << std::endl; lpSectionHeader++; } } }
遍历节表的步骤:
PE文件有效性校验。
定位到 PE header 的起始地址。
从 file header 的NumberOfSections域获取节数。
通过两种方法定位节表: ImageBase+SizeOfHeaders 或者 PE header的起始地址+ PE header结构大小。 (节表紧随 PE header)。如果不是使用文件映射的方法,可以用SetFilePointer 直接将文件指针定位到节表。节表的文件偏移量存放在 SizeOfHeaders域里。(SizeOfHeaders 是 IMAGE_OPTIONAL_HEADER 的结构成员)
处理每个 IMAGE_SECTION_HEADER 结构。
相关文章推荐
- PE文件-检验PE文件的有效性--转自iczelion,附vc示范
- PE文件-导出表[EXPORT TABLE]--转自iczelion,附vc示范和图例
- PE文件-分析vc示范所有代码[不包含EXPORT TABLE]
- PE文件-分析vc示范所有代码[包含EXPORT TABLE]
- VC读取PE文件的OEP(程序入口)
- 为pe文件添加CRC32自效验的小程序(vc)
- vc编写自己的壳之一:对pe文件OEP的修改
- VC编译出来的PE文件大小优化
- PE文件-引入表[IMPORT TABLE]--转自iczelion,附vc示范
- VC PE导出/输入表演示(文件影射版本)
- VC中改变PE文件中Dos Stub程序(显示This program cannot be run in DOS mode)那一段程序.
- (高手勿进)为pe文件添加CRC32自效验的小程序(vc)
- VC实现简单的PE文件感染(增加的是downloader功能)
- 关于VC中生成的PE(exe, dll, sys...)文件中对函数名称的修饰
- VC往PE文件末尾读写数据
- vc编写自己的壳之一:对pe文件OEP的修改
- PE文件基础补注(VC + 纯API版)
- VC修改PE文件字符串(类似木马生成器)
- VC++中的DUMPBIN 工具用来查看PE(可移植执行体)文件(EXE、dll。。。)
- vc编写的用UDP协议传输文件的代码