c++虛函數逆向分析

虛函數表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其真實反應實際的函數。這樣,在有虛函數的類的實例中分配了指向這個表的指針的內存,所以,當用父類的指針來操作一個子類的時候,這張虛函數表就顯得尤爲重要了,它就像一個地圖一樣,指明瞭實際所應該調用的函數。
虛函數表中,主要是一個類的虛函數的地址表,這張表解決了繼承、覆蓋的問題,保證其真實反應實際的函數。這樣,在有虛函數的類的實例中分配了指向這個表的指針的內存,所以,當用父類的指針來操作一個子類的時候,這張虛函數表就顯得尤爲重要了,它就像一個地圖一樣,指明瞭實際所應該調用的函數。

無繼承時虛函數表

編寫demo

#include <iostream>
using namespace std;

class base_class
{
private:
    int m_base;
public:
    virtual void v_func1()
    {
        cout << "This is base_class's v_func1()" << endl;
    }
    virtual void v_func2()
    {
        cout << "This is base_class's v_func2()" << endl;
    }
    virtual void v_func3()
    {
        cout << "This is base_class's v_func3()" << endl;
    }
};

OD載入逆向分析
構造函數
這裏寫圖片描述

我們查看一下虛表指針
這裏寫圖片描述

內存中應該爲:
這裏寫圖片描述

虛表單一繼承:

改寫demo:

class base_class
{
public:
    virtual void v_func1()
    {
        cout << "This is base_class's v_func1()" << endl;
    }
    virtual void v_func2()
    {
        cout << "This is base_class's v_func2()" << endl;
    }
    virtual void v_func3()
    {
        cout << "This is base_class's v_func3()" << endl;
    }
};
class dev_class : public base_class
{
public:
    virtual void v_func4()
    {
        cout << "This is dev_class's v_func4()" << endl;
    }
    virtual void v_func5()
    {
        cout << "This is dev_class's v_func5()" << endl;
    }
};

構造函數逆向如下
基類構造函數改寫指針
這裏寫圖片描述
此時虛表
這裏寫圖片描述
在派生類中又改寫了虛函數指針
這裏寫圖片描述
此時虛表
這裏寫圖片描述
在內存中的佈局應該爲:
這裏寫圖片描述

在改寫虛表指針的時候,按照父類-子類的順序存放在虛表中

重寫父類虛函數繼承

繼續改寫demo:

class base_class
{
public:
    virtual void v_func1()
    {
        cout << "This is base_class's v_func1()" << endl;
    }
    virtual void v_func2()
    {
        cout << "This is base_class's v_func2()" << endl;
    }
    virtual void v_func3()
    {
        cout << "This is base_class's v_func3()" << endl;
    }
};
class dev_class : public base_class
{
public:
    virtual void v_func3()
    {
        cout << "This is dev_class's v_func4()" << endl;
    }
    virtual void v_func4()
    {
        cout << "This is dev_class's v_func5()" << endl;
    }
};

OD載入分析構造函數
我們按照上述方法打印虛表
在構造基類的時候
這裏寫圖片描述
在派生類修改虛表指針後
這裏寫圖片描述

可以很清楚的發現,在第三個虛函數地址被派生類修改
內存中佈局應該是這樣
這裏寫圖片描述

多重繼承下的虛函數表_子類沒有改寫父類

繼續改寫demo:

class base_class_A
{
public:
    virtual void v_func1()
    {
        cout << "This is base_class_A's v_func1()" << endl;
    }
    virtual void v_func2()
    {
        cout << "This is base_class_A's v_func2()" << endl;
    }

};
class base_class_B
{
public:
    virtual void v_func3()
    {
        cout << "This is base_class_B's v_func1()" << endl;
    }
    virtual void v_func4()
    {
        cout << "This is base_class_B's v_func2()" << endl;
    }
};
class dev_class : public base_class_A,base_class_B
{
public:
    virtual void v_func5()
    {
        cout << "This is dev_class`s v_func" << endl;
    }

};

OD載入分析構造函數
這裏寫圖片描述
這裏寫圖片描述
Base_a 虛表:
這裏寫圖片描述
Base_b虛表:
這裏寫圖片描述
dev虛表
這裏寫圖片描述
修改虛表指針
這裏寫圖片描述
這裏寫圖片描述

這裏寫圖片描述
通過分析我們可以發現當多重繼承中會存在多張虛表
內存中的佈局應該爲:
這裏寫圖片描述

多重繼承下的虛函數表_子類改寫父類

繼續改寫demo

class base_class_A
{
public:
    virtual void v_func1()
    {
        cout << "This is base_class_A's v_func1()" << endl;
    }
    virtual void v_func2()
    {
        cout << "This is base_class_A's v_func2()" << endl;
    }

};
class base_class_B
{
public:
    virtual void v_func3()
    {
        cout << "This is base_class_B's v_func1()" << endl;
    }
    virtual void v_func4()
    {
        cout << "This is base_class_B's v_func2()" << endl;
    }
};
class dev_class : public base_class_A,base_class_B
{
public:
    virtual void v_func1()
    {
        cout << "This is dev_class`s v_func1" << endl;
    }
    virtual void v_func3()
    {
        cout << "This is dev_class`s v_func3" << endl;
    }
    virtual void v_fun5()
    {
        cout << "This is dev_class`s v_func5" << endl;
    }

};

虛表爲:
這裏寫圖片描述
內存中的佈局爲:
這裏寫圖片描述

我們稍微修改下我們的demo

加入成員變量:

class root 
{
private:
    int m_r1;
    int m_r2;
public:
    root()
    {
        m_r1 = 1;
        m_r2 = 2;
    }
    ~root(){};
    virtual void v_funr()
    {
        cout << "This is root" << endl;
    }


};
class base_class_A : public root
{
private:
    int m_a;

public:
    base_class_A()
    {
        m_a = 3;
    }
    ~base_class_A(){};
    virtual void v_func1()
    {
        cout << "This is base_class_A's v_func1()" << endl;
    }
    virtual void v_func2()
    {
        cout << "This is base_class_A's v_func2()" << endl;
    }

};
class base_class_B : public root
{
private: 
    int m_b ;

public:
    base_class_B()
    {
        m_b = 4;
    }
    ~base_class_B(){};
    void v_func3()
    {
        cout << "This is base_class_B's v_func1()" << endl;
    }
    void v_func4()
    {
        cout << "This is base_class_B's v_func2()" << endl;
    }
};

class dev_class : public base_class_A,base_class_B
{
private: 
    int m_a;
    int m_b;
    int m_c;
public:
    dev_class();
    ~dev_class(){};
    virtual void v_func1()
    {
        cout << "This is dev_class`s v_func1" << endl;
    }
    virtual void v_func3()
    {
        cout << "This is dev_class`s v_func3" << endl;
    }
    virtual void v_fun5()
    {
        cout << "This is dev_class`s v_func5" << endl;
    }

};

 dev_class :: dev_class():m_a(1),m_b(2),m_c(3)
{

}

我們看下最開始的基類root的構造:
這裏寫圖片描述
虛表爲:
這裏寫圖片描述

虛擬多重繼承

改寫demo:

class root 
{
private:
    int m_r1;
    int m_r2;
public:
    root()
    {
        m_r1 = 1;
        m_r2 = 2;
    }
    ~root(){};
    virtual void v_funr()
    {
        cout << "This is root" << endl;
    }


};
class base_class : virtual public root
{
private:
    int m_a;
    int m_b;

public:
    base_class()
    {
        m_a = 3;
        m_b = 4;
    }
    ~base_class(){};
    virtual void v_funr()
    {
        cout << "This is base_class_A's v_funcr()" << endl;
    }
    virtual void v_func1()
    {
        cout << "This is base_class_A's v_func1()" << endl;
    }
    virtual void v_func2()
    {
        cout << "This is base_class_A's v_func2()" << endl;
    }

};

class dev_class :virtual public base_class
{
private: 
    int m_a;
    int m_b;
    int m_c;
public:
    dev_class();
    ~dev_class(){};
    virtual void v_funr()
    {
        cout << "This is dev_class's v_funcr()" << endl;
    }
    virtual void v_func1()
    {
        cout << "This is dev_class`s v_func1" << endl;
    }
    virtual void v_func3()
    {
        cout << "This is dev_class`s v_func3" << endl;
    }
    virtual void v_fun5()
    {
        cout << "This is dev_class`s v_func5" << endl;
    }

};
dev_class :: dev_class():m_a(1),m_b(2),m_c(3)
{

}

