PE复写:模仿PE加载过程

FileBuffer->ImageBuffer->NewBuffer->存盘

一.主要函数实现

1.PE文件到FileBuffer

这个过程还是单纯的只是PE文件的读取,详情见上一章,这里只截取部分代码
PE复写:模仿PE加载过程

2.FileBuffer到ImageBuffer

这是个文件拉伸的过程,如果PE文件的SectionAlignment(内存对齐)和FileAlignment(硬盘对齐)不一样,而我们要读取到的ImageBuffer空间是要将PE文件进行拉伸的,那么申请的ImageBuffer空间大小就是可选PE头里面的SizeOfImage,这个函数在以后解析也会经常用到,所以单独写出来这个函数,如下
函数名称:

  • Copy_FileBufferToImageBuffer

函数功能:

  • 将读取到FileBuffer空间的PE文件拉伸存储到ImageBuffer空间
DWORD Copy_FileBufferToImageBuffer(IN void* FileBuffer, OUT void** ImageBuffer)
{
    //先将用到的结构体指针定义出来
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS pNTHeader = NULL;
	PIMAGE_FILE_HEADER pPEHeader = NULL;
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;
	//判断传进来的FileBuffer指针是否存在
	if (!FileBuffer)
	{
		printf("为空指针!!,无效!!");
		return 0;
	}
	//上面如果有效,那么就给那些结构体指针赋值
	pDosHeader = (PIMAGE_DOS_HEADER)FileBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)FileBuffer + pDosHeader->e_lfanew);
	pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
	//判断是否是MZ标志
	if (*(WORD*)pDosHeader != IMAGE_DOS_SIGNATURE)
	{
		printf("这不是有效的MZ标志!!");
		return 0;
	}
	//判断是否是有效的PE标志
	if (*(DWORD*)pNTHeader != IMAGE_NT_SIGNATURE)
	{
		printf("这不是有效的PE标志!!");
		return 0;
	}
	//申请Temp_ImageBuffer堆空间
	void* Temp_ImageBuffer = malloc(pOptionHeader->SizeOfImage);
	if (!Temp_ImageBuffer)
	{
		printf("分配Temp_ImageBuffer堆空间失败!!");
		return 0;
	}
	//初始化Temp_ImageBuffer堆空间
	memset(Temp_ImageBuffer, 0, pOptionHeader->SizeOfImage);
	//根据SizeOfHeaders,先复制头儿,头部永远都是这个大小
	memcpy(Temp_ImageBuffer, pDosHeader, pOptionHeader->SizeOfHeaders);
//*******************************最重要的一个环节循环复制节的数据****************************
  	for (WORD i = 0; i < pPEHeader->NumberOfSections; i++, pSectionHeader++)
	{
		memcpy(
		    (void*)((DWORD)Temp_ImageBuffer + pSectionHeader->VirtualAddress),
			(void*)((DWORD)FileBuffer + pSectionHeader->PointerToRawData),
			pSectionHeader->SizeOfRawData
			);
	}
	//将传进来的ImageBuffer指针重新赋值
	*ImageBuffer = Temp_ImageBuffer;
	//返回文件大小
	return pOptionHeader->SizeOfImage;
}

3.ImageBuffer->NewBuffer

注意前面申请ImageBuffer空间的时候因为有个属性SizeOfImage告诉你了在加载的时候PE文件拉伸的大小,但是压缩的时候没法这样直接得到,需要自己计算(当然也可以设计这个函数的时候传入FileSize,FileSize就是第一个函数的返回值)。
这个过程是一个压缩的过程,自己计算的话要先计算出头部的大小,然后加上每个节的大小就是总共要申请的NewBuffer空间的大小,一起malloc即可

函数名称:

  • Copy_ImageBufferToNewBuffer

函数功能:

  • 拉伸后的PE文件数据压缩到NewBuffer空间
DWORD Copy_ImageBufferToNewBuffer(IN void* ImageBuffer, OUT void** NewBuffer)
{
	//先将用到的结构体指针定义出来
	PIMAGE_DOS_HEADER pDosHeader = NULL;
	PIMAGE_NT_HEADERS pNTHeader = NULL;
	PIMAGE_FILE_HEADER pPEHeader = NULL;
	PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
	PIMAGE_SECTION_HEADER pSectionHeader = NULL;
	//判断传进来的指针能不能用
	if (!ImageBuffer)
	{
		printf("为空指针!!,无效!!");
		return 0;
	}
	//如果传入成功给这些结构体指针赋值
	pDosHeader = (PIMAGE_DOS_HEADER)ImageBuffer;
	pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)ImageBuffer + pDosHeader->e_lfanew);
	pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pNTHeader + 4);
	pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + 20);
	pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
	//判断是否是MZ标志
	if (*(WORD*)pDosHeader != IMAGE_DOS_SIGNATURE)
	{
		printf("这不是有效的MZ标志!!");
		return 0;
	}
	//判断是否是有效的PE标志
	if (*(DWORD*)pNTHeader != IMAGE_NT_SIGNATURE)
	{
		printf("这不是有效的PE标志!!");
		return 0;
	}
//*********************最重要的环节获取NewBuffer空间的大小******************************
	DWORD Temp_NewBuffer_Size = pOptionHeader->SizeOfHeaders;
	PIMAGE_SECTION_HEADER Temp_pSectionHeader = pSectionHeader;//保留pSectionHeader
	for (size_t i = 0; i < pPEHeader->NumberOfSections; i++, Temp_pSectionHeader++)
	{
		Temp_NewBuffer_Size += Temp_pSectionHeader->SizeOfRawData;
	}
//**********************************************************************************
	//分配Temp_NewBuffer空间
	void* Temp_NewBuffer = malloc(Temp_NewBuffer_Size);
	if (!Temp_NewBuffer)
	{
		printf("分配内存失败!!!!!");
		return 0;
	}
	//初始化Temp_ImageBuffer堆空间
	memset(Temp_NewBuffer, 0, Temp_NewBuffer_Size);
	//先copy头
	memcpy(Temp_NewBuffer, ImageBuffer, pOptionHeader->SizeOfHeaders);
	//循环复制节到Temp_NewBuffer空间
	Temp_pSectionHeader = pSectionHeader;
	for (size_t i = 0; i < pPEHeader->NumberOfSections; i++, Temp_pSectionHeader++)
	{
		memcpy(
		       (void*)((DWORD)Temp_NewBuffer + Temp_pSectionHeader->PointerToRawData),
			   (void*)((DWORD)ImageBuffer + Temp_pSectionHeader->VirtualAddress),
			   Temp_pSectionHeader->SizeOfRawData
			  );
	}
	//返回数据
	*NewBuffer = Temp_NewBuffer;
	//返回NewBuffer空间大小
	return Temp_NewBuffer_Size;	
}	

4.NewBuffer->存盘

  1. 既然要存盘,那么当然有一个参数是你存到什么地方filepath
  2. 要存如多少数据MemorySize,你的数据的大小
  3. 从那个地方存pMemoryBuffer,你的数据的开始地址
DWORD Memory_TO_File(IN void* pMemoryBuffer, IN DWORD MemorySize, OUT char* filepath)
{
	FILE* fileAdress = fopen(filepath, "wb");
	if (!fileAdress)
	{
		printf("打开文件失败(写入)!!");
		return 0;
	}
	fwrite(pMemoryBuffer, MemorySize, 1, fileAdress);
	fclose(fileAdress);
	return 1;
}

二:测试函数

1.测试上述函数,完成PE加载

之前说过代码规范的问题,现在另写一个函数用来测试一下PE加载过程
函数名称:
void Test_PEloader()
函数功能:
模拟PE加载到内存中的过程,然后还原存盘

void Test_PEloader()
{
	//声明三个空间的指针
	void* FileBuffer = NULL;
	void* ImageBuffer = NULL;
	void* NewBuffer = NULL;
	//文件->FileBuffer
	DWORD FileSize = ReadPEfile_TO_FileBuffer(FILEPATH_IN, &FileBuffer);
	printf("FileBuffer空间大小为%x\n", FileSize);
	//FileBuffer->ImageBuffer
	DWORD ImageBufferSize = Copy_FileBufferToImageBuffer(FileBuffer, &ImageBuffer);
	printf("ImageBuffer空间大小为%x\n", ImageBufferSize);
	//ImageBuffer->NewBuffer
	DWORD NewBufferSize = Copy_ImageBufferToNewBuffer(ImageBuffer, &NewBuffer);
	printf("NewBuffer空间大小为%x\n", NewBufferSize);
	//NewBuffer->存盘,FILEPATH_OUT就是你要存盘的路径,自己定义个宏
	DWORD Judge = Memory_TO_File(NewBuffer, NewBufferSize, FILEPATH_OUT);
	if (Judge == 1)
	{
		printf("存盘成功!!");
	}
	else
	{
		printf("存盘失败!!");
	}
	//释放堆空间
	free(FileBuffer);
	free(ImageBuffer);
	free(NewBuffer);
}

三.执行结果

1.在主函数里面执行测试函数

int main(int argc,char* argv[])
{
	Test_PEloader();
	return 0;
}

2.执行结果

就拿notepad.exe来做测试
PE复写:模仿PE加载过程
我们先打开notepad.exe看看
PE复写:模仿PE加载过程
ok成功打开
下面再打开我们存盘的exe
PE复写:模仿PE加载过程
双击打开
PE复写:模仿PE加载过程
ok,成功打开!!!

上一篇:PE文件入门(一)


下一篇:RobotFramework操作API