FileBuffer->ImageBuffer->NewBuffer->存盘
一.主要函数实现
1.PE文件到FileBuffer
这个过程还是单纯的只是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->存盘
- 既然要存盘,那么当然有一个参数是你存到什么地方filepath
- 要存如多少数据MemorySize,你的数据的大小
- 从那个地方存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来做测试
我们先打开notepad.exe看看
ok成功打开
下面再打开我们存盘的exe
双击打开
ok,成功打开!!!