Dev_class的時候
這裏寫圖片描述
此時[eax+0x4]和[eax+0x2c]存放的不再爲虛表指針,而是一個偏轉
我們可以查看下地址

這裏寫圖片描述
這裏寫圖片描述
在root構造時,

00A22BA7    8B45 F8         mov eax,dword ptr ss:[ebp-0x8]
00A22BAA    C700 90DCA200   mov dword ptr ds:[eax],offset vft.base_class::`vftable'
00A22BB0    8B45 F8         mov eax,dword ptr ss:[ebp-0x8]
00A22BB3    8B48 04         mov ecx,dword ptr ds:[eax+0x4]                                          ; 得到偏轉表地址
00A22BB6    8B51 04         mov edx,dword ptr ds:[ecx+0x4]                                          ; 得到偏移地址
00A22BB9    8B45 F8         mov eax,dword ptr ss:[ebp-0x8]
00A22BBC    C74410 04 A0DCA>mov dword ptr ds:[eax+edx+0x4],offset vft.base_class::`vftable'         ; 通過偏轉地址計算得到虛基類指針並修改
00A22BC4    8B45 F8         mov eax,dword ptr ss:[ebp-0x8]
00A22BC7    8B48 04         mov ecx,dword ptr ds:[eax+0x4]
00A22BCA    8B51 04         mov edx,dword ptr ds:[ecx+0x4]
00A22BCD    83EA 10         sub edx,0x10                                                            ; 減去類大小得到相對長度
00A22BD0    8B45 F8         mov eax,dword ptr ss:[ebp-0x8]
00A22BD3    8B48 04         mov ecx,dword ptr ds:[eax+0x4]                                          ; 得到偏移表地址
00A22BD6    8B41 04         mov eax,dword ptr ds:[ecx+0x4]                                          ; 得到偏移
00A22BD9    8B4D F8         mov ecx,dword ptr ss:[ebp-0x8]
00A22BDC    891401          mov dword ptr ds:[ecx+eax],edx                                          ; 將偏移大小存放在虛基類前
00A22BDF    8B45 F8         mov eax,dword ptr ss:[ebp-0x8]
00A22BE2    C740 08 0300000>mov dword ptr ds:[eax+0x8],0x3
00A22BE9    8B45 F8         mov eax,dword ptr ss:[ebp-0x8]

可以發現剛剛分析 出來的偏轉地址均指向 虛基類(root)的虛表指針
這裏寫圖片描述

而FFFFFFFC則爲-4,指向偏轉表的前一個DWORD地址
我們繼續看base類的構造
這裏寫圖片描述
通過偏移,使子類可以很容易訪問到虛基類,進而對虛基類指針進行改寫

