注意:有些函数在kernel32.dll的导出目录中存储的并不是函数的真实地址,可能会通过导出表的得到的地址去调用另一个模块中的内核中的函数,例如heapalloc函数。具体以后会将,总之就是目前的自定义函数是有局限性的
首先必须明白PE结构
基本上做一个思路以及简化的实例代码:
主要包括以下步骤:
- 获取 kernel32.dll 模块的句柄:
你需要获取 kernel32.dll 模块在当前进程中的句柄,可以使用 GetModuleHandle 函数来获取。
- 获取导出表 RVA:
从获取的模块句柄中,你需要找到导出表的 RVA(Relative Virtual Address,相对虚拟地址)。导出表是一个数据结构,包含了模块中所有导出的函数名称和地址。
- 解析导出表:
在导出表的 RVA 处,你需要解析导出表结构,找到需要的 API 函数的名称在导出表中的位置。
- 获取 API 函数的内存地址:
通过获取 API 函数的名称在导出表中的位置,你可以计算出函数的内存地址。通常,导出表中存储的是相对虚拟地址(RVA),你需要将其转换为实际的内存地址。
代码如下:
#include<windows.h>
#include <stdio.h>
FARPROC GetFunctionAddress(const char* moduleName, const char* functionName, DWORD functionOrdinal) {
HMODULE hModule = GetModuleHandleA(moduleName);
//获取 kernel32.dll 模块的句柄
if (hModule == NULL) {
return NULL; // 模块未找到
}
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;//DOS头,入口地址
PIMAGE_NT_HEADERS pNtHeader = (PIMAGE_NT_HEADERS)((BYTE*)pDosHeader + pDosHeader->e_lfanew);
DWORD exportRVA = pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)((BYTE*)hModule + exportRVA);
PDWORD pAddressOfFunctions = (PDWORD)((BYTE*)hModule + pExportDir->AddressOfFunctions);
PDWORD pAddressOfNames = (PDWORD)((BYTE*)hModule + pExportDir->AddressOfNames);
PWORD pAddressOfNameOrdinals = (PWORD)((BYTE*)hModule + pExportDir->AddressOfNameOrdinals);
FARPROC functionAddress = NULL;
if (functionName != NULL) {
// 根据函数名称查找
for (DWORD i = 0; i < pExportDir->NumberOfNames; ++i) {
if (strcmp((char*)((BYTE*)hModule + pAddressOfNames[i]), functionName) == 0) {
functionAddress = (FARPROC)((BYTE*)hModule + pAddressOfFunctions[pAddressOfNameOrdinals[i]]);
break;
}
}
} else if (functionOrdinal != 0) {
// 根据函数序号查找
if (functionOrdinal <= pExportDir->NumberOfFunctions) {
functionAddress = (FARPROC)((BYTE*)hModule + pAddressOfFunctions[functionOrdinal - 1]);
}
}
return functionAddress;
}
int main()
{
FARPROC loadLibraryAAddress = GetFunctionAddress("kernel32.dll", "LoadLibraryA", 0);
}
在导出表中,函数序号是从 1 开始的,而在数组中的索引是从 0 开始的。因此,为了获取正确的函数地址,需要将函数序号减去 1,以匹配数组的索引。
那我们判断是利用序号还是名称获取导入表函数,一般是通过 (DOWRD)lpProcName>0xffff 判断是否参数大于两个字节(windows是这样判断的)