网站首页 > 技术教程 正文
磁盘中的PE文件如果想要在操作系统上运行就必须要将其加载到内存中。同样,如果我们要对一个磁盘PE文件进行读写操作,也需要将其加载到内存。接下来我们将通过实验介绍两种不同的方法,将一个磁盘PE文件读进内存。我们先介绍第一种方法,使用ReadFile函数将PE文件读入一个由VirtualAlloc函数分配的虚拟内存空间。
实验十九:ReadFile将PE文件读进内存(C语言)
以32位和64位记事本程序为例,使用ReadFile函数将记事本程序以内存格式读进内存:
●源码
/*------------------------------------------------------------------------
FileName:ReadPe1.c
实验19:ReadFile将PE文件读进内存
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <stdio.h>
#include <windows.h>
#define X64
int main(int argc, char* argv[])
{
HANDLE hFile;
LPVOID lpvResult, lpvResult2;
char buffer[16] = {0};
DWORD dwPageSize;
DWORD dwBytesRead = 0;
BOOL bReadFile;
LPCSTR szFileName = TEXT("c:\\notepad64.exe");
PIMAGE_DOS_HEADER psImageDOSHeader;
#ifdef X64
PIMAGE_NT_HEADERS64 psImageNTHeader;
#else
PIMAGE_NT_HEADERS32 psImageNTHeader;
#endif
PIMAGE_SECTION_HEADER sImageSecctionHeader[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);
//实现将磁盘文件地址偏移至内存地址
#ifdef X64 //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
psImageDOSHeader = (PIMAGE_DOS_HEADER)lpvResult;
psImageNTHeader = (PIMAGE_NT_HEADERS32)
((BYTE*)lpvResult + psImageDOSHeader->e_lfanew);
sImageSecctionHeader[0] = (PIMAGE_SECTION_HEADER)
((BYTE*)psImageNTHeader + sizeof(IMAGE_NT_HEADERS32));
sImageSecctionHeader[1] = (PIMAGE_SECTION_HEADER)
((BYTE*)sImageSecctionHeader[0] + sizeof(IMAGE_SECTION_HEADER));
sImageSecctionHeader[2] = (PIMAGE_SECTION_HEADER)
((BYTE*)sImageSecctionHeader[1] + sizeof(IMAGE_SECTION_HEADER));
#endif
//已4KB为单位分配内存空间
dwPageSize = sImageSecctionHeader[2]->Misc.VirtualSize +
sImageSecctionHeader[2]->VirtualAddress;
dwPageSize = (dwPageSize / 0x1000 + 1) * 0x1000;//已4KB为单位对齐
lpvResult2 = VirtualAlloc(
NULL,
dwPageSize, // 内存大小, 以字节为单位
MEM_COMMIT, // 将申请到的虚拟内存提交到物理内存
PAGE_READWRITE); // read/write 属性
//拷贝PE文件头
#ifdef X64
CopyMemory(lpvResult2, lpvResult, psImageDOSHeader->e_lfanew +
sizeof(IMAGE_NT_HEADERS64) + 3 * sizeof(IMAGE_SECTION_HEADER));
#else
CopyMemory(lpvResult2, lpvResult, psImageDOSHeader->e_lfanew +
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 (DWORD i = 0; i < dwPageSize; i++)
{
if (i % 8 == 0) printf(" ");
if (i % 16 == 0 )
{
if (i != 0)
CopyMemory(buffer, (PBYTE)lpvResult2 + i - 16,16);
for (int j = 0;j < 16 && i != 0;j++)
{
if(*((PBYTE)buffer + j)>=32 && *((PBYTE)buffer + j)<= 126)
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>
运行:
总结
示例代码中,我们分析一下实现的流程:
第一步:首先调用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 < pImageFileHeader->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 <stdio.h>
#include <windows.h>
//#define X64
int main(int argc, char* argv[])
{
HANDLE hFile;
HANDLE hMapFile = NULL;
PBYTE lpMemory = NULL; //PE文件内存映射文件地址
char buffer[16] = {0};
DWORD dwFileSize;
DWORD dwBytesRead = 0;
LPCSTR szFileName = TEXT("c:\\notepad32.exe");
PIMAGE_DOS_HEADER lpstDOS;
#ifdef X64
PIMAGE_NT_HEADERS64 lpstNT;
#else
PIMAGE_NT_HEADERS32 lpstNT;
#endif
//PIMAGE_SECTION_HEADER sImageSecctionHeader[3]; //不一定为3个节表项
hFile = CreateFile(szFileName,
GENERIC_READ, // 只读打开
FILE_SHARE_READ, // 允许其他进程以读取方式打开文件
NULL, // 默认安全属性
OPEN_EXISTING, // 打开已存在的文件
FILE_ATTRIBUTE_NORMAL, // 普通文件
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("非PE格式文件!\n");
}
}
}
//打印输出
printf(" Offset 0 1 2 3 4 5 6 7 8 9 A B C D E F\n");
printf("_____________________________________________________________________________\n");
for (DWORD i = 0; i < dwFileSize; i++)
{
if (i % 8 == 0) printf(" ");
if (i % 16 == 0 )
{
if (i != 0)
CopyMemory(buffer, (PBYTE)lpMemory + i - 16,16);
for (int j = 0;j < 16 && i != 0;j++)
{
if(*((PBYTE)buffer + j)>=32 && *((PBYTE)buffer + j)<= 126)
printf("%c", *((PBYTE)buffer + j));
else
printf(".");
}
memset(buffer,0,16);
printf("\n %08X ",i);
}
printf("% 02X ", *((PBYTE)lpMemory + i));
}
CloseHandle(hFile);
printf("\n");
system("pause");
return 0;
}
运行:
总结
1.示例代码实现流程:
实验二十使用文件映射的方法将PE文件读取到内存映射区。
第一步调用CreateFile函数打开磁盘PE文件。
第二步调用GetFileSize函数获取PE文件大小。
第三步调用CreateFileMapping函数创建文件映射对象。
第四步调用MapViewOfFile函数将文件映射的视图映射到调用进程的地址空间中。返回文件中映射区内的起始地址。
上述将PE文件读进内存的实现方法简单便捷,比使用ReadFile函数读取PE文件的效率高的多。
【注意】 PE有两个状态 一种是文件状态(FileBuffer) 一种是内存状态(ImageBuffer )。
2.拷贝节表
现在理解PE被分为多个区块,我们称之为节区。节区是错开的,其实PE错开的可能非常大,只是我们见到的节区并没错开。磁盘PE文件节表是可以交叉,可以重叠,可以乱序。而内存中的PE映像文件不可以交叉乱序重叠。如图3-21所示:
- 上一篇: Windows内存映射文件
- 下一篇: c# 10 教程:24 本机和 COM 互操作性
猜你喜欢
- 2025-01-12 Spring Boot RESTful API设计:最佳实践指南
- 2025-01-12 由 Mybatis 源码畅谈软件设计(二):MappedStatement 和 SqlSource
- 2025-01-12 详细介绍一下Spring Boot中如何使用Hive?
- 2025-01-12 OGG同步到Kafka
- 2025-01-12 由 Mybatis 源码畅谈软件设计(五):ResultMap 的循环引用
- 2025-01-12 【从零开始】5. 向量数据库选型与搭建
- 2025-01-12 Spring Boot 项目轻松集成 Redis
- 2025-01-12 How China's Drone Manufacturers Leapfrog From Latecomers to Global Leaders
- 2025-01-12 Spring Boot与MyBatis:简化数据库操作
- 2025-01-12 SpringBoot整合ElasticSearch实现数据存储?
你 发表评论:
欢迎- 01-12Spring Boot RESTful API设计:最佳实践指南
- 01-12由 Mybatis 源码畅谈软件设计(二):MappedStatement 和 SqlSource
- 01-12详细介绍一下Spring Boot中如何使用Hive?
- 01-12OGG同步到Kafka
- 01-12由 Mybatis 源码畅谈软件设计(五):ResultMap 的循环引用
- 01-12【从零开始】5. 向量数据库选型与搭建
- 01-12Spring Boot 项目轻松集成 Redis
- 01-12How China's Drone Manufacturers Leapfrog From Latecomers to Global Leaders
- 最近发表
-
- Spring Boot RESTful API设计:最佳实践指南
- 由 Mybatis 源码畅谈软件设计(二):MappedStatement 和 SqlSource
- 详细介绍一下Spring Boot中如何使用Hive?
- OGG同步到Kafka
- 由 Mybatis 源码畅谈软件设计(五):ResultMap 的循环引用
- 【从零开始】5. 向量数据库选型与搭建
- Spring Boot 项目轻松集成 Redis
- How China's Drone Manufacturers Leapfrog From Latecomers to Global Leaders
- Spring Boot与MyBatis:简化数据库操作
- SpringBoot整合ElasticSearch实现数据存储?
- 标签列表
-
- sd分区 (65)
- raid5数据恢复 (81)
- 地址转换 (73)
- 手机存储卡根目录 (55)
- tcp端口 (74)
- project server (59)
- 双击ctrl (55)
- 鼠标 单击变双击 (67)
- debugview (59)
- 字符动画 (65)
- flushdns (57)
- ps复制快捷键 (57)
- 清除系统垃圾代码 (58)
- web服务器的架设 (67)
- 16进制转换 (69)
- xclient (55)
- ps源文件 (67)
- filezilla server (59)
- 句柄无效 (56)
- word页眉页脚设置 (59)
- ansys实例 (56)
- 6 1 3固件 (59)
- sqlserver2000挂起 (59)
- vm虚拟主机 (55)
- config (61)
本文暂时没有评论,来添加一个吧(●'◡'●)