• 实验环境:

    • 操作系统: Windows XP Professional Service Pack 3
    • 集成开发环境: Microsoft Visual C++ 6.0
    • 构建版本: Debug版本
  • 既然要讨论菱形继承, 那么就要先说说为什么会出现菱形继承, 看下面代码:
    •  1 #include <iostream>
       2
       3 // Subject class
       4 class Subject
       5 {
       6 protected:
       7   // subject id
       8   unsigned int id;
       9 public:
      10   Subject() { std::cout << "Subject constructor" << std::endl; }
      11   ~Subject() { std::cout << "Subject destructor" << std::endl; }
      12 };
      13
      14 // Programming class
      15 class Programming : public Subject
      16 {
      17 public:
      18   Programming() { std::cout << "Programming constructor" << std::endl; }
      19   ~Programming() { std::cout << "Programming destructor" << std::endl; }
      20
      21   void setProgrammingId(unsigned int id) { this->id = id; }
      22   unsigned int getProgrammingId() { return this->id; }
      23 };
      24
      25 // Math class
      26 class Math : public Subject
      27 {
      28 public:
      29   Math() { std::cout << "Math constructor" << std::endl; };
      30   ~Math() { std::cout << "Math destructor" << std::endl; }
      31
      32   void setMathId(unsigned int id) { this->id = id; }
      33   unsigned int getMathId() { return this->id; }
      34 };
      35
      36 // Assembly class
      37 class Assembly : public Programming, public Math
      38 {
      39 public:
      40   Assembly() { std::cout << "Assembly constructor" << std::endl; }
      41   ~Assembly() { std::cout << "Assembly destructor" << std::endl; }
      42
      43   // void setAssemblyId(unsigned int id) { this->id = id; }
      44   // unsigned int getAssemblyId() { return this->id; }
      45 };
      46
      47 int main(int argc, char **argv, const char **envp)
      48 {
      49   // create a Assembly instance
      50   Assembly obj;
      51
      52   obj.setProgrammingId(0);
      53   obj.setMathId(1);
      54
      55   std::cout << obj.getProgrammingId() << std::endl;
      56   std::cout << obj.getMathId() << std::endl;
      57
      58   // obj.setAssemblyId(2);
      59
      60   // std::cout << obj.getAssemblyId() << std::endl;
      61
      62     return 0;
      63 }

    • 注释掉的部分是带有二义性的代码, 编译期间不能通过.这几个类之间的结构图如下:
    • 也就是说如果创建一个Assembly类的实例, 那么这个实例中就会存在2个id成员, 如果要使用这个id的时候不能简单的this->id, 因为如果不明确的指出到底是Programming类的id还是Math类的id, 那么编译器就会报二义性错误.使用的时候必须this->Programming::id或者this->Math::id显式指明.这很麻烦, 而且有的时候我们并不需要在内存中保存2个id, 所以就有了虚继承.
    • 通过反汇编上述代码也可以发现, 这2个id是不同的(排除了无关代码):
    • 55:     std::cout << obj.getProgrammingId() << std::endl;
      ; 用寄存器传参的方式传入this指针, 该this指针指向父类的Programming部分
      00401645   lea         ecx,[ebp-14h]
      ; 调用getProgrammingId函数
      00401648   call        @ILT+240(Programming::getProgrammingId) (004010f5)

    • 22:     unsigned int getProgrammingId() { return this->id; }
      ; 将this指针存放到ecx中
      00401749   pop         ecx
      ; 将this指针存放到[ebp-4]中
      0040174A   mov         dword ptr [ebp-4],ecx
      ; 将this指针存放到eax中
      0040174D   mov         eax,dword ptr [ebp-4]
      ; 将this指针指向的1个双字存放到eax中, 此处即为Programming部分的id
      00401750   mov         eax,dword ptr [eax]

    • getMathId函数部分也是类似的, 只不过this指针为[ebp -18h]. 可以看出, 这2个id, 由于this指针指向的是不同的部分, 所以id也是不同的.
    • 接下来看下使用菱形继承的代码:
    •  1 #include <iostream>
       2
       3 // Subject class
       4 class Subject
       5 {
       6 protected:
       7   // subject id
       8   unsigned int id;
       9 public:
      10   Subject() { std::cout << "Subject constructor" << std::endl; }
      11   ~Subject() { std::cout << "Subject destructor" << std::endl; }
      12 };
      13
      14 // Programming class
      15 class Programming : virtual public Subject
      16 {
      17 public:
      18   Programming() { std::cout << "Programming constructor" << std::endl; }
      19   ~Programming() { std::cout << "Programming destructor" << std::endl; }
      20
      21   void setProgrammingId(unsigned int id) { this->id = id; }
      22   unsigned int getProgrammingId() { return this->id; }
      23 };
      24
      25 // Math class
      26 class Math : virtual public Subject
      27 {
      28 public:
      29   Math() { std::cout << "Math constructor" << std::endl; };
      30   ~Math() { std::cout << "Math destructor" << std::endl; }
      31
      32   void setMathId(unsigned int id) { this->id = id; }
      33   unsigned int getMathId() { return this->id; }
      34 };
      35
      36 // Assembly class
      37 class Assembly : public Programming, public Math
      38 {
      39 public:
      40   Assembly() { std::cout << "Assembly constructor" << std::endl; }
      41   ~Assembly() { std::cout << "Assembly destructor" << std::endl; }
      42
      43   // void setAssemblyId(unsigned int id) { this->id = id; }
      44   // unsigned int getAssemblyId() { return this->id; }
      45 };
      46
      47 int main(int argc, char **argv, const char **envp)
      48 {
      49   // create a Assembly instance
      50   Assembly obj;
      51
      52   obj.setProgrammingId(0);
      53   obj.setMathId(1);
      54
      55   std::cout << obj.getProgrammingId() << std::endl;
      56   std::cout << obj.getMathId() << std::endl;
      57
      58   // obj.setAssemblyId(2);
      59
      60   // std::cout << obj.getAssemblyId() << std::endl;
      61
      62     return 0;
      63 }

    • 整个代码几乎和上一个代码一模一样, 只是15行和26行都多了个virtual关键字, 这样就使用的是虚继承. 至于为什么要在父类上写virtual关键字, 是因为, Assembly之所以会有2个id, 是因为它继承的是有公共父类的2个类, 所以它只是果, 而不是因, 要杜绝这种重复现象, 应该从因入手, 而不是从果, 所以virtual关键字加在父类的继承关系上.
    • 下面再来看看, 用了虚继承之后的汇编代码有什么不同:
    • 22:     unsigned int getProgrammingId() { return this->id; }
      ; 将this指针存放到ecx中
      00401759   pop         ecx
      ; 将this指针存放到[ebp - 4]中
      0040175A   mov         dword ptr [ebp-4],ecx
      ; 将this指针存放到eax中
      0040175D   mov         eax,dword ptr [ebp-4]
      ; 将this指针指向的1个双字存放到ecx中
      00401760   mov         ecx,dword ptr [eax]
      ; 将[ecx + 4]的内从容存放到edx中
      00401762   mov         edx,dword ptr [ecx+4]
      ; 将[ebp - 4]中的this指针存放到eax中
      00401765   mov         eax,dword ptr [ebp-4]
      ; 将[eax + edx]的内容存放到eax中
      00401768   mov         eax,dword ptr [eax+edx]

    • 这里可能会有疑惑, this指针的首地址中的第一个双字存放的是什么, 这里存放了一个地址, 查看这个地址的数据, 可以看到, 在偏移+4的地方存放了一个双字的0x8, 这个其实是父类的成员相对于当前this指针的偏移值, 也就是说Programming部分的首地址比父类的id成员地址小8个字节.所以eax + edx就是父类的id成员的地址, 取出放到eax中. 这里的id成员的地址是0x0012FF70.
    • 可以顺便看看getMathId的反汇编代码:
    • 33:     unsigned int getMathId() { return this->id; }
      ; 代码解释同上
      004017EA   mov         dword ptr [ebp-4],ecx
      004017ED   mov         eax,dword ptr [ebp-4]
      004017F0   mov         ecx,dword ptr [eax]
      004017F2   mov         edx,dword ptr [ecx+4]
      004017F5   mov         eax,dword ptr [ebp-4]
      004017F8   mov         eax,dword ptr [eax+edx]

    • 看下此时的内存数据图: 可以看到, 大体结构与上面getProgrammingId没有区别, 再到相应的地址看看, 会发现, 这里的偏移值是0x4, 而不是上面的0x8, 原因是0x0012FF6C + 0x4 == 0x0012FF68 + 0x8 == 0x0012FF70, 看到这里明白了吧? 就是说, 内存中只有一份id的地址, 但是在每一个具有公共父类的直接父类部分的this指针的首部都保留了一个指针, 这个指针偏移0x4处存放了一个偏移量, 这个偏移量就是当前this指针与父类成员的偏移值, 所以, 每个部分根据不同的this指针和不同的偏移值, 就可以访问同一个共同父类部分的成员了.
  • 如果错误, 欢迎指正, 谢谢.

转载于:https://www.cnblogs.com/fishbool/p/6735471.html

C++菱形继承逆向分析相关推荐

  1. 菱形继承,多继承,虚继承、虚表的内存结构全面剖析(逆向分析基础)

    // 声明:以下代码均在Win32_Sp3   VC6.0_DEBUG版中调试通过.. 在逆向还原代码的时候,必须得掌握了菱形继承,多继承,虚继承虚函数的内存虚表结构.所以,这篇文章献给正在学习C++ ...

  2. 继承和多态 3.0 -- 菱形继承

    单继承和多继承 C++的继承方式是支持单继承和多继承的,首先看一下代码,分清单继承和多继承 单继承 class A { public:int _a; };class B :public A { pub ...

  3. 详解虚函数的实现过程之菱形继承(5)

    大家看到标题,会不会菱形继承的虚表会不会是重复的呢?祖父类的虚表会不会在子类会不会是两份相同呢?那么我们一起来探索一下吧,冲冲冲!! 首先我们来分析一下: 它一共定义了四个类,分别为CFurnitur ...

  4. 安卓 sharedpreferences可以被其它activity读取_【安卓逆向】“一份礼物”之我要o泡逆向分析...

    最近安卓手机有个恶搞程序"一份礼物"登上热搜. 该app的效果是打开之后手机便会以最大音量循环播放"我要o泡"音乐,并且无法调小音量,无法退出程序,甚至无法关机 ...

  5. 内存首地址为1000h_C++虚继承,菱形继承,内存分布

    前言 在叙述C++虚继承之前,我先给大家抛出一个问题.例如现在有4个类,分别是class A, class B, class C, class D.它们的关系如下图. 如上如所示,class B和cl ...

  6. [系统安全] 六.逆向分析之条件语句和循环语句源码还原及流程控制

    您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列.因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全.逆向分 ...

  7. [安全攻防进阶篇] 四.逆向分析之条件语句和循环语句源码还原及流程控制逆向

    从2019年7月开始,我来到了一个陌生的专业--网络空间安全.初入安全领域,是非常痛苦和难受的,要学的东西太多.涉及面太广,但好在自己通过分享100篇"网络安全自学"系列文章,艰难 ...

  8. SpyNote5.0 Client_APK逆向分析

    1. SpyNote5.0 是什么? SpyNote是用来创建Android恶意程序的工具.它的功能引人注目,读取联系人.录音.命令执行.应用管理.键盘记录.GPS定位等等.这些功能对于研究安Andr ...

  9. Android逆向分析案例——某点评APP登陆请求数据解密

    今天,七夕,单身23载的程序汪,默默地写着博客~ 上一次的逆向分析案例中讲了如何去分析某酒店的APP登陆请求,为了进一步学习如何逆向分析以及学习其他公司的网络传输加解密,本次案例将继续就登陆请求的数据 ...

最新文章

  1. ORB-SLAM3中的3d-2d匹配
  2. Today:基于 Electron 和 Vue.js 的 GTD 应用
  3. goland 配置goroot找不到SDK
  4. RPC 和 RESTful
  5. TextView跑马灯效果
  6. PDF 与 Word互转工具。 在线的 和安装软件
  7. mysql遵循acid_关系型数据库遵循ACID规则
  8. 互联网公司的规律.txt
  9. 复旦nlp实验室 nlp-beginner 任务二:基于深度学习的文本分类
  10. CoolFire系列讲座 第3讲:如何连接ISP并且对其解码
  11. 移动应用开发学习笔记(一)
  12. QFIL and FASTBOOT
  13. TARA-Asset穷举
  14. ubuntu 设置虚拟内存 解决内存不足
  15. 用java如何画动物_用画小狗的方法来解释Java值传递
  16. android 分享文件功能实现
  17. 机器学习周志华——机器学习的应用领域
  18. keras yolo3 使用 CIOU Loss
  19. 评价类模型(层次分析法与模糊评价模型)
  20. SketchUp:SketchUp草图大师软件简介、安装、使用方法之详细攻略

热门文章

  1. python 异常点检测 cook距离_DLI 精选课程 | 三种AI方法检测网络、业务或设备异常状况...
  2. 繁体字_学认繁体字?你可能是低估了汉字的难度
  3. C语言丨定积分的近似计算
  4. linux部署python web项目 详细_linux下nginx+python+uwsgi部署总结(django+web.py)
  5. python面部颜色分析_Python图像处理之颜色的定义与使用分析
  6. java弱_Java 强、弱、软、虚,你属于哪一种?
  7. 创建oracle方法,简单的Oracle存储过程的创建方法
  8. html中在哪儿使用div,使用javascript在html中使用div
  9. 2018北语c语言程序2答案,北语21春《JAVA语言程序设计》作业2题目【标准答案】...
  10. Python datetime timedelta