從逆向的角度去理解C++虛函數表


  很久沒有寫過文章了,自己一直是做C/C++開發的,我一直認為,作為一個C/C++程序員,如果能夠好好學一下匯編和逆向分析,那么對於我們去理解C/C++將會有很大的幫助,因為程序中所有的奧秘都藏在匯編中,很多問題我們從表面上無法看出到底是為什么,只要用逆向工具一分析,很快就能知道其中的所以然來。我們都知道虛函數表是放在類對象的最前面,但是很多人並不知道虛函數表的第一項存放的是什么,下面我用IDA分析一下C++的虛函數調用,從匯編的角度去理解虛函數。此文只適合具有逆向分析基礎的讀者,如果沒有逆向分析基礎,請直接繞過。有如下C++代碼:

class Test
{
public:
    Test()
    {
        printf("Test::Test\n");
    }
    virtual ~Test()
    {
        printf("Virtual ~Test()\n");
    }
    virtual void prointer()=0;
    virtual void pointf()=0;
};
class TestA:public Test
{
public:
    TestA()
    {
        printf("TestA::TestA\n");
    }
    virtual ~TestA()
    {
        printf("TestA::TestA\n");
    }
    virtual void prointer()
    {
        printf("Derive Class TestA::Pointer\n");
    }
    virtual void pointf()
    {
        printf("Derive Class TestA::Pointf\n");
    }
};


int _tmain(int argc, _TCHAR* argv[])
{
    TestA *pTest=new TestA;
    pTest->pointf();
    pTest->prointer();
    delete pTest;
    return 0;
}

    用IDA打開EXE文件,用流程圖模式顯示:

運行到調用new的地方:

一層一層跟進去,我們會發現,new的內部其實就是調用的malloc進行內存分配,msvcr100d_malloc便是malloc函數:

往下走,發現如下指令:

text:00C3149F call    j_??2@YAPAXI@Z_0                ; operator new(uint)
.text:00C314A4 add     esp, 4
.text:00C314A7 mov     [ebp+this], eax
.text:00C314AD mov     [ebp+var_4], 0
.text:00C314B4 cmp     [ebp+this], 0
.text:00C314BB jz      short loc_C314D0
.text:00C314BD mov     ecx, [ebp+this]                 ; this
.text:00C314C3 call    j_??0TestA@@QAE@XZ              ; TestA::TestA(void)
.text:00C314C8 mov     [ebp+var_10C], eax              ; new的返回值賦給[epb+var_10C]
.text:00C314CE jmp     short loc_C314DA                ; eax=this
.text:00C314D0 ; ---------------------------------------------------------------------------
.text:00C314D0
.text:00C314D0 loc_C314D0:                             ; CODE XREF: _wmain+5Bj
.text:00C314D0 mov     [ebp+var_10C], 0
.text:00C314DA
.text:00C314DA loc_C314DA:                             ; CODE XREF: _wmain+6Ej
.text:00C314DA mov     eax, [ebp+var_10C]              ; eax=this
.text:00C314E0 mov     [ebp+var_104], eax              ; [ebp+var_104]=this
.text:00C314E6 mov     [ebp+var_4], 0FFFFFFFFh
.text:00C314ED mov     ecx, [ebp+var_104]              ; ecx=this
.text:00C314F3 mov     [ebp+pTest], ecx                ; [ebp+pTest]=this
.text:00C314F6 mov     eax, [ebp+pTest]                ; eax=this
.text:00C314F9 mov     edx, [eax]
.text:00C314FB mov     esi, esp
.text:00C314FD mov     ecx, [ebp+pTest]
.text:00C31500 mov     eax, [edx+8]                    ; eax=this+8
.text:00C31503 call    eax                             ; 調用this+8處的代碼

跟進去,發現如下指令:

