用C语言给PE文件添加一个新 section

向PE文件尾部添加一个新的section主要分为3步:

① 构造新节区的结构体,设置结构体中各成员的属性

② 修改 FileHeader.NumberOfSections + 1,修改 OptionalHeader.SizeOfImage + 新的大小

③ 扩充PE文件,实际修改其大小

>> 用 E2A 作为实验对象,其原始节区表如下,目标就是给他添加一个新的节区名为 .newSec

 

>> 添加效果如下

>> 检验文件大小

 

>> 实现代码如下

#include <windows.h>
#include <Commdlg.h>
#include <stdio.h>
#ifndef _IMAGEHLP_H 
#include "imagehlp.h"
#pragma comment ( lib, "imagehlp.lib" )
#endif
#pragma warning(disable : 4996)

// 根据传入的dwAlign粒度调整内存中或文件中对齐后的大小
DWORD Align(DWORD dwNum, DWORD dwAlign){
	if (dwNum % dwAlign == 0){
		return dwNum;
	}else{
		return (dwNum / dwAlign + 1) * dwAlign;
	}
}

int main(int argc, char* argv[]){
	char szFilePath[MAX_PATH];					// PE 路径
	OPENFILENAME ofn;							
	HANDLE hFile;								// 文件句柄
	HANDLE hMapping;							// 映射文件句柄
	LPVOID ImageBase;							// 映射基址
	PIMAGE_DOS_HEADER	   pDH  = NULL;			// 指向 IMAGE_DOS 结构的指针
	PIMAGE_NT_HEADERS	   pNtH = NULL;			// 指向 IMAGE_NT 结构的指针
	PIMAGE_FILE_HEADER	   pFH  = NULL;			// 指向 IMAGE_FILE 结构的指针
	PIMAGE_OPTIONAL_HEADER pOH  = NULL;			// 指向 IMAGE_OPTIONALE 结构的指针
	PIMAGE_SECTION_HEADER  pSH1 = NULL;			// 指向 IMAGE_SECTION_TABLE 结构的指针first
	PIMAGE_SECTION_HEADER  pSH2 = NULL;			// 指向 IMAGE_SECTION_TABLE 结构的指针two
	PIMAGE_SECTION_HEADER  pSH3 = NULL;			// 指向 IMAGE_SECTION_TABLE 结构的指针three

	memset(szFilePath, 0, MAX_PATH);
	memset(&ofn, 0, sizeof(ofn));
	ofn.lStructSize = sizeof(ofn);
	ofn.hwndOwner = NULL;
	ofn.hInstance = GetModuleHandle(NULL);
	ofn.nMaxFile = MAX_PATH;
	ofn.lpstrInitialDir = ".";
	ofn.lpstrFile = szFilePath;
	ofn.lpstrTitle = "选择 PE 文件打开";
	ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;
	ofn.lpstrFilter = "*.exe\0*.exe\0";
	if (!GetOpenFileName(&ofn)){
		MessageBox(NULL, "打开文件错误", NULL, MB_OK);
		return 0;
	}

	// 选择要分析的文件后,经过3步打开并映射 PE 到虚拟内存中_CreateFile_CreateFileMapping_MapViewOfFile
	// 1.创建文件内核对象,其句柄保存于 hFile,将文件在物理存储器的位置通告给操作系统
	hFile = CreateFile(szFilePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0);
	if (!hFile){
		MessageBox(NULL, "打开文件错误", NULL, MB_OK);
		return 0;
	}

	// 2.创建文件映射内核对象(分配虚拟内存),句柄保存于 hFileMapping
	hMapping = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
	if (!hMapping){
		CloseHandle(hFile);
		return FALSE;
	}

	// 3.将文件数据映射到进程的地址空间,返回的映射基址保存在 ImageBase 中
	ImageBase = MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, 0);
	if (!ImageBase){
		CloseHandle(hMapping);
		CloseHandle(hFile);
		return FALSE;
	}

	pDH = (PIMAGE_DOS_HEADER)ImageBase;								// IMAGE_DOS Header 结构指针
	pNtH = (PIMAGE_NT_HEADERS)((DWORD)pDH + pDH->e_lfanew);			// IMAGE_NT Header 结构指针
	pFH = &pNtH->FileHeader;										// IMAGE_File Header 结构指针
	pOH = &pNtH->OptionalHeader;									// IMAGE_Optional Header结构指针
	pSH1 = IMAGE_FIRST_SECTION(pNtH);								// IMAGE_FIRST_SECTION 宏
	pSH2 = (PIMAGE_SECTION_HEADER)((DWORD)pNtH + sizeof(IMAGE_NT_HEADERS));
	pSH3 = (PIMAGE_SECTION_HEADER)((DWORD)pDH + pOH->SizeOfHeaders);
	if (pDH->e_magic != IMAGE_DOS_SIGNATURE || pNtH->Signature != IMAGE_NT_SIGNATURE){
		printf("Not a valid PE file...");
		return -1;
	}

	// 创建PSection指针指向原程序中的第一个Section,并创建一个新的Section结构体 secToAdd
	PIMAGE_SECTION_HEADER pSection = NULL;
	IMAGE_SECTION_HEADER secToAdd = { 0 };												// 节区名 Name
																						// 属性 Characteristics
																						// 实际被使用的区块大小 Misc.VirtualSize
																						// 节区在磁盘文件中的大小 SizeOfRawData
																						// 节区在内存中的RVA VirtualAddress
																						// 节区在磁盘中的偏移 PointerToRawData
	pSection = (PIMAGE_SECTION_HEADER)((BYTE*)pOH + pFH->SizeOfOptionalHeader);			// 定位到节区表
	DWORD dwSectionNum = pFH->NumberOfSections;
	DWORD dwSectionAlign = pOH->SectionAlignment;
	DWORD dwFileAlign = pOH->FileAlignment;
	DWORD dwOEP = pOH->AddressOfEntryPoint;												// 程序执行入口地址VA
	dwOEP = (DWORD)(pOH->ImageBase + dwOEP);											// 映射起始地址+执行入口地址
	pSection = pSection + dwSectionNum - 1;												// 将PSection指向原程序节区表中最后一个Section表,这是关键一步,因为新节区所有属性和原来最后一个节区是相同的
	strcpy((char *)secToAdd.Name, ".newSec");											// 设置新添加的section的名字
	secToAdd.Characteristics = pSection->Characteristics;								// 设置新添加的section的属性值,与最后一个section取值相同
	DWORD vsize = 0x2A01;																// 新section大小设置
	secToAdd.Misc.VirtualSize = vsize;
	secToAdd.SizeOfRawData = Align(secToAdd.Misc.VirtualSize, dwFileAlign);				// 根据之前定义的Align函数调用,得到经过处理后的section尺寸大小,经调整后实际大小为0x234,对齐后大小为0x400
	secToAdd.VirtualAddress = pSection->VirtualAddress +
		Align(pSection->Misc.VirtualSize, dwSectionAlign);								// 调用Align函数,得到经过内存对齐处理后的尺寸,新Section的RVA等于原程序最后一个Section的RVA加上该节在内存中的映射尺寸
	secToAdd.PointerToRawData = pSection->PointerToRawData + pSection->SizeOfRawData;	// 新Section的FA地址等于最后一个Section的FA加上该节的文件对齐尺寸
	pSection++;																			// pSection指向原程序中最后一个节表的下一个,写入新的节表结构
	secToAdd.Characteristics = 0xE00000E0;												// pSection->Characteristics = 0xE00000E0;
	memcpy(pSection, &secToAdd, sizeof(IMAGE_SECTION_HEADER));							// 将最后一个节区拷贝到新建的节区

	// 打印新节区信息
	printf("\n节表添加成功,新节表的信息为:\n");
	printf("\nName = %s", secToAdd.Name);
	printf("\nVirtualSize = %08lX", secToAdd.Misc.VirtualSize);
	printf("\nVirtualAddress = %08lX", secToAdd.VirtualAddress);
	printf("\nSizeOfRawData = %08lX", secToAdd.SizeOfRawData);
	printf("\nPointerToRawData = %08lX", secToAdd.PointerToRawData);
	printf("\n");

	// 添加完信息之后要进行三个修改:①修改节区数量NumberOfSections
	//								②映像大小
	//								③扩充原PE文件的大小,将Section真正写入到尾部
	pNtH->FileHeader.NumberOfSections += 0x1;												// 更改PE文件中节表的数量
	pOH->SizeOfImage = pOH->SizeOfImage + Align(secToAdd.Misc.VirtualSize, dwSectionAlign);	// 修改程序的映像大小,比如VirtualSize=200,SectionAlign=200,那么最后就是400
	BYTE bNum = '\x0';																		// 修改文件大小
	DWORD dwWritten = 0;
	::SetFilePointer(hFile, 0, 0, FILE_END);
	::WriteFile(hFile, &bNum, secToAdd.SizeOfRawData, &dwWritten, NULL);
	::UnmapViewOfFile(ImageBase);
	::CloseHandle(hMapping);
	::CloseHandle(hFile);

	return 0;
}

 

>> 练习这个的原因在于,感染型病毒也很喜欢在PE文件的尾部添加一个恶意节区,而且有助于进一步理解PE结构,由于以功能实现为导向,并未添加判断PE文件等代码。

 

展开阅读全文
©️2020 CSDN 皮肤主题: 像素格子 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值