爲了探究虛表的今生前世,先來一段測試代碼
虛函數類:
class CTest
{
public:
int m_nData;
virtual void PrintData()
{
printf("Data = 0x%x\n", m_nData);
}
};
class CBase1
{
public:
int m_nData;
virtual void PrintData1() = 0;
};
class CBase2
{
public:
int m_nData;
virtual void PrintData2() = 0;
};
class CBaseTest : public CBase1, public CBase2
{
public:
void PrintData1()
{
printf("Data = 0x%x\n", CBase1::m_nData);
}
void PrintData2()
{
printf("Data = 0x%x\n", CBase2::m_nData);
}
};
測試代碼:
void Test()
{
CTest oCTest;
CTest* pCTest = new CTest();
pCTest->m_nData = 0x8888;
pCTest->PrintData();
oCTest.m_nData = 888;
oCTest.PrintData();
delete pCTest;
}
void BaseTest()
{
CBaseTest oCBaseTest;
oCBaseTest.PrintData1();
}
1、虛表位於何處?
WinDbg顯示虛表的地址的屬性:Usage RegionUsageImage(代表此地址區域被映射到二進制文件的鏡像),爲只讀屬性。
2、同一個類對象的虛表位置相同嗎?
同一個類對象的虛表位置相同。
加載模塊的內存位置:0x00380000
虛表的VA = 0x003FDF2C - 0x00380000 = 0x0007DF2C3、虛表需要在加載後進行初始化嗎?
否,虛表的位置在PE文件的 .rdata 節中,.rdata 是存放程序常量的地方,屬性爲只讀
其VA = 0x003A9EFB- 0x00380000 = 0x00029EFB
由於該PE文件的基址 = 0x00040000(默認情況下)
故其未重定位前的虛擬地址 = 0x00040000 + 0x00029EFB = 0x00429EFB
由於x86平臺文件都是小尾儲存,倒過來寫就是 FB 9E 42 00,即如圖左上紅圈所示
說明PE文件在編譯好後已經將虛表和虛表中的虛函數地址填寫完畢並寫入.rdata區
4、多父類繼承的虛表如何存放?
多重繼承的子類對象實際上是將每個父類的完整數據按順序依次排布,所以擁有每個父類的虛表,父類每個虛表的位置同樣在每個父類的起始位置。
oCBaseTest對象內存分佈
5、何爲虛表Hook?通過以上對虛表的來龍去脈的分析,有IAT Hook基礎的同學可以很容易的想到如何進行虛表Hook了。
5.1 改PE文件
直接改掉PE文件虛表位置的函數指針,指向自己僞造的虛表地址(當然必須保證自己的虛表函數指針有效,且函數調用形式和參數個數一致)
5.2 內存中Hook
當PE文件加載後,直接在內存中修改虛表函數指針(注意要去寫保護,當然調試工具是沒問題的)。
5.3 Hook測試(內存Hook的方式)
測試代碼:
class CVFHook
{
public:
typedef void (CVFHook::* MemberFunctionPtr)();
public:
static BOOL Hook(void* pVirtrulFunctionVA);
private:
void PrintData();
};
BOOL CVFHook::Hook(void* pVirtrulFunctionVA)
{
HMODULE hHookedModule = ::GetModuleHandle(NULL);
if (NULL == hHookedModule)
{
_tprintf(_T("Get module handle fail\n"));
return FALSE;
}
//取重定位後的虛表地址
MemberFunctionPtr* pMemberFunctionPtr = (MemberFunctionPtr*)((INT64)pVirtrulFunctionVA + (INT64)hHookedModule);
//去除讀寫保護
DWORD dwOldProtect = 0;
::VirtualProtect(pMemberFunctionPtr, sizeof(pMemberFunctionPtr), PAGE_READWRITE, &dwOldProtect);
*pMemberFunctionPtr = &CVFHook::PrintData;
return TRUE;
}
void CVFHook::PrintData()
{
printf("PrintData is facked!!!\n");
}
然後再dll的加載位置傳入虛表的VA參數調用即可:
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
CVFHook::Hook((void*)0x0007DF2C);
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
注意:這裏我們僞造的虛函數 CVFHook::PrintData() 我直接用的是與要 Hook 的虛函數相同的成員函數形式,由於Release版的代碼可能會被編譯器優化掉函數調用形式,如果如此,需要我們手動編寫彙編形式的虛函數代碼。
啓動測試程序 Console.exe ,然後用 APIMonitor 工具將 VirtrualFunctionHook.dll 注入,然後按任意鍵觀察結果如下:
我們來對前面的測試代碼進行反彙編後觀察: