根据 PDB 文件分析 DLL 内部符号的地址

最近遇到给定一个 DLL 文件,但是想快速定位内部函数(没有导出)的地址,想到 Sym 系列函数可以实现根据已知的 PDB 调试符号数据库直接分析出函数的地址,参考了 MSDN 可以知道我们需要依次调用如下函数:

SymInitialize -> SymSetOptions -> SymLoadModuleEx -> SymEnumSymbols -> CallBackProc

最后就是进入到我们的回调函数 CallBackProc 中处理信息。

SymEnumSymbols 支持第三个参数传入结构体指针,这里我们将需要的信息放在一个结构体中,以便于回调函数内部使用它们:

typedef struct _tagCALLBACKPACKAGE
{
    LPCTSTR   szDllPath = NULL;   // 要检索的 Dll 文件路径,以 null 结尾的字符串
    FILE*     logfile = NULL;              // 打开的日志文件的路径,必须具有写入权限
    DWORD64   BaseOfDll = NULL; // 当前模块的基地址
}CALLBACKPACKAGE, * LPCALLBACKPACKAGE;

完整代码如下:

#include <iostream>
#include <Windows.h>
#include <imagehlp.h>
#include <locale.h>
#include <Shlwapi.h>

#pragma comment(lib, "DbgHelp.lib")
#pragma comment(lib, "Shlwapi.lib")

typedef struct _tagCALLBACKPACKAGE
{
	LPCTSTR   szDllPath = NULL;
	FILE*     logfile = NULL;
	DWORD64   BaseOfDll = NULL;
}CALLBACKPACKAGE, * LPCALLBACKPACKAGE;

BOOL CALLBACK CallBackProc(
	PSYMBOL_INFO pSymInfo, 
	ULONG SymbolSize, 
	PVOID UserContext
)
{
	if (UserContext == nullptr)
	{
		SetLastError(ERROR_INVALID_PARAMETER);
		printf("参数 UserContext 不能为空。\n");
		return FALSE;
	}

	auto      pkg = (LPCALLBACKPACKAGE)UserContext;
	FILE*     hLogFile = pkg->logfile;
	DWORD64   BaseOfDll = pkg->BaseOfDll;
	fprintf(hLogFile,
		"函数名: %s\r\n地址: 0x%I64X \r\n\r\n", 
		pSymInfo->Name, 
		pSymInfo->Address - BaseOfDll);
	return TRUE;
}

char* UnicodeToAnsi(const wchar_t* szStr, char* szDest)
{
	int nLen = WideCharToMultiByte(CP_ACP, 0, szStr, -1, NULL, 0, NULL, NULL);
	if (nLen == 0)
	{
		return NULL;
	}
	char* pResult = new char[nLen];
	WideCharToMultiByte(CP_ACP, 0, szStr, -1, pResult, nLen, NULL, NULL);
	strcpy_s(szDest, nLen, pResult);
	delete[] pResult;
	return szDest;
}

