Analyze the address of DLL internal symbols based on PDB file

Recently, I encountered a given DLL file, but I wanted to quickly locate the address of an internal function (not exported). I thought that the Sym series of functions can directly analyze the address of the function based on the known PDB debugging symbol database. By referring to MSDN, we can know what we need. Call the following functions in sequence:

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

The last step is to enter our callback function CallBackProc to process the information.

SymEnumSymbols supports passing the structure pointer as the third parameter. Here we put the required information in a structure so that they can be used inside the callback function:

typedef struct _tagCALLBACKPACKAGE
{
LPCTSTR szDllPath = NULL; // Dll file path to be retrieved, null-terminated string
FILE* logfile = NULL; // The path of the open log file, which must have write permissions
DWORD64 BaseOfDll = NULL; //The base address of the current module
}CALLBACKPACKAGE, *LPCALLBACKPACKAGE;

The complete code is as follows:

#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,
ULONGSymbolSize,
PVOID UserContext
)
{
if (UserContext == nullptr)
{
SetLastError(ERROR_INVALID_PARAMETER);
printf("The parameter UserContext cannot be empty.\
");
return FALSE;
}

auto pkg = (LPCALLBACKPACKAGE)UserContext;
FILE* hLogFile = pkg->logfile;
DWORD64 BaseOfDll = pkg->BaseOfDll;
fprintf(hLogFile,
"Function name: %s\r\
Address: 0x%I64X \r\
\r\
",
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("The parameter lpCallBackPackage cannot be empty.\
");
return FALSE;
}

CALLBACKPACKAGE* pkg = (*lpCallBackPackage);
HANDLE hProcess = NULL;
hProcess = OpenProcess(PROCESS_ALL_ACCESS,
FALSE, GetCurrentProcessId()); // Get the handle of the current process
\t
/* Initialize the symbol handler for the process.
// The SymInitialize function is used to initialize the symbol handler of the process.
// In the context of a symbol handler, a process is a convenience object to use when collecting symbol information.
// Typically, debuggers and other tools use symbol handlers,
// These tools need to load symbols for the process being debugged.
*/

if (!SymInitialize(hProcess, NULL, FALSE))
{
CloseHandle(hProcess);
SetLastError(ERROR_DELAY_LOAD_FAILED);
printf("Initialization of the process's symbol handler failed.\
");
return FALSE;
}

//Change the option mask of the current symbolic debugger
//
// These options can be changed multiple times when an application uses the library.
// Any option changes will affect all future calls to the symbol handler.

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

// UNICODE conversion
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("Cannot find PDB file.\
");
return FALSE;
}
szFileName[len - 1] = 'l';
szFileName[len - 2] = 'l';
szFileName[len - 3] = 'd';
// The symbol handler creates an entry for the module, if the delayed symbol loading option is turned off,
// Then try to load the symbol. If lazy symbol loading is enabled, the module will
// is marked as deferred and the symbol will not be loaded until a reference to the symbol in the module is made.
// Therefore, after calling SymLoadModuleEx, you should always call
// SymGetModuleInfo64 function.

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

if (0 == dwSymModule)
{
SymCleanup(hProcess);
SetLastError(ERROR_DELAY_LOAD_FAILED);
printf("Loading symbol module failed.\
");
return -1;
}
//Module base address
wprintf(L"SymModuleBaseAddress: 0x%I64X\
", dwSymModule);
pkg->BaseOfDll = dwSymModule;
// Enumerate module information
if (!SymEnumSymbols(
hProcess,
dwSymModule,
0,
CallBackProc,
pkg))
{
SymCleanup(hProcess);
SetLastError(ERROR_ACCESS_DENIED);
printf("Failed to enumerate module information. err_code: [%d]\
", GetLastError());
\t\t
return -1;
}
return SymCleanup(hProcess);
}

int main(int argc, char* argv[])
{
//Initially change variables and allocate memory for the structure
FILE* hLogFile = NULL;
CALLBACKPACKAGE* pkg = new CALLBACKPACKAGE;
if (pkg == nullptr)
{
SetLastError(ERROR_STACK_OVERFLOW);
printf("The system currently has insufficient memory.\
");
return 1;
}
//Open the specified log file
fopen_s( & amp;hLogFile, "F:\storage_log.txt", "w");// Information saving path
if (hLogFile == NULL)
{
pkg->logfile = NULL;
delete pkg;
SetLastError(ERROR_FILE_NOT_FOUND);
printf("The specified file cannot be opened.\
");
return 2;
}
//Set structure members
pkg->logfile = hLogFile;
pkg->szDllPath = L"F:\windows.storage.dll"; // DLL path to be queried

if (!GetSymbolProc( & amp;pkg))// Call the traversal function
{
fclose(hLogFile);
pkg->logfile = NULL;
delete pkg;
SetLastError(ERROR_ACCESS_DENIED);
printf("Failed to traverse PDB file information. err_code: [%d]\
", GetLastError());
return 3;
}
printf("The operation has been completed.\
");

// Clean up the environment
fclose(hLogFile);
pkg->logfile = NULL;
delete pkg;
\t
system("pause");
return 0;
}

The test result is to save the analyzed address and function name to a text document under the path. The effect after opening is as follows:

The visible addresses are all offsets relative to the module’s first address, and the calculation results are consistent with the IDA analysis results.

Please indicate the source when reprinting: Analyze the address of DLL internal symbols according to PDB fileicon-default.png?t=N7T8http://t.csdnimg.cn/dnlqA