近期在学习PE文件的结构,这里整理一下调试好的代码,用于遍历PE文件的导入表信息:
#include <iostream>
#include <stdio.h>
#include <windows.h>
#include <Dbghelp.h> //ImageRvaToVa
#include <cassert>
#include <vector>
#include <string>
#pragma comment(lib,"Dbghelp.lib")
void PrintFunctionInfoByOridinal(const char* moduleName, DWORD dwOrdinal);
int main(int argc, char* argv[])
{
if (argc != 2 ||
strlen(argv[1]) <= 1
) { printf("Invalid Parameters.\n"); return 0; }
else
{
printf("File binPath:[%s].\n", argv[1]);
}
int i, j;
HANDLE hFile = CreateFileA(
argv[1], //PE文件名
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("Create File Failed.\n");
return 0;
}
HANDLE hFileMapping = CreateFileMappingA(hFile, NULL, PAGE_READONLY, 0, 0, NULL);
if (hFileMapping == NULL || hFileMapping == INVALID_HANDLE_VALUE)
{
printf("Could not create file mapping object (%d).\n", GetLastError());
return 0;
}
LPBYTE lpBaseAddress = (LPBYTE)MapViewOfFile(hFileMapping, // handle to map object
FILE_MAP_READ, 0, 0, 0);
if (lpBaseAddress == NULL)
{
printf("Could not map view of file (%d).\n", GetLastError());
return 0;
}
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)lpBaseAddress;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(lpBaseAddress + pDosHeader->e_lfanew);
//导入表的rva:0x2a000;
DWORD Rva_import_table = pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
if (Rva_import_table == 0)
{
printf("NoFound Import Table!");
//关闭文件,句柄。。
UnmapViewOfFile(lpBaseAddress);
CloseHandle(hFileMapping);
CloseHandle(hFile);
return 0;
}
//这个虽然是内存地址,但是减去文件开头的地址,就是文件地址了
//这个地址可以直接从里面读取你想要的东西了
PIMAGE_IMPORT_DESCRIPTOR pImportTable = (PIMAGE_IMPORT_DESCRIPTOR)ImageRvaToVa(
pNtHeaders,
lpBaseAddress,
Rva_import_table,
NULL
);
//减去内存映射的首地址,就是文件地址了。。(很简单吧)
printf("Address Of ImportTable: 0x%08X\n", ((DWORD)pImportTable - (DWORD)lpBaseAddress));
//现在来到了导入表的面前:IMAGE_IMPORT_DESCRIPTOR 数组(以0元素为终止)
//定义表示数组结尾的null元素!
IMAGE_IMPORT_DESCRIPTOR null_iid;
IMAGE_THUNK_DATA null_thunk;
memset(&null_iid, 0, sizeof(null_iid));
memset(&null_thunk, 0, sizeof(null_thunk));
//每个元素代表了一个引入的DLL。
for (i = 0; memcmp(pImportTable + i, &null_iid, sizeof(null_iid)) != 0; i++)
{
//LPCSTR: 就是 const char*
LPCSTR szDllName = (LPCSTR)ImageRvaToVa(
pNtHeaders, lpBaseAddress,
pImportTable[i].Name, //DLL名称的RVA
NULL);
//拿到了DLL的名字
printf("-----------------------------------------\n");
printf("Module [%d]: %s\n", i + 1, szDllName);
printf("-----------------------------------------\n");
PIMAGE_THUNK_DATA32 pThunk = NULL;
//if (pImportTable[i].OriginalFirstThunk) {
//现在去看看从该DLL中引入了哪些函数
//我们来到该DLL的 IMAGE_TRUNK_DATA 数组(IAT:导入地址表)前面
pThunk = (PIMAGE_THUNK_DATA32)ImageRvaToVa(
pNtHeaders, lpBaseAddress,
pImportTable[i].OriginalFirstThunk, //【注意】这里使用的是OriginalFirstThunk
NULL);
//}
for (j = 0; memcmp(pThunk + j, &null_thunk, sizeof(null_thunk)) != 0; j++)
{
//这里通过RVA的最高位判断函数的导入方式,
//如果最高位为1,按序号导入,否则按名称导入
if (pThunk[j].u1.AddressOfData & IMAGE_ORDINAL_FLAG64)
{
printf("\t [%d] \t %ld \t 按序号导入\n", j + 1, pThunk[j].u1.AddressOfData & 0xffff);
}
else //if(pThunk[j].u1.AddressOfData)
{
//按名称导入,我们再次定向到函数序号和名称
//注意其地址不能直接用,因为仍然是RVA!
PIMAGE_IMPORT_BY_NAME pFuncName = (PIMAGE_IMPORT_BY_NAME)ImageRvaToVa(
pNtHeaders, lpBaseAddress,
pThunk[j].u1.AddressOfData,
NULL);
if (pFuncName != NULL)
{
if (pFuncName->Hint == NULL)
printf("\t [%d] \t -- \t %s\n", j + 1, pFuncName->Name);
else
printf("\t [%d] \t %ld \t %s\n", j + 1, pFuncName->Hint, pFuncName->Name);
}
else if((pThunk[j].u1.AddressOfData & 0xffff) != 0) {
{
printf("\t [%d] \t ", j + 1);
PrintFunctionInfoByOridinal(szDllName, pThunk[j].u1.AddressOfData);
}
}
}
}
}
//关闭文件,句柄。。
UnmapViewOfFile(lpBaseAddress);
CloseHandle(hFileMapping);
CloseHandle(hFile);
printf("\n操作已经完成。");
getchar();
return 0;
}
std::vector<std::vector<std::string>> GetDllFunctionInfo(const char* moduleName)
{
HMODULE hModule = NULL;
SetLastError(0);
hModule = GetModuleHandleA(moduleName);
if (hModule == nullptr)
{
hModule = LoadLibraryA(moduleName);
if (hModule == nullptr)
return {};
}
//printf("GetModuleHandleA err %d\n", GetLastError());
const IMAGE_DOS_HEADER* pDosHeader = reinterpret_cast<IMAGE_DOS_HEADER*>(hModule);
const IMAGE_NT_HEADERS* pNtHeader = reinterpret_cast<IMAGE_NT_HEADERS*>
(reinterpret_cast<BYTE*>(hModule) + pDosHeader->e_lfanew);
const IMAGE_EXPORT_DIRECTORY* pExportDirectory =
reinterpret_cast<IMAGE_EXPORT_DIRECTORY*>(reinterpret_cast<BYTE*>(
hModule) + pNtHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
const DWORD* pAddressOfFunctions = reinterpret_cast<DWORD*>(
reinterpret_cast<BYTE*>(hModule) + pExportDirectory->
AddressOfFunctions);
const DWORD* pAddressOfNames = reinterpret_cast<DWORD*>(
reinterpret_cast<BYTE*>(hModule) + pExportDirectory->AddressOfNames);
const WORD* pAddressOfNameOrdinals = reinterpret_cast<WORD*>(
reinterpret_cast<BYTE*>(hModule) + pExportDirectory->AddressOfNameOrdinals);
// 返回格式 [[函数名, 函数地址, 函数序号], ...]
std::vector<std::vector<std::string>> result;
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++)
{
const char* pName = reinterpret_cast<char*>(reinterpret_cast<BYTE*>(hModule) + pAddressOfNames[i]);
const DWORD dwAddress = pAddressOfFunctions[pAddressOfNameOrdinals[i]];
const DWORD dwOrdinal = pAddressOfNameOrdinals[i] + pExportDirectory->Base;
// 根据自身需求,选择是否直接函数内打印
// std::cout << "Function Name: " << pName << std::endl;
// std::cout << "Function Address: " << dwAddress << std::endl;
// std::cout << "Function Ordinal: " << dwOrdinal << std::endl;
// std::cout << std::endl;
result.push_back({ pName, std::to_string(dwAddress), std::to_string(dwOrdinal) });
}
return result;
}
void PrintFunctionInfoByOridinal(const char* moduleName, DWORD dwOrdinal)
{
// 获取指定模块中的指定函数信息:
std::vector<std::vector<std::string>> functionInfo = GetDllFunctionInfo(moduleName);
std::string funcOrdinal = std::to_string(dwOrdinal);
std::vector<std::string> result;
for (const auto& function : functionInfo)
{
if (function[2] == funcOrdinal)
{
result = function;
break;
}
}
if (result.empty())
//std::cout << "Function not found." << std::endl;
printf("Function not found.\n");
else
{
//std::cout << "Function Name: " << result[0] << std::endl;
//std::cout << "Function Address: " << result[1] << std::endl;
//std::cout << "Function Ordinal: " << result[2] << std::endl;
printf("%d \t %s\n", dwOrdinal, result[0].c_str());
}
// 销毁过程
functionInfo.clear();
functionInfo.shrink_to_fit();
}
先说一下,上面的结合了其他大佬的部分思路,修改了按名称导入的部分,部分情况下导入表没有函数名称,只有序号时候会出问题,或者这个DLL是延迟加载 DLL则会失败,此时改用我们的 PrintFunctionInfoByOridinal() 加载这个链接库,分析出调用函数的信息。
测试截图:
效果比较满意。