附錄 I ms_rtti4.idc
這是我寫的解析RTTI和vtfable的腳本。你可以從Microsoft VC++ Reversing Helpers打包下載我的兩篇文章和腳本。這個腳本的特點包括:
- 解析RTTI結構,用對應的類名重命名vftables
- 對於某些簡單情形,識別和重命名構造函數和析構函數
- 輸出所有的虛函數表,引用的函數,及類的層次到一個文件裏
使用說明:
在第一次分析結束後,加載ms_rtti4.idc。它會問你是否想要掃描exe文件來獲得vtable。注意這可能是一個漫長的過程。即使你跳過了掃描,你還是可以手工分析vtables。若你選擇了掃描,腳本將會試着識別所有的vtables,RTTI,重命名它們,識別和重命名構造函數、析構函數。有時候它會失敗,特別是存在虛繼承時。掃描結束後,它會打開記錄了結果的文本文件。
在加載腳本後,你可以用下面的快捷鍵手動分析一些MSVC結構:
- Alt-F8 - 分析vtable。光標應該位於vtable的開始處。若有RTTI,腳本會使用類名。若沒有RTTI,你可以輸入一個類名,然後腳本將重命名vtable。若有可識別的虛析構函數,腳本也會重命名它。
- Alt-F7 - 分析FuncInfo。FuncInfo是存在於有對象分配在棧中或使用了異常處理的函數中的結構體。它的地址被傳給函數異常處理程序的_CxxFrameHandler。
mov eax, offset FuncInfo1
jmp _CxxFrameHandler
大多數情況下,它可以被IDA自動識別和分析。但我的腳本提供了更豐富的信息。你也可以用我第一篇文章中的_ehseh.idc分析所有的FuncInfo。
把光標放在FuncInfo的開始處,用快捷鍵。
- Alt-F9 - 分析ThrowInfo。ThrowInfo是_CxxThrowException用來實現_throw_操作符的一個輔助結構。它的地址是_CxxThrowException的第二個參數。
lea ecx, [ebp+e]
call E::E()
push offset ThrowInfo_E
lea eax, [ebp+e]
push eax
call _CxxThrowException
把光標放在ThrowInfo的開始處,使用該快捷鍵。腳本會分析該結構體,重複添加thrown類的名字到註釋中。它還可以識別和重命名異常的析構函數和拷貝構造函數。
附錄II:恢復一個類的實踐
我們的題目是:MSN Messenger 7.5(msnmsgr.exe版本號是7.5.324,大小7094272字節)。它使用了大量的C++,含有很多RTTI信息。讓我們考慮兩個vftable,地址分別在.0040EFD8和.0040EFE0。它們完整的RTTI結構層次如下圖:
RTTI hierarchy for MSN Messenger 7.5
所以,這兩個vftables都屬於一個類-CContentMenuItem。通過查看它的基類描述符,我們看到:
- CContentMenuItem包括三個基類-CDownloader, CNativeEventSink和CNativeEventSource。
- CDownloader包含一個基類-CNativeEventSink
- 因此CContentMenuItem直接從CDownloader, CNativeEventSink和CNativeEventSource繼承,而CDownloader從CNativeEventSink繼承。
- CDownloader位於完整對象的起始處,CNativeEventSource是在0x24偏移處。
所以我們可以得出結論,第一個vftable列出了CNativeEventSource的方法,第二個列出了CDownloader或CNatvieEventSink的方法(若干二者均沒有虛方法,CContentMenuItem將複用CNativeEventSource的vftable)。現在我們看看有什麼指向了這兩個表。它們都被兩個函數引用,在.052B5E0和.052B547。(這更說明了它們都屬於同一個類)。進一步,如果我們看看在函數.052B547的開始處,_state_變量初始化爲6,意味着那個函數是析構函數。因爲一個類只有一個析構函數,我們可以斷定.052B5E0就是它的構造函數。讓我們看得更近些:
CContentMenuItem::CContentMenuItem proc near
this = esi
push this
push edi
mov this, ecx
call sub_4CA77A
lea edi, [this+24h]
mov ecx, edi
call sub_4CBFDB
or dword ptr [this+48h], 0FFFFFFFFh
lea ecx, [this+4Ch]
mov dword ptr [this], offset const CContentMenuItem::'vftable'{for 'CContentMenuItem'}
mov dword ptr [edi], offset const CContentMenuItem::'vftable'{for 'CNativeEventSource'}
call sub_4D8000
lea ecx, [this+50h]
call sub_4D8000
lea ecx, [this+54h]
call sub_4D8000
lea ecx, [this+58h]
call sub_4D8000
lea ecx, [this+5Ch]
call sub_4D8000
xor eax, eax
mov [this+64h], eax
mov [this+68h], eax
mov [this+6Ch], eax
pop edi
mov dword ptr [this+60h], offset const CEventSinkList::'vftable'
mov eax, this
pop this
retn
sub_52B5E0 endp
編譯器在prolog後的第一件事情就是從exc拷貝_this_指針到esi,因此隨後的地址引用都是基於esi。在初始化vfptr前,它調了兩個其它函數,一定是基類的構造函數 - 我們的例子中就是CDownloader和CNativeEventSource。進到這兩個函數中,我們可以確認第一個用CDownloader::’vftable’初始化它的vfptr,第二個用CNativeEventSource::’vftable’。我們還可以進一步看看CDownloader的構造函數-它調用了基類CNativeEventSink的構造函數。
同樣,從edi中取得傳給第二個函數的_this_指針,它指向this+24h。根據我們的類結構圖,這個地址是CNativeEventSource子對象的位置。這從另一個方向確認了調用的第二個函數是CNativeEventSource的構造函數。
調用完基類構造函數後,基類對象的vfptr都被CContentMenuItem的實現重寫了,意味着CContentMenuItem覆蓋了基類的某些虛方法(或添加了它自己的)。(如果有需要,我們可以比較這些表,查看哪些指針被改變或者添加-被添加的就是CContentMenuItem新實現的。
下面我們看看幾個在地址.04D8000的函數調用,這時ecx從this+4Ch被設置到this+5Ch - 很顯然,初始化了一些成員變量。我們如何得知那是一個編譯器生成的構造函數調用還是以程序員寫的初始化函數呢?這裏有幾個提示:
- 函數使用_thiscall_調用習慣,而且是第一次訪問這些域。
- 這些域的初始化是按照地址增長的方向進行的。
爲了保證我們可以查看析構函數中的unwind funclet - 那裏我們可以看得爲這些成員變量,編譯器生成的析構函數調用。
這個新類不包括虛函數,也就沒有RTTI,所以我們不知道它的真實名字。就叫它RefCountedPtr吧。我們已經確定,4D8000是它的構造函數。析構函數我們可以從CContentMenuItem析構函數的unwind funclet找到,它在63CCB4。
回到CContentMenuItem的構造函數,我們看得3個域初始化爲0,還有一個vftable指針。這看起來像是一個成員變量內聯展開的構造函數(不是基類的,因爲若是基類,就應該在繼承樹中存在)。從用到的vftable的RTTI,我們看得它是CEventSinkList模板的一個實例。
現在,我們來寫一個可能的類聲明:
class CContentMenuItem: public CDownloader, public CNativeEventSource
{
/* 00 CDownloader */
/* 24 CNativeEventSource */
/* 48 */ DWORD m_unknown48;
/* 4C */ RefCountedPtr m_ptr4C;
/* 50 */ RefCountedPtr m_ptr50;
/* 54 */ RefCountedPtr m_ptr54;
/* 58 */ RefCountedPtr m_ptr58;
/* 5C */ RefCountedPtr m_ptr5C;
/* 60 */ CEventSinkList m_EventSinkList;
/* size = 70? */
};
我們不確定在偏移48處的變量是否不是CNativeEventSource的一部分,因爲在CNativeEventSource的構造函數中沒有訪問過,它很可能是CContentMenuItem的一部分。包含被重命名的方法的構造函數和類結構如下:
public: __thiscall CContentMenuItem::CContentMenuItem(void) proc near
push this
push edi
mov this, ecx
call CDownloader::CDownloader(void)
lea edi, [this+CContentMenuItem._CNativeEventSource]
mov ecx, edi
call CNativeEventSource::CNativeEventSource(void)
or [this+CContentMenuItem.m_unknown48], -1
lea ecx, [this+CContentMenuItem.m_ptr4C]
mov [this+CContentMenuItem._CDownloader._vfptr], offset const CContentMenuItem::'vftable'{for 'CContentMenuItem'}
mov [edi+CNativeEventSource._vfptr], offset const CContentMenuItem::'vftable'{for 'CNativeEventSource'}
call RefCountedPtr::RefCountedPtr(void)
lea ecx, [this+CContentMenuItem.m_ptr50]
call RefCountedPtr::RefCountedPtr(void)
lea ecx, [this+CContentMenuItem.m_ptr54]
call RefCountedPtr::RefCountedPtr(void)
lea ecx, [this+CContentMenuItem.m_ptr58]
call RefCountedPtr::RefCountedPtr(void)
lea ecx, [this+CContentMenuItem.m_ptr5C]
call RefCountedPtr::RefCountedPtr(void)
xor eax, eax
mov [this+CContentMenuItem.m_EventSinkList.field_4], eax
mov [this+CContentMenuItem.m_EventSinkList.field_8], eax
mov [this+CContentMenuItem.m_EventSinkList.field_C], eax
pop edi
mov [this+CContentMenuItem.m_EventSinkList._vfptr], offset const CEventSinkList::'vftable'
mov eax, this
pop this
retn
public: __thiscall CContentMenuItem::CContentMenuItem(void) endp
鏈接和參考資料
[1] http://msdn.microsoft.com/archive/default.asp?url=/archive/en-us/dnarvc/html/jangrayhood.asp
with illustrations (but in Japanese): http://www.microsoft.com/japan/msdn/vs_previous/visualc/techmat/feature/jangrayhood/
C++: Under the Hood (PDF)
[2] http://www.lrdev.com/lr/c/virtual.html
[3] Microsoft patents which describe various parts of their C++ implementation. Very insightful.
- 5410705: Method for generating an object data structure layout for a class in a compiler for an object-oriented programming language
- 5617569: Method for implementing pointers to members in a compiler for an object-oriented programming language
- 5754862: http://freepatentsonline.com/5854931.html Method and system for accessing virtual base classes
- 5297284: Method and system for implementing virtual functions and virtual base classes and setting a this pointer for an object-oriented programming language
- 5371891: Method for object construction in a compiler for an object-oriented programming language
- 5603030: Method and system for destruction of objects using multiple destructor functions in an object-oriented computer system
- 6138269: Determining the actual class of an object at run time
[4] Built-in types for compiler's RTTI and exception support.
http://members.ozemail.com.au/[email protected]/samples/programming/msvc/language/predefined/index.html
[5] #pragma init_seg
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/vclang/html/_predir_init_seg.asp
|