直接將一個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;
}