C语言的反汇编

来源:互联网 发布:数据结构图的应用 编辑:程序博客网 时间:2024/04/27 20:03

先贴一个寄存器的表格:

寄存器

用途

EAXEBXEDXECX

通用寄存器,由程序员自己指定用途,也有一些不成文的用法:

EAX:常用于运算。

EBX:常用于地址索引。

ECX:常用于计数。

EDX:常用于数据传递。

EIP

指令寄存器,指出当前指令所在的地址。

ESP

栈指针,指向当前线程的栈顶。

EBP

栈基址指针,对调试起着很重要的作用。

EDIESI

没有规定作什么用,一般用在源指针和目标指针的操作。

FR

标志寄存器,由多个标志位组成,存放运算结果的标志,比如借位,进位,是否为0等等。

FS

Windows中,FS:[0]用来指向异常处理机制的链接头。

再来看看一个简单的例子:


程序很短,大家看我输入一次,留意成对编码原则,防止对低级的错误

void  boxer(int a,int b)
{
  int c=a+b;
}

void  main()
{
  boxer(1,2);
}

一个简单的有2个形参的boxer()函数,接着在main()主函数调用boxer()函数

然后我们debug看看,F10单步

------------------------------------------------------------------------------

6:    void    main()
7:    {
00401060   push        ebp    ;保存ebp,          执行这句前ESP = 0012FF84,EBP = 0012FFC0
          ;push的结果是esp总减少,执行后ESP = 0012FF80,EBP = 0012FFC0
00401061   mov         ebp,esp    ;将esp放入ebp中此时ebp和esp相同,即执行后ESP = EBP = 0012FF80


;原EBP值已经被压栈(位于栈顶),而新的EBP又恰恰指向栈顶。
;此时EBP寄存器就已经处于一个非常重要的地位,该寄存器中存储着栈中的一个地址(原EBP入栈后的栈顶),
;从该地址为基准,向上(栈底方向)能获取返回地址、参数值,向下(栈顶方向)能获取函数局部变量值,
;而该地址处又存储着上一层函数调用时的EBP值!

------------------------------------------------------------------------------

00401063   sub         esp,40h    ;把esp往上移动一个范围
          ;等于在栈中空出一片空间来存局部变量
          ;执行这句后ESP = 0012FF40
00401066   push        ebx    ;下面3句都是保存3个寄存器
00401067   push        esi
00401068   push        edi
00401069   lea         edi,[ebp-40h]  ;把ebp-40h加载到edi中,目的是保存局部变量的区域
0040106C   mov         ecx,10h
00401071   mov         eax,0CCCCCCCCh  ;从ebp-40h开始的区域初始化成全部0CCCCCCCCh,就是int3断点
00401076   rep stos    dword ptr [edi]  ;拷贝字符串,初始化局部变量空间



;以上的语句就是在栈中开辟一块空间放局部变量
;然后把这块空间都初始化为0CCCCCCCCh,就是int3断点,一个中断指令。
;因为局部变量不可能被执行,执行了就会出错,这时候发生中断提示开发者。
;到时候我们可以用od很直观的看到

------------------------------------------------------------------------------

8:        boxer(1,2);
00401078   push        2      ;参数2入栈,执行前ESP = 0012FF34,执行后ESP = 0012FF30
0040107A   push        1      ;参数1入栈,执行后ESP = 0012FF2C
0040107C   call        @ILT+0(boxer) (00401005)  ;调用boxer()函数,可以按F11跟进
00401081   add         esp,8      ;调用完函数后恢复/释放栈,执行后ESP = 0012FF34


其实call 指令调用一个过程, 但它有一个小动作
;在参数入栈以后, 被调用函数执行 之前, 它会将当前函数的下一条指令地址, 即EIP的值压入(调试的时候注意)


;在 c/c++ 中, 函数的默认调用约定为 cdecl, 它约定参数从右到左入栈, 
;由调用者清理堆栈, 所谓清理, 即调整ESP的值, 使得原来的局部数据不再属于栈


------------------------------------------------------------------------------

9:    }
00401084   pop         edi      ;下面3句都是恢复寄存器,上面怎样push,这里就要对应反过来pop
00401085   pop         esi      ;简单来说就是先进来最后才出去,最后进来的先出去
00401086   pop         ebx
00401087   add         esp,40h      ;恢复esp,对应上面的sub esp,40h
0040108A   cmp         ebp,esp      ;检查esp是否恢复正常,不正常就进入下面的call里面debug
0040108C   call        __chkesp (004010b0)  ;处理可能出现的堆栈错误(如果出错,将陷入debug)。
00401091   mov         esp,ebp      ;将栈顶指针放回esp
00401093   pop         ebp      ;恢复原来的ebp和esp,让上一个调用的函数正常使用
00401094   ret          ;将返回地址存入EIP, 转移流程


;以上那部分代码在vc的debug调试版才会有,主要检查栈是否被破坏了。这样就能及时在你出错的地方停下了告诉你。  
;发行版自动消除这些代码(调试产生的)。
;如果函数有返回值,返回值将放在eax返回(这就是很多软件给秒杀爆破的原因了,因为eax的返回值是可以改的)

