危险漫步博客
新鲜的“黑客思维”就是从全新的角度看待黑客技术,从更高的层面去思考;专注于黑客精神及技术交流分享的独立博客。
文章2289 浏览18790394

C++函数深入剖析

做逆向分析的时候最怕遇到c++的东西,因为它的各种机制实在是令人郁闷,今天这里深入的介绍一下在么去分析c++中的虚函数,深入其中,你会发现更多的秘密。本文的阅读需要具备c++功底,危险漫步的粉丝们,你们要多多学习啊。

首先来看我们的工程代码,很简单的c++代码,希望读者有c++-基础。代码太长我就不写下来了,有需要的朋友可以来找我要。

下面开始我们的逆向分析,看看内存中这些类、虚函数以及虚函数表是怎么个存放顺序。

首先在程序正式开始执行之前,在内存映射执行之后,从指向的内存区域我们可以看出,在程序没有执行之前,位于0040DOF4的内存区域就已经存在我们的虚函数的地址,这说明在代码编译完毕后,我们的虚函数偏移地址就已经存在。我们知到,对虚函数的访问是通过两次间接访问进行的,既然虚函数的内存地址已经确定,那是不是第一间接访问的内存地址(虚函数表指针)也已经确定昵?我们到堆区域看一下。我发现,这部分内存是杂七杂八的数据,这说明,专一级偏移地址在程序运行前是不知道,那么什么时候会知道呢?开始对程序进行断点跟踪,在跟踪过程中,在主函数调用之前,这块的内存将被申请并修改,被修改的大小为:所有类大小之和+8阶字节。继续跟踪,下面进入构造函数,首先是最外层构造函数。最外层的构造函是fish():a(8){cout”entering fish< endl;),数据a被初始化为8。但这个0040D114是什么暂时还不知道。然后是第二层构造函数,构造函数为bi_rd():b(7) {cout”entering  bud-<<endl;),数据b被初始化为7,0040D108的含义也不知道。最后一个是子类的构造函数。子类的构造函数为tt():c(6)(cout”entering tt..”endl;};,成员c被初始化为6,但两个表的基址指针实在构造函数调用前确定的。这里看到了,图中红色画线的指向,在最后一个类,也就是子类初始化完毕之后,这两个内存位置的数据被改了,而0040DOF40040DOE8恰恰是我们两个虚函数表的基地址。最后再看一下虚函数表的内存。在这里我发现一个奇怪的现象,就是“后来者居上”,就是说,越在后面创建的虚函数表在内存的排序越靠前,这一点在资料上是找不到。

通过上面的分析,我们得到了关于c++中虚函数的以下几个结论:

1c++虚函数的函数地址是在编译期间确定的,但是其一级基地址却是在运行时得到的。

2虚函数表基址指针(不管有几个表)是在最后的子类够构造函数执行前确定的。

3虚函数表在内存中存放,越晚形成的表越靠前。

4虚函数表的末尾并不是null,是一个指针,但这个指针指向的函数却是null。

5虚函数表的表指针安插在类的头部,这个插入的过程是在运行时执行的,并不是编译期间执行的。

6在内存中,所有的类彼此相邻,而且按照声明的顺序依次排列。

7类中的数据成员存放在类的内部  对c++的程序逆向分析非常复杂,当然,如果是简单的控制台应用程序的话,会简单一些,但是当一个程序使用上了c++几乎所有的特性(模版、多态)的时候,逆向分析这样一个程序简直就是噩梦。最好的方法就是平时将它的特性单独拿出来进行分析,当他们组合在一起的时候我们才能最快的去发现这些特征。

相关推荐