PE文件(三)节表

节表引入

PE文件的结构是由DOS头+PE标记+标准PE头+可选PE头+节表+多个节构成的,如下便是一个pe文件结构图,它的每一段都可以被称作节

图中这么多的节在硬盘上和内存中的存储位置都由节表去管理和记录,而不是随意的存储。

节表相当于是一个对各个节(.text、.idata等)进行概要性描述的具有管理权限的一个目录,与之相对的DOS头和NT头则是对整个文件的概要性描述

节表位置与个数

节表个数

一个PE文件有多少个节,就有多少个节表,(标准PE头中的NumberOfsections的值便是节的数量,也就是节表的数量)。每一个节表都是一个结构体,他们的的大小都是40字节,用来记录管理一个节的信息

节表位置:节表紧接着可选PE头后面,如果有多个节表,它们就一个接一个顺序排列直到没有节表可排 。

硬盘上的地址: DOS头大小(64字节) + 垃圾空位 + PE签名大小(4字节) + 标准PE头大小(20字节) + 可选PE头大小(查标准PE头中的SizeOfOptionalHeader字段的值)。所以e_lfanew + 4 + 20 + SizeOfOptionalHeader = 节表开始地址

内存中的地址:节表在4GB内存中的地址要加上imagebase的值,才是节表真正在内存中的起始地址

节表结构

节表是一个结构体,其具体结构如下:

#define IMAGE_SIZEOF_SHORT_NAME   8 //宏定义

 

由上图可知,每个节都有相应的节表,每个节表记录着对应的节的信息。节表数据紧接着可选PE头数据后面,如果有多个节表,它们就一个接一个顺序排列直到没有节表可排 。所有的节表一起记录了该.exe文件中所有的节的信息,每一个节都有对应的节表来存储信息,所有的节对应的节表组合在一起就构成了PE文件的节表结构

节表主要字段含义

1.Name[IMAGE_SIZEOF_SHORT_NAME]:8个字节大小,一般情况下是以"\0"结尾的ASCII码来表示节的名称,名字可以自定义(一般是编译器加的)。

注意:数组元素是从最后一个元素开始倒着入栈的,所以从低地址往高地址分别是从数组的第一个元素到最后一个元素

容易出现的安全问题:

Name数组的长度最大为8字节,如果定义节的名称为.text,由于.text对应的ASCII码分别为0x2E, 0x74, 0x65, 0x78,0x74,所以Name数组中的数据为2E 74 65 78 74 00 00 00,一共8字节。如果此时用一个指针指向Name数组首地址,即char* np = Name。由于使用printf("%s",np)的方式去打印一个数组,程序会从数组首地址一直打印到\0停下,即0x00结束打印。所以此时以该方式打印此节表的名字,将会正常打印.text

但是如果我们自定义名字或者编译器帮我们定义的名字长度等于8,把这8位全占了,没有给\0留位置存储。比如.abcdefg,每个字符转换成1字节的ASCII码后, Name数组中的数据为2E 61 62 63 64 65 66 67,一共8字节。如果此时我们还是使用char* p = Name作为数组的起始地址,使用printf("%s",p);来打印名字,就会有越界问题。这是因为该数组没有以\0结尾了,所以程序会接着把Name后面的内存中的数据打印出来,直到遇到一个0x00为止。此时打印的名字就可能是.abcdefg 5J@.??.等乱码

解决方法:自己定义一个char arr[9] 的数组,然后使用库函数strncpy(arr,name,8),或者自己写一个循环将名字依次赋值进我们自定义的数组中,最后一位补‘\0’,此时使用char* p = arr作为数组的起始地址,printf("%s",p);的方法便可以正常打印出节的名字了

2.Misc:该成员为联合体类型变量,大小为4字节,用于表示该节表管理的节在没有对齐前(装入内存)的真实尺寸,该值不确定。该值的修改了对程序的运行没有影响

由于有的编译器或者软件喜欢用PhysicalAddress,,有些喜欢用VirtualSize,但这两个东西是一样的,为了减少内存占用就选择使用了联合体

