新书推荐:3.3.1 PE文件读进内存的两种方法

B站影视 2024-11-28 16:06 2

摘要:磁盘中的PE文件如果想要在操作系统上运行就必须要将其加载到内存中。同样,如果我们要对一个磁盘PE文件进行读写操作,也需要将其加载到内存。接下来我们将通过实验介绍两种不同的方法,将一个磁盘PE文件读进内存。我们先介绍第一种方法,使用ReadFile函数将PE文件

磁盘中的PE文件如果想要在操作系统上运行就必须要将其加载到内存中。同样,如果我们要对一个磁盘PE文件进行读写操作,也需要将其加载到内存。接下来我们将通过实验介绍两种不同的方法,将一个磁盘PE文件读进内存。我们先介绍第一种方法,使用ReadFile函数将PE文件读入一个由VirtualAlloc函数分配的虚拟内存空间。

实验十九:ReadFile将PE文件读进内存(C语言)

以32位和64位记事本程序为例,使用ReadFile函数将记事本程序以内存格式读进内存:

●源码

/*

FileName:ReadPe1.c

实验19:ReadFile将PE文件读进内存

(c) bcdaren, 2024

*/

#include

#include

#defineX64

intmain(intargc, char* argv)

{

HANDLEhFile;

LPVOIDlpvResult, lpvResult2;

charbuffer[16] = {0};

DWORDdwPageSize;

DWORDdwBytesRead = 0;

BOOLbReadFile;

LPCSTRszFileName = TEXT("c:\\notepad64.exe");

PIMAGE_DOS_HEADERpsImageDOSHeader;

#ifdefX64

PIMAGE_NT_HEADERS64psImageNTHeader;

#else

PIMAGE_NT_HEADERS32psImageNTHeader;

#endif

PIMAGE_SECTION_HEADERsImageSecctionHeader[3]; //不一定为3个节表项

hFile = CreateFile(szFileName,

GENERIC_READ, // 只读打开

FILE_SHARE_READ, // 允许其他进程以读取方式打开文件

NULL, // 默认安全属性

OPEN_EXISTING, // 打开已存在的文件

FILE_ATTRIBUTE_NORMAL, // 普通文件

NULL);

dwPageSize = GetFileSize(hFile, 0);//获得文件大小可通过结构体获取

lpvResult = VirtualAlloc(// 给文件分配内存空间

NULL, // 系统自动选择起始地址

dwPageSize, // 指定要分配的内存大小,以字节为单位

MEM_COMMIT, // 将申请到的虚拟内存提交到物理内存

PAGE_READWRITE); // read/write 属性

//将文件读至分配的内存

bReadFile = ReadFile(hFile, lpvResult, dwPageSize, &dwBytesRead, 0);

//实现将磁盘文件地址偏移至内存地址

#ifdefX64//64位记事本有6个节区,只打印前3个节区

psImageDOSHeader = (PIMAGE_DOS_HEADER)lpvResult;

psImageNTHeader = (PIMAGE_NT_HEADERS64)

((BYTE*)lpvResult + psImageDOSHeader->e_lfanew);

sImageSecctionHeader[0] = (PIMAGE_SECTION_HEADER)

((BYTE*)psImageNTHeader + sizeof(IMAGE_NT_HEADERS64));

sImageSecctionHeader[1] = (PIMAGE_SECTION_HEADER)

((BYTE*)sImageSecctionHeader[0] + sizeof(IMAGE_SECTION_HEADER));

sImageSecctionHeader[2] = (PIMAGE_SECTION_HEADER)

((BYTE*)sImageSecctionHeader[1] + sizeof(IMAGE_SECTION_HEADER));

#else

psImageNTHeader = (PIMAGE_NT_HEADERS32)

((BYTE*)psImageNTHeader + sizeof(IMAGE_NT_HEADERS32));

#endif

//已4KB为单位分配内存空间

dwPageSize = sImageSecctionHeader[2]->Misc.VirtualSize +

sImageSecctionHeader[2]->VirtualAddress;

dwPageSize = (dwPageSize / 0x1000 + 1) * 0x1000;//已4KB为单位对齐

lpvResult2 = VirtualAlloc(

NULL

dwPageSize, // 内存大小, 以字节为单位

//拷贝PE文件头

#ifdefX64

CopyMemory(lpvResult2, lpvResult, psImageDOSHeader->e_lfanew +

sizeof(IMAGE_NT_HEADERS64) + 3 * sizeof(IMAGE_SECTION_HEADER));

#else

sizeof(IMAGE_NT_HEADERS32) + 3 * sizeof(IMAGE_SECTION_HEADER));

#endif

//拷贝第0个节区数据

CopyMemory((BYTE*)lpvResult2 + sImageSecctionHeader[0]->VirtualAddress

(BYTE*)lpvResult + sImageSecctionHeader[0]->PointerToRawData

sImageSecctionHeader[0]->Misc.VirtualSize);

//拷贝第1个节区数据

CopyMemory(

(BYTE*)lpvResult2 + sImageSecctionHeader[1]->VirtualAddress

(BYTE*)lpvResult + sImageSecctionHeader[1]->PointerToRawData

sImageSecctionHeader[1]->Misc.VirtualSize);

//拷贝第2个节区数据

CopyMemory(

(BYTE*)lpvResult2 + sImageSecctionHeader[2]->VirtualAddress

(BYTE*)lpvResult + sImageSecctionHeader[2]->PointerToRawData

sImageSecctionHeader[2]->Misc.VirtualSize);

//打印输出

printf(" Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");

printf("

\n");

for (DWORDi = 0; i

{

if (i % 8 == 0) printf(" ");

if (i % 16 == 0 )

{

if (i != 0)

CopyMemory(buffer, (PBYTE)lpvResult2 + i - 16,16);

for (intj = 0;j

{

if(*((PBYTE)buffer + j)>=32 && *((PBYTE)buffer + j)

printf("%c", *((PBYTE)buffer + j));

else

printf(".");

}

memset(buffer,0,16);

printf("\n %08X ",i);

}

printf("% 02X ", *((PBYTE)lpvResult2 + i));

CloseHandle(hFile);

printf("\n");

system("pause");

return 0;

注意

1.此例为控制台程序,在控制台窗口输出读进内存中PE文件二进制数据。

2.读进内存中的PE文件按内存格式存储,以4KB(1000H)为单位对齐,其中PE文件头占据一页内存空间。虚拟内存空间由VirtualAlloc函数按PE文件在内存中以4KB(页)为单位对其后的大小分配。

3.为了在控制台窗口右侧输出ASCII码字符,采用ANSI多字节字符集。

4.程序采用条件编译形式,支持读取32位PE文件和64位PE文件。

5.程序只读取了64位notepad64.exe前3个节区,由于输出内容超过一页,控制台窗口无法打印全部内容,可以采用命令行定向输出的方式,将打印内容输出到文本文件中。打开命令行窗口,命令行命令如下所示:

Microsoft Windows [版本 10.0.16299.2166]

(c) 2017 Microsoft Corporation。保留所有权利。

C:\Users\16400>d:

D:\code\winpe\Debug>ReadPe.exe > output.txt

D:\code\winpe\Debug>

运行:

图3-19 打印输出内存中的PE文件一

总结

示例代码中,我们分析一下实现的流程:

第一步:首先调用CreateFile函数打开一个磁盘PE文件。

第二步:调用GetFileSize函数获取文件大小。

第三步:调用VirtualAlloc函数分配与PE文件相同大小的缓冲区。

第四步:调用ReadFile函数将PE文件读入缓冲区。

第五步:计算并调用VirtualAlloc函数分配PE映像文件所需内存空间的大小。计算方法为取最后一个节区在虚拟内存中的起始地址+节区的实际大小,再向下取整。

//以4KB为单位分配内存空间

dwPageSize = sImageSecctionHeader[2]->Misc.VirtualSize +

sImageSecctionHeader[2]->VirtualAddress;

dwPageSize = (dwPageSize / 0x1000 + 1) * 0x1000;//已4KB为单位对齐

第六步:拷贝PE文件头和节区到已分配的虚拟内空间。

//从文件拷贝到内存:

CopyMemory(pNFileBuffer,pImageInMem,pImageOptionalHeader->SizeOfHeaders);

for (i = 0 ; i NumberOfSections ; i++)

{

CopyMemory(pNFileBuffer+pImageSectionHeader->PointerToRawData ,

pImageInMem + pImageSectionHeader->VirtualAddress ,

pImageSectionHeader->SizeOfRawData);

pImageSectionHeader++;

}

第七步:在控制台窗口打印PE内存映像,或者以文件的形式将PE映像文件写入磁盘。

【流程图】

实验二十:CreateFileMapping将PE文件读进内存(C语言)

以32位和64位记事本程序为例,使用CreatFileMapping函数将记事本程序以内存格式读进内存:

●源码

/*

FileName:ReadPe2.c

实验20:使用文件映射将PE文件读进内存

(c) bcdaren, 2024

*/

#include

#include

//#define X64

{

HANDLEhFile;

HANDLEhMapFile = NULL;

PBYTElpMemory = NULL; //PE文件内存映射文件地址

charbuffer[16] = {0};

DWORDdwFileSize;

DWORDdwBytesRead = 0;

LPCSTRszFileName = TEXT("c:\\notepad32.exe");

PIMAGE_DOS_HEADERlpstDOS;

#ifdef X64

PIMAGE_NT_HEADERS64lpstNT;

#else

PIMAGE_NT_HEADERS32lpstNT;

#endif

//PIMAGE_SECTION_HEADER sImageSecctionHeader[3]; //不一定为3个节表项

NULL);

if (hFile == INVALID_HANDLE_VALUE)

printf("打开文件失败!\n");

else

{

dwFileSize = GetFileSize(hFile, 0);//获得文件大小可通过结构体获取

//创建内存映射文件

if (dwFileSize)

{

if (hMapFile = CreateFileMapping(hFile,NULL, PAGE_READONLY, 0, 0, NULL))

{

//获得文件在内存的映象起始位置

lpMemory = MapViewOfFile(hMapFile, FILE_MAP_READ, 0, 0, 0);

if (!lpMemory)

printf("获取映像起始地址失败!\n");

//检查PE文件是否有效

lpstDOS = (IMAGE_DOS_HEADER *)lpMemory;

if (lpstDOS->e_magic != IMAGE_DOS_SIGNATURE)

printf("非PE格式文件!\n");

#ifdef X64

lpstNT = (PIMAGE_NT_HEADERS64)(lpMemory + lpstDOS->e_lfanew);

#else

lpstNT = (PIMAGE_NT_HEADERS32)(lpMemory + lpstDOS->e_lfanew);

#endif

if (lpstNT->Signature != IMAGE_NT_SIGNATURE)

}

}

}

//打印输出

printf(" Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");

printf("\n");

for (DWORDi = 0; i

{

if (i % 16 == 0 )

{

if (i != 0)

CopyMemory(buffer, (PBYTE)lpMemory + i - 16,16);

{

else

printf(".");

}

}

printf("% 02X ", *((PBYTE)lpMemory + i));

}

return 0;

}

运行:

图3-20 使用映射方法读取PE文件

总结

1.示例代码实现流程:

实验二十使用文件映射的方法将PE文件读取到内存映射区。

第一步调用CreateFile函数打开磁盘PE文件。

第二步调用GetFileSize函数获取PE文件大小。

第三步调用CreateFileMapping函数创建文件映射对象。

第四步调用MapViewOfFile函数将文件映射的视图映射到调用进程的地址空间中。返回文件中映射区内的起始地址。

上述将PE文件读进内存的实现方法简单便捷,比使用ReadFile函数读取PE文件的效率高的多。

【注意】 PE有两个状态 一种是文件状态(FileBuffer) 一种是内存状态(ImageBuffer )。

2.拷贝节表

现在理解PE被分为多个区块,我们称之为节区。节区是错开的,其实PE错开的可能非常大,只是我们见到的节区并没错开。磁盘PE文件节表是可以交叉,可以重叠,可以乱序。而内存中的PE映像文件不可以交叉乱序重叠。如图3-21所示:

来源:小羊科技论

相关推荐