C++菱形继承逆向分析
- 实验环境:
- 操作系统: 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++菱形继承逆向分析相关推荐
- 菱形继承,多继承,虚继承、虚表的内存结构全面剖析(逆向分析基础)
// 声明:以下代码均在Win32_Sp3 VC6.0_DEBUG版中调试通过.. 在逆向还原代码的时候,必须得掌握了菱形继承,多继承,虚继承虚函数的内存虚表结构.所以,这篇文章献给正在学习C++ ...
- 继承和多态 3.0 -- 菱形继承
单继承和多继承 C++的继承方式是支持单继承和多继承的,首先看一下代码,分清单继承和多继承 单继承 class A { public:int _a; };class B :public A { pub ...
- 详解虚函数的实现过程之菱形继承(5)
大家看到标题,会不会菱形继承的虚表会不会是重复的呢?祖父类的虚表会不会在子类会不会是两份相同呢?那么我们一起来探索一下吧,冲冲冲!! 首先我们来分析一下: 它一共定义了四个类,分别为CFurnitur ...
- 安卓 sharedpreferences可以被其它activity读取_【安卓逆向】“一份礼物”之我要o泡逆向分析...
最近安卓手机有个恶搞程序"一份礼物"登上热搜. 该app的效果是打开之后手机便会以最大音量循环播放"我要o泡"音乐,并且无法调小音量,无法退出程序,甚至无法关机 ...
- 内存首地址为1000h_C++虚继承,菱形继承,内存分布
前言 在叙述C++虚继承之前,我先给大家抛出一个问题.例如现在有4个类,分别是class A, class B, class C, class D.它们的关系如下图. 如上如所示,class B和cl ...
- [系统安全] 六.逆向分析之条件语句和循环语句源码还原及流程控制
您可能之前看到过我写的类似文章,为什么还要重复撰写呢?只是想更好地帮助初学者了解病毒逆向分析和系统安全,更加成体系且不破坏之前的系列.因此,我重新开设了这个专栏,准备系统整理和深入学习系统安全.逆向分 ...
- [安全攻防进阶篇] 四.逆向分析之条件语句和循环语句源码还原及流程控制逆向
从2019年7月开始,我来到了一个陌生的专业--网络空间安全.初入安全领域,是非常痛苦和难受的,要学的东西太多.涉及面太广,但好在自己通过分享100篇"网络安全自学"系列文章,艰难 ...
- SpyNote5.0 Client_APK逆向分析
1. SpyNote5.0 是什么? SpyNote是用来创建Android恶意程序的工具.它的功能引人注目,读取联系人.录音.命令执行.应用管理.键盘记录.GPS定位等等.这些功能对于研究安Andr ...
- Android逆向分析案例——某点评APP登陆请求数据解密
今天,七夕,单身23载的程序汪,默默地写着博客~ 上一次的逆向分析案例中讲了如何去分析某酒店的APP登陆请求,为了进一步学习如何逆向分析以及学习其他公司的网络传输加解密,本次案例将继续就登陆请求的数据 ...
最新文章
- ORB-SLAM3中的3d-2d匹配
- Today:基于 Electron 和 Vue.js 的 GTD 应用
- goland 配置goroot找不到SDK
- RPC 和 RESTful
- TextView跑马灯效果
- PDF 与 Word互转工具。 在线的 和安装软件
- mysql遵循acid_关系型数据库遵循ACID规则
- 互联网公司的规律.txt
- 复旦nlp实验室 nlp-beginner 任务二:基于深度学习的文本分类
- CoolFire系列讲座 第3讲:如何连接ISP并且对其解码
- 移动应用开发学习笔记(一)
- QFIL and FASTBOOT
- TARA-Asset穷举
- ubuntu 设置虚拟内存 解决内存不足
- 用java如何画动物_用画小狗的方法来解释Java值传递
- android 分享文件功能实现
- 机器学习周志华——机器学习的应用领域
- keras yolo3 使用 CIOU Loss
- 评价类模型(层次分析法与模糊评价模型)
- SketchUp:SketchUp草图大师软件简介、安装、使用方法之详细攻略
热门文章
- python 异常点检测 cook距离_DLI 精选课程 | 三种AI方法检测网络、业务或设备异常状况...
- 繁体字_学认繁体字?你可能是低估了汉字的难度
- C语言丨定积分的近似计算
- linux部署python web项目 详细_linux下nginx+python+uwsgi部署总结(django+web.py)
- python面部颜色分析_Python图像处理之颜色的定义与使用分析
- java弱_Java 强、弱、软、虚,你属于哪一种?
- 创建oracle方法,简单的Oracle存储过程的创建方法
- html中在哪儿使用div,使用javascript在html中使用div
- 2018北语c语言程序2答案,北语21春《JAVA语言程序设计》作业2题目【标准答案】...
- Python datetime timedelta