------------------------------------------------------------------------------

好了,到了这里我们基本弄清主函数的详细过程了,其实无论主函数还是其他的普通函数,过程都是基本是一样的

下面我们看看boxer()有那些不同就行了

------------------------------------------------------------------------------

1:    void    boxer(int a,int b)
2:    {
00401020   push        ebp
00401021   mov         ebp,esp
00401023   sub         esp,44h
00401026   push        ebx
00401027   push        esi
00401028   push        edi
00401029   lea         edi,[ebp-44h]
0040102C   mov         ecx,11h
00401031   mov         eax,0CCCCCCCCh
00401036   rep stos    dword ptr [edi]

;上面的代码跟前面介绍过的几乎一样了

------------------------------------------------------------------------------

3:        int c=a+b;
00401038   mov         eax,dword ptr [ebp+8]  ;取第一个参数a放在eax里面
0040103B   add         eax,dword ptr [ebp+0Ch]  ;取第二个参数b,加上a的值放在eax中
0040103E   mov         dword ptr [ebp-4],eax  ;将最终结果放在c中



;一般而言,ss:[ebp+4]处为返回地址
;ss:[ebp+8]处为第一个参数值(这里是a),ss:[ebp+0Ch]处为第二个参数(这里是b,这里8+4=12=0Ch)
;ss:[ebp-4]处为第一个局部变量(这里是c),ss:[ebp]处为上一层EBP值
;ebp和函数返回值是32位,所以占4个字节


------------------------------------------------------------------------------

4:    }
00401041   pop         edi
00401042   pop         esi
00401043   pop         ebx
00401044   mov         esp,ebp
00401046   pop         ebp

00401047   ret


2.从上面的例子可以引出函数的调用规则:

函数调用规则指的是调用者和被调用函数间传递参数及返回参数的方法,在Windows上,常用的有Pascal方式、WINAPI方式(_stdcall)、C方式(_cdecl)。

A、_cdecl C调用规则:(上面的例子即为C调用约定

(a)参数从右到左进入堆栈;      (上面的例子为先push b,在push a)

(b)在函数返回后,调用者要负责清除堆栈,这种调用方式通常会生成较大的可执行程序。 

       在上面的例子中 调用则最后几句即为清理堆栈

    pop         edi
    pop         esi
    pop         ebx
    mov         esp,ebp
    pop         ebp

B、_stdcall又称为WINAPI,调用规则如下:

(a)参数从右到左进入堆栈;

(b)被调用的函数在返回前自行清理堆栈,这种方式生成的代码比cdecl小。

C、Pascal调用规则(主要用于Win16函数库中,现在基本不用):

(a)参数从左到右进入堆栈;

(b)被调用的函数在返回前自行清理堆栈。

(c)不支持可变参数的函数调用。

3.栈幁,栈对齐

               注意:下图中return addr应该在ebp的上方,在汇编中即为ebp+4即为返回值的地址

根据前面反汇编的例子可以知道程序在运行时  内存中栈的分布大致如上图,后一个ebp总是储存上一个ebp的地址,这样当被调用函数执行完后pop ebp 则ebp寄存器立马回到调用函数ebp的位置,方便取参数或者局部变量。 这样每两个ebp之间的片段可以称之为一个栈幁。


更为详尽的一个例子:

void c(){}

 

void b(){c();}

 

void a(){b();}

 

int main(){

   a();

   return 1;

}


函数调用栈的状态:

      +-------------------------+----> 高地址 


   
  | EIP (Main 函数返回地址)   | 

     +-------------------------+ 

     | EBP (Main 函数的EBP)       | --+ <------ 当前函数A 的EBPA ( 即SFP 框架指针) 

    +-------------------------+   +-->offsetA 

     | A 中的局部变量            | --+ <------ESP 指向函数新分配的局部变量, 局部变量可以通过EBPA-offsetA 访问 

     +-------------------------+

    | Arg .( 函数B 的参数)      |   --+ <------ B 函数的参数可以由B 的EBPB+offsetB 访问

      +-------------------------+   +--> offsetB

    | EIP (A 函数的返回地址)    |   | 
      +-------------------------+ --+ 
     | EBP (A
 函数的EBP)        |<--+ <------ 当前函数的EBPB ( 即SFP 框架指针) 
     +-------------------------+ 
    
  | B 中的局部变量           |   
   
  +-------------------------+   
   
  | Arg .(
 函数C 的参数)       |   
   
  +-------------------------+   
   
  | EIP (B 函数的返回地址)     |   
   
  +-------------------------+  
    
  | EBP (B 函数的EBP)       | --+ <------ 当前函数的EBPC ( 即SFP 框架指针) 
    +-------------------------+
     | C 中的局部变量          | 
      | ..........              | <------ ESP
 指向函数C 新分配的局部变量

  +-------------------------+----> 低地址 


栈对齐同自然对齐。





原创粉丝点击