PE文件-节表--转自iczelion,附vc示范

我们已经学了许多关于 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

同样,不是所有成员都是很有用的,我们只关心那些真正重要的。

FieldMeanings
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_SECTION_HEADER 结构,再来模拟一下 PE装载器的工作吧:

  1. 读取 IMAGE_FILE_HEADER NumberOfSections域,知道文件的节数目。
  2. SizeOfHeaders 域值作为节表的文件偏移量,并以此定位节表。
  3. 遍历整个结构数组检查各成员值。
  4. 对于每个结构,我们读取PointerToRawData域值并定位到该文件偏移量。然后再读取SizeOfRawData域值来决定映射内存的字节数。将VirtualAddress域值加上ImageBase域值等于节起始的虚拟地址。然后就准备把节映射进内存,并根据Characteristics域值设置属性。
  5. 遍历整个数组,直至所有节都已处理完毕。

注意我们并没有使用节名: 这其实并不重要。

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++;
			
		}
	}
}


 

遍历节表的步骤:

  1. PE文件有效性校验。
  2. 定位到 PE header 的起始地址。
  3. file header NumberOfSections域获取节数。
  4. 通过两种方法定位节表: ImageBase+SizeOfHeaders 或者 PE header的起始地址+ PE header结构大小。 (节表紧随 PE header)。如果不是使用文件映射的方法,可以用SetFilePointer 直接将文件指针定位到节表。节表的文件偏移量存放在 SizeOfHeaders域里。(SizeOfHeaders IMAGE_OPTIONAL_HEADER 的结构成员)
  5. 处理每个 IMAGE_SECTION_HEADER 结构。

 

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值