逆向第十九讲——类继承和成员类运算符重载模板逆向20171211

Posted DennyChenD

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了逆向第十九讲——类继承和成员类运算符重载模板逆向20171211相关的知识,希望对你有一定的参考价值。

一、类继承逆向
    在C++中使用到继承,主要是为了实现多态,那么多态就必须会用到虚函数,即会产生虚表指针。
    (1)父类和子类中有没用到虚函数的四种情形
    1)父类和子类中都没有用到虚函数
    如果父类和子类中都没有用到虚函数,那么子类中就只是继承了父类中的成员变量和成员函数,当然还得视父类中成员变量和成员函数的公有私有性质和继承方式而定,在此继承中有一种特殊形式,当父类和子类中含有同名同参数同返回值的函数时,用父类对象指针调该函数,则调的是父类中的该函数,用子类对象指针调该函数时,调的是子类中的而该函数,类似的当父类子类中都含有同名同类型的成员变量时,用各自的类型指针调各自的成员变量,这里严格上说就没用上继承的了,因为子类自己也有,对于父类中和子类中同名的成员变量,不会是合并,而是各自存放在各自的对象范畴中,当然父类对象包含在子类对象内。
    2)父类有申明虚函数、子类中没申明虚函数
    这种情形下,子类对象中自然也是有虚表的,调试时可发现有虚表覆盖的过程,如果子类中有同名同参同返回值的函数,那么子类虚表中相应偏移处函数指针就是子类中的那个同名同参同返回值的函数指针,如果没有同名同参同返回值的函数,那么子类虚表中相应偏移处函数指针就是父类中相应函数的指针。子类中没析构函数,父类中没析构函数,那么父类中没有默认析构函数,父类中有析构函数,子类中会有默认析构函数。对于构造函数也是一样,子类中有,父类中没默认,父类中,子类中会有默认析构函数。当一个父类中有虚函数时,并且没有构造和析构函数时,子类对象定义时会有默认构造,无默认析构。
    3)父类没有申明虚函数、子类中有申明虚函数
    这种情形下,父类对象中会没有虚表指针。
    4)父类和子类中都有用到虚函数
    类似2),父类和子类中都会有虚表指针。
 
    (2)类的构造和析构函数中是否调用虚函数
    类的构造和析构函数中一般不调用虚函数,因为父类和子类各自构造和析构自己,所以没必要使用虚函数。从粗略的角度分析,当在父类的构造函数中调用虚函数时,会调到子函数的成员函数去了,但这时子函数还没有构造,但析构父类时,又回调到子函数的析构函数,这时子类已经析构掉了,当然从编译器的操作上是表面了这种情形发生的,因为在父类构造和析构时都会填一次自己的虚表指针,即不会出现隐患,这是编译器做的防止隐患的一招。当构造和析构函数中调用虚函数时,会直接使用虚函数的指针,不会经过虚表指针。
class CParent  
{
public:
     CParent()
    {
        Show();
    }
     ~CParent();
    virtual void Show()
    {
        printf("class CParent");
    }
    int m_nInt;
};
//汇编代码
15:       Show();
004010D0   mov         ecx,dword ptr [ebp-4]
004010D3   call        @ILT+35(CParent::Show) (00401028)
//普通指针调用虚函数Show();
11:       pobj->Show();
0040109E   mov         edx,dword ptr [ebp-10h]
004010A1   mov         eax,dword ptr [edx]
004010A3   mov         esi,esp
004010A5   mov         ecx,dword ptr [ebp-10h]
004010A8   call        dword ptr [eax]
004010AA   cmp         esi,esp
(3)父类和成员类的区别
    如果父类、成员类和子类中都没有虚表,则当结构体处理。对于有虚表的情形,得从以下三点进行识别:1)虚表指针个数,2)初始化时机,3)各虚表覆盖的情况。汗一个父类、一个成员类的情形各类主要情况如下:
    1)父类、成员类和子类中都有虚表
    父类的虚表指针最先初始化,再次是成员类初始化,初始偏移从紧接父类和成员类在子类中前边成员偏移开始,紧接的是其它的子类成员,父类的虚表指针被覆盖,析构时反向。
    2)父类中无虚表、子类中都有虚表
    构造时,对象首四个字节会腾出来给子类填虚表指针,父类构造时,从子类对象地址的后四个字节开始。
    3)其它的情形都比较好认识,当有多重继承和多个成员类时,直接用递归的方法。在对象首四个字节下写入断点时,可以看到虚表指针被覆盖多少次,被覆盖多少次就有多少重继承。有的时候,父类和成员类没法分清,那么这是还原成哪一种都行。在逆向C++时,得想对类成员函数,不管是虚函数和是非成员函数(野成员函数), 进行建模,建模好之后,再分发逆向。建模也是一个很重要的过程,加上这一过程,就相当于是逆向工程。
 
