滴水逆向 C++逆向基础

滴水c++ 逆向分析基础

1.封装
(1).结构体

1.结构体做参数

 23:     int x = Plus(s.a, s.b);
 00932103  mov         eax,dword ptr [ebp-8]  //参数
 00932106  push        eax  
 00932107  mov         ecx,dword ptr [ebp-0Ch]  //参数
 0093210A  push        ecx  
 0093210B  call        00931424  
 00932110  add         esp,8  
 00932113  mov         dword ptr [ebp-18h],eax  
 结构体创建汇编
 sclass s;
 007D227F  lea         ecx,[ebp-10h]  
 007D2282  call        007D1429  
 
 ebp-10 ->0000000a 0000000c
 结构体的成员在栈中开辟 传值也是与数组差不多

2.结构体传递指针参数

 int x = Plus(&s);
 00FA1F9D  lea         eax,[ebp-18h]  //传的是地址 由编译器进入查找值
 00FA1FA0  push        eax  
 00FA1FA1  call        00FA142E  //函数
 00FA1FA6  add         esp,4  
 00FA1FA9  mov         dword ptr [ebp-24h],eax  

3.函数在结构体内

 s.call();
 007B211D  lea         ecx,[ebp-18h]  
 007B2120  call        007B1267  
 40: s.callz();
 007B2125  lea         ecx,[ebp-18h]  
 007B2128  call        007B12EE
 //函数的call独立于外 由编译器分配

在c++中,类就是由结构体而来

(2).this指针

不进行汇编分析,this指针指向的就是存储着不同的成员,每一个对象都有着不同的this指针,需要用到对象成员的时候,会传this指针进去,从而找到成员

2.类的特性
(1).构造 析构
 //构造之谜
 sclass s;
 008B210F  lea         ecx,[ebp-18h]//给这个成员对象内的值赋值需要  
 008B2112  call        008B140B  //调用构造函数
 //步入
 sclass(){
 008B1E70  push        ebp  
 008B1E71  mov         ebp,esp  
 008B1E73  sub         esp,0CCh  
 008B1E79  push        ebx  
 008B1E7A  push        esi  
 008B1E7B  push        edi  
 008B1E7C  push        ecx  
 008B1E7D  lea         edi,[ebp-0Ch]  
 008B1E80  mov         ecx,3  
 008B1E85  mov         eax,0CCCCCCCCh  
 008B1E8A  rep stos    dword ptr es:[edi]  
 008B1E8C  pop         ecx  
 008B1E8D  mov         dword ptr [ebp-8],ecx  
 008B1E90  mov         ecx,8BC038h  
 008B1E95  call        008B135C  
     11: printf("啊哈哈");
 008B1E9A  push        8B7C30h  
 008B1E9F  call        008B1410  
 008B1EA4  add         esp,4  
     12: }

析构 由编译器检测到栈中开辟的成员被销毁时自动调用

3.继承本质
a

继承前的汇编

继承后

z

故此 继承的本质就是数据的复制 复制了一块成员过去,继承减少了代码的复用

父类指针指向子类

 

q

如图 父类指针指向子类对象时可以访问的内存区域 故此父类指针指向子类对象不会报安全问题

但是子类指针指向父类对象 内存访问区域会越界

多层继承 与 多重继承

m

多层继承 时 对应的指针指向的内存区域

1.当有同名成员变量的时候 是被隐藏不是被覆盖

 struct X
 {
  int a;
  int b;
 };
 struct Y :X
 {
  int a;
  int d;
 };
 struct Z :Y
 {
  int e;
  int f;
 };//A B C D 16 按推测继承 a b c d->但是是24 6个变量
 void test()
 {
  Z z;
 
  z.X::a = 1;
  z.b = 2;
  z.Y::a = 3;
  z.d = 4;
  z.e = 5;
  z.f = 6;
 
  printf("%d\n", sizeof(z));
 }
 
 
多重继承
     多重继承:       
 
  struct X
  {
  int a;
  int b;
  };
  struct Y
  {
  int c;
  int d;
  };
  struct Z:X,Y
  {
  int e;
  int f;
  };
 

一个子类可以有多个父类,即多重继承

QQ截图20230607211638

指针指向有区别

4.访问权限

public的意思是,这个成员哪里都可以用,不用担心被修改,所以,一旦发布成public的成员,是不能够改名字的.

private的意思是,这个成员只用于内部使用,不要在其他的地方使用.

总结: 1、对外提供的函数或者变量,发布成public的 但不能随意改动. 2、可能会变动的函数或者变量,定义成private的 这样编译器会在使用的时候做检测. 3、只有结构体内部的函数才可以访问private的成员. 4、public/private可以修饰函数也可以修饰变量.

private的强制访问

     struct Test         
  {
  private:
  int x;
  public:
  int y;
  void Init(int x,int y)
  {
  this->x = x;
  this->y = y;
  }
  };
  Test t;
  t.Init(1,2);
  int* p = (int*)&t; //取this指针 内是成员变量的值
  int n = *p; //改第一个成员变量的值
  int m = *(p+1); //改第2个成员变量的值
  printf("%d %d\n",n,m);

私有继承

 class Base
 {
 public:
  int x;
  int y;
 };
 class Sub :private Base
 {
 public:
  int a;
  int b;
 };
 
 void test()
 {
  Sub z; //不能访问
  z.x;
  printf("%d\n", sizeof(z));//16 -4个成员变量
 }
 this 指针
     00000001 00000001 00000001 00000001
     也是给私有变量给了空间进行存储,虽然编译器不允许我们访问

