読者です 読者をやめる 読者になる 読者になる

るくすの日記 ~ Out_Of_Range ~

主にプログラミング関係

Debug Interface Access SDKを使って内部関数の名前解決を行う

IDAを使ってソフトウェアの解析をしてるといつも思うんですが、デバッグ情報って便利ですよね。IDAはイメージファイルをロードした後にpdbの検索も行っていて、シンボル情報とかのラベルを自動で埋め込んでくれます。これがあるおかげで、アセンブリをあまり読まなくても大体の動作は理解できてしまいます。今回はこのpdbを解析してIDAが行っている(アドレス -> シンボル)とは逆の(シンボル -> アドレス)を自前でやってみたいと思います。
とは言っても、残念ながらpdbの仕様は非公開です。(多分)
IDAはIMAGEHELP.dllを使ってpdbを解析してるみたいなんですが(下記参照)↓
https://www.hex-rays.com/products/ida/support/idadoc/1374.shtml
正直dbghelp.dll(IMAGEHELPの縮小版です)あたりのAPIは仕様変更が激しく、動作が不安定なイメージがあるのであまり使いたくないです。何か良い手は無いかといろいろ調べてみたところ、Debug Interface Access SDKという物を見つけました。 ↓
http://blogs.msdn.com/b/vcblog/archive/2010/01/05/dia-based-stack-walking.aspx

何これ、超ホット...
とても良さげだったのでこれを使う事にしました。とはいえ、DIAの日本語資料がほぼ皆無だったので結構苦労しましたorz
あと、DIA自体が単体で配布されている訳では無くて、VisualStudioをダウンロードした際に一緒について来るみたいです。(expressは無いです)

