[分享] 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 学习中.... 恩 学习了。。。。很不错。。 好强大啊。。。。。利害。。。。 学习中~支持下~
页:
[1]