总结: 1、父类中的私有成员是会被继承的 2、只是编译器不允许直接进行访问

4.虚函数表
(1).虚函数在汇编中的调用
 Base s;
 00A5225F 8D 4D F4             lea         ecx,[ebp-0Ch]  
 00A52262 E8 82 F0 FF FF       call        00A512E9  
     26: s.Function_1();
 00A52267 8D 4D F4             lea         ecx,[ebp-0Ch]  
 00A5226A E8 AC F0 FF FF       call        00A5131B  
     27: s.Function_2();
 00A5226F 8D 4D F4             lea         ecx,[ebp-0Ch]  
 00A52272 E8 3F F1 FF FF       call        00A513B6  //直接call
 
   // 通过指针调用时
  s1->Function_1();
 00A522B6 8B 4D E8             mov         ecx,dword ptr [ebp-18h]  
 00A522B9 E8 5D F0 FF FF       call        00A5131B  
 31: s1->Function_2();
 00A522BE 8B 45 E8             mov         eax,dword ptr [ebp-18h]  
 00A522C1 8B 10                mov         edx,dword ptr [eax]  
 00A522C3 8B F4                mov         esi,esp  
 00A522C5 8B 4D E8             mov         ecx,dword ptr [ebp-18h]  
 00A522C8 8B 02                mov         eax,dword ptr [edx]  
 00A522CA FF D0                call        eax //FF 间接call
 1、通过对象调用时,virtual函数与普通函数都是E8 Call               
 
 2、通过指针调用时,virtual函数是FF Call,也就是间接Call
(2).虚函数表分析

当类中有虚函数时 ,会有四个字节的大小 当我们继续往里面写虚函数不会增加

分析虚函数表

环境:一个类中有多个虚函数

 this指针->0x01549B58->007e8ba0//虚函数表
 007e8ba0->007e14ce 007e14d3 007e14d8//对应的函数位置
 //汇编
 007E217E  mov         eax,dword ptr [edx]  
 007E2180  call        eax  //调用第一个test1
 
 eax=0x007E14CE
   
 //刚好对上

故此多出的属性是一个地址,指向一张表,里面存储了所有虚函数的地址

编写代码调用所有虚函数

 void test()
 {
  Base *zs=new Base;
  printf("base 的虚函数表地址为:%x\n", *(int*)zs);
  //通过函数指针调用函数,验证正确性
  typedef void(*pFunction)(void);
  pFunction pFn;
  for (int i = 0; i < 3; i++)
  {
  int temp = *((int*)(*(int*)zs) + i);
  pFn = (pFunction)temp;
  pFn();
  }
 }

QQ截图20230608154313

(1.)单继承有无函数覆盖

1.无函数覆盖

QQ截图20230608154953

2.有函数覆盖

QQ截图20230608155055

base内的fun1 fun2 被sub的虚函数覆盖

(2).多继承有无函数覆盖

无函数覆盖

class1 ->继承class2 class3 会生成8字节 两张虚函数表

class1 ->继承class2 class3 class4 会生成12字节 三张虚函数表

先继承谁 谁就与他生成一张虚表 随后的就再生成一张 如果再次继承 那么就再次生成虚表

有函数覆盖

先继承谁就与谁生成一张虚表哦,若先继承的表有函数覆盖,则函数覆盖的那个虚函数与下一个继续生成虚

(5).绑定

绑定就是将函数调用与地址关联起来.

2、什么是前期绑定?什么是后期绑定、动态绑定、运行期绑定?

     50:     pb->Function_1();       //函数调用  
 00DD20CF  mov         ecx,dword ptr [ebp+8]  
 00DD20D2  call        00DD146F  //前期绑定
     51:
     52: pb->Function_2();
 00DD20D7  mov         eax,dword ptr [ebp+8]  
 00DD20DA  mov         edx,dword ptr [eax]  
 00DD20DC  mov         esi,esp  
 00DD20DE  mov         ecx,dword ptr [ebp+8]  
 00DD20E1  mov         eax,dword ptr [edx]  
 00DD20E3  call        eax  //后期绑定
 00DD20E5  cmp         esi,esp  

//体现出了不同的行为 称为多态

 1、只有virtual的函数是动态绑定.                        
 2、动态绑定还有一个名字:多态
6.算法

冒泡排序 -折半查找 (省)

7.模板

模版是代码的复制

8.引用

在汇编中无差别

 1、引用类型是C++里面的类型                             
 
 2、引用类型只能赋值一次,不能重新赋值
 
 3、引用只是变量的一个别名.
 
 4、引用可以理解成是编译器维护的一个指针,但并不占用空间(如何去理解这句话?).
 
 5、使用引用可以像指针那样去访问、修改对象的内容,但更加安全.
9.友元

可以访问私有的变量 私有的本身就能强访 声明友元函数使得它可以访问

友元函数是不能被继承的,就像父亲的朋友未必是儿子的朋友

10.运算符重载

实质上是函数的替换 详细见博客函数重载


笔记下载 https://kxd.lanzoul.com/idr0H0ym89pe


posted @ 2023-06-06 14:50  大橘|博客  阅读(344)  评论(0)    收藏  举报