如图是一个节在内存中被0填充对齐后的数据,圆圈中的数据便是没有对齐前的真实尺寸:

3.VirtualAddress:该值为该节表所管理的节的起始位置在内存中相对imagebase的偏移地址,所以该值加上imagebase的值才是.exe文件运行时,此节在4GB内存中的真实地址。

4.SizeOfRawData:4字节大小,用于表示该节表所管理的节在文件中对齐后所有数据大小

5.PointerToRawData:该节表所管理的节对齐后在文件中的偏移地址:即文件在硬盘上时,经过文件对齐后,该节相对于文件起始地址的偏移量,一定是文件对齐的整数倍。

6.Characteristics:节的属性: 4字节(32位),每一位都表示该节表所管理的节的一个属性。我们一般关注的属性是是否可读、可写、可执行

如下便是常见的属性和值的对照表:

特征对照表的解释:

上表中每一个值转换为32位值以后,都有某一位的值是1,每一个1都表示一个属性,如下举例:

如果某值为0x00000020,转换成32位二进制后,它的第6位为1,表示这个节包含可执行代码

如果某值为0x00000040,转换成32位二进制后,它的第7位为1,表示此节包含已初始化的数据

如果某值为0x20000000,转换成32位二进制后,它的第30位为1,表示此节可执行

依此类推,便可得出任意一个值他所表示的属性,接下来我们举一个综合的例子:

一个名为.text的节的characteristics字段值为0x60000020,当它转换为32位值以后发现它的第31位为1、第30位为1、第6位为1,根据上面的分析可知,该.text节的属性是可读、可执行、包含可执行代码

作业

 本次作业以notepad进行演示,如下是其在硬盘上的内存

1.手动解析节表

由标准pe头可知,一共由7个节也就是7个节表,可选pe头的大小是0X00F0,即240字节大小

根据上述我们所获取的信息,找到节表的首地址为0x01F8

.text