text:00C31820 push    ebp
.text:00C31821 mov     ebp, esp
.text:00C31823 sub     esp, 0CCh
.text:00C31829 push    ebx
.text:00C3182A push    esi
.text:00C3182B push    edi
.text:00C3182C push    ecx
.text:00C3182D lea     edi, [ebp+var_CC]
.text:00C31833 mov     ecx, 33h
.text:00C31838 mov     eax, 0CCCCCCCCh
.text:00C3183D rep stosd
.text:00C3183F pop     ecx
.text:00C31840 mov     [ebp+this], ecx
.text:00C31843 mov     esi, esp
.text:00C31845 push    offset aDeriveClassT_0          ; "Derive Class TestA::Pointf\n"
.text:00C3184A call    ds:__imp__printf
.text:00C31850 add     esp, 4
.text:00C31853 cmp     esi, esp
.text:00C31855 call    j___RTC_CheckEsp
.text:00C3185A pop     edi
.text:00C3185B pop     esi
.text:00C3185C pop     ebx
.text:00C3185D add     esp, 0CCh
.text:00C31863 cmp     ebp, esp
.text:00C31865 call    j___RTC_CheckEsp
.text:00C3186A mov     esp, ebp
.text:00C3186C pop     ebp
.text:00C3186D retn
.text:00C3186D ?pointf@TestA@@UAEXXZ endp

這就是pTest->pointf();調用的匯編代碼,繼續往下走:

調用了[edx+4]其實也就是this+4處的代碼,跟進去,代碼如下:

.text:00C517B0 push    ebp
.text:00C517B1 mov     ebp, esp
.text:00C517B3 sub     esp, 0CCh
.text:00C517B9 push    ebx
.text:00C517BA push    esi
.text:00C517BB push    edi
.text:00C517BC push    ecx
.text:00C517BD lea     edi, [ebp+var_CC]
.text:00C517C3 mov     ecx, 33h
.text:00C517C8 mov     eax, 0CCCCCCCCh
.text:00C517CD rep stosd
.text:00C517CF pop     ecx
.text:00C517D0 mov     [ebp+this], ecx
.text:00C517D3 mov     esi, esp
.text:00C517D5 push    offset aDeriveClassTes          ; "Derive Class TestA::Pointer\n"
.text:00C517DA call    ds:__imp__printf
.text:00C517E0 add     esp, 4
.text:00C517E3 cmp     esi, esp
.text:00C517E5 call    j___RTC_CheckEsp
.text:00C517EA pop     edi
.text:00C517EB pop     esi
.text:00C517EC pop     ebx
.text:00C517ED add     esp, 0CCh
.text:00C517F3 cmp     ebp, esp
.text:00C517F5 call    j___RTC_CheckEsp
.text:00C517FA mov     esp, ebp
.text:00C517FC pop     ebp
.text:00C517FD retn
.text:00C517FD ?prointer@TestA@@UAEXXZ endp

以上其實就是調用pTest->prointer();的匯編代碼,繼續往下執行:

這里調用的就是this處的指令,跟進去,有如下指令:

text:011D1890 push    ebp
.text:011D1891 mov     ebp, esp
.text:011D1893 sub     esp, 0CCh
.text:011D1899 push    ebx
.text:011D189A push    esi
.text:011D189B push    edi
.text:011D189C push    ecx
.text:011D189D lea     edi, [ebp+var_CC]
.text:011D18A3 mov     ecx, 33h
.text:011D18A8 mov     eax, 0CCCCCCCCh
.text:011D18AD rep stosd
.text:011D18AF pop     ecx
.text:011D18B0 mov     [ebp+this], ecx
.text:011D18B3 mov     ecx, [ebp+this]                 ; this
.text:011D18B6 call    j_??1TestA@@UAE@XZ              ; TestA::~TestA(void)
.text:011D18BB mov     eax, [ebp+arg_0]
.text:011D18BE and     eax, 1
.text:011D18C1 jz      short loc_11D18CF
.text:011D18C3 mov     eax, [ebp+this]
.text:011D18C6 push    eax                             ; void *
.text:011D18C7 call    j_??3@YAXPAX@Z_0                ; operator delete(void *)
.text:011D18CC add     esp, 4

此處就是析構函數調用的地方,綜上所述,我們可以得出結論,new最終是調用的malloc進行內存分配,在虛函數表中,虛析構函數是放在最前面的。當然這只是最簡單的類繼承體系,而且有些地方寫得不夠詳細,大家可以自己動手寫代碼用IDA去進行逆向分析,探索其中的奧秘,以上只是本人的一些理解,如果有不合理的地方歡迎大家指正,希望能和大家共同學習,共同進步。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM