直接将一个pe文件读到内存中是无法运行的,必须经过拉伸,然后再做其它处理才能可运行;
文件中的pe文件状态为文件映像;
拉伸后的状态为内存映像;
1.pe加载过程
1)根据SizeOfImage的大小,开辟一块缓冲区(ImageBuffer).
SizeOfImage是可选pe头中的一个属性;
2)根据SizeOfHeaders的大小,将头信息从FileBuffer拷贝到ImageBuffer
文件映像和内存影响的头部信息是相同的,可以直接拷贝过去;
头部信息包括:dos头、标准pe头、可选pe头、节表;
SizeOfHeaders是可选pe头的属性;表示所有头加起来然后按文件对齐后的大小;
3)根据节表中的信息循环讲FileBuffer中的节拷贝到ImageBuffer中.
节表中的属性PointerToRawData,表示文件映像中节的位置 ,决定了从哪里开始复制;
节表中有个属性VirtualAddress,表示内存镜像中该节相对于dos头开始的偏移;决定了该复制到哪个地方;
4)Misc.VirtualSize 和 SizeOfRawData谁大?
VirtualSize是实际节的大小,也就是不包含文件对齐补的0;
SizeOfRawData是节文件对齐后的大小;
VirtualSize是可能比SizeOfRawData大的;
例如:char buf[1000];这段代码声明了一个数组,但没初始化,编译器编译时没给它分配空间;
但加载到内存时会分配空间,导致实际节大小变大,可能大于文件对齐节的大小;
拷贝节时可以用两种方案:1】按VirtualSize拷;2】按SizeOfRawData拷;都可以只要保证数据不会丢失即可;
有一种极端情况,可能未初始化的数据太多,导致VirtualSize过大,按VirtualSize拷贝可能占了下个节的空间;
按SizeOfRawData来拷贝最靠谱,因为不会超过下个节的起始地址;
5)FileBuffer与ImageBuffer谁大?
6)ImageBuffer状态的开始地址
注意:ImageBuffer状态还不是可运行状态,只是比文件映像更加接近可运行状态;
因此不是以ImageBase作为起始地址的;
真正的起始地址是malloc申请的内存地址;
真正以ImageBase开头是文件被加载到独立的4gb空间能运行时;
2.用程序模拟pe文件的拉伸
如图:将一个exe文件读到内存,然后拉伸;
1)初始化内存的memset函数
memset 函数是内存赋值函数,用来给某一块内存空间进行赋值的;
包含在<string.h>头文件中,可以用它对一片内存空间逐字节进行初始化;
原型为 :
void *memset(void *s, int v, size_t n);
这里s可以是数组名,也可以是指向某一内在空间的指针;
v为要填充的值;
n为要填充的字节数;
2)memcpy内存拷贝函数
原型:
void *memcpy(void *dest, const void *src, size_t n);
功能:由src所指内存区域复制n个字节到dest所指内存区域。
说明:src和dest所指内存区域不能重叠,函数返回指向dest的指针。
3)代码
头文件petool.h
#ifndef PETOOL_H
#define PETOOL_H
#include "stdafx.h"
#include <stdlib.h>
#include <windows.h>
//函数声明
//**************************************************************************
//ReadPEFile:将文件读取到缓冲区
//参数说明:
//lpszFile 文件路径
//pFileBuffer 缓冲区指针
//返回值说明:
//读取失败返回0 否则返回实际读取的大小
//**************************************************************************
DWORD ReadPEFile(IN LPSTR lpszFile,OUT LPVOID* pFileBuffer);
//**************************************************************************
//CopyFileBufferToImageBuffer:将文件从FileBuffer复制到ImageBuffer
//参数说明:
//pFileBuffer FileBuffer指针
//pImageBuffer ImageBuffer指针
//返回值说明:
//读取失败返回0 否则返回复制的大小
//**************************************************************************
DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer,OUT LPVOID* pImageBuffer);
#endif
头文件实现petool.cpp:
#include "stdafx.h"
#include "petool.h"
//ReadPEFile:将文件读取到缓冲区
DWORD ReadPEFile(IN LPSTR lpszFile,OUT LPVOID* pFileBuffer){
//1.打开文件
FILE* file = fopen(lpszFile, "rb");
if(!file){
printf("打开文件失败\n");
return 0;
}
//2.计算文件大小
fseek(file, 0, SEEK_END);
DWORD len = ftell(file);
fseek(file, 0, SEEK_SET);
//3.申请内存
LPVOID buf = malloc(len);
if(!buf){
fclose(file);
printf("申请内存失败\n");
return 0;
}
//4.读取文件到内存
size_t n = fread(buf, len, 1, file);
if(!n){
printf("读取文件失败\n");
free(buf);
fclose(file);
return 0;
}
//5.返回
printf("读取文件到缓冲区成功\n");
*pFileBuffer = buf;
buf = NULL;
fclose(file);
return len;
}
//将文件从FileBuffer复制到ImageBuffer
DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer,OUT LPVOID* pImageBuffer){
//定义pe头结构指针
PIMAGE_DOS_HEADER dosHeader = NULL; //dos头指针
PIMAGE_NT_HEADERS ntHeader = NULL; //nt头指针
PIMAGE_FILE_HEADER peHeader = NULL; //pe头指针
PIMAGE_OPTIONAL_HEADER32 optionHeader = NULL; //可选pe头指针
PIMAGE_SECTION_HEADER sectionHeader = NULL; //节表指针
if(!pFileBuffer){
printf("文件加载失败\n");
return 0;
}
//判断是否有mz标记
if(*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE){
printf("不是有效mz标记\n");
free(pFileBuffer);
return 0;
}
//找到dos头
dosHeader = (PIMAGE_DOS_HEADER) pFileBuffer;
//根据dos头的e_flanew找到nt头并判断是否有pe标记
if(*((PDWORD)((DWORD)pFileBuffer + dosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE){
printf("不是有效pe标记\n");
free(pFileBuffer);
return 0;
}
//找到pe头
peHeader = (PIMAGE_FILE_HEADER) ((DWORD)pFileBuffer + dosHeader->e_lfanew +4);
//找到可选pe头
optionHeader = (PIMAGE_OPTIONAL_HEADER32) ((DWORD)peHeader + IMAGE_SIZEOF_FILE_HEADER);
//**********开始复制***********
//1.申请内存空间,拉伸后的大小在可选pe头的SizeOfImage中
DWORD imageSize = optionHeader ->SizeOfImage;
LPVOID image = malloc(imageSize);
if(!image){
printf("申请imagebuf内存空间失败\n");
return 0;
}
//初始化内存空间
memset(image, 0, imageSize);
//2.拷贝头部文件,内存镜像和文件镜像的头部是一样的
DWORD headSize = optionHeader->SizeOfHeaders;
memcpy(image, pFileBuffer, headSize);
//3.拷贝各个节
WORD sectionNum = peHeader->NumberOfSections; //节的数量在pe头中
WORD opHeaderSize = peHeader->SizeOfOptionalHeader; //可选pe头的字节数,用来计算节表文件镜像的位置;
//找到节表开头
sectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)optionHeader + opHeaderSize);
for(int i=0; i<sectionNum; i++,sectionHeader++){
memcpy((LPVOID)((DWORD)image + sectionHeader->VirtualAddress),
(LPVOID)((DWORD)pFileBuffer + sectionHeader->PointerToRawData),
sectionHeader->SizeOfRawData);
}
//4.返回
*pImageBuffer = image;
image = NULL;
printf("拉伸文件镜像成功\n");
return imageSize;
}
主函数:
#include "stdafx.h"
#include "petool.h"
int main(int argc, char* argv[])
{
//读取文件到内存
LPVOID buf = NULL;
ReadPEFile("C:\\Users\\Administrator\\Desktop\\CRACKME.EXE", &buf);
//拉伸文件
LPVOID image = NULL;
CopyFileBufferToImageBuffer(buf, &image);
getchar();
printf("释放内存空间\n");
free(buf);
free(image);
return 0;
}