滴水逆向 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.继承本质
继承前的汇编
继承后
故此 继承的本质就是数据的复制 复制了一块成员过去,继承减少了代码的复用
父类指针指向子类
如图 父类指针指向子类对象时可以访问的内存区域 故此父类指针指向子类对象不会报安全问题
但是子类指针指向父类对象 内存访问区域会越界
多层继承 与 多重继承
多层继承 时 对应的指针指向的内存区域
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;
};
一个子类可以有多个父类,即多重继承
指针指向有区别
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();
}
}
(1.)单继承有无函数覆盖
1.无函数覆盖
2.有函数覆盖
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
