要读取已经执行的进程的 PE 结构,可以使用以下步骤:
- 获取目标进程的句柄(使用
OpenProcess
函数)。 - 获取目标进程的基址(即模块句柄)(使用
GetModuleHandleEx
或EnumProcessModules
函数)。 - 读取目标进程内存中的 DOS 头(使用
ReadProcessMemory
函数)。 - 从 DOS 头中获取 NT 头的偏移位置(即 DOS 头中的
e_lfanew
字段)。 - 读取目标进程内存中的 NT 头(使用
ReadProcessMemory
函数)。 - 从 NT 头中获取可选头的偏移位置。
- 读取目标进程内存中的可选头(使用
ReadProcessMemory
函数)。 - 根据可选头中的数据目录表中的导入表项,获取导入表的位置。
- 读取目标进程内存中的导入表(使用
ReadProcessMemory
函数)。
void OpenProcessAnalyzeImportTable(DWORD dwProcessId)
{
// 13056
HANDLE hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, FALSE, dwProcessId);
if (hProcess != NULL)
{
// 获取目标进程模块句柄
HMODULE hModules[1];
DWORD cbNeeded;
if (!EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded))
{
printf("获取模块句柄失败\n");
CloseHandle(hProcess);
return;
}
//解析DOS头
IMAGE_DOS_HEADER DosHeader;
ReadProcessMemory(hProcess, (LPVOID)hModules[0], &DosHeader, sizeof(IMAGE_DOS_HEADER), NULL);
printf("Dos e_magic : %08X\n", DosHeader.e_magic);
printf("Dos e_lfanew : %08X\n", DosHeader.e_lfanew);
// 获取 NT 头的偏移位置
DWORD ntHeaderOffset = DosHeader.e_lfanew;
// e_lfanew 字段中的偏移是相对于可执行文件的起始位置的偏移
// 读取目标进程的 NT 头
IMAGE_NT_HEADERS ntHeader;
if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + ntHeaderOffset, &ntHeader, sizeof(ntHeader), NULL))
{
printf("读取 NT 头失败\n");
CloseHandle(hProcess);
return;
}
printf("NT Signature : %08X\n", ntHeader.Signature);
//标准文件头
IMAGE_FILE_HEADER fileHeader;
DWORD fileHeaderOffset = DosHeader.e_lfanew + sizeof(DWORD);
if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + fileHeaderOffset, &fileHeader, sizeof(fileHeader), NULL))
{
printf("读取标准文件头失败\n");
CloseHandle(hProcess);
return;
}
printf("Section number : %08X\n", fileHeader.NumberOfSections);
//扩展文件头
IMAGE_OPTIONAL_HEADER optionHeader;
DWORD optionHeaderOffset = DosHeader.e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER);
if (!ReadProcessMemory(hProcess,(LPBYTE)hModules[0] + optionHeaderOffset, &optionHeader, sizeof(optionHeader), NULL))
{
printf("读取扩展头失败\n");
CloseHandle(hProcess);
return;
}
printf("IMAGE_OPTIONAL_HEADER Magic %08X\n", optionHeader.Magic);
for (size_t i = 0; i < IMAGE_NUMBEROF_DIRECTORY_ENTRIES; i++)
{
printf("%s\n", DataDirName[i]);
printf("\t%08X\n", optionHeader.DataDirectory[i].VirtualAddress);
}
// 获取导入表的位置
DWORD importTableRva = optionHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
// 读取目标进程的导入表
IMAGE_IMPORT_DESCRIPTOR importTable;
if (!ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + importTableRva, &importTable, sizeof(IMAGE_IMPORT_DESCRIPTOR), NULL))
{
printf("读取导入表失败\n");
CloseHandle(hProcess);
return;
}
DWORD dwIndex = 1;
while (importTable.OriginalFirstThunk != 0 || importTable.FirstThunk != 0)
{
// 读取模块名 !!!
// importTable.Name RVA
char szModuleName[MAX_PATH];
LPVOID moduleNameAddress = (LPBYTE)hModules[0] + importTable.Name;
if (!ReadProcessMemory(hProcess, moduleNameAddress, szModuleName, sizeof(szModuleName), NULL))
{
printf("读取模块名失败\n");
CloseHandle(hProcess);
return;
}
printf("模块名称 : %s\n", szModuleName);
printf("\t日期时间标志 : %08X\n", importTable.TimeDateStamp);
printf("\tForwarderChain : %08X\n", importTable.ForwarderChain);
printf("\t名称Offset : %08X\n", importTable.Name);
printf("\tFirstThunk : %08X\n", importTable.FirstThunk);
printf("\tOriginalFirstThunk : %08X\n", importTable.OriginalFirstThunk);
ReadProcessMemory(hProcess, (LPBYTE)hModules[0] + importTableRva + sizeof(IMAGE_IMPORT_DESCRIPTOR) * dwIndex, &importTable, sizeof(IMAGE_IMPORT_DESCRIPTOR), NULL);
dwIndex++;
}
}
else
{
DWORD dwError = GetLastError();
printf("进程打开失败,错误代码 %d\n", dwError);
}
}
OpenProcess 函数
OpenProcess
是一个 Windows API 函数,用于打开一个已存在的进程并返回其句柄。
语法
HANDLE OpenProcess(
DWORD dwDesiredAccess, // 指定对进程对象的访问权限
BOOL bInheritHandle, // 指定新句柄是否可被子进程继承
DWORD dwProcessId // 要打开的进程的标识符。可以使用进程标识符(PID)来指定目标进程
);
示例
DWORD dwProcessId = 1234;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);
if (hProcess != NULL) {
printf("进程句柄获取成功!\n");
// 对进程进行操作...
CloseHandle(hProcess);
} else {
DWORD dwError = GetLastError();
printf("进程句柄获取失败,错误代码:%d\n", dwError);
}
GetModuleHandleEx 函数
GetModuleHandleEx
函数用于获取指定进程中模块的句柄(基址)。
函数原型
BOOL GetModuleHandleEx(
DWORD dwFlags, // 标志位,用于指定搜索模块的方式
LPCTSTR lpModuleName, // 要获取句柄的模块名称。可以为 NULL,表示获取当前进程中的模块句柄
HMODULE *phModule // 用于接收获取到的模块句柄的指针
);
GetModuleHandleEx 函数获取当前进程的模块句柄
HMODULE hModule = NULL;
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS, NULL, &hModule)
GetModuleHandleEx 函数获取指定进程的模块句柄
// 获取指定进程的句柄
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
// 获取模块的基址
GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,NULL, &hModule)
进程句柄 & 内存基址 & 模块句柄 & 模块基址
- 进程的句柄与加载到内存的基址是相关的。
- 进程句柄只是一个用于标识和引用进程的标识符,它本身并不直接提供进程的内存位置信息。
- 但是,通过
使用进程句柄可以获取进程加载到内存中的模块句柄(Module Handle)或模块基址(Module Base Address)
- 模块句柄或模块基址是指表示进程中加载的某个模块(如动态链接库或可执行文件)在内存中的起始地址。通过获取进程的模块句柄或模块基址,可以进一步定位和操作模块内的数据、函数和资源。
- 对于一个已加载的模块,
模块句柄和模块基址通常是相同的
,它们都指向模块在内存中的起始位置。因此,在大多数情况下,可以将模块句柄和模块基址视为相同的概念,用于引用和访问模块。
EnumProcessModules 函数
EnumProcessModules 函数是Windows API中的一个函数,用于获取指定进程中加载的所有模块(DLL)的句柄。
BOOL EnumProcessModules(
HANDLE hProcess, // 要枚举模块的进程的句柄
HMODULE *lphModule, // 指向模块句柄数组的指针,用于接收模块句柄
DWORD cb, // 指定模块句柄数组的大小(以字节为单位)
LPDWORD lpcbNeeded // 指向接收实际返回的模块句柄数的变量的指针
);
该函数的作用是枚举指定进程中加载的模块
,并将每个模块的句柄存储在提供的数组中
。通过指定的句柄数组大小,可以控制要获取的模块句柄的数量。
示例代码 获取指定进程中加载的模块(DLL)的文件名
tips
- 获取所有模块,定义接收模块数组
HMODULE hModules[1024];
,数组中的第一个元素(hModules[0])对应的是主模块(也就是进程自身的模块)的句柄
DWORD processId = 1234; // 替换为目标进程的 ID
// 打开进程获取句柄
HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processId);
HMODULE hModules[1024];
DWORD cbNeeded;
// 获取进程中加载的模块句柄
if (EnumProcessModules(hProcess, hModules, sizeof(hModules), &cbNeeded))
{
int moduleCount = cbNeeded / sizeof(HMODULE);
for (int i = 0; i < moduleCount; i++)
{
TCHAR szModuleName[MAX_PATH];
// 获取模块文件名
if (GetModuleFileNameEx(hProcess, hModules[i], szModuleName, sizeof(szModuleName) / sizeof(TCHAR)))
{
std::cout << "Module " << i + 1 << ": " << szModuleName << std::endl;
}
}
}
else
{
std::cout << "Failed to enumerate modules" << std::endl;
}
ReadProcessMemory 函数
ReadProcessMemory
是一个 Windows API 函数,用于从一个指定进程的内存中读取数据。
语法
BOOL ReadProcessMemory(
HANDLE hProcess, // 要读取内存的目标进程的句柄
LPCVOID lpBaseAddress, // 要读取的内存地址在目标进程中的起始位置
LPVOID lpBuffer, // 用于存储读取的数据的缓冲区
SIZE_T nSize, // 要读取的字节数
SIZE_T *lpNumberOfBytesRead // 指向存储实际读取字节数的变量的指针
);
示例
DWORD dwProcessId = 1234;
HANDLE hProcess = OpenProcess(PROCESS_VM_READ, FALSE, dwProcessId);
if (hProcess != NULL) {
LPVOID lpBaseAddress = (LPVOID)0x12345678;
BYTE buffer[1024];
SIZE_T nSize = sizeof(buffer);
SIZE_T nBytesRead = 0;
BOOL bSuccess = ReadProcessMemory(hProcess, lpBaseAddress, buffer, nSize, &nBytesRead);
if (bSuccess) {
printf("读取成功!读取到的字节数:%d\n", nBytesRead);
// 处理读取到的数据...
} else {
DWORD dwError = GetLastError();
printf("读取失败,错误代码:%d\n", dwError);
}
CloseHandle(hProcess);
} else {
DWORD dwError = GetLastError();
printf("进程句柄获取失败,错误代码:%d\n", dwError);
}