专栏首页熊彪的专栏静态逆向反汇编获取函数调用关系链
原创

静态逆向反汇编获取函数调用关系链

一般情况下,为了获取函数之间的调用关系都是通过对源码进行静态分析得到。例如doxygen就是通过分析源码来获取函数调用关系链的,但是却存在一个缺点---需要依赖于源码,而且在跨模块的调用关系的获取上存在缺陷。本文提出一种通过逆向二进制文件的方式,通过反汇编的指令获取函数之间的调用关系。

站在逆向二进制的角度观察函数的调用关系,可以将函数分为以下几种类型:

1、普通函数的调用,分为两种一个是call指令调用,另一个是跳转指令调用。

2、函数指针的调用,指的是将函数作为参数进行传递,通过参数/变量进行调用。

3、类中虚函数的调用,通过虚表指针间接调用具体的子类函数。

先通过流程图描述核心思想,再一一详细介绍:

图1

首先,来介绍一下普通函数调用的情况:

这里所谓的普通函数的调用,指的是可以直接通过函数的虚拟地址进行直接的调用。从C/C++语言的角度来看,这个函数可以是一个纯C函数或者类成员非虚函数(补充:对于宏,在编译时就已将其替换为其所代表的项,所以在逆向的角度而言,若要获取宏的调用关系还需要进一步的将替换者变为宏,这个......)。从PE文件的角度考虑这个函数可能存储在.text的代码区,导入表,导出表三个地方中。

对于普通函数而言,在汇编层面直接调用的使其所在的函数地址,ida所在的加载器会将这个调用的实际函数地址替换成对应的函数名称,如下图1所示:

图2

通过对逆向汇编的分析,C/C++代码中的函数调用在编译成二进制之后,逆向成汇编语言,从普通函数的角度观察,调用函数的指令有两类:一类是call指令。另一类是跳转指令。如图2所示:

图3

汇编角度而言,普通函数的调用是最常用的一种形式,也最容易解析。

其次,介绍函数指针的具体情况:

函数指针一种使用形式就是回调函数(把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数)。函数指针的主旨是:作为参数,不管是函数的参数,还是作为一个成员变量。如GF皮肤库中的消息传递:

从汇编的角度而言,参数的传递主要通过以下两个指令:mov/push指令。以函数作为参数/变量进行传递的两种情况如图3所示:

图4

对于函数指针,我们只需要判断push/mov指令传递的地址是否是一个函数实际地址,若判断为真,就将其标明为一个函数指针的调用情况。

最后,介绍虚函数调用的具体情况:

虚函数基本概念的描述:作为面向对象最具特色的概念,对象的多态性需要通过虚表和虚表指针来完成,虚函数指针被定义在对象首地址的前4个字节处,因此虚函数必须作为成员函数使用。由于非成员函数没有this指针,因此无法获得虚函数表指针,进而无法获取虚表,也就无法访问虚函数。

在C++中,使用关键字virtual声明函数为虚函数,当类中定义有虚函数时,编译器会将该类中所有虚函数的首地址保存在一张地址表中,这张表被称为虚函数地址表,简称虚表。同时,编译器还会在类中添加一个隐藏数据成员,称为虚表指针。该指针中保存着虚表的首地址,用于记录和查找虚函数。如图5所示:包含虚函数的类的定义

图5

int nsize = sizeof(CVirtual);大小为8字节数据,多出了4字节数据,这4字节数据用于保存虚表指针。在虚表指针所指向的函数指针数组中,保存了虚函数GetNumber和SetNumber的首地址。对于开发者而言,虚表和虚函数指针都是隐藏的,在常规的开发过程中感觉不到她们的存在。对象中的虚表和虚函数指针的关系如图5所示:

图6

虚表指针的初始化是通过编译器在构造函数内插入代码来完成的。在虚表指针初始化的过程中,对象执行了构造函数后,就得到了虚表指针,当其他代码访问这个对象的虚函数时,会根据对象的首地址,取出对应虚表元素。当函数被调用时,会间接访问虚表,得到对应的虚函数首地址,并执行调用。此种调用是一个间接调用的过程,需要多次寻址才可以完成。

这种通过虚表间接寻址访问的情况只有在使用对象的指针或者引用来调用虚函数的时候才会出现。当直接使用对象调用自身的虚函数时,没有必要查表访问。这是已经明确调用的是自身成员函数,根本没有构成多态性,查询虚表只会画蛇添足,降低程序执行效率。

在逆向静态分析中虚函数缺失父调用函数关系,那么为什么会缺失父函数呢?看图:

图7

从图中我们可以知道子调用关系,却不知道父调用关系。那么为什么会产生这个问题呢?让我们一起看看一个有虚函数调用的函数的汇编实现:

