前言

逆向水的一批,顶多反汇编看看有啥过程


正文

x64虽然快点,但是老的还是从x86开始


类的函数调用约定this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<iostream>

class Box{
int hp;
public:
int Add(int a, int b){
return hp + a + b;
}
};

int main(){

Box b1;
b1.Add(100, 200);

return 0;
}
  1. 模式切换成release,选择x86
  2. 关闭优化
  3. 禁用安全检查

安全检查会增加不少的汇编代码,不利于常规分析。

然后随便打断点反汇编查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(){
003A1020 55 push ebp
003A1021 8B EC mov ebp,esp
003A1023 51 push ecx

Box b1;
b1.Add(100, 200);
003A1024 68 C8 00 00 00 push 0C8h
003A1029 6A 64 push 64h
003A102B 8D 4D FC lea ecx,[ebp-4]
003A102E E8 CD FF FF FF call 003A1000

return 0;
003A1033 33 C0 xor eax,eax
}
003A1035 8B E5 mov esp,ebp
003A1037 5D pop ebp
003A1038 C3 ret

可以看到精简了很多。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Box{
int hp;
public:
int Add(int a, int b){
003A1002 EC in al,dx
003A1003 51 push ecx
003A1004 89 4D FC mov dword ptr [ebp-4],ecx
return hp + a + b;
003A1007 8B 45 FC mov eax,dword ptr [ebp-4]
003A100A 8B 00 mov eax,dword ptr [eax]
003A100C 03 45 08 add eax,dword ptr [ebp+8]
003A100F 03 45 0C add eax,dword ptr [ebp+0Ch]
}
003A1012 8B E5 mov esp,ebp
003A1014 5D pop ebp
003A1015 C2 08 00 ret 8

至于显示符号名这个可开可不开,都是便于理解的东西

1
2
3
4
5
6
	Box b1;
b1.Add(100, 200);
003A1024 68 C8 00 00 00 push 0C8h
003A1029 6A 64 push 64h
003A102B 8D 4D FC lea ecx,[ebp-4]
003A102E E8 CD FF FF FF call 003A1000

调用b1.Add的时候也是从右至左把参数压入栈中。
lea指令就是把地址传给寄存器,那么这里ebp-4自然就是实例化对象b1的内存地址。
call 003a1000 就是调用函数,这里也就调用了.Add函数。

直接将地址copy进来,跳转,自然就是Box::Add的位置了。

反汇编里,看起来成员函数的调用传参都和正常函数没差。
因为要看this所以把符号名打开了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Box{
int hp;
public:
int Add(int a, int b){
003A1000 55 push ebp
003A1001 8B EC mov ebp,esp
003A1003 51 push ecx
003A1004 89 4D FC mov dword ptr [this],ecx
return hp + a + b;
003A1007 8B 45 FC mov eax,dword ptr [this]
003A100A 8B 00 mov eax,dword ptr [eax]
003A100C 03 45 08 add eax,dword ptr [a]
003A100F 03 45 0C add eax,dword ptr [b]
}
003A1012 8B E5 mov esp,ebp
003A1014 5D pop ebp
003A1015 C2 08 00 ret 8

函数入栈ebp跟随上来,然后esp跟ebp持平保持栈平衡
然后调用了一个ecx寄存器,又将ecx的值传给this指针,其实就是ebp-4 实例化 b1的地址,它的内存上也就一个hp。
然后将this指针的值传给eax寄存器,后面这个把eax的值又赋给eax其实有点迷惑操作。
可能先前是为了获取这个对象的地址,然后再取出hp的值,如果多个成员估计就能看出区别了。
按照这种道理,其实就是this->hp+a+b。先通过实例化对象,再去找它需要的成员。

后面的两个add就没啥问题,就是hp+a+b顺序执行。

不过按照这个反汇编,倒是看到,在类中调用自身成员其实都是通过this->获取
即使代码上不加,反汇编的时候还是遵循这个过程。
所以之前加上倒是便于理解了。


_thiscall

_thiscall是c++类中的成员函数访问时定义的函数调用约定

  1. 寄存器ecx用来存放类的指针
  2. 参数依旧是从右往左入栈
  3. 堆栈平衡由被调用者负责恢复

类中的非静态成员函数都可以调用this指针,this指针本身就是实例化对象的地址通过ecx传给成员函数。
所以当类中成员函数访问其成员变量的时候,都是指针+偏移的形式访问。
而且不管你是否明确使用this->,编译器默认都是按照这个逻辑。

不过如果这个ecx被人逮到了,那里面的东西其实都可以通过便宜访问到了。


静态成员函数没有this指针

_cdecl

类的静态成员函数调用约定_cdecl

  1. 参数从右往左入栈
  2. 由调用者恢复堆栈平衡 – 就是这个函数结束的时候会add esp,8 当然数值不固定由内存决定

前面也说了,静态成员函数本质上就是一个普通的函数,他不会随着对象实例化而传递。
因此他也不会有this指针这种东西,没有了this指针也就注定它不能访问类中的成员变量
而静态成员变量能访问是因为静态变量本质上也是一个全局的变量,内存地址固定,与类实例化后互不影响。
所以静态成员可以通过作用域方式访问到:类::静态成员变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>

class Box{
int hp;
public:
int Add(int a, int b){
return hp + a + b;
}

static int test(int a,int b){
return a + b;
}
};

int main(){

Box b1;
//b1.Add(100, 200);

b1.test(100,200);

return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int main(){
00EA1010 55 push ebp
00EA1011 8B EC mov ebp,esp

Box b1;
//b1.Add(100, 200);

b1.test(100,200);
00EA1013 68 C8 00 00 00 push 0C8h
00EA1018 6A 64 push 64h
00EA101A E8 E1 FF FF FF call Box::test (0EA1000h)
00EA101F 83 C4 08 add esp,8

return 0;
00EA1022 33 C0 xor eax,eax
}
00EA1024 5D pop ebp
00EA1025 C3 ret

参数入栈没啥毛病,都是从右往左,call的时候也是正常跳转函数
然后函数结束完后主动恢复栈平衡add esp,8
但是会注意到明明是b1调用的,但是却没有出现ecx。

1
2
3
4
5
6
7
8
	static int test(int a,int b){
00EA1002 EC in al,dx
return a + b;
00EA1003 8B 45 08 mov eax,dword ptr [a]
00EA1006 03 45 0C add eax,dword ptr [b]
}
00EA1009 5D pop ebp
00EA100A C3 ret

至于这个函数本身是没调用类的成员,也没有出现ecx。

所以说当你用b1调用静态成员函数和直接作用域调用是一样的。它没有this指针。

所以静态成员函数不能访问类的非静态成员变量


类是否一定有构造函数

刚学类的时候就说了,类默认都会有一个构造函数和析构函数,如果没定义,那就是自动生成一个空的构造函数和析构函数。

至于在编译器编译的时候,为什么没看到。

1
2
3
4
5
6
7
8
	Box b1;
//b1.Add(100, 200);

b1.test(100,200);
00EA1013 68 C8 00 00 00 push 0C8h
00EA1018 6A 64 push 64h
00EA101A E8 E1 FF FF FF call Box::test (0EA1000h)
00EA101F 83 C4 08 add esp,8

就拿这个来说,实例化对象b1,没有自动调用构造,首先是因为没定义,其次就是本身就是空的构造。那么有没有意义都不大,就被编译器主动删掉了。


结语

还是有点糙,就是简单的分析