有用的Windows代码: 获取进程中加载的PE模块; 获取进程中加载的PE模块的节表

文章介绍了如何通过C++代码在给定进程ID的情况下,获取进程加载的PE模块的内存加载基址,并提取模块的节表信息,包括节的名称、虚拟地址、大小等。作者通过实例展示了如何在测试中查看和解读节表内容。
摘要由CSDN通过智能技术生成

获取进程中加载的PE模块

根据所给PE模块的绝对路径(libPath),获取目标进程中已加载的PE模块的内存加载基址。

HMODULE FindModuleByPath(DWORD pid, const WCHAR* libPath) {
	HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); // 根据进程id获取进程句柄
	if (hProcess == 0) {
		return NULL;
	}

	HMODULE hModules[1024] = {};
	SIZE_T hModulesSize = sizeof(hModules);
	DWORD hModulesSizeNeeded = 0;
	DWORD moduleNameSize = 0;
	SIZE_T hModulesCount = 0;
	WCHAR rModuleName[MAX_PATH] = { 0 };
	HMODULE rModule = NULL;

	EnumProcessModules(hProcess, hModules, hModulesSize, &hModulesSizeNeeded); // 枚举进程模块, 将所有模块的内存加载地址存到hModules数组
	hModulesCount = hModulesSizeNeeded / sizeof(HMODULE);
	size_t i = 0;
	for (; i < hModulesCount; i++) {
		rModule = hModules[i]; // 这个是模块的内存加载基址
		GetModuleFileNameExW(hProcess, rModule, rModuleName, MAX_PATH); // 获取PE模块的文件路径
		if (lstrcmpi(rModuleName, libPath) == 0) { // 比较字符串(忽略大小写)
			break;
		}
	}
	if (i == hModulesCount) 
		return NULL;
	return rModule;
}

获取进程中加载的PE模块的节表

int GetPESecsInfo(HANDLE hProcess, LPCVOID ModuleBase) {
	DWORD headerBufferSize = 0x1000;
	LPVOID peHeader = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, headerBufferSize); // 分配空间, 用于保存读取到的PE头
	if (peHeader == NULL) return -1;
	ReadProcessMemory(hProcess, ModuleBase, peHeader, headerBufferSize, NULL); // 读取dll的PE头

	PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)peHeader;
	PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)peHeader + dosHeader->e_lfanew);

	PIMAGE_SECTION_HEADER curSec = IMAGE_FIRST_SECTION(ntHeader), nextSec = NULL; // curSec为节表第一个节
	WORD secNum = ntHeader->FileHeader.NumberOfSections;
	WORD i;
	for (i = 0; i < secNum; i++) { // 遍历节表, 找到.text节
		if (curSec == NULL) return -1;
		if (i + 1 < secNum) {
			nextSec = curSec + 1; // 得到`.text`节的下一节的信息
		}
		PVOID secStart = (PBYTE)ModuleBase + (size_t)curSec->VirtualAddress; // 计算节的开始地址
		PVOID secEndDataRVA = (PBYTE)curSec->VirtualAddress + curSec->Misc.VirtualSize; // 计算节的内容的结束地址
		PVOID secDataEnd = (PBYTE)ModuleBase + (size_t)secEndDataRVA; // 计算节中数据的结束地址
		PVOID secEnd;
		if (nextSec) {
			secEnd = (PBYTE)ModuleBase + (size_t)nextSec->VirtualAddress; // 计算节的结束地址(即下一节的起始地址)
		} else {
			PVOID secEndDataRVA = (PBYTE)curSec->VirtualAddress + curSec->SizeOfRawData;
			secEnd = (PBYTE)ModuleBase + (size_t)secEndDataRVA; // 计算节的结束地址
		}
		printf("Sec: %s; VirtualAddress: 0x%x; VirtualSize: 0x%x; PointerToRawData: 0x%x; SizeOfRawData: 0x%x; secStart: 0x%p; secDataEnd: 0x%p; secEnd: 0x%p\n", curSec[i].Name, curSec->VirtualAddress, curSec->Misc.VirtualSize, curSec->PointerToRawData, curSec->SizeOfRawData, secStart, secDataEnd, secEnd);
		curSec = nextSec;
	}
	HeapFree(GetProcessHeap(), NULL, peHeader);
	return 0;
}

测试

测试中查看PING.exe进程中的PING.exe模块。首先使用LordPE查看在磁盘中的PE文件的节表(图一):
在这里插入图片描述
使用Process Hacker查看在内存中的PE文件的节表(图二):
在这里插入图片描述
Process HackerPING.exe模块在内存中的分区概况(图三):
在这里插入图片描述
Process HackerPING.exe模块的.text节内容(也即0x7ff62d0d1000起始处的内存区)(图四):
在这里插入图片描述
前述代码打印的.text节的信息(图五):
在这里插入图片描述
借此测试,复习一下节表涉及到地址的各字段的含义:

  • VOffset:对应IMAGE_SECTION_HEADER::VirtualAddress字段。该值加上模块的内存基址,得到节的内存基址。
  • VSize:对应IMAGE_SECTION_HEADER::Misc.VirtualSize字段。以内存中节表的该值加上节的内存基址,得到节的内容的结束地址。图五中打印.text节的VirtualSize0x2c60,而在图四中可看到,.text节的数据正好在偏移0x2c60前结束。
  • ROffset:对应IMAGE_SECTION_HEADER::PointerToRawData字段。这个是节在磁盘文件中的偏移位置。
  • RSize:对应IMAGE_SECTION_HEADER::SizeOfRawData字段。看起来这个值在磁盘和内存中各不相同(0x2a000x2e00),但比较有用的是其在磁盘中的值。比如,.text节的磁盘偏移0x400加上其RSize0x2a00),得到下一个节.data节的磁盘偏移0x2e00。在内存中,要以内存页的大小0x1000对齐,所以0x1000+0x2e00得到0x3e00,对齐后得到下一节的地址是0x4000
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值