zoukankan      html  css  js  c++  java
  • 《C++反汇编与逆向分析技术揭秘》之十——构造函数

    对象生成时会自动调用构造函数。只要找到了定义对象的地方,就找到了构造函数调用的时机。不同作用域的对象的生命周期不同,如局部对象、全局对象、静态对象等的生命周期各不相同,只要知道了对象的生命周期,便可以推断出构造函数的调用时机

    • 局部对象

    反汇编:

    获取对象首地址并调用构造函数:

    对象的地址为:

    进入构造函数,先是push一堆寄存器:

     

    还原ecx寄存器,并初始化:

     构造函数属于成员函数,在调用时要用到this指针。

    如何识别?1、构造函数时这个对象在作用域内调用的第一个成员函数,根据this指针可以区分每个对象;2、返回this指针是构造函数的特征之一(这是识别局部变量构造函数的必要条件)

    • 堆对象

    堆对象的识别重点在于识别堆空间的申请与使用

    举例:

    new以及new的大小:

     返回的地址:

    调用构造函数:

    先判断new是否从成功,如果失败就跳过构造函数:

    执行构造函数初始化对象:

    定位到对象的第一个成员变量,并赋值为0xb:

    new对象返回对象的首地址时,也检查了是否为NULL,进行了一次判断,如果失败就跳过构造函数,所以可以从这一点入手,找到堆对象的构造函数

     考虑下我们这里有两个成员变量的情况:

    new的大小变了:

    给第二个成员变量赋值时的定位变了:

    • 参数对象

    当对象作为函数参数时,调用一个特殊的构造函数——拷贝构造函数。拷贝构造函数只有一个参数,即为对象的引用

    例子如下:

     1、初始化一个MyString对象,先调用了一个autoclassinit函数:

    类对象的地址为:

    构造函数内部在初始化成员m_pString时调用了memset函数:

    这里的ecx就是类对象的首地址。

    2、调用构造函数:

    ecx保存的是类对象,给成员赋值:

    3、调用成员函数SetString:

    里边会先获取字符串长度:

    然后再去拷贝:

    参数:

    返回:

     4、Show(MyString)利用了拷贝构造函数(本小节重点)

    先调用一个拷贝构造函数,拷贝MyString。即一个新的CMyString对象调用拷贝构造函数,这个拷贝构造函数的参数是前面的MyString对象。

    拷贝构造函数的调用,且新的对象的地址就是esp的值

    拷贝构造函数中,会用this作为返回值

    其中ecx是新的CMyString对象的首地址:

    eax是前面定义的、这里被拷贝的MyString对象的地址:

    返回的是生成的新对象的地址,也是esp的值:

    调用完拷贝构造函数后紧接着调用Show函数,并没有push操作来压入参数:

     

    其参数就是新返回的对象的成员(这里我们发现,刚刚返回的新的CMyString的地址就是esp的值):

    验证:

    如果CMyString中有两个成员变量,那么在给Show传参的时候,就压入了两个数据:

    传给Show的参数其实并不是通过push操作压入的参数,而是通过拷贝构造函数帮忙压入的。因为拷贝构造函数新构造的那个对象的地址就是esp所指向的位置,所以拷贝构造函数执行完成之后生成的数据就直接放在了栈顶位置

    • 返回对象 

    返回时需要对返回对象进行拷贝,因此同样会使用到拷贝构造函数

    举例:

     除了那个autoclassInit函数之外,其实就调用了GetMyString这么一个函数:

     那么可以说明给MyString对象赋值的情况是在GetMyString中完成的。而我们可以知道,GetMyString中并没有刻意实现给MyString赋值的功能,那么究竟是如何做到的呢?

    GetMyString()并没有参数,但是还是push了一个eax,这个eax是由autoclassInit函数返回的仅仅是一个地址,也就是MyString的地址(但是没有调用MyString的构造函数进行初始化):

    随后以这个eax为参数调用了GetMyString,进入其中我们来到return MyString的部分,这里调用了一个拷贝构造函数:

    寄存器的值为:

    也就是说,是函数外面创建的那个对象,复制了函数内部的临时的对象。并返回12FF5C:

    这里我们可以看出,并不是GetMyString得到一个对象后,再去拷贝给某个定义好的等待接收的变量,而是在GetMyString过程中,就给main中的MyString变量构建好了(借助传递地址参数)

    • 全局对象与静态对象

     我们必须清楚的知道全局对象与静态对象构造的时机。所有的全局对象都会在同一地点调用构造函数进行初始化,即_cinit函数。_cinit的_initterm函数中逐一初始化了全局对象。

    定位方法一——直接定位法:mainCRTStartup->_cinit->_initterm->构造代理函数

    定位方法二——利用栈回溯:全局对象的地址固定,可以先对这些数据下读写断点。

    定位方法三——定位atexit:对atexit下断点。

    进入:

    先找到:

    在下面这个循环中,会调用构造代理函数:

     进入这个call eax函数中,跟踪找到了一个构造函数,但并不一定是我们定义的那全局对象的构造函数:

     继续找,找到第一个全局对象,一般先是调用构造函数再有一个注册析构函数

    构造函数内部:

    构建时先是初始化一块地址:

    返回值:

     调用了带一个参数的构造函数:

    其中赋值:

    继续找找到第二个全局对象:

    考虑,如果是一个全局变量数组,那么构造函数是如何被调用的呢?比如:

    那么不会是分别调用三次call eax函数,每次构造一个对象。而是调用一次call eax函数,里边调用一个构造代理函数来分别构建三个对象:

    进入构造代理函数内部,发现下面的循环逐一调用构造函数:

    其逻辑类似于书上P249所述。

    •  每个对象都有默认构造函数吗

    举例:

    这里不会为CMyString提供默认的构造函数:

    这里采用的做法是给ecx寄存器传入一个地址,给SetInt传入一个参数,然后把这个参数的值写入对应的地址:

    但是如果你在类中定义了构造函数,哪怕是什么也没做,也会调用构造函数的,因为你一旦自己定义了一个构造函数,就不会被提供任何默认构造函数了

    所以我们一直要讨论的,就是没有提供构造函数的情况下,编译器是否会给你提供默认构造函数。

    以下几种情况会提供默认的构造函数:

    1、本类中存在虚函数

    默认的构造函数里进行了虚表的初始化操作:

    虚表中的内容如下:

    2、本类中定义的成员对象有虚函数

    如果类的成员对象只是普通的成员函数,则该类没有构造函数:

    但是如果成员对象由虚函数:

    构造函数内部调用了成员对象的构造函数,其目的显然是为了初始化虚表

    3、父类中存在虚函数(道理同上)

     4、父类中定义的对象带有构造函数

    5、本类中定义的对象带有构造函数

  • 相关阅读:
    HDU 5585 Numbers
    HDU 3308 LCIS
    POJ 2991 Crane
    POJ 1436 Horizontally Visible Segments
    POJ 3667 Hotel
    HaiHongOJ 1003 God Wang
    【SDOI 2008】 递归数列
    5月19日省中提高组题解
    【HDU 1588】 Gauss Fibonacci
    【POJ 3233】Matrix Power Series
  • 原文地址:https://www.cnblogs.com/predator-wang/p/8031071.html
Copyright © 2011-2022 走看看