dujie110 发表于 2009-9-21 15:01

[分享] C++反汇编揭秘1 – 一个简单C++程序反汇编解析 (Rev. 3)

如果想要了解C++内部的实现原理,没有什么比观察C++代码对应的汇编代码来的更直接了。本系列主要从汇编角度研究C++代码和汇编的对应关系,揭示C++内部的机制和原理。在第一篇文章中我将从一个简单的C++程序着手快速解释一下C++反汇编代码的基本的结构和内容,相当于一个简单的Preview。而在后续的文章中,我将根据不同的Topic,详细解释C++代码对应的反汇编代码。

一个简单的C++程序示例如下:

class my_class

{

public :

    my_class()

    {

      m_member = 1;

    }



    void method(int n)

    {

      m_member = n;

    }



    ~my_class()

    {

      m_member = 0;

    }



private :

    int m_member;

};



int _tmain(int argc, _TCHAR* argv[])

{

    my_class a_class;

    a_class.method(10);



    return 0;

}
可以直接Debug的时候看到Assembly代码,不过这样获得的代码注释比较少。比较理想的方法是利用VC编译器的一个选项/FAs来生成对应的汇编代码。/FAs还会在汇编代码中加入注释注明和C++代码的对应关系,十分有助于分析。在VS2005中可以这样打开/FAs:http://p.blog.csdn.net/images/p_blog_csdn_net/atfield/275434/o_cpp_disasm_1_1.JPG
Build代码,可以在输出目录下发现对应的.ASM文件。本文将逐句分析汇编代码和C++的对应关系。

首先是WinMain:
_TEXT SEGMENT

_wmain      PROC

      pushebp                                 ; 保存旧的ebp

      mov   ebp, esp                            ; ebp保存当前栈的位置

      push-1                                  ; 建立SEH(Structured Exception Handler)链

                                                ; -1表示表头,没有Prev

      push__ehhandler$_wmain                  ; SEH异常处理程序的地址

      mov   eax, DWORD PTR fs:0               ; fs:0指向TEB的内容,头4个字节是当前SEH链的地址

      pusheax                                 ; 保存起来

      sub   esp, d8H                            ; 分配d8H字节的空间

      pushebx

      pushesi

      pushedi

      lea   edi, DWORD PTR             ; 确定局部变量的起始地址。e4H = d8H + 4 * 3,跳过之前建立SEH链所用的3个Push指令所占用的栈的空间,以及sub esp, d8h为局部变量分配的d8H字节空间

      mov   ecx, 36H                            ; 36H*4H=d8H,也就是用36H个ccccccccH填满刚才分配的d8H字节空间

      mov   eax, ccccccccH

      rep stosd

      mov   eax, DWORD PTR ___security_cookie   

      xor   eax, ebp   

      pusheax                                 ; ebp ^ __security_cookie压栈保存

      lea   eax, DWORD PTR             ; ebp-0cH是之前main的起始代码中在堆栈中建立的SEH结构的首地址

      mov   DWORD PTR fs:0, eax               ; 设置到TEB中作为当前Active的SEH链表末尾
到此为止栈的内容是这样的:

低地址
Security cookie after XOR

Edi

Esi

Ebx

Local stack: d8H

Old fs:0

__ehhandler$_wmain

ffffffffH

Old ebp

高地址

main接着后面调用my_class的构造函数
lea   ecx, DWORD PTR

      call??0my_class@@QAE@XZ               ; 调用my_class::my_class, ??my_class@@QAE@XZ是经过Name Mangling后的名字

      mov   DWORD PTR , 0                ; 进入__try块,在Main中有一个隐式的__try/__except块


接着调用my_class::method:

   push10                                  ; 参数入栈

      lea   ecx, DWORD PTR             ; 遵循thiscall调用协定,ecx存放的是this指针

      call?method@my_class@@QAEXH@Z         ; 调用子程序my_class:method(10)


之后是析构:
      mov   DWORD PTR , 0            ; 用来放置返回值

      mov   DWORD PTR , -1               ; 标记TRY的正常结束

      lea   ecx, DWORD PTR             ; a_class的地址作为this存入ECX

      call??1my_class@@QAE@XZ               ; my_class::~my_class

      mov   eax, DWORD PTR             ; 返回值按照约定放入eax中


Main函数退出代码如下:
      pushedx

      mov   ecx, ebp

      pusheax

      lea   edx, DWORD PTR $LN7@wmain

      call@_RTC_CheckStackVars@8            ; 检查栈

      pop   eax

      pop   edx

      mov   ecx, DWORD PTR             ; 取出之前保存的旧的fs:0,并恢复

      mov   DWORD PTR fs:0, ecx

      pop   ecx

      pop   edi

      pop   esi

      pop   ebx

      add   esp, e4H                            ; 退掉分配的d8H + 建立SEH链所需的0cH字节

      cmp   ebp, esp

      call__RTC_CheckEsp                      ; 检查esp值,这个时候esp应该和ebp匹配,否则说明出现了栈不平衡的情况,这种情况下调用子程序报错

      mov   esp, ebp                            ; 恢复ebp到esp

      pop   ebp                                 ; 恢复原来的ebp值

      ret   0

_wmain      ENDP


专门用于SEH的子程序。__unwindfunclet$_wmain$0当异常发生的时候被调,负责进行栈展开,主要是调用析构函数。__ehhandler$_wmain则是在exception被抛出的时候调用。
Text$x      SEGMENT

