为了将程序读到内存指定位置,本节我们将讨论如何使用两种不同的方法遍历导出表。此外,我们还将给出一个打印进程调用kernel32中的API信息的示例程序。
本节必须掌握的知识点:
遍历导出表
打印kernel32
5.2.1 遍历导出表
■方法一
实验三十四:遍历导出表方法一
以下代码实现打印进程导出表的函数信息(导出表一般只存在于dll中,OD除外)。
使用常规方法实现,将程序读到内存指定位置,进行磁盘与内存的转换。
/*------------------------------------------------------------------------
FileName: GetExportInfo.c
实验34:方法一,使用常规方法实现,将程序读到内存指定位置,进行磁盘与内存的转换。
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>
#include <dbghelp.h>
#pragma comment (lib,"dbghelp")
#define WIN64
DWORD RvaToFoa(PIMAGE_NT_HEADERS ntHeaders, DWORD rva) {
//ntHeaders+4+sizeof(IMAGE_FILE_HEADER)+FileHeader.SizeOfOptionalHeader(32或64位PE)
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(ntHeaders);
WORD numberOfSections = ntHeaders->FileHeader.NumberOfSections;
for (WORD i = 0; i < numberOfSections; i++) {
DWORD sectionStartRva = sectionHeader->VirtualAddress;
DWORD sectionEndRva = sectionStartRva + sectionHeader->SizeOfRawData;
if (rva >= sectionStartRva && rva < sectionEndRva) {
DWORD foa = sectionHeader->PointerToRawData +
(rva - sectionStartRva);
return foa;
}
sectionHeader++;
}
return 0; // RVA not found
}
void main()
{
#ifdef WIN64
//LPSTR szFileName;
HANDLE hFile;
LPVOID lpvResult;
DWORD dwPageSize;
DWORD dwBytesRead = 0;
BOOL bReadFile;
PIMAGE_DOS_HEADER psImageDOSHeader;
PIMAGE_NT_HEADERS64 psImageNTHeader;
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;
DWORD* pdwAddrOfFunc;
DWORD* pdwAddrOfName;
WORD* pdwAddrOfNameOrd;
DWORD* pdwTemp;
TCHAR szFileName[] = TEXT("c:\\kernel32_64.dll");
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);
//读入内存
bReadFile = ReadFile(hFile, lpvResult, dwPageSize, &dwBytesRead, 0);
psImageDOSHeader = (PIMAGE_DOS_HEADER)lpvResult;
psImageNTHeader = (PIMAGE_NT_HEADERS64)\
((BYTE*)lpvResult + psImageDOSHeader->e_lfanew);
DWORD dwTemp = psImageNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
dwTemp = RvaToFoa(psImageNTHeader, (DWORD)dwTemp);
pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwTemp + (ULONGLONG)lpvResult);
pdwTemp = (DWORD*)(RvaToFoa(psImageNTHeader, pImageExportDirectory->AddressOfNames) + (ULONGLONG)lpvResult);
pdwAddrOfFunc = (DWORD*)(RvaToFoa(psImageNTHeader, pImageExportDirectory->AddressOfFunctions) + (ULONGLONG)psImageDOSHeader);
pdwAddrOfNameOrd = (WORD*)(RvaToFoa(psImageNTHeader,pImageExportDirectory->AddressOfNameOrdinals) + (ULONGLONG)psImageDOSHeader);
printf("序号\t函数地址\t函数名\n");
for (DWORD i = 0; i < pImageExportDirectory->NumberOfFunctions; i++)
{
pdwAddrOfName = (DWORD*)((DWORD)RvaToFoa(psImageNTHeader, *pdwTemp) +
(ULONGLONG)psImageDOSHeader);
printf("0x%04x\t", *pdwAddrOfNameOrd);
printf("0x%08x\t", *pdwAddrOfFunc);
printf("%s\n", (char *)pdwAddrOfName);
pdwTemp++;
pdwAddrOfNameOrd++;
pdwAddrOfFunc++;
}
#else
//LPSTR szFileName;
HANDLE hFile;
LPVOID lpvResult;
DWORD dwPageSize;
DWORD dwBytesRead = 0;
BOOL bReadFile;
PIMAGE_DOS_HEADER psImageDOSHeader;
PIMAGE_NT_HEADERS32 psImageNTHeader;
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;
DWORD* pdwAddrOfFunc;
DWORD* pdwAddrOfName;
WORD* pdwAddrOfNameOrd;
DWORD* pdwTemp;
TCHAR szFileName[] = TEXT("c:\\kernel32_32.dll");
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);
//读入内存
bReadFile = ReadFile(hFile, lpvResult, dwPageSize, &dwBytesRead, 0);
psImageDOSHeader = (PIMAGE_DOS_HEADER)lpvResult;
psImageNTHeader = (PIMAGE_NT_HEADERS32)\
((BYTE*)lpvResult + psImageDOSHeader->e_lfanew);
DWORD dwTemp = psImageNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
dwTemp = RvaToFoa(psImageNTHeader, (DWORD)dwTemp);
pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dwTemp +
(DWORD)lpvResult);
pdwTemp = (DWORD*)(RvaToFoa(psImageNTHeader, pImageExportDirectory->AddressOfNames) + (DWORD)lpvResult);
pdwAddrOfFunc = (DWORD*)(RvaToFoa(psImageNTHeader, pImageExportDirectory->AddressOfFunctions) + (DWORD)psImageDOSHeader);
pdwAddrOfNameOrd = (WORD*)(RvaToFoa(psImageNTHeader, pImageExportDirectory->AddressOfNameOrdinals) + (DWORD)psImageDOSHeader);
printf("序号\t函数地址\t函数名\n");
for (DWORD i = 0; i < pImageExportDirectory->NumberOfFunctions; i++)
{
pdwAddrOfName = (DWORD*)((DWORD)RvaToFoa(psImageNTHeader, *pdwTemp) +
(DWORD)psImageDOSHeader);
printf("0x%04x\t", *pdwAddrOfNameOrd);
printf("0x%08x\t", *pdwAddrOfFunc);
printf("%s\n", (char *)pdwAddrOfName);
pdwTemp++;
pdwAddrOfNameOrd++;
pdwAddrOfFunc++;
}
#endif
system("pause");
return;
}
运行:
序号 函数地址 函数名
0x0000 0x00001160 AnimateClose
0x0001 0x00001010 AnimateOpen
0x0002 0x00001280 FadeInOpen
0x0003 0x000013c0 FadeOutClose
请按任意键继续. . .
总结
32位和64位PE文件导出表描述符是相同的,遍历导出表的区别只有两点,32位基址和64位基址,以及NT32位NT头和64位NT头的区别。
■方法二
实验三十五:遍历导出表方法二
使用LoadLibrary将DLL程序读到内存指定位置,进行磁盘与内存的转换。
/*------------------------------------------------------------------------
FileName: GetExportInfo.c
实验35:方法二,使用常规方法实现,将程序读到内存指定位置,进行磁盘与内存的转换。
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <windows.h>
#include <stdio.h>
//#define WIN64
void main()
{
#ifdef WIN64
HANDLE hMoudle;
DWORD dwBytesRead = 0;
PIMAGE_DOS_HEADER psImageDOSHeader;
PIMAGE_NT_HEADERS64 psImageNTHeader;
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;
DWORD* pdwAddrOfFunc;
DWORD* pdwAddrOfName;
WORD* pdwAddrOfNameOrd;
DWORD** ppdwTemp;
//有依赖项,加载失败
hMoudle = LoadLibrary(TEXT("C:\\winResult_64.dll"));
psImageDOSHeader = (PIMAGE_DOS_HEADER)hMoudle;
psImageNTHeader = (PIMAGE_NT_HEADERS64)((ULONGLONG)hMoudle + psImageDOSHeader->e_lfanew);
pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)psImageNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (ULONGLONG)hMoudle);
ppdwTemp = (DWORD**)(pImageExportDirectory->AddressOfNames + (ULONGLONG)psImageDOSHeader);
pdwAddrOfFunc = (DWORD*)(pImageExportDirectory->AddressOfFunctions + (ULONGLONG)psImageDOSHeader);
pdwAddrOfNameOrd = (WORD*)(pImageExportDirectory->AddressOfNameOrdinals + (ULONGLONG)psImageDOSHeader);
printf("序号\t函数地址\t函数名\n");
for (DWORD i = 0; i < pImageExportDirectory->NumberOfFunctions; i++)
{
pdwAddrOfName = (DWORD*)((DWORD)(*ppdwTemp) + (ULONGLONG)psImageDOSHeader);
printf("0x%04x ", *pdwAddrOfNameOrd);
printf("0x%08x ", *pdwAddrOfFunc);
printf("%s\n", (char *)pdwAddrOfName);
((DWORD*)ppdwTemp)++;//改为32位地址
pdwAddrOfNameOrd++;
pdwAddrOfFunc++;
}
#else
HANDLE hMoudle;
DWORD dwBytesRead = 0;
PIMAGE_DOS_HEADER psImageDOSHeader;
PIMAGE_NT_HEADERS32 psImageNTHeader;
PIMAGE_EXPORT_DIRECTORY pImageExportDirectory;
DWORD* pdwAddrOfFunc;
DWORD* pdwAddrOfName;
WORD* pdwAddrOfNameOrd;
DWORD** ppdwTemp;
hMoudle = LoadLibrary(TEXT("C:\\Windows\\System32\\kernel32.dll"));//不可以在"C:\"
psImageDOSHeader = (PIMAGE_DOS_HEADER)hMoudle;
psImageNTHeader = (PIMAGE_NT_HEADERS32)((DWORD)hMoudle + psImageDOSHeader->e_lfanew);
pImageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)psImageNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (DWORD)hMoudle);
ppdwTemp = (DWORD**)(pImageExportDirectory->AddressOfNames + (DWORD)psImageDOSHeader);
pdwAddrOfFunc = (DWORD*)(pImageExportDirectory->AddressOfFunctions + (DWORD)psImageDOSHeader);
pdwAddrOfNameOrd = (WORD*)(pImageExportDirectory->AddressOfNameOrdinals + (DWORD)psImageDOSHeader);
printf("序号\t函数地址\t函数名\n");
for (DWORD i = 0; i < pImageExportDirectory->NumberOfFunctions; i++)
{
pdwAddrOfName = (DWORD*)((DWORD)(*ppdwTemp) + (DWORD)psImageDOSHeader);
printf("0x%04x ", *pdwAddrOfNameOrd);
printf("0x%08x ", *pdwAddrOfFunc);
printf("%s\n", (char *)pdwAddrOfName);
ppdwTemp++;
pdwAddrOfNameOrd++;
pdwAddrOfFunc++;
}
#endif
system("pause");
return;
}
注意
关于IMAGE_DATA_DIRECTORY的成员Size指定了VirtualAddress指向结构体的的大小,没有实际含义,因为目标结构体内同样包含结构体指针变量,该指针可以指向任何位置(包括入口点或自身)。所以将Size修改为0,不会影响pe文件的执行。
5.2.2 打印进程模块列表
实验三十六:打印进程模块列表
以下示例打开指定进程,枚举并打印进程中的所有模块信息,包括模块名称、基址和大小。
/*------------------------------------------------------------------------
FileName:EnumerateProcessModules.c
实验36:打印指定进程模块列表。
(c) bcdaren, 2024
-----------------------------------------------------------------------*/
#include <Windows.h>
#include <stdio.h>
#include <psapi.h>
#include <tchar.h>
void EnumerateProcessModules(HANDLE hProcess) {
HMODULE hModules[1024];
DWORD cbNeeded;
unsigned int i;
if (EnumProcessModulesEx(hProcess, hModules, sizeof(hModules), &cbNeeded, LIST_MODULES_ALL)) {
for (i = 0; i < (cbNeeded / sizeof(HMODULE)); i++) {
TCHAR szModuleName[MAX_PATH];
FARPROC pEntryPoint;
// 获取模块名称
if (GetModuleFileNameEx(hProcess, hModules[i], szModuleName, sizeof(szModuleName) / sizeof(TCHAR))) {
_tprintf(_T("Module Name: %s\n"), szModuleName);
}
// 获取模块基址和大小
MODULEINFO moduleInfo;
if (GetModuleInformation(hProcess, hModules[i], &moduleInfo, sizeof(MODULEINFO))) {
_tprintf(_T("Base Address: 0x%p\n"), moduleInfo.lpBaseOfDll);
_tprintf(_T("Module Size: 0x%x\n"), moduleInfo.SizeOfImage);
}
_tprintf(_T("\n"));
}
}
}
int main() {
//HANDLE hProcess = GetCurrentProcess();
DWORD processId;
HANDLE hProcess;
// 输入需要打开的进程 ID
printf("Enter the process ID to open: ");
scanf_s("%u", &processId);
// 打开进程
hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processId);
if (hProcess == NULL) {
printf("Failed to open the process. Error code: %u\n", GetLastError());
return 1;
}
EnumerateProcessModules(hProcess);
CloseHandle(hProcess);
return 0;
}
运行:
Enter the process ID to open: 11864
Module Name: D:\code\winpe\notepad32.exe
Base Address: 0x01000000
Module Size: 0x13000
Module Name: C:\Windows\SYSTEM32\ntdll.dll
Base Address: 0x773C0000
Module Size: 0x18c000
Module Name: C:\Windows\System32\KERNEL32.DLL
Base Address: 0x75520000
Module Size: 0xd0000
Module Name: C:\Windows\System32\KERNELBASE.dll
Base Address: 0x749E0000
Module Size: 0x1d8000
Module Name: C:\Windows\System32\comdlg32.dll
…
总结
这段代码使用了 EnumProcessModules 函数遍历进程中的模块,并使用 GetModuleInformation 函数获取模块的基址。然后,它打印出每个模块的名称和基址。
请确保在编译时链接 psapi.lib 库,以便使用 EnumProcessModules 和 GetModuleInformation 函数。
PE文件在内存中格式如下图:
图5-6 PE文件中内存中的格式