• 虚函数逆向分析


    [背景]

           虚函数表中,主要是一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其真实反应实际的函数。这样,在有虚函数的类的实例中分配了指向这个表的指针的内存,所以,当用父类的指针来操作一个子类的时候,这张虚函数表就显得尤为重要了,它就像一个地图一样,指明了实际所应该调用的函数。

          编译器应该保证虚函数表的指针存在于对象实例中最前面的位置(这是为了保证取到虚函数表的有最高的性能——如果有多层继承或是多重继承的情况下)。 这意味着可以通过对象实例的地址得到这张虚函数表,然后就可以遍历其中函数指针,并调用相应的函数。

     

     

    无继承时虚函数表

     1 编写demo:
     2 #include <iostream>
     3 using namespace std;
     4  
     5 class base_class
     6 {
     7 private:
     8 int m_base;
     9 public:
    10 virtual void v_func1()
    11 {
    12 cout << "This is base_class's v_func1()" << endl;
    13 }
    14 virtual void v_func2()
    15 {
    16 cout << "This is base_class's v_func2()" << endl;
    17 }
    18 virtual void v_func3()
    19 {
    20 cout << "This is base_class's v_func3()" << endl;
    21 }
    22 };

    OD载入逆向分析

    构造函数

    我们查看一下虚表指针

    内存中应该为:

     

     

     

     

     

    虚表单一继承:

     1 改写demo
     2 class base_class
     3 {
     4 public:
     5 virtual void v_func1()
     6 {
     7 cout << "This is base_class's v_func1()" << endl;
     8 }
     9 virtual void v_func2()
    10 {
    11 cout << "This is base_class's v_func2()" << endl;
    12 }
    13 virtual void v_func3()
    14 {
    15 cout << "This is base_class's v_func3()" << endl;
    16 }
    17 };
    18 class dev_class : public base_class
    19 {
    20 public:
    21 virtual void v_func4()
    22 {
    23 cout << "This is dev_class's v_func4()" << endl;
    24 }
    25 virtual void v_func5()
    26 {
    27 cout << "This is dev_class's v_func5()" << endl;
    28 }
    29 };

    构造函数逆向如下

    基类构造函数改写指针

    此时虚表

     

    在派生类中又改写了虚函数指针

    此时虚表

    在内存中的布局应该为:

     

    在改写虚表指针的时候,按照父类-子类的顺序存放在虚表中

     

    重写父类虚函数继承

     1 我们改写demo:
     2 class base_class
     3 {
     4 public:
     5 virtual void v_func1()
     6 {
     7 cout << "This is base_class's v_func1()" << endl;
     8 }
     9 virtual void v_func2()
    10 {
    11 cout << "This is base_class's v_func2()" << endl;
    12 }
    13 virtual void v_func3()
    14 {
    15 cout << "This is base_class's v_func3()" << endl;
    16 }
    17 };
    18 class dev_class : public base_class
    19 {
    20 public:
    21 virtual void v_func3()
    22 {
    23 cout << "This is dev_class's v_func4()" << endl;
    24 }
    25 virtual void v_func4()
    26 {
    27 cout << "This is dev_class's v_func5()" << endl;
    28 }
    29 };

    OD载入分析构造函数

    我们按照上述方法打印虚表

    在构造基类的时候

    在派生类修改虚表指针后

    可以很清楚的发现,在第三个虚函数地址被派生类修改

    内存中布局应该是这样

     

     

     

    多重继承下的虚函数表_子类没有改写父类

     1 我们改写demo
     2  
     3 class base_class_A
     4 {
     5 public:
     6 virtual void v_func1()
     7 {
     8 cout << "This is base_class_A's v_func1()" << endl;
     9 }
    10 virtual void v_func2()
    11 {
    12 cout << "This is base_class_A's v_func2()" << endl;
    13 }
    14 
    15 };
    16 class base_class_B
    17 {
    18 public:
    19 virtual void v_func3()
    20 {
    21 cout << "This is base_class_B's v_func1()" << endl;
    22 }
    23 virtual void v_func4()
    24 {
    25 cout << "This is base_class_B's v_func2()" << endl;
    26 }
    27 };
    28 class dev_class : public base_class_A,base_class_B
    29 {
    30 public:
    31 virtual void v_func5()
    32 {
    33 cout << "This is dev_class`s v_func" << endl;
    34 }
    35 
    36 };

    OD载入分析构造函数

    Base_a 虚表

    Base_b

    Dev:

    修改虚表指针

    通过分析我们可以发现当多重继承中会存在多张虚表

    内存中的布局应该为:

     

     

    多重继承下的虚函数表_子类改写父类

     1 我们改写demo
     2 class base_class_A
     3 {
     4 public:
     5 virtual void v_func1()
     6 {
     7 cout << "This is base_class_A's v_func1()" << endl;
     8 }
     9 virtual void v_func2()
    10 {
    11 cout << "This is base_class_A's v_func2()" << endl;
    12 }
    13 
    14 };
    15 class base_class_B
    16 {
    17 public:
    18 virtual void v_func3()
    19 {
    20 cout << "This is base_class_B's v_func1()" << endl;
    21 }
    22 virtual void v_func4()
    23 {
    24 cout << "This is base_class_B's v_func2()" << endl;
    25 }
    26 };
    27 class dev_class : public base_class_A,base_class_B
    28 {
    29 public:
    30 virtual void v_func1()
    31 {
    32 cout << "This is dev_class`s v_func1" << endl;
    33 }
    34 virtual void v_func3()
    35 {
    36 cout << "This is dev_class`s v_func3" << endl;
    37 }
    38 virtual void v_fun5()
    39 {
    40 cout << "This is dev_class`s v_func5" << endl;
    41 }
    42 
    43 };

    虚表为

    内存中的布局为

     

    我们稍微修改下我们的demo

    加入成员变量:

     1 class root 
     2 {
     3 private:
     4 int m_r1;
     5 int m_r2;
     6 public:
     7 root()
     8 {
     9 m_r1 = 1;
    10 m_r2 = 2;
    11 }
    12 ~root(){};
    13 virtual void v_funr()
    14 {
    15 cout << "This is root" << endl;
    16 }
    17  
    18 
    19 };
    20 class base_class_A : public root
    21 {
    22 private:
    23 int m_a;
    24  
    25 public:
    26 base_class_A()
    27 {
    28 m_a = 3;
    29 }
    30 ~base_class_A(){};
    31 virtual void v_func1()
    32 {
    33 cout << "This is base_class_A's v_func1()" << endl;
    34 }
    35 virtual void v_func2()
    36 {
    37 cout << "This is base_class_A's v_func2()" << endl;
    38 }
    39 
    40 };
    41 class base_class_B : public root
    42 {
    43 private: 
    44 int m_b ;
    45  
    46 public:
    47 base_class_B()
    48 {
    49 m_b = 4;
    50 }
    51 ~base_class_B(){};
    52 void v_func3()
    53 {
    54 cout << "This is base_class_B's v_func1()" << endl;
    55 }
    56 void v_func4()
    57 {
    58 cout << "This is base_class_B's v_func2()" << endl;
    59 }
    60 };
    61  
    62 class dev_class : public base_class_A,base_class_B
    63 {
    64 private: 
    65 int m_a;
    66 int m_b;
    67 int m_c;
    68 public:
    69 dev_class();
    70 ~dev_class(){};
    71 virtual void v_func1()
    72 {
    73 cout << "This is dev_class`s v_func1" << endl;
    74 }
    75 virtual void v_func3()
    76 {
    77 cout << "This is dev_class`s v_func3" << endl;
    78 }
    79 virtual void v_fun5()
    80 {
    81 cout << "This is dev_class`s v_func5" << endl;
    82 }
    83 
    84 };
    85  
    86  dev_class :: dev_class():m_a(1),m_b(2),m_c(3)
    87 {
    88  
    89 }

    加入成员变量

    我们看下最开始的基类root的构造

    虚表为

     

     

    虚拟多重继承

     1 Demo:
     2 class root 
     3 {
     4 private:
     5 int m_r1;
     6 int m_r2;
     7 public:
     8 root()
     9 {
    10 m_r1 = 1;
    11 m_r2 = 2;
    12 }
    13 ~root(){};
    14 virtual void v_funr()
    15 {
    16 cout << "This is root" << endl;
    17 }
    18  
    19  
    20 };
    21 class base_class : virtual public root
    22 {
    23 private:
    24 int m_a;
    25 int m_b;
    26  
    27 public:
    28 base_class()
    29 {
    30 m_a = 3;
    31 m_b = 4;
    32 }
    33 ~base_class(){};
    34 virtual void v_funr()
    35 {
    36 cout << "This is base_class_A's v_funcr()" << endl;
    37 }
    38 virtual void v_func1()
    39 {
    40 cout << "This is base_class_A's v_func1()" << endl;
    41 }
    42 virtual void v_func2()
    43 {
    44 cout << "This is base_class_A's v_func2()" << endl;
    45 }
    46  
    47 };
    48  
    49 class dev_class :virtual public base_class
    50 {
    51 private: 
    52 int m_a;
    53 int m_b;
    54 int m_c;
    55 public:
    56 dev_class();
    57 ~dev_class(){};
    58 virtual void v_funr()
    59 {
    60 cout << "This is dev_class's v_funcr()" << endl;
    61 }
    62 virtual void v_func1()
    63 {
    64 cout << "This is dev_class`s v_func1" << endl;
    65 }
    66 virtual void v_func3()
    67 {
    68 cout << "This is dev_class`s v_func3" << endl;
    69 }
    70 virtual void v_fun5()
    71 {
    72 cout << "This is dev_class`s v_func5" << endl;
    73 }
    74  
    75 };
    76 dev_class :: dev_class():m_a(1),m_b(2),m_c(3)
    77 {
    78  
    79 }

    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也有这样的结构

    我们现在总结在内存中,虚拟继承结构如下:

     

    结论:

    在分析虚函数,当存在多重继承(虚拟继承中有虚函数)情况下,虚表的结构会发生变化,将会多出一个偏转表,通过对偏移地址的操作进而去访问和改写父类虚表指针。而其在内存中的结构也与普通继承有些不同(考虑跟编译器有关!)。

     

    *转载请注明来自游戏安全实验室(GSLAB.QQ.COM)

  • 相关阅读:
    cantor 数表
    利用form的“acceptcharset”在不同编码的页面间提交表单
    <li>标签,在文字超出宽度时引起混乱的解决办法
    java中 Integer.getInteger(String str)的疑惑
    SQL语句集锦
    禁用鼠标右键
    ROW_NUMBER() OVER函数的基本用法
    listview
    decodeResource
    LinkedList
  • 原文地址:https://www.cnblogs.com/nothx/p/8537334.html
Copyright © 2020-2023  润新知