通过二进制程序得到函数间的调用关系的探究
2016-02-26 {{allComments.length}} 3350 干货分享【背景介绍】
通过静态分析反汇编后的代码取得函数调用关系的一种探究linux ELF Executable and Linkable Format,就是我们比较常见linux下可执行二进制程序,这个世上任何文件都有它自己的格式,linux下二进制程序文件也不例外即elf。windows下的为PE格式(传说pe格式是由elf格式演变来的)。elf包含了程序文件的大量的信息,本文就是分析了这些信息实现了函数调用关系的产出。
一、 elf格式二进制程序的信息采集
Objdump是个强大的工具,命令行显示程序反汇编
Objdump –d targetfile
Tagetfile就是要分析的二进制程序
效果如图:
反汇编后的代码形式虽然是汇编代码组成但与cpp源代码一样,也是由N个函数体所组成,但函数体变得更规范且把所以函数体放在一个文体里方便分析,所有函数调用的地方都是”callq”关键字(与编译器有关),这一点很助于数据格式的抽取与分析,如上图函数名为”_Z6thr_fnPv”,编译器碰到C++函数就会把它变成这种形状,linux自带了另一个非常强大的工具c++filt:
得到了函数的原形“thr_fn(void*)”。
如果你想得到函数调用的源代码位置也可以
在编译程序时加上-g选项,你会得到更多的程序信息,
Objdump –S -l targetfile
反汇编请见下图:
如图所示,这是源代码与反汇编混合的代码形式,我们可以取得每一个函数定义的代码位置。
可见main函数的代码位置threadtest.cpp:40
也可以得到每次调用函数时的代码位置
可见pthread_create函数的被main函数的调用代码位置为threadtest.cpp:42
至此,我们可以得到函数的名字,函数定义的代码位置,函数体内调用某函数时代码位置,根据这些要素,我们就可以知道,函数的调用关系及其代码位置。下面是怎么获取并保存关系图的算法。
二、 算法
1、函数调用要素的采集算法 (shell代码实现)
A、 函数定义的信息采集
“000000000043a03c:"
if [[ "$linetmp" =~ " <.*>:" ]]; then 认为本行为函数定义
callfun=`echo $line|awk -F “<" '{print $2}' |awk -F ">” ‘{print $1}’|sed ‘s/@plt$//g’|c++filt`,可以取得定义的函数名。
如果它有源代码信息它的下一行要满足if [[ "$linetmp" =~ "):$" ]]; then,
第三行满足if [[ "$linetmp" =~ "[a-zA-Z][:][0-9][0-9]*$” ]]; then 此时就可以 取得函数定义的源代码位置
B、函数调用的信息采集
if [[ "$linetmp" =~ " callq " ]]; then认为是调用函数的位置
callfun=`echo $line|awk -F ” 取得被调用的函数名
调用位置的前面最近的代码位置就是调用函数时的代码位置
2、函数调用图算法(c++代码实现)
每个函数的关联就是它们之间的直接调用关系,
struct functionnode
{
string funname; //函数全名
string definefuncpp; //定义的代码代置
list fatherfunlist; //调用此函数的函数list
list callthislist; //父函数调用本函数的代码位置,与fatherfunlist对应
list childfunlist; //此函数调用的函数列表
};
如同一个双向链表,每一个函数结点,都有指向被它调用的函数列表,和调用的函数列表。
这样就形成了一张网络图,如图