#define IMAGE_SIZEOF_SHORT_NAME  8 //宏定义    
typedef struct _IMAGE_SECTION_HEADER{
    BYTE Name[IMAGE_SIZEOF_SHORT_NAME];  0X000000747865742E
    union{
        DWORD   PhysicalAddress;
        DWORD   VirtualSize;
    }Misc;  0X00027D02
    DWORD VirtualAddress;  0X00001000
    DWORD SizeOfRawData;  0X00028000
    DWORD PointerToRawData; 0X00001000
    DWORD PointerToRelocations; 0X00000000
    DWORD PointerToLinenumbers;0X00000000
    WORD NumberOfRelocations; 0X0000
    WORD NumberOfLinenumbers;0X0000
    DWORD Characteristics;  0X60000020
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
 

.rdata

#define IMAGE_SIZEOF_SHORT_NAME 8 
typedef struct _IMAGE_SECTION_HEADER{
    BYTE Name[IMAGE_SIZEOF_SHORT_NAME];  0X000061746164722E
    union{
        DWORD   PhysicalAddress;
        DWORD   VirtualSize;
    }Misc;  0X0000A608
    DWORD VirtualAddress;  0X00029000
    DWORD SizeOfRawData; 0X0000B000
    DWORD PointerToRawData; 0X00029000
    DWORD PointerToRelocations; 0X00000000
    DWORD PointerToLinenumbers; 0X00000000
    WORD NumberOfRelocations; 0X0000
    WORD NumberOfLinenumbers; 0X0000
    DWORD Characteristics;  0X40000040
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
 

.data

#define IMAGE_SIZEOF_SHORT_NAME 8
typedef struct _IMAGE_SECTION_HEADER{
    BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; 0X000000617464642E
    union{
        DWORD   PhysicalAddress;
        DWORD   VirtualSize;
    }Misc;  000026C0
    DWORD VirtualAddress;  0X00034000
    DWORD SizeOfRawData; 0X00001000
    DWORD PointerToRawData; 0X00034000
    DWORD PointerToRelocations; 0X00000000
    DWORD PointerToLinenumbers; 0X00000000
    WORD NumberOfRelocations; 0X0000
    WORD NumberOfLinenumbers;0X0000
    DWORD Characteristics; 0XC0000040
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
 

由于该文件所有节过多,此处不再多演示,具体操作都是一样的。

2.控制台输出解析所有节表

#include<stdio.h>
#include<Windows.h>

char* ReadPEFile(const char* lpszFile)
{
	FILE* pFile = NULL;
	pFile = fopen(lpszFile, "rb");
	DWORD fileSize = 0;
	char* pFileBuffer = NULL;
	if (!pFile)
	{
		printf("无法打开EXE文件");
		return NULL;
	}
	fseek(pFile, 0, SEEK_END);
	fileSize = ftell(pFile);
	fseek(pFile, 0, SEEK_SET);
	pFileBuffer = (char*)malloc(sizeof(char)*fileSize);
	if (!pFileBuffer)
	{
		printf("分配空间失败");
		fclose(pFile);
		return NULL;
	}
	size_t i = fread(pFileBuffer, fileSize, 1, pFile);
	if (!i)
	{
		printf("读取数据失败!");
		free(pFileBuffer);
		fclose(pFile);
		return NULL;
	}
	fclose(pFile);
	return pFileBuffer;
}
BOOL PrintNTHeaders(const char* lpszFile)
{
	IMAGE_DOS_HEADER  *pDosHeader;
	IMAGE_NT_HEADERS *pNTHeader;
	IMAGE_FILE_HEADER *pPEHeader;
	IMAGE_SECTION_HEADER *pSecHeader;
	int i = 0;
	char* pFileBuffer = ReadPEFile(lpszFile);
	pDosHeader = (IMAGE_DOS_HEADER*)pFileBuffer;
	if (pDosHeader->e_magic != IMAGE_DOS_SIGNATURE)
	{
		printf("不是有效MZ标志,打印结束\n");
		free(pFileBuffer);
		pFileBuffer = NULL;
		return FALSE;
	}
	pNTHeader = (IMAGE_NT_HEADERS*)((char*)pFileBuffer + pDosHeader->e_lfanew);
	if (pNTHeader->Signature != IMAGE_NT_SIGNATURE)
	{
		printf("不是有效的PE标志,打印结束\n");
		free(pFileBuffer);
		pFileBuffer = NULL;
		return FALSE;
	}
	pPEHeader = (IMAGE_FILE_HEADER*)((char*)pNTHeader + 4);
	printf("NumberOfSections(节表的数量):%04X\n", pPEHeader->NumberOfSections);
	printf("SizeOfOptionalHeader(可选pe头的大小):%04X\n", pPEHeader->SizeOfOptionalHeader); 
	printf("\n");
	printf("节表信息解析开始");
	pSecHeader = (IMAGE_SECTION_HEADER*)((char*)pPEHeader + sizeof(_IMAGE_FILE_HEADER) + pPEHeader->SizeOfOptionalHeader);
	for (int i = 0; i < pPEHeader->NumberOfSections; i++)
	{	
		char SecName[9] = "\0";
		printf("这是第%d个节表\n", i+1);
		char* Name = (char*)pSecHeader->Name;
		strcpy(SecName, Name);
		printf("Name:%s\n", SecName);
		printf("VirtualSize:%08X\n", pSecHeader->Misc.VirtualSize);
		printf("VirtualAddress:%08X\n", pSecHeader->VirtualAddress);
		printf("SizeOfRawData:%08X\n", pSecHeader->SizeOfRawData);
		printf("PointerToRawData:%08X\n", pSecHeader->PointerToRawData);
		printf("Characteristics:%08X\n", pSecHeader->Characteristics);
		printf("\n");
		pSecHeader++;
	}
	free(pFileBuffer);
	pFileBuffer = NULL;
	return TRUE;
}
int main(int argc, char* argv[])
{
	const char* lpszFile = "C:\\Windows\\notepad.exe";
	PrintNTHeaders(lpszFile);
	return 0;
}

  • 20
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值