逆向第十九讲——类继承和成员类、运算符重载、模板逆向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
时间: 2024-10-14 13:57:51

逆向第十九讲——类继承和成员类、运算符重载、模板逆向20171211的相关文章

C++ Primer 学习笔记_27_操作符重载与转换(2)--++/--运算符重载、!运算符重载、赋值运算符重载 、String类([]、 +、 += 运算符重载)、&gt;&gt;和&lt;&lt;运算符重载

C++ Primer 学习笔记_27_操作符重载与转换(2)--++/--运算符重载.!运算符重载.赋值运算符重载 .String类([]. +. += 运算符重载).>>和<<运算符重载 一.++/--运算符重载 1.前置++运算符重载 成员函数的方式重载,原型为: 函数类型 & operator++(); 友元函数的方式重载,原型为: friend 函数类型 & operator++(类类型 &); 2.后置++运算符重载 成员函数的方式重载,原型为:

第九周项目一-复数类的中的运算符重载(续)

在复数类中的运算符重载基础上 (1)再定义一目运算符 -,-c相当于0-c. (2)定义Complex类中的<<和>>运算符的重载,实现输入和输出,改造原程序中对运算结果显示方式,使程序读起来更自然 /* * Copyright (c) 2015,烟台大学计算机学院 * All right reserved. * 作者:赵嵩 * 文件:Demo.cpp * 完成时间:2015年05月16日 * 版本号:v1.0 */ #include <iostream> using

《80X86汇编语言程序设计教程》十九 操作系统类指令与输入输出保护

1.  通常只在操作系统代码中使用,80386支持4个特权等级,操作系统指令也可分3种:实模式和任何特权级下可执行指令.实模式及特权级0下可执行的指令和仅在保护模式下执行的指令. 1)  实模式和任何特权级下可执行的指令 a)存储全局和中断描述符表寄存器指令 GDT与IDT整个系统各只有一张,它们的定位信息分别保存在GDTR与IDTR中,这两个寄存器的值可以被保存.须注意,LDT表示任务私有,存储LDTR值的指令不属于这一类. i)存储全局描述符表寄存器指令:SGDT  DST DST是48位(

第三十九讲:Android之AndroidManifest.xml文件中注册权限

积土而为山,积水而为海.--<荀子·儒效> 本讲内容:android权限详细 1 访问登记属性 android.permission.ACCESS_CHECKIN_PROPERTIES ,读取或写入登记check-in数据库属性表的权限 2 获取错略位置 android.permission.ACCESS_COARSE_LOCATION,通过WiFi或移动基站的方式获取用户错略的经纬度信息,定位精度大概误差在30~1500米 3 获取精确位置 android.permission.ACCESS

第五十九讲:四大组件之BroadcastReceiver(二)

没有任何动物比蚂蚁更勤奋,然而它却最沉默寡言. 本讲内容: Broadcast Receiver 广播接收者的使用 上一讲我们讲解了一个接收者来接收广播,如果有多个接收者都注册了相同的广播地址,又会是什么情况呢,这就涉及到普通广播和有序广播的概念了. 一.普通广播(Normal Broadcast) 普通广播对于多个接收者来说是完全异步的,通常每个接收者都无需等待即可以接收到广播,接收者相互之间不会有影响.对于这种广播,接收者无法终止广播,即无法阻止其他接收者的接收动作. 我们新建二个Broad

第十九讲:ListView与Adapter(一)

天将降大任于是人也,必先苦其心志,劳其筋骨,饿其体肤,空乏其身,行拂乱其所为.--<孟子·告子下> 本讲内容:ListView列表组件 与 Adapter适配器的用法 一.ListView列表组件: 作用:ListView通常有两个职责. (1)将数据填充到布局. (2)处理用户的选择点击等操作(通过绑定监听器). 创建一个ListView需要3个元素. (1)ListView展示每一列的View. (2)填入View的数据或者图片等. (3)连接数据与ListView的适配器. ListVi

第十九讲:ListView与Adapter(二)

会当凌绝顶,一览众山小. -- 杜  甫<望岳> 本讲内容:ListView列表组件 与 Adapter适配器的用法 一.ListView使用SimpleAdapter 很多时候需要在列表中展示一些除了文字以外的东西,比如图片等.这时候可以使用SimpleAdapter.可以通过它 使用simpleAdapter的数据一般都是用HashMap构成的列表,列表的每一节对应ListView的每一行.通过SimpleAdapter的构造函数,将HashMap的每个键的数据映射到布局文件中对应控件上.

第十九讲:职责链模式

public class CarBodyHandler extends CarHandler{ @Override public void HandlerCar() { // TODO Auto-generated method stub System.out.println("组装车身"); } } public abstract class CarHandler { public abstract void HandlerCar(); } public class CarHeadH

Scratch第四十九讲:完美的下落和反弹

做了很多小游戏,都会遇到碰撞和反弹的情况,CC哥大多时候也都是简单处理一下,包括之前的讲座也有提过,但是没有认真的讲解过.今天就专门为这个主题做一讲,把这部分内容彻底讲透,大家可以一起探讨一下. 是不是觉得很简单,就是一个小球落到地上再弹起来,但是让我们一起来过过这个下落反弹里面有多少坑. 1:匀速下坠 先来看下落,一个最简单的下落程序是这样的: 小球匀速下落,碰到黑线停下来.这肯定不完美,因为下坠应该是加速度的,而不是匀速的.v=at,这是加速度公式.所以下坠的每一步都随着时间的增加,下坠的距