静态代码分析之函数的调用序列

静态代码分析之函数的调用序列

这个是我调试栈回溯欺骗得到的一小部分,一直在一个地方卡住,后来才发现是原码、补码问题,郁闷死..

这部分主要是search call,就是静态检测一个函数中调用了哪几个函数,由于是静态检测,遇到递归类函数应该会出现问题,还没测试..

以下代码中的Trace()函数可以检测到main函数里调用了哪几个函数,调用顺序是什么。检测深度为一层,类似调试里的 逐过程 调试,修改下代码应该也可以完成逐语句的功能,即遇到call跟进,而不是步过.

代码如下:

 1 #include <iostream>
 2 #include "stdarg.h"
 3 using namespace std;
 4 
 5 void a(){}
 6 
 7 void b(){}
 8 
 9 void c(){}
10 
11 void d(){}
12 
13 void e(){}
14 
15 void Trace(int count,)
16 {
17     cout<<"Main()";
18     if (count<=0)
19         return;
20     int* ary = new int[count];
21     va_list arg_ptr;
22     va_start(arg_ptr,count);
23     int m = va_arg(arg_ptr,int);
24     for (int i=1;i<count;i++)
25     {
26         ary[i] = va_arg(arg_ptr,int);
27     }
28     ary[0= (int)Trace;
29     va_end(arg_ptr);
30     
31     m = *(int*)(m+1+ m + 4;
32     while(true)
33     {
34         if (*(unsigned char*)(m++== 0xE8)// search the call 
35         {
36             int temp = *(int*)m + m + 4;
37             if (temp == ary[0])
38             {
39                 cout<<"->Trace()"<<endl;
40                 break;
41             }
42             for (int i=1;i<count;i++)
43             {
44                 if (temp == ary[i])
45                 {
46                     cout<<"->";
47                     printf("0x%08x",ary[i]);
48                     break;
49                 }
50             }
51         }
52     }
53 
54 }
55 
56 int main()
57 {
58     a();
59     b();
60     c();
61     d();
62     
63     printf("a:\t0x%08x\n",(int)a);
64     printf("b:\t0x%08x\n",(int)b);
65     printf("c:\t0x%08x\n",(int)c);
66     printf("d:\t0x%08x\n",(int)d);
67     printf("e:\t0x%08x\n",(int)e);
68 
69     Trace(6,int(main),(int)c,(int)d,(int)b,(int)a,(int)e);
70 
71     return 0;
72 }


先解释Trace()函数:

原型:void Trace(int count,...);

使用了不定参数列表技术,第一个参数代表调用时给了多少个参数(不包括第一个参数)。

其它参数均为各个函数的函数名,其实函数名就是一个指针,对应的是函数的首地址,这个首地址还有点特殊,调用时其实是对应一个IDT表的索引,假设函数a,其首地址为a,反汇编下观察a的位置:

00411140 E9 8B 11 00 00   jmp         std::char_traits<char>::eof (4122D0h) 
00411145 E9 C6 0E 00 00   jmp         std::basic_ostream<char,std::char_traits<char>  
0041114A E9 
41 04 00 00   jmp         d (411590h) 
0041114F E9 0C 
04 00 00   jmp         c (411560h) 
00411154 E9 A7 03 00 00   jmp         a (411500h) //就是这里

如图,a应该为0x00411500,但是call a;这句话是如何执行的?

其实就是先索引到0x00411154,然后执行jmp 0x00411500

注意机器码:

E9:JMP的机器码

A7 03 00 00如何解释?

经过我连续两天晚上无聊的调试,我终于明白了

(0x00411154 + 0x01) + 0x000003A7 + 0x04 =  ?

就等于0x00411500 ,就是a的代码段(code segment)的首地址!

我来解释下为何如此:

首先现在代码走到了0x00411154,越过一个字节的jmp指令即(0x00411154 + 0x01)

A7 03 00 00,这个由Intel大顶机模式可知,在内存中为逆序,实际为0x000003A7,这个代表什么,其实代表的是偏移量(下一条语句的偏移量,所以要加0x05..我一直把这句话当call来看待,就一直没弄懂,在这卡了两天郁闷死..

还有,我之所以卡着的情况比较复杂,这里是0x0003A7,一眼就明白是偏移量,我调试的时候是0Xfffff748,原因是它是向上偏移的,结果就是个负值,然后负数的反码表示我又没想起,多个原因加起来,我就死这了..

Trace的原理很简单,就是先摸到Main函数的code segment,然后搜索0xE8,也就是call的机器码位置,再判断是哪个函数。




根据上面即可判断:

调用顺序是:

Main()->a()->b()->c()->d()->Trace();

现修改Trace的调用参数:

Trace(5,int(main),(int)c,(int)d,(int)a,(int)e);

截图如下:




正确的发现了所有的调用顺序..

    这个玩意是我弄栈回溯的一丁点东西,主要是浪费时间太多了,拿来说下,比较无聊的东西,而且很鸡肋,必须加上回溯功能才有可能被利用,例如溢出的检测等..


                                                                                                                                 -----by  NewSketcher 

 

 


 

 

 

posted @ 2009-01-08 16:32  端木  阅读(805)  评论(0编辑  收藏  举报