以下参考にしたサイト(とういかほぼ丸パクリ

Debug Interface Access SDK
[Win32] [COM] How to read PDB Symbol file using DIA SDK | すなのかたまり

これで(シンボル -> アドレス)変換ができるようになりました。ところでこれができると何が嬉しいのかと言うと、たとえばDLLインジェクションで他プロセスにコードを注入したりする時にそのプロセス内の関数を呼び出したい時があると思います。この時、いちいちデバッガや逆アセンブラなどで関数の場所を特定するのはめんどうです。こういう時に、内部関数の名前解決(つまり シンボル -> アドレスの変換)がDLL内で自動でできれば、呼び出したい関数名を指定するだけで良くなるのでとても楽です。(まあもっと良い方法があるのかもしれませんが...
たとえばゲーム内のGame::Win(void)みたいな関数を直接DLLから名前指定で呼び出したりできるわけです。



で、書いたコードがこんな感じ↓

#include <windows.h>
#include <tlhelp32.h>
#include <psapi.h>
#include <stdio.h>
#include <tchar.h>
#include <map>

#include "C:\Program Files\Microsoft Visual Studio 12.0\DIA SDK\include\dia2.h"

#define DIA_SDK_PATH "C:\\Program Files\\Microsoft Visual Studio 12.0\\DIA SDK\\"

#pragma comment (lib, "psapi.lib")

typedef HRESULT (__stdcall *DLLGETCLASSOBJECT)(
  _In_   REFCLSID rclsid,
  _In_   REFIID riid,
  _Out_  LPVOID *ppv
);


DWORD FindProcessId(TCHAR* processName);//プロセス名 -> PID
LPVOID GetBaseAddressFromName(TCHAR* ProcessName);//イメージベースの取得
BOOL DumpSymbolsFromDll(TCHAR* DllPath, LPCWSTR PdbFile, LPCWSTR SymbolName);
VOID DumpSymbols(IDiaEnumSymbols *EnumSymbols);
BOOL DumpSymbolsInternal(IDiaDataSource *MsdiaInstance, LPCWSTR PdbFile, LPCWSTR SymbolName);

std::map<BSTR,DWORD> NameToRVA;//関数名とRVAの対応
TCHAR* target_function = TEXT("MyFunction");
DWORD target_RVA = 0;

int _tmain(int argc, TCHAR* argv[]){
	if(argc < 4){
		fprintf(stderr,"%s  <pdb file> <symbol pattern> <target process> <target dll>\n",argv[0]);
		return 1;
	}
	TCHAR* dll_path = TEXT("C:\\Program Files\\Microsoft Visual Studio 12.0\\DIA SDK\\bin\\msdia120.dll");
	LPVOID baseAddress = GetBaseAddressFromName(argv[3]);
	DumpSymbolsFromDll(dll_path,argv[1],argv[2]); 
	void (*MyFunc)(void);
	/*for(std::map<BSTR,DWORD>::iterator it = NameToRVA.begin(); it != NameToRVA.end(); ++it){
		wprintf(TEXT("%s %08X\n"),(*it).first,(*it).second);
	}*/
	if(!target_RVA){ fprintf(stderr,"Cannot find function\n");  return 1;}
	printf("\tBase Address: %08X\n\tRVA: %08X\n",(int)baseAddress,target_RVA);
	MyFunc = (void(*)())((int)baseAddress + target_RVA - 1);//ベースアドレス + 指定関数のRVA
	printf("\tMyFunction: %08X\n",(int)MyFunc);
	
	MyFunc();

}

/* プロセス名 -> PID */
DWORD FindProcessId(TCHAR* processName)
{
	PROCESSENTRY32 processInfo;
	processInfo.dwSize = sizeof(processInfo);

	HANDLE processesSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
	if ( processesSnapshot == INVALID_HANDLE_VALUE )
		return 0;

	Process32First(processesSnapshot, &processInfo);
	if ( !wcscmp(processName,processInfo.szExeFile) )
	{
		CloseHandle(processesSnapshot);
		return processInfo.th32ProcessID;
	}

	while ( Process32Next(processesSnapshot, &processInfo) )
	{
		if ( !wcscmp(processName,processInfo.szExeFile) )
		{
			CloseHandle(processesSnapshot);
			return processInfo.th32ProcessID;
		}
	}
	
	CloseHandle(processesSnapshot);
	return 0;
}

/* イメージベースの取得 */
LPVOID GetBaseAddressFromName(TCHAR* ProcessName)
{
	HANDLE hProcess;
	HMODULE ModuleHandles[1000];
	DWORD ModuleNum;
	DWORD ReturnSize;
	TCHAR      szModule[256];
	MODULEINFO ModInfo;
	DWORD i;
	
	DWORD dwProcessId = FindProcessId(ProcessName);//プロセス名 -> PID

	/* 指定された名前のプロセスハンドルを取得 */
	hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwProcessId);

	/* 現在のプロセスにロードされているモジュールの一覧と数を取得 */
	EnumProcessModules(hProcess, ModuleHandles, sizeof(ModuleHandles), &ReturnSize);
	ModuleNum = ReturnSize / sizeof(HMODULE);

	/* 各モジュールの情報を表示 */
	for(i=0; i<ModuleNum; i++) {
		/* モジュールのファイル名と情報を取得 */
		GetModuleBaseName(hProcess, ModuleHandles[i], szModule, sizeof(szModule));
		GetModuleInformation(hProcess, ModuleHandles[i], &ModInfo, sizeof(ModInfo));

		/* 結果を表示 */
		wprintf(TEXT("\tName: %s\n"),szModule);
		wprintf(TEXT("\tBase Address: %p\n"), ModInfo.lpBaseOfDll);
		wprintf(TEXT("\tSize: %08X\n"), ModInfo.SizeOfImage);
		wprintf(TEXT("\tEntryPoint: %p\n"), ModInfo.EntryPoint);

		if(!wcscmp(szModule,ProcessName)) return ModInfo.EntryPoint;

	}
	return NULL;
}

VOID DumpSymbols(IDiaEnumSymbols *EnumSymbols) {
    HRESULT hr = 0;
    BOOL Ret = FALSE;
    ULONG Retrieved = 0;

    for (;;) {
        IDiaSymbol *Symbol = NULL;
        BSTR Name = NULL;
        BSTR Undecorated = NULL;
        DWORD Rva = 0;
        DWORD SymTag = 0;

        hr = EnumSymbols->Next(1, &Symbol, &Retrieved);
        
       
        if ( Retrieved==0 ) break;

        hr = Symbol->get_relativeVirtualAddress(&Rva);
        hr = Symbol->get_name(&Name);
        hr = Symbol->get_undecoratedName(&Undecorated);
        hr = Symbol->get_symTag(&SymTag);

        //wprintf(TEXT("%4d RVA=%08x %-30s %-30s\n"), SymTag, Rva, Name, Undecorated);
		//NameToRVA[Name] = Rva;
		if(!wcscmp(Name,target_function)) target_RVA = Rva;
       
        if ( Name ) SysFreeString(Name);
        if ( Undecorated ) SysFreeString(Undecorated);
        if ( Symbol ) Symbol->Release();
    }
}

BOOL DumpSymbolsInternal(IDiaDataSource *MsdiaInstance, LPCWSTR PdbFile, LPCWSTR SymbolName) {
    HRESULT hr = 0;
    BOOL Ret = FALSE;
    IDiaSession *Session = NULL;
    IDiaSymbol  *Global = NULL;
    IDiaEnumSymbols *EnumSymbols = NULL;

    hr = MsdiaInstance->loadDataFromPdb(PdbFile);

    hr = MsdiaInstance->openSession(&Session);
   
    hr = Session->get_globalScope(&Global);
   
    hr = Global->findChildren(SymTagNull, SymbolName, nsfRegularExpression, &EnumSymbols);

    DumpSymbols(EnumSymbols);

cleanup:
    if ( EnumSymbols ) EnumSymbols->Release();
    if ( Global ) Global->Release();
    if ( Session ) Session->Release();

    return Ret;
}

BOOL DumpSymbolsFromDll(TCHAR* DllPath, LPCWSTR PdbFile, LPCWSTR SymbolName) {
	HRESULT hr = 0;
    BOOL Ret = FALSE;
    HMODULE MsDiaDll = NULL;
    DLLGETCLASSOBJECT MsDiaDllGetClassObject = NULL;
    IClassFactory *Factory = NULL;
    IDiaDataSource *Source = NULL;

    MsDiaDll = LoadLibrary(DllPath);
    if ( !MsDiaDll ) {
        printf("LoadLibrary failed ? 0x%08x\n", GetLastError());
        goto cleanup;
    }
   
    MsDiaDllGetClassObject = (DLLGETCLASSOBJECT)GetProcAddress(MsDiaDll, "DllGetClassObject");
    if ( !MsDiaDllGetClassObject ) {
        printf("LoadLibrary failed ? 0x%08x\n", GetLastError());
        goto cleanup;
    }

    hr = MsDiaDllGetClassObject(__uuidof(DiaSource), __uuidof(IClassFactory), (void**)&Factory);

    hr = Factory->CreateInstance(NULL, __uuidof(IDiaDataSource), (void**)&Source);
   
    Ret = DumpSymbolsInternal(Source, PdbFile, SymbolName);

cleanup:
    if(Source) Source->Release();
    if(Factory) Factory->Release();
    if(MsDiaDll) FreeLibrary(MsDiaDll);

    return Ret;
}


実行すると

    Name: test.exe
    Base Address: 00890000
    Size: 00029000
    EntryPoint: 008A1235
    Base Address: 008A1235
    RVA: 00014050
    MyFunction: 008B5284

こんな感じでtest.exe内のMyFunction関数のアドレスを表示してくれます。

今回はあくまでDIAの紹介なのであまり上記のコードはあてにしないでください。