00FE2C8C    837D 08 00      cmp dword ptr ss:[ebp+0x8],0x0                                          ; 判斷虛基類
00FE2C90    74 51           je Xvft.00FE2CE3
00FE2C92    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2C95    C740 04 58DDFE0>mov dword ptr ds:[eax+0x4],offset vft.dev_class::`vbtable'              ; 偏轉表
00FE2C9C    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2C9F    C740 2C 68DDFE0>mov dword ptr ds:[eax+0x2C],offset vft.dev_class::`vbtable'             ; 偏轉表
00FE2CA6    8B4D EC         mov ecx,dword ptr ss:[ebp-0x14]
00FE2CA9    83C1 18         add ecx,0x18                                                            ; 得到虛基類指針
00FE2CAC    E8 5FE7FFFF     call vft.00FE1410
00FE2CB1    C745 FC 0000000>mov dword ptr ss:[ebp-0x4],0x0
00FE2CB8    8B85 20FFFFFF   mov eax,dword ptr ss:[ebp-0xE0]
00FE2CBE    83C8 01         or eax,0x1
00FE2CC1    8985 20FFFFFF   mov dword ptr ss:[ebp-0xE0],eax                                         ; 虛基類已經構造
00FE2CC7    6A 00           push 0x0
00FE2CC9    8B4D EC         mov ecx,dword ptr ss:[ebp-0x14]
00FE2CCC    83C1 28         add ecx,0x28
00FE2CCF    E8 BFE6FFFF     call vft.00FE1393                                                       ; base構造
00FE2CD4    8B85 20FFFFFF   mov eax,dword ptr ss:[ebp-0xE0]
00FE2CDA    83C8 02         or eax,0x2
00FE2CDD    8985 20FFFFFF   mov dword ptr ss:[ebp-0xE0],eax
00FE2CE3    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2CE6    C700 30DDFE00   mov dword ptr ds:[eax],offset vft.dev_class::`vftable'                  ; dev的虛表指針(指向fun3,fun5)
00FE2CEC    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2CEF    8B48 04         mov ecx,dword ptr ds:[eax+0x4]
00FE2CF2    8B51 04         mov edx,dword ptr ds:[ecx+0x4]                                          ; 得到偏移
00FE2CF5    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2CF8    C74410 04 40DDF>mov dword ptr ds:[eax+edx+0x4],offset vft.dev_class::`vftable'          ; 通過偏移訪問到虛基類並修改
00FE2D00    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2D03    8B48 04         mov ecx,dword ptr ds:[eax+0x4]
00FE2D06    8B51 08         mov edx,dword ptr ds:[ecx+0x8]                                          ; 取到base類偏移
00FE2D09    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]                                         ; 得到基址
00FE2D0C    C74410 04 4CDDF>mov dword ptr ds:[eax+edx+0x4],offset vft.dev_class::`vftable'          ; 修改base類虛表指針
00FE2D14    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2D17    8B48 04         mov ecx,dword ptr ds:[eax+0x4]
00FE2D1A    8B51 04         mov edx,dword ptr ds:[ecx+0x4]                                          ; 得到長度
00FE2D1D    83EA 14         sub edx,0x14                                                            ; 減去類大小
00FE2D20    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2D23    8B48 04         mov ecx,dword ptr ds:[eax+0x4]
00FE2D26    8B41 04         mov eax,dword ptr ds:[ecx+0x4]
00FE2D29    8B4D EC         mov ecx,dword ptr ss:[ebp-0x14]
00FE2D2C    891401          mov dword ptr ds:[ecx+eax],edx                                          ; 將偏移存放在虛基類前
00FE2D2F    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2D32    8B48 04         mov ecx,dword ptr ds:[eax+0x4]
00FE2D35    8B51 08         mov edx,dword ptr ds:[ecx+0x8]
00FE2D38    83EA 24         sub edx,0x24
00FE2D3B    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]                                         ; 得到基址
00FE2D3E    8B48 04         mov ecx,dword ptr ds:[eax+0x4]                                          ; 偏轉表
00FE2D41    8B41 08         mov eax,dword ptr ds:[ecx+0x8]                                          ; 偏移大小
00FE2D44    8B4D EC         mov ecx,dword ptr ss:[ebp-0x14]                                         ; 基址
00FE2D47    891401          mov dword ptr ds:[ecx+eax],edx                                          ; 將相對偏移存放在base前
00FE2D4A    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2D4D    C740 08 0100000>mov dword ptr ds:[eax+0x8],0x1                                          ; m_a = 1
00FE2D54    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2D57    C740 0C 0200000>mov dword ptr ds:[eax+0xC],0x2                                          ; m_b = 2
00FE2D5E    8B45 EC         mov eax,dword ptr ss:[ebp-0x14]
00FE2D61    C740 10 0300000>mov dword ptr ds:[eax+0x10],0x3                                         ; m_c = 3

最終虛表爲:
這裏寫圖片描述

在虛表中我們發現
這裏寫圖片描述
而我們在funcr時發現有這樣的結構:
這裏寫圖片描述
在dev.func1也有這樣的結構
這裏寫圖片描述

我們現在總結在內存中,虛擬繼承結構如下:
這裏寫圖片描述

總結:

在分析虛函數,當存在多重繼承(虛擬繼承中有虛函數)情況下,虛表的結構會發生變化,將會多出一個偏轉表,通過對偏移地址的操作進而去訪問和改寫父類虛表指針。而其在內存中的結構也與普通繼承有些不同(考慮跟編譯器有關!)。

本文如需轉載,請註明出處

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章