
一、单类继承
在父类中声明为私有的成员,子类对象无法直接访问,但是在子类对象的内存结构中,父类私有的成员数据依然存在。C++语法规定的访问限制仅限于编译层面,在编译过程中进行语法检查,因此访问控制不会影响对象的内存结构。
子类未提供构造函数或析构函数,而父类却需要构造函数与析构函数时,编译器会为子类提供默认的构造函数与析构函数。但是子类有构造函数,而父类不存在构造函数,且没有虚函数,则编译器不会为父类提供默认的构造函数。
1 内存结构:
①先安排父类的数据
②后安排子类新定义的数据
说明:基于上述的内存排列方法,即父类数据成员被安排前面。不管是父类的对象,还是子类的对象,父类的数据成员在内存中相对于对象的首地址的偏移值都是一样的。而且成员数据访问都是基于this指针间接寻址的。所以,对于子类对象而言,使用父类指针或者子类指针都可以正确访问其父类数据。
2 虚表:
虚表的排列顺序是按虚函数在类继承层次中首次声明的顺序依次排列的。只要继承了父类,其派生类的虚函数表中的父类部分的排列就与父类一样。子类新定义的虚函数会按照声明顺序紧跟其后。
3 构造函数:
①先调用父类构造函数
②然后按照声明顺序调用成员数据变量的构造函数和初始化列表中指定的成员
③最后再执行子类构造函数的函数体。
说明:
①父类构造函数,虚表指针修改为指向父类的虚表,所以在父类构造函数内调用虚函数,调用的是父类的虚函数。
②子类构造函数,虚表指针修改为指向子类的虚表
4 析构函数:
①先调用子类析造函数
②然后成员对象的析构函数,按照声明的顺序以倒序方式依次调用成员对象的析构函数。
③再执行父类构造函数
说明:
析构函数执行会首先设置虚表指针为自身虚表,再调用自身的析构函数。防止父类析构函数内调用子类对象的虚函数。
类有派生与继承关系,需要声明析构函数为虚函数。若析构函数不是虚函数时,当使用父类指针指向堆中的子类对象,并使用delete释放对象空间时,编译器会按照指针类型调用父类的析构函数,从而引发错误。
识别类之间的关系:
先定位构造函数,根据构造先后顺序得到与之相关的其他类。
再根据虚表,利用IDA中使用引用参考功能可得到所有的构造和析构函数。
二、多重继承
1 内存排列:
数据成员的排列顺序由继承父类的先后顺序所决定,从左向右依次排列。
子类虚表指针的个数取决于所继承的父类的个数,有几个父类便对应几个虚表指针(虚基类除外)。
将一个子类对象赋值给某个父类指针时,该父类指针便指向该父类所对应的虚表指针。
三、单类继承与多重继承比较:
单继承类
在类对象占用的内存空间中,只保存一份虚表指针
只有一个虚表指针,对应的也只有一个虚表
虚表中各项保存了类中各虚函数的首地址
构造时先构造父类,再构造自身,并且只调用一次父类构造函数
析构时限析构自身,再析构父类,并且只调用一次父类析构函数
多重继承类
在类中所占用的内存空间中,根据继承父类的个数保存对应的虚表指针
根据所保存的虚表指针的个数,对应产生相应个数的虚表。
转换父类指针时,需要跳转到对象的首地址。
构造时需要按照继承顺序调用多个父类构造函数。
析构时先析构自身,然后以与构造函数相反的顺序调用所有父类的析构函数
当对象作为成员时,整个类对象的内存结构和多重继承相似。当类中无虚函数时,整个类对象内存结构和多重继承完全一样,可酌情还原;当父类或成员对象存在虚函数时,通过过观察虚表指针的位置和构造函数、析构函数中填写虚表指针的数目及目标地址,来还原继承或成员关系。
四、 示例
1 单类继承:
C++源码
1 #include <iostream>
2 using namespace std;
3
4 class Base {
5 public:
6 Base(){ nBase= 1;printf("CBase"); }
7 ~Base(){ printf("~CBase"); }
8 virtual void f() { printf("Base:f()");}
9 virtual void g() { printf("Base:g()");}
10 private:
11 int nBase;
12
13 };
14
15
16 class Derive : public Base {
17 public:
18 Derive(){ nDerive=2;printf("Derive"); }
19 ~Derive(){ printf("~Derive"); }
20 virtual void g(){ printf("Dervie:g()");}
21 virtual void h(){ printf("Dervie:h()");}
22 private:
23 int nDerive;
24 };
25
26
27
28 int main()
29 {
30 Derive d;
31 Base b = &d;
32 b->g();
33 return 0;
34 }
汇编代码(VS2010编译)
1 内存分布
1 类Derive对象
2 0019FD30 0139583C =>rdata:const Derive::`vftable'
3 0019FD34 00000001 =>BasenBase
4 0019FD38 00000002 =>DerivenDerive
5
6 虚函数表
7 0139583C 01391163 Base::f(void)
8 01395840 0139110E Derive::g(void)
9 01395844 013911AE Derive::h(void)
2 构造函数
1 pop ecx ;=>this指针出栈
2 mov [ebp+this], ecx ;=>保存this指针
3 mov ecx, [ebp+this]
4 call j_0Base@@QAE@XZ ;=>调用基类构造函数Base::Base(void)
5 mov eax, [ebp+this] ;=>eax=this指针
6 mov dword ptr [eax], offset _7Derive@@6B@ ;=>初始化虚表指针为const Derive::`vftable'
3 析构函数
1 pop ecx ;=>this指针出栈
2 mov [ebp+this], ecx ;=>保存this指针
3 mov eax, [ebp+this]
4 mov dword ptr [eax], offset _7Derive@@6B@ ;=>重置虚表指针为const Derive::`vftable'
5 mov esi, esp
6 push offset aDerive ; "~Derive"
7 call ds:__imp__printf
8 add esp, 4
9 cmp esi, esp
10 call j___RTC_CheckEsp
11 mov ecx, [ebp+this] ;=>ecx传参this指针
12 call j_1Base@@QAE@XZ ; =>调用基类析构函数 Base::~Base(void)
2 多重继承:
C++源码
1 #include <iostream>
2 using namespace std;
3
4 class Base1 {
5 public:
6 virtual void f() { cout << "Base1::f" << endl; }
7 virtual void g() { cout << "Base1::g" << endl; }
8 Base1(){b1 = 1; printf("Base1"); }
9 ~Base1(){ printf("~Base1"); }
10 private:
11 int b1;
12
13 };
14
15 class Base2 {
16 public:
17 virtual void f() { cout << "Base2::f" << endl; }
18 virtual void g() { cout << "Base2::g" << endl; }
19 Base2(){b2 = 2; printf("Base2"); }
20 ~Base2(){ printf("~Base2"); }
21 private:
22 int b2;
23 };
24
25 class Derive : public Base1, public Base2{
26 public:
27 virtual void f() { cout << "Derive::f" << endl; }
28 virtual void g1() { cout << "Derive::g1" << endl; }
29 Derive(){ d1 = 3; printf("Derive"); }
30 ~Derive(){ printf("~Derive"); }
31 private:
32 int d1;
33
34 };
35
36 typedef void(Fun)(void);
37
38 int main()
39 {
40
41 Derive d;
42 Base1 b1 = &d;
43 b1->f();
44 b1->g();
45 Base2 b2 = &d;
46 b2->f();
47 b2->g();
48 return 0;
49 }
汇编代码(VS2010编译)
1内存分布
;内存布局
0019FA0C 008F584C ;=>rdata:const Derive::`vftable'{for `Base1'} 第一个虚表
0019FA10 00000001 ;=>Base1b1
0019FA14 008F583C ;=>rdata:const Derive::`vftable'{for `Base2'} 第二个虚表
0019FA18 00000002 ;=>Base2b2
0019FA1C 00000003 ;=>Derived1
;虚函数表Derive::`vftable'{for `Base1'}
00FB584C 00FB1041 ;=>Derive::f(void)
00FB5850 00FB1118 ;=>Base1::g(void)
00FB5854 00FB111D ;=>Derive::g1(void)
;虚函数表Derive::`vftable'{for `Base2'}
00FB583C 00FB1113 ;=>Base2::g(void)
00FB5840 00FB1028 ;=>[thunk]:Derive::f`adjustor{8}' (void)
↓
;追踪地址:00FB1028
00FB1028 jmp f@Derive@@W7AEXXZ ;=>[thunk]:Derive::f`adjustor{8}' (void)
↓
;追踪函数:f@Derive@@W7AEXXZ
00FB1F30 f@Derive@@W7AEXXZ proc near
00FB1F30 sub ecx, 8 ;=>调整this指针 this = this+8,则this=>Derive::`vftable'{for `Base2'}
00FB1F33 jmp j_f@Derive@@UAEXXZ ;=>Derive::f(void)
2构造函数
1 00FB14D9 mov [ebp-4], ecx ;=>this指针保存在esp-4处
2 00FB14DC mov ecx, [ebp-4] ;=>ecx获得this指针
3 00FB14DF call j_0Base1@@QAE@XZ ;=>调用构造函数 Base1::Base1(void)
4 00FB14E4 mov ecx, [ebp-4] ;=>ecx获得this指针
5 00FB14E7 add ecx, 8 ;=>ecx获得this+8
6 00FB14EA call j_0Base2@@QAE@XZ ;=>调用构造函数 Base2::Base2(void)
7 00FB14EF mov eax, [ebp-4] ;=>eax获得this指针
8 00FB14F2 mov dword ptr [eax], offset _7Derive@@6BBase1@@@ ;=>初始化第一个虚表指针为const Derive::`vftable'{for `Base1'}
9 00FB14F8 mov eax, [ebp-4] ;=>eax获得this指针
10 00FB14FB mov dword ptr [eax+8], offset _7Derive@@6BBase2@@@ ;=>初始化第二个虚表指针const Derive::`vftable'{for `Base2'}
11 00FB1502 mov eax, [ebp-4] ;=>eax获得this指针
12 00FB1505 mov dword ptr [eax+10h], 3
13 00FB150C push offset Format ; "Derive"
14 00FB1511 call ds:__imp__printf
15 00FB1517 add esp, 4
3析构函数
00FB17C9 mov [ebp-4], ecx ;=>this指针保存在esp-4处
00FB17CC mov eax, [ebp-4] ;=>ecx获得this指针
00FB17CF mov dword ptr [eax], offset _7Derive@@6BBase1@@@ ;=>重置第一个虚表指针为const Derive::`vftable'{for `Base1'}
00FB17D5 mov eax, [ebp-4]
00FB17D8 mov dword ptr [eax+8], offset _7Derive@@6BBase2@@@ ;=>重置第二个虚表指针为const Derive::`vftable'{for `Base2'}
00FB17DF push offset aDerive ; "~Derive"
00FB17E4 call ds:__imp__printf
00FB17EA add esp, 4
00FB17ED mov ecx, [ebp-4] ;=>ecx获得this指针
00FB17F0 add ecx, 8 ;=>ec;得this+8
00FB17F3 call j_1Base2@@QAE@XZ ;=>调用虚构函数Base2::~Base2(void)
00FB17F8 mov ecx, [ebp-4] ;=>ecx获得this指针
00FB17FB call j_1Base1@@QAE@XZ ;=>调用虚构函数Base1::~Base1(void)
4虚函数调用
;Base1 b1 = &d;
00FB1431 lea eax, [ebp-14h] ;=>eax获得this指针
00FB1434 mov [ebp-18h], eax ;=>局部变量b1赋值为this指针
;b1->f();
00FB1437 mov eax, [ebp-18h] ;=>eax获得b1值
00FB143A mov edx, [eax] ;=>edx指向第一个虚表
00FB143C mov ecx, [ebp-18h] ;=>ecx传递this指针
00FB143F mov eax, [edx] ;=>eax获得成员函数Derive::f(void)的地址
00FB1441 call eax ;=>调用成员函数Derive::f(void)
;b1->g();
00FB1443 mov eax, [ebp-18h] ;=>eax获得b1值
00FB1446 mov edx, [eax] ;=>edx指向第一个虚表
00FB1448 mov ecx, [ebp-18h] ;=>ecx传递this指针
00FB144B mov eax, [edx+4] ;=>eax获得成员函数Derive::g(void)的地址
00FB144E call eax ;=>调用成员函数Derive::g(void)
;Base2 b2 = &d;
00FB1457 lea ecx, [ebp-14h] ;=>ecx获得this指针
00FB145A add ecx, 8 ;=>ecx=this+8指向第二个虚表
00FB145D mov [ebp-64h] ;=>保存ecx到临时变量中
00FB1460 jmp short loc_FB1469 ;--|
; |
00FB1469 mov edx, [ebp-64h];<----|
00FB146C mov [ebp-1Ch], edx ;=>局部变量b2赋值为this+8
;b2->f();
00FB146F mov eax, [ebp-1Ch] ;=>eax获得b2值
00FB1472 mov edx, [eax] ;=>edx获得指向第二个虚表
00FB1474 mov ecx, [ebp-1Ch] ;=>ecx传递this+8
00FB1477 mov eax, [edx+4] ;=>eax为第二个虚表的第二个元素=>[thunk]:Derive::f`adjustor{8}' (void),间接调用Derive::f(void)
00FB147A call eax
;b2->g();
00FB147C mov eax, [ebp+b2] ; =>eax获得b2值
00FB147F mov edx, [eax] ;=>edx获得指向第二个虚表
00FB1481 mov ecx, [ebp+b2] ;=>ecx传递this+8
00FB1484 mov eax, [edx] ;=>eax为第二个虚表的第一个元素=>Base2::g(void)
00FB1486 call eax ;=>调用Base2::g(void)
1。对于“汇编调用”:
我知道你要调用func,而不是它本身,但如果这个函数比较复杂时是必须用逆向先分析func这个函数,然后再确定参数列表和返回值的……
2。对于你的内联汇编的代码:
这里到底要不要用add %3, %%rsp;还是一个问题,因为要看函数使用的是什么调用标准,有标准C的,VB的,Pascal的,包括fastcall,stdcall,cdecl等……
3。对于“知道函数参数的起始地址和长度”:
这个的话,除了参数中有字符数组和直接结构体什么的,所有的基本变量基本都是每8字节(64位)一个,并且Intel一般都用bigendian的,也就是说,在内存中 01 02 03 04 05 06 07 08 读入寄存器后会变为: 0x0807060504030201
所以说对于简单的函数,用8字节一个参数来做就好了……
而对于有字符数组什么的就必须用逆向分析了……
这个……只能进行逆向分析了……
反正你知道了函数的地址和长度……
就是你把编译为机器码的程序用反编译工具翻译成汇编,然后分析一下就好了,C语言的汇编还是比较简单……
比如这个函数:
int func(int a, int b) {
// float要用到CPU的FPU,指令记不得,要查下
// 为了简单就改为int
printf("a = %d, b = %d\n", a, b);
return a;
}
编译成机器码后,反编译,如果不加优化,一般都会这样:
(假设函数入口地址为0400000h)
sub_0400000:
push rbp
mov rbp,rsp ; C函数参数度取使用堆栈式
; 参数在内存中这样: || a | b | |
; 由于是64位,故8字节对齐
mov rax,[rbp+8] ; rax = (rbp+8) // 这里就是 rax = a
push rsi
mov rsi,[rbp+16] ; rsi = (rbp+16) // rsi = b
; 调用C函数都是这样堆栈式,最后一个参数最先入栈
push [rsi]
push rax
push "a = %d, b = %d\n" ; 这里是便于理解,实际上是push这个字符串常量的指针
call printf ; printf("a = %d, b = %d\n",rax,rsi)
add rsp,24 ; 平衡堆栈,用了3个参数,要还原38=24字节,但根据函数类型的不同去平衡,像调用VB的函数就不需要平衡堆栈……
; 还原数据
mov rsp,rbp
pop rsi
pop rbp
; 一般返回数据都用rax装载
mov rax,[rbp+8] ;rax=a
ret ; return rax
想调用未知参数列表的函数就是把以上过程倒过来,看着汇编把C的代码写出来……
破解注册码什么也是这样玩的……
实际上你可以直接用反编译的软件,比如IDA,直接自动分析,它反编译的虽然是汇编,但参数列表还是大部分都显示的……
但是,当编译器加优化大部分情况就必须自己分析了,因为:
int func(int a, int b) {
printf("a = %d, b = %d\n", a, b);
return a;
}
在优化情况下可能为(直接用寄存器传递数据):
sub_040000:
push rdx
mov rdx,rax
push rax
push rbx
push "a = %d, b = %d\n"
call printf
mov rax,rdx
pop rdx
ret
其实像>
本套课程主要学习FPS类游戏安全
由于FPS类游戏本身的特性问题,可能产生一些通用的游戏安全问题
在通过逆向与正向对FPS类游戏分析之后,找到其可能出现的不安全点
才能更好的保护游戏不被外部力量侵犯
对于本套课程作测试用到的程序或者游戏,仅仅是为了演示效果
邮箱 service@yxfzeducom
QQ:851920120
以三角函数等算法实现FPS类游戏实现方框透视
包括三角函数,游戏内坐标转屏幕坐标,绘制等知识点
对游戏安全,游戏逆向感兴趣人群
课程目录:
方框透视(三角函数)
勾股定理
正切函数与反正切函数
三维空间的计算
计算水平角度
计算高低角度
计算水平角度差
计算高低角度差
三维坐标转屏幕坐标X
三维坐标转屏幕坐标Y
三维坐标转屏幕坐标水平可视角
三维坐标转屏幕坐标高低可视角
取游戏窗口信息
绘制-画框
绘制-画线
绘制-文字
游戏中绘制方框实现透视
游戏中绘制文字
游戏中绘制直线
观看地址: >
以上就是关于c++反汇编与逆向分析技术揭秘 crackme怎么无法运行全部的内容,包括:c++反汇编与逆向分析技术揭秘 crackme怎么无法运行、如何用汇编实现C语言函数调用、FPS游戏逆向-方框透视(三角函数)等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)