前言
逆向水的一批,顶多反汇编看看有啥过程
正文
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; }
|
- 模式切换成release,选择x86
- 关闭优化
- 禁用安全检查
安全检查会增加不少的汇编代码,不利于常规分析。
然后随便打断点反汇编查看
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++类中的成员函数访问时定义的函数调用约定
- 寄存器ecx用来存放类的指针
- 参数依旧是从右往左入栈
- 堆栈平衡由被调用者负责恢复
类中的非静态成员函数都可以调用this指针,this指针本身就是实例化对象的地址通过ecx传给成员函数。
所以当类中成员函数访问其成员变量的时候,都是指针+偏移的形式访问。
而且不管你是否明确使用this->,编译器默认都是按照这个逻辑。
不过如果这个ecx被人逮到了,那里面的东西其实都可以通过便宜访问到了。
静态成员函数没有this指针
_cdecl
类的静态成员函数调用约定_cdecl
- 参数从右往左入栈
- 由调用者恢复堆栈平衡 – 就是这个函数结束的时候会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.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.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.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,没有自动调用构造,首先是因为没定义,其次就是本身就是空的构造。那么有没有意义都不大,就被编译器主动删掉了。
结语
还是有点糙,就是简单的分析