二、运算符重载和模板
    运算符重载和模板是分辨不出来的,只能还原成相应的函数,当然可以根据自己分析的情况,进行还原成运算符重载和模板。
int operator+(CChild1 obj1, CChild1 obj2)
{
    return obj1.m_nInt + obj2.m_nInt;
}
;17:       int m = obj1 + obj2;
004011DF   sub         esp,0Ch
004011E2   mov         ecx,esp
004011E4   mov         dword ptr [ebp-38h],esp
004011E7   lea         edx,[ebp-28h]
004011EA   push        edx
004011EB   call        @ILT+45(CChild1::CChild1) (00401032)
;18:       m = operator+(obj1, obj2);
00401226   sub         esp,0Ch
00401229   mov         ecx,esp
0040122B   mov         dword ptr [ebp-40h],esp
0040122E   lea         edx,[ebp-28h]
00401231   push        edx
00401232   call        @ILT+45(CChild1::CChild1) (00401032)
template<typename T>  
T My_Add(T m, T n)
{
    return m + n;
}
20:       My_Add(1, 2);  //两个不同的函数
00401158   push        2
0040115A   push        1
0040115C   call        @ILT+25(My_Add) (0040101e)
00401161   add         esp,8
21:       My_Add(1.0, 2.0); //两个不同的函数
00401164   push        40000000h
00401169   push        0
0040116B   push        3FF00000h
00401170   push        0
00401172   call        @ILT+10(My_Add) (0040100f)
00401177   fstp        st(0)

  对于运算符的书写顺序分中缀式、波兰式、逆波兰式,中缀式在数学书中公式常用,波兰式在编译器中常用,逆波兰式在公式文字描述中用的较多,各式转换方式如下:

1.中缀式
    a + b/c - d*e;
2.中缀式转波兰式,先按序转换成指令
sub(add(a, div(b,c)), mul(d,e))
-+a/bc*de 即为波兰式
3.文字描述 
(a与(b、c之商)之和)与(d、e之积)的差
abc/+de*- 即为逆波兰式
三.纯虚函数怎么实现
    VC6.0中通过19号错误来实现:
;__purecall      proc near               ; DATA XREF: .rdata:const CParent::`vftable‘o
.text:004018A0                 push    ebp
.text:004018A1                 mov     ebp, esp
.text:004018A3                 push    19h
.text:004018A5                 call    __amsg_exit
.text:004018A5 __purecall      endp

34:   {
004018A0   push        ebp
004018A1   mov         ebp,esp
35:           _amsg_exit(_RT_PUREVIRT);
004018A3   push        19h
004018A5   call        _amsg_exit (004019e0)
004018AA   add         esp,4
36:   }
;VS2013中虚函数编译器操作,用了DecodePointer和_abort
sub_40115E      proc near               ; DATA XREF: .rdata:off_40D154o
.text:0040115E                 push    Ptr             ; Ptr
.text:00401164                 call    ds:DecodePointer
.text:0040116A                 test    eax, eax
.text:0040116C                 jz      short loc_401170
.text:0040116E                 call    eax
.text:00401170
.text:00401170 loc_401170:                             ; CODE XREF: sub_40115E+Ej
.text:00401170                 push    1
.text:00401172                 push    0
.text:00401174                 call    sub_402CCD
.text:00401179                 pop     ecx
.text:0040117A                 pop     ecx
.text:0040117B                 jmp     _abort
.text:0040117B sub_40115E      endp

 

 
 

以上是关于逆向第十九讲——类继承和成员类运算符重载模板逆向20171211的主要内容,如果未能解决你的问题,请参考以下文章

类模板 友元重载形式 各种运算符重载 new delete ++ = +=

C++继承

C++继承

C++继承

操作符(运算符)重载注意事项(含模板类中<<;;重载)

C++Primer 第十四章