__unwindfunclet$_wmain$0:                     ; 当SEH发生的时候会调用该函数,析购a_class

      lea   ecx, DWORD PTR             ; ecx = ,也就是a_class的地址

      jmp   ??1my_class@@QAE@XZ               ; 调用my_class::~my_class

__ehhandler$_wmain:

      mov   edx, DWORD PTR             ; esp = 当前的fs:0, = 之前的SEH结构,也就是main中建立的

      lea   eax, DWORD PTR             ; edx + 0Ch = 当前的ebp,也就是main的ebp,此时不能直接使用ebp因为可能会从任意函数调过来,此时ebp是该函数的ebp,而不是main的ebp

      mov   ecx, DWORD PTR             ; 之前存下去的__security_cookie ^ ebp

      xor   ecx, eax                            ; 再次和ebp相异或

      call@__security_check_cookie@4          ; 此时ecx应该等于__security_cookie,否则说明栈的内容被恶意改动(或者编程错误)

      mov   eax, OFFSET __ehfuncinfo$_wmain

      jmp   ___CxxFrameHandler3

text$x      ENDS


My_class::my_class构造函数如下。构造函数本质上就是一个全局函数,名字是经过打乱的(Name Mangling),这样可以和同一Class和其他Class的同名方法区别开来。不同编译器有不同规则,因此不必过于深究。

TEXT SEGMENT

??0my_class@@QAE@XZ PROC

      pushebp                                 ; 保存旧的ebp

      mov   ebp, esp                            ; ebp保存当前栈的位置

      sub   esp, ccH                            ; 给栈分配ccH个字节

      pushebx                                 ; 保存常用寄存器

      pushesi

      pushedi

      pushecx

      lea   edi, DWORD PTR             ; 从分配的位置开始

      mov   ecx, 33H                            ; 写33H个ccccccccH

      mov   eax, ccccccccH                      ; 也就是33H*4H=ccH,正好是分配的大小

      rep stosd                                 ; 从而把整个栈上当前分配的空间用ccH填满

      pop   ecx

      mov   DWORD PTR , ecx            ; 按照约定,一般用ECX保存this指针

                                                ; 把this存入到ebp-8,并不是很必要,因为这是Debug版本

                                                



; 10   :   {

; 11   :         m_member = 1;



      mov   eax, DWORD PTR             ; eax中存放this

      mov   DWORD PTR , 1                  ; this的头四个byte是m_member的内容



; 12   :   }



      mov   eax, DWORD PTR             ; 多余的一句话,可以优化掉

      pop   edi

      pop   esi

      pop   ebx

      mov   esp, ebp                            ; 恢复esp,因此就算是中间栈运算出错,最后也不会导致灾难性的结果,只要ebp还是正确的

      pop   ebp

      ret   0

??0my_class@@QAE@XZ ENDP


My_class::method的实现如下:
_TEXT SEGMENT

?method@my_class@@QAEXH@Z PROC                  ; my_class::method



; 15   :   {



      pushebp

      mov   ebp, esp

      sub   esp, ccH

      pushebx

      pushesi

      pushedi

      pushecx

      lea   edi, DWORD PTR

      mov   ecx, 33H

      mov   eax, ccccccccH

      rep stosd

      pop   ecx

      mov   DWORD PTR , ecx



; 16   :         m_member = n;



      mov   eax, DWORD PTR             ; eax中存放this

      mov   ecx, DWORD PTR             ; ebp -> ebp

                                                ; ebp + 4 -> IP

                                                ; ebp + 8 -> n

                                                ; 把n存入ecx中

      mov   DWORD PTR , ecx                ; this头四个字节是m_member, 因此这句话就是m_member = n



; 17   :   }



      pop   edi

      pop   esi

      pop   ebx

      mov   esp, ebp

      pop   ebp

      ret   4                                 ; 等价于

                                                ; ret 恢复EIP,返回调用地址

                                                ; add esp, 4 -> 把n从栈上Pop掉

?method@my_class@@QAEXH@Z ENDP


最后的析构函数,和前面的代码并无区别。
_TEXTSEGMENT

??1my_class@@QAE@XZ PROC                        ; my_class::~my_class



; 20   :   {



       push   ebp

       mov    ebp, esp

       sub    esp, 204                        

       push   ebx

       push   esi

       push   edi

       push   ecx

       lea    edi, DWORD PTR

       mov    ecx, 33H                        

       mov    eax, ccccccccH                  

       rep stosd

       pop    ecx

       mov    DWORD PTR _this$, ecx



; 21   :         m_member = 0;



       mov    eax, DWORD PTR

       mov    DWORD PTR , 0



; 22   :   }



       pop    edi

       pop    esi

       pop    ebx

       mov    esp, ebp

       pop    ebp

       ret    0

??1my_class@@QAE@XZ ENDP                        ; my_class::~my_class

_TEXTENDS

gaga 发表于 2009-9-21 21:48

学习中....

mars1199 发表于 2009-10-22 17:35

恩 学习了。。。。很不错。。

菜鸟破解 发表于 2009-10-23 17:20

好强大啊。。。。。利害。。。。

wudigazhu 发表于 2010-8-21 12:32

学习中~支持下~
页: [1]
查看完整版本: [分享] C++反汇编揭秘1 – 一个简单C++程序反汇编解析 (Rev. 3)