图8

从上图可以很明白的知道,为什么虚函数父调用的关系缺失了,因为在汇编中这其实是一个地址的调用,要建立寄存器与具体虚表的关系是很困难的(或许本身就不可为)。一个解决方案是对IDA逆向C/C++伪码去获取虚函数名称(数据流指令的分析),然后通过虚函数名称去补全父函数调用关系, 但是通过对管家不同模块使用逆向伪码的功能,发现ida在逆向虚函数的时候准确率只能达到30%多,并且对不同版本IDA PRO的逆向虚函数伪码的功能进行的测试(除了最新的6.6未提供下载),发现准确率都很低。并且寻找了多个处理面向对象语言的插件效果也都不佳。在ida做了这么多年逆向虚函数的工作来看,这块工作耗时而且收效甚微。那么我们就退而求其次,做到现在可以确定做的事情:1、对于普通的非虚函数变更可以精确到函数级别的调用关系链的影响。 2、对于虚函数当其发生了变更,因为影响不能精确到函数级别,但是可以做到类级别。类之间的调用关系可以通过构造函数去确定,因为构造函数不是虚函数,这个前提是肯定的。

那么对于类调用关系的获取在管家有大致两种情况需要处理:

第一种是没有经过封装的直接的类之间的调用(包括模块内与模块间)。另一种是COM类跨模块间的类调用关系的处理,用如下流程图来表述:

图9

对于COM组件中数据的逆向处理因为比较复杂,这里不详细展开(后面特别用一篇文章描述)。

对于虚函数的处理,因为在静态逆向分析的情况下不能获取实际函数的调用,在万不得已的情况下,只能用类调用关系类弥补这方面数据的缺失。对于虚函数展示类调用关系,也可满足我们的业务需求。

下面是二进制变更/调用链与doxygen的对比图:

图10

上述整体描述了如何逆向分析获取函数调用关系链的方方面面,若是有讲的有误的地方,请大家指点改进,或者对虚函数的处理有更好的方法,要不吝赐教哦。

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 精准测分:基于函数调用关系链的用例消振算法 ( 上帝视角 )

    地球文明不是孤岛,函数呢?从静态的视角观察函数,她只是一个一个在文件中孤立存在的代码片段。但从动态的视角观察,函数与函数之间就天然的发生了关联。这个关联是怎么产...

    熊彪
  • Python黑帽编程2.5 函数

    写了几节的基础知识,真心感觉有点力不从心。这块的内容说实话,看文档是最好的方式,本人的写作水平,真的是找不出更好的写法,头疼。简单带过和没写一样,写详细了和本系...

    用户1631416
  • Python3.0科学计算学习之函数

    函数允许程序的控制在不同的代码片段之间切换,函数的重要意义在于可以在程序中清晰地分离不同的任务,将复杂的问题分解为几个相对简单的子问题,并逐个解决。即“分而治之...

    py3study
  • Windows客户端C/C++编程规范“建议”——函数

    等级:【要求】 说明:每个函数的代码行数控制应该控制在80行以内。如果超过这个限制函数内部逻辑一般可以拆分。如果试图超过这个标准,请列出理由。但理由不包含如下...

    方亮
  • 第23讲 函数层面的优化

    谈到函数,我们会很自然地联想到Verilog中的module或者VHDL中的entity,没错,它们之间是相互对应的。通常在Vivado HLS中,每个函数会生...

    Lauren的FPGA
  • JavaScript 函数参数-Arguments(实参)对象

    上式的 x 就是square函数的 参数。每次运行的时候,需要提供这个值,否则得不到结果。

    WEBING
  • 深入理解JavaScript中的函数

    函数于软件开发者而言并不是什么奇幻世界。如果你的日常活动涉及到编码,哪怕是一点点,那么在一天结束的时候,你一定创建/修改了一个或多个函数。

    疯狂的技术宅
  • Unity基础系列(三)——数学表面(数字雕刻)

    在完成前面的教程之后,我们有一个基于线条的视图,并在游戏模式下显示一个正弦波动画。当然还可以通过修改代码来显示其他数学函数。甚至可以在Unity编辑器处于播放模...

    放牛的星星
  • 说说Python中闭包是什么?

    小猿会从最基础的面试题开始,每天一题。如果参考答案不够好,或者有错误的话,麻烦大家可以在留言区给出自己的意见和讨论,大家是要一起学习的 。

    程序IT圈
  • 说说Python中闭包是什么?

    答:可以将闭包理解为一种特殊的函数,这种函数由两个函数的嵌套组成,外函数和内函数。在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返...

    用户1564362

扫码关注云+社区

领取腾讯云代金券