目录
1.char* Loading_File(char* path);
2.char* FileBuffer_To_ImageBuffer(char * FileBuf);
3.char* ImageBuffer_To_FileBuffer(char* ImageBuf, int * len)
4.void Dump_File(char* FileBuf, int num, char * path)
前言
当我们双击一个可执行程序的时候,操作系统会把文件从硬盘上读入到内存中,那么读到内存中的文件和硬盘中的文件是否有所区别呢?答案是肯定的,今天我们来说一下操作系统在PE加载过程中所作的第一步,那就是文件从硬盘载入内存的拉伸过程,也就是FILE_BUFFER到IMAGE_BUFFER,我这篇博客中又多做了一步,也就是从IMAGE_BUFFER再压缩回FILE_BUFFER,然后存盘,看看是否可以正常使用。
在写代码之前给大家说一下思路:
①将文件从硬盘中读出到内存。
②通过寻找一些关键性成员确定IMAGE_BUFFER的大小和进行拉伸
③将IMAGE_BUFFER按照和第二步类似的思路压缩回去
④存盘
搞定!!
过程
首先先看一下封装的函数和包含的库
通过下列四个函数完成功能,下面详细介绍一下每个函数的过程。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <malloc.h>
#include <string.h>
//将文件读入内存
char* Loading_File(char* path);
//进行PE加载
char* FileBuffer_To_ImageBuffer(char * FileBuf);
//将ImageBUFFer转换成FILEbuffer
char* ImageBuffer_To_FileBuffer(char* ImageBuf, int * len);
//存盘
void Dump_File(char* FileBuf, int num, char * path);
1.char* Loading_File(char* path);
这个是文件的读入,简单说一下,通过fopen打开一个文件,然后通过fseek将文件指针置到文件末尾,通过ftell算出文件的大小,通过ftell的大小分配内存,然后通过fread读入内存。
//参数:传入文件路径
char* Loading_File(char* path)
{
if (path == NULL)
{
printf("路径为空\n");
return 0;
}
FILE* fp = fopen(path, "rb");
if (fp == NULL)
{
printf("打开文件失败\n");
return 0;
}
fseek(fp, 0, SEEK_END);
int len = ftell(fp);
fseek(fp, 0, SEEK_SET);
char* FileBuf = (char*)malloc(len);
if (FileBuf == 0)
{
printf("分配内存失败\n");
return 0;
}
fread(FileBuf, 1, len, fp);
fclose(fp);
return FileBuf;
}
2.char* FileBuffer_To_ImageBuffer(char * FileBuf);
要实现把FileBuffer拉伸成ImageBuffer,大家务必需要知道的几个知识就是,如果文件的文件对齐和内存对齐是相同的就不需要拉伸了,如果不同就需要拉伸。
那么在拉伸的过程中需要怎么做呢?
首先我们应该先知道我们拉伸后的ImageBuffer有多大,这个在可选PE头里面的SizeOfImage中存放着。
char * FileBuf中存放着FileBuffer的首地址,我们知道这是指向DOS头部的,DOS头部的大小是64个字节,最后一个成员记录着NT头的开始位置偏移(相对于FILEBUFFER)
我们通过int* IntPointTemp = (int*)(FileBuf + 0x3c);拿到PE头偏移。
拿到偏移之后,指向的是50450000的标识,也就是PE标识,跳过这四个字节就是标准PE头了,为了下面的功能实现,我们还需要拿到SizeOfOptionHeaders(可选PE头的大小)和NumberOfSection(节的数量,同样是节表的数量),这是为了锁定到节表,然后这里我们拿到这些之后,CharPointTemp是指向NT头的,通过CharPointTemp +4(PE标识)+20(标准PE头)+0x38(SizeOfImage的相对偏移),就找到了SizeOfImage,再+2(SizeOfHeaders的相对偏移),然后这里我们都已经找到了所需要的东西。
然后下面开始我们代码的编写:
这里给大家说一下这个代码的思路,所有的代码也已经给大家贴在下面了,
1.拿到SizeOfImage之后,我们就可以分配内存了,这里我们要注意把内存清0。
2.先拷贝PE头,通过SizeOfHeaders。
PE头拷贝完成之后,接下来就是循环拷贝节了,节的VirtualAddress,和SizeOfRawData,PointOfRawData,是我们拷贝的关键。
3.这里写个循环,外层循环条件是NumberOfSection,然后内部每一次都获取到节的VirtualAddress,和SizeOfRawData,PointOfRawData,内层循环以SizeOfRawData为循环条件,这里大家要注意VirtualAddress,PointOfRawData是相对与ImageBuffer和FileBuffer的偏移,对应到这里为其在堆中分配的内存首地址,VirtualAddress,PointOfRawData加上他们才是真正的目的地址和源地址。每一个节表的长度是40个字节,每次循环之后记得+40索引下一个节表。
//参数FileBuf:传入FileBuf在内存当作的首地址
char* FileBuffer_To_ImageBuffer(char * FileBuf)
{
if (FileBuf == NULL)
{
printf("FileBuf载入失败");
return 0;
}
//拿到PE头偏移
int* IntPointTemp = (int*)(FileBuf + 0x3c);
int Turn = *IntPointTemp;
char* CharPointTemp = (FileBuf + Turn);
char* CharPointTemp_Bak = CharPointTemp;
//拿到节的数量和可选PE头
short* ShortPointTemp = (short*)(CharPointTemp+4+2);
short NumberOfSections = *ShortPointTemp;
ShortPointTemp += 7;
short SizeOfOptionalHeader = *ShortPointTemp;
CharPointTemp = (CharPointTemp + 24 + 0x38);
//拿到Size_Of_Image
IntPointTemp = (int*)CharPointTemp;
int SizeOfImage = *IntPointTemp;
//拿到SizeOfHeaders
int SizeOfHeaders = *(IntPointTemp + 1);
//分配内存ImageBuffer
char* ImageBuffer = (char*)malloc(SizeOfImage);
if (ImageBuffer == NULL)
{
printf("ImageBuffer内存分配失败\n");
return 0;
}
//ImageBuffer清0
memset(ImageBuffer, 0, SizeOfImage);
//PE头拷贝
char* FileBuf_Bak = FileBuf;
char* ImageBuffer_Bak = ImageBuffer;
int i = 0;
while (i < SizeOfHeaders)
{
*(ImageBuffer_Bak + i) = *(FileBuf_Bak + i);
i++;
}
//节的拷贝
FileBuf_Bak = FileBuf;
ImageBuffer_Bak = ImageBuffer;
//CharPointTemp_Bak指向节表
CharPointTemp_Bak = (CharPointTemp_Bak + 24 + SizeOfOptionalHeader);
i = 0;
while (i < NumberOfSections)
{
int* VirtualAddress = (int*)(CharPointTemp_Bak + 0xc);
int* SizeOfRawData = (int*)(CharPointTemp_Bak + 0x10);
int* PointerToRawData = (int*)(CharPointTemp_Bak + 0x14);
char* VirtualAddress_02 = ((*VirtualAddress) + ImageBuffer_Bak);
char* PointerToRawData_02 = ((*PointerToRawData) + FileBuf_Bak);
int j = 0;
while (j < (*SizeOfRawData))
{
*(VirtualAddress_02 + j) = *(PointerToRawData_02 + j);
j++;
}
CharPointTemp_Bak += 40;
i++;
}
return ImageBuffer;
}
3.char* ImageBuffer_To_FileBuffer(char* ImageBuf, int * len)
这里一个要注意的点是我们要计算应该为File_Buffer分配多大的内存空间。
思路是SizeOfHeaders+每一个节表中的SizeOfRawData
1.找到应该为File_Buffer分配的内存大小
2.先拷贝PE头
3.一样的我们还是需要获取VirtualAddress,和SizeOfRawData,PointOfRawData,只不过拷贝的方向相反了。
//ImageBuF:ImageBuffer在内存的首地址,len为了返回压缩后的FileBuffer的长度
char* ImageBuffer_To_FileBuffer(char* ImageBuf, int * len)
{
if (ImageBuf == NULL)
{
return 0;
}
char* ImageBuf_Bak = ImageBuf;
//拿到PE头偏移
int* IntPointTemp = (int*)(ImageBuf + 0x3c);
int Turn = *IntPointTemp;
//指向PE头
char* CharPointTemp = (ImageBuf + Turn);
char* CharPointTemp_Bak = CharPointTemp;
//拿到节的数量和可选PE头
short* ShortPointTemp = (short*)(CharPointTemp + 4 + 2);
short NumberOfSections = *ShortPointTemp;
ShortPointTemp += 7;
short SizeOfOptionalHeader = *ShortPointTemp;
CharPointTemp = (CharPointTemp + 24 + 0x38);
//拿到Size_Of_Image
IntPointTemp = (int*)CharPointTemp;
int SizeOfImage = *IntPointTemp;
//拿到SizeOfHeaders
int SizeOfHeaders = *(IntPointTemp + 1);
//计算为File_Buffer分配的内存数量
int Total_Len_Of_File_Buffer = SizeOfHeaders;
CharPointTemp_Bak = (CharPointTemp_Bak + 4 + 20 + SizeOfOptionalHeader);
char* CharPointTemp_Bak_2 = (CharPointTemp_Bak + 0x10);
int i = 0;
while (i < NumberOfSections)
{
int* SizeOfRawData = (int*)CharPointTemp_Bak_2;
Total_Len_Of_File_Buffer += *(SizeOfRawData);
CharPointTemp_Bak_2 += 40;
i++;
}
//分配内存
char* FileBuf = (char*)malloc(Total_Len_Of_File_Buffer);
if (FileBuf == NULL)
{
printf("FileBuf分配内存失败\n");
return 0;
}
memset(FileBuf, 0, Total_Len_Of_File_Buffer);
char* FileBuf_Bak = FileBuf;
//写入FileBuffer
i = 0;
while (i < SizeOfHeaders)
{
if ((FileBuf + i == NULL) || (ImageBuf + i == NULL))
{
printf("内存越界了\n");
return 0;
}
*(FileBuf + i) = *(ImageBuf + i);
i++;
}
i = 0;
CharPointTemp_Bak_2 = CharPointTemp_Bak;
while (i < NumberOfSections)
{
int* VirtualAddress = (int*)(CharPointTemp_Bak_2 + 0xc);
int * PointerToRawData = (int*)(CharPointTemp_Bak_2 + 0x14);
int* SizeOfRawData = (int*)(CharPointTemp_Bak_2 + 0x10);
int j = 0;
while(j < (*SizeOfRawData))
{
*(FileBuf_Bak+(*PointerToRawData) + j) = *(ImageBuf_Bak + (*VirtualAddress) + j);
j++;
}
CharPointTemp_Bak_2 += 40;
i++;
}
*len = Total_Len_Of_File_Buffer;
return FileBuf_Bak;
}
4.void Dump_File(char* FileBuf, int num, char * path)
这个就是一个内存写入到文件的过程,我们上一个函数返回的len就是为了传入给这个函数做为读出的长度。
//FileBuf:内存首地址
//num:要拷贝的字节数
//写入文件的路径
void Dump_File(char* FileBuf, int num, char * path)
{
FILE* fp = fopen(path, "wb");
fwrite(FileBuf, 1, num, fp);
}
5.函数调用
int main(void)
{
char path[] = "D:\\QQcache\\notepad++.exe";
//加载内存
char* FileBuf = Loading_File(path);
if (FileBuf == NULL)
{
printf("Failed to FileBuf\n");
return 0;
}
//filebuf到imageBuf
char * Image_Buf = FileBuffer_To_ImageBuffer(FileBuf);
if (Image_Buf == NULL)
{
printf("Failed to FileBuffer_To_ImageBuffer\n");
}
//imageBuf到filebuf
int num = 0;
char* FileBuf02 = ImageBuffer_To_FileBuffer(Image_Buf, &num);
if (FileBuf02 == NULL)
{
printf("Failed to ImageBuffer_To_FileBuffer");
}
//写入磁盘
char path02[] = "D:\\QQcache\\notepad++2.exe";
Dump_File(FileBuf02, num, path02);
return 0;
}
总结
这里验证可以运行。