BOOL GetSymbolProc(LPCALLBACKPACKAGE* lpCallBackPackage)
{
	if ((*lpCallBackPackage) == nullptr)
	{
		SetLastError(ERROR_INVALID_PARAMETER);
		printf("参数 lpCallBackPackage 不能为空。\n");
		return FALSE;
	}

	CALLBACKPACKAGE* pkg = (*lpCallBackPackage);
	HANDLE hProcess = NULL;
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, 
		FALSE, GetCurrentProcessId()); // 获取当前进程的句柄
	
	/* 初始化进程的符号处理程序。
	// SymInitialize 函数用于初始化进程的符号处理程序。 
	// 在符号处理程序的上下文中,进程是收集符号信息时要使用的方便对象。 
	// 通常,调试器和其他工具使用符号处理程序,
	// 这些工具需要为正在调试的进程加载符号。
	*/

	if (!SymInitialize(hProcess, NULL, FALSE))
	{
		CloseHandle(hProcess);
		SetLastError(ERROR_DELAY_LOAD_FAILED);
		printf("初始化进程的符号处理程序失败。\n");
		return FALSE;
	}

	// 更改当前符号调试器的选项掩码
	// 
	// 当应用程序使用库时,可以多次更改这些选项。 
	// 任何选项更改都会影响将来对符号处理程序的所有调用。

	DWORD dwOpt = SymGetOptions();
	SymSetOptions(
		dwOpt | SYMOPT_DEFERRED_LOADS | 
		SYMOPT_UNDNAME | SYMOPT_CASE_INSENSITIVE);

	// UNICODE 转换
	char szFileName[MAX_PATH] = { 0 };
	UnicodeToAnsi(pkg->szDllPath, szFileName);
	size_t len = strlen(szFileName);
	szFileName[len] = '\0';
	szFileName[len - 1] = 'b';
	szFileName[len - 2] = 'd';
	szFileName[len - 3] = 'p';

	if (!PathFileExistsA(szFileName))
	{
		SetLastError(ERROR_FILE_NOT_FOUND);
		printf("找不到 PDB 文件。\n");
		return FALSE;
	}
	szFileName[len - 1] = 'l';
	szFileName[len - 2] = 'l';
	szFileName[len - 3] = 'd';
	// 符号处理程序为模块创建一个条目,如果已关闭延迟符号加载选项,
	// 则尝试加载符号。 如果启用了延迟符号加载,则模块将
	// 被标记为延迟,并且不会加载符号,直到对模块中的符号进行引用。
	// 因此,在调用 SymLoadModuleEx 后,应始终调用
	// SymGetModuleInfo64 函数。

	DWORD64 dwSymModule = SymLoadModuleEx(
		hProcess, NULL, 
		szFileName, NULL, 
		0, 0, NULL, 0);

	if (0 == dwSymModule)
	{
		SymCleanup(hProcess);
		SetLastError(ERROR_DELAY_LOAD_FAILED);
		printf("加载符号模块失败。\n");
		return -1;
	}
	// 模块基地址
	wprintf(L"SymModuleBaseAddress: 0x%I64X\n", dwSymModule);
	pkg->BaseOfDll = dwSymModule;
	// 枚举模块信息
	if (!SymEnumSymbols(
		hProcess, 
		dwSymModule, 
		0,
		CallBackProc, 
		pkg))
	{
		SymCleanup(hProcess);
		SetLastError(ERROR_ACCESS_DENIED);
		printf("枚举模块信息失败。err_code: [%d]\n", GetLastError());
		
		return -1;
	}
	return SymCleanup(hProcess);
}

int main(int argc, char* argv[])
{
	// 初始换变量,为结构体分配内存
	FILE* hLogFile = NULL;
	CALLBACKPACKAGE* pkg = new CALLBACKPACKAGE;
	if (pkg == nullptr)
	{
		SetLastError(ERROR_STACK_OVERFLOW);
		printf("系统当前内存不足。\n");
		return 1;
	}
	// 打开指定的日志文件
	fopen_s(&hLogFile, "F:\\storage_log.txt", "w");// 信息保存路径
	if (hLogFile == NULL)
	{
		pkg->logfile = NULL;
		delete pkg;
		SetLastError(ERROR_FILE_NOT_FOUND);
		printf("无法打开指定的文件。\n");
		return 2;
	}
	// 设置结构体成员
	pkg->logfile = hLogFile;
	pkg->szDllPath = L"F:\\windows.storage.dll"; // 要查询的 DLL 路径

	if (!GetSymbolProc(&pkg))// 调用遍历函数
	{
		fclose(hLogFile);
		pkg->logfile = NULL;
		delete pkg;
		SetLastError(ERROR_ACCESS_DENIED);
		printf("遍历 PDB 文件信息失败。err_code: [%d]\n", GetLastError());
		return 3;
	}
	printf("操作已经完成。\n");

	// 清理环境
	fclose(hLogFile);
	pkg->logfile = NULL;
	delete pkg;
	
	system("pause");
	return 0;
}

 测试结果是将分析的地址和函数名称保存至路径下的一个文本文档下,打开后效果如图:

可见地址都是相对于模块首地址的偏移,计算结果和 IDA 分析结果一致。

转载请注明出处:根据 PDB 文件分析 DLL 内部符号的地址icon-default.png?t=N7T8http://t.csdnimg.cn/dnlqA

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

涟幽516

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值