main函数的汇编代码

电脑杂谈  发布时间:2019-09-11 10:03:25  来源:网络整理

硬盘esp是什么意思_esp故障是什么意思_汇编esp是什么意思

本文电子版(Droplr)

一个windows下的小软件,查看二进制代码对应的汇编码(Droplr)

本文主要对main函数编译后生成的汇编码进行观察,为了简单起见,main函数的内容为空。实验步骤如下:首先在不同环境下编译源代码,收集生成的可执行文件;随后将可执行文件使用IDAPro(版本为5.5,这里赞一下强大的IDA!)进行反汇编;最后观察main函数的汇编码(所有汇编码格式都是Intel风格的),进行探讨与非常。本文重点在于探讨一些最基本的概念,有助于读者熟悉各类环境生成的汇编码,更好地进行二进制分析。需要注意的是,在C语言的层面来看,main函数是程序的起始入口,但实际上对于可执行文件来说,CPU真正执行的第一条指令通常并不是main函数汇编码的第一条指令,这里仅预测main函数的汇编码,对于可执行文件中的其它个别就忽视不谈了。

intmain()

{

return0;

}

main函数的汇编代码

首先以vc2010(Microsoft Visual C++ 2010Express)release方式生成的可执行文件为例,上图为main函数的汇编码,可以看见,内容非常简单。

第一条指令xoreax,eax是对eax进行赋值运算,这是对寄存器赋0值的一种常见方式,通常约定把变量的返回值放到eax中返回(32位,16位放在ax中),因此可见这是在为return0;语句做准备;第二条指令retn是过程近(near)返回指令,从堆栈弹出返回地址压到eip中,与之对应的也有远(far)返回指令retf,首先弹出eip,然后弹出cs(其实,对于现代操作系统来讲,每个进程都有其单独的相似的逻辑地址空间,段寄存器的值由操作系统设定且固定,与之相关的汇编指令也就很少再使用了),而指令ret根据PROC伪指令,自动判定是近返回还是远返回(当然,从可执行文件是看不到伪指令的)。

仅看main函数的汇编指令,可以说和C语言的源代码是一样的简单。

main函数的汇编代码

接着观察vc2010debug模式生成的可执行文件的汇编码,见上图,可以看见相比release方式要复杂许多,之所以会有这么的差别,是因为在debug方式包括有安装信息,且未进行改进,release方式把一些执行过程改进掉了。

汇编esp是什么意思_esp故障是什么意思_硬盘esp是什么意思

下面简单解释一下代码的意思:

因为main函数也是数组,所以它与变量的执行过程同样:调用前释放数组参数(本例中没有参数),进入时为数组的局部变量分配空间,并在撤出时释放那些空间。这里要介绍一下栈帧(stack frame)的概念,栈帧,也称为活动记录(activationrecord),它是为释放的参数、子实例的返回地址、局部变量和储存的解释器保留的堆栈空间。栈帧的两端是以两个指针定界的,寄存器ebp作为帧指针,表示栈帧的上面,等于数组读入前运行栈的栈顶指针的值,在变量调用过程中不改变其值,当变量调用结束时可以借助帧指针的值将栈帧空间传递掉;寄存器esp是运行栈的栈顶指针,同时也表示栈帧的顶部,在运行时是可以改变的(见图示)。

main函数的汇编代码

第一条汇编码pushebp首先保存ebp的值,因为很快将使用它成为帧指针;第二条汇编码mov ebp,esp将ebp赋为当前的栈顶指针,也就是帧指针,从此时开始,ebp就被作为译码所有子实例参数的基址指针使用了;第三条汇编码sub esp,0C0h是将栈(也是栈帧)扩大0C0h大小,但这时并没有在其中填充内容,这样做通常是为了给局部变量留出空间,这里明明没有任何局部变量,那0C0h大小是怎样跑出来的呢,稍后将解释这个问题。

根据惯例,eax、edx、ecx的值由调用方负责储存,即在变量内部这3个寄存器可以随意使用;而ebx、esi、edi的值由被读取方负责储存,使用之前必须将原来的值保存到栈中,这只是为什么接下来的3条代码将这3个寄存器分别压入栈中的理由。

接下来的几条指令是专门用作调试。leaedi,[ebp+var_C0]实际上就是把地址存入到edi中汇编esp是什么意思,地址的值就是刚才留出0C0h大小的区域的最低位置;接着对ecx赋值为30h;对eax赋值为0CCCCCCCCh;最后执行指令rep stosd,这条指令的意思是将stosd这条指令重复ecx(即30h)次,而stosd指令的意思是将eax的值(0CCCCCCCCh)复制到内存中,内存的地址为es:edi,每次执行后edi改变,这条指令合在一起的含义就是将es:edi为起始地址,大小为ecx*4的存储的所有字节均设为0CCh,就是把今天留出的0C0h(30h * 4 =0C0h)的空间全部填为0CCh。

只但是这样做是为了便于调试:0CCh是汇编指令int3的二进制码,这条指令的含义是读取3号中断服务程序,会造成一个断点,如果想体验一下实际的运行效果可以用上面的代码:

intmain()

{

__asmint3;

return0;

}

而将一大片区域都设定作为断点的含义在于:若程序存在漏洞,执行时可能会误执行这片区域中的内容,因为这片区域内容都是0CCh,运行时立即报错,便于看到漏洞,说白了就是在栈中有用的数据里面附着陷阱,一个正确的程序执行是绝不会踏入陷阱中的。

esp故障是什么意思_汇编esp是什么意思_硬盘esp是什么意思

这个过程结束以后,就是之前介绍过的xoreax,eax,如果这个main函数有其它句子,那汇编起来的代码都会在rep stosd与xoeeax,eax之间。接着还原edi、esi、ebx寄存器的值。

此时函数的执行将要基本结束汇编esp是什么意思,之前开辟出的栈帧的使命即将结束,mov esp,ebp将esp恢复到数组调用前的状况,接着恢复ebp,最后返回,整个过程结束。

此外,由于栈帧的建立与传递非常普遍,intel提供了两条简化的汇编指令enter和leave。其中,enterimm,0与push ebp; movebp,esp; sub esp,imm相等价;leave与movesp,ebp; pop ebp相等价。

情况与vc2010下完全相似,没有看出编译器的变化。

main函数的汇编代码

情况与winxp下的相似,但毕竟找到了main函数,却不知为何没有对其进行命名。虽然vc的版本是VS2010pro,但按说编译器应该是同样的。

main函数的汇编代码

与release方式同样,仅是main名称没有识别起来的问题。

以下实验使用的编译命令均为gcc -o testtest.c。

main函数的汇编代码

汇编码见上图,结合前面讲述的栈帧概念很好理解。如果认真注意的话,会看到与vcdebug的汇编码相比,这里没有对edi、esi、ebx的保存与回复操作,而且由于没有用到esp,所以最终也没有movesp,ebp的操作。

我又设优先级为O0(gcc默认的优先级是O1)重新编译了一遍,发现结果是同样的,看来gcc编译时会记录寄存器的使用状况。

我又以优先级O2、O3重新编译,结果如下图(两个结果同样):发现首先mov eax,0变成xoreax,eax,异或运算执行速率要快于传送运算;接着发现这条改进后的指令位置向下挪动,跑到了mov ebp,esp指令的上边,这个理由我就不知道了。

esp故障是什么意思_硬盘esp是什么意思_汇编esp是什么意思

main函数的汇编代码

再来看一个较低版本的,先是O0(O1相同)的(见左下图):

main函数的汇编代码main函数的汇编代码

可以看见比上一个版本情况复杂了不少,而且也有错误信息提醒(见最终一行),说是栈指针(stack pointer)分析失败(sp-analysisfailed),造成这个出错的缘由是因为堆栈不平衡而造成的,在IDA中点击Options->General->Disassembly,将选项stackpointer打勾,汇编码就会显示栈指针的状况,如右上图,最后一条指令retn前为“004”而不是“000”,因此会报错,接下来我们预测一下这段汇编码的意义:

lea ecx,[esp+arg_0]

将esp加4的给与的地址存入ecx,即ecx指向第1个参数

and esp,0FFFFFFF0h

这里调整esp的值,使之末4位0(即按16字节对齐)

push dword ptr[ecx-4]

将最起初esp所指的地址的值(即变量返回地址)入栈

建立栈帧

压入ecx(即old esp+4)保存,这条指令以后开启main函数

由于我们的main函数没有内容,所以直接开始返回操作

汇编esp是什么意思_硬盘esp是什么意思_esp故障是什么意思

恢复ecx(即old esp+4)与ebp

恢复esp然后返回

看到此处,你会发现esp在执行前后是同样的,其实当main函数中发生了跳转指令后,错误提醒就消失了,也许这仅仅IDA的一个bug吧。见左下图:

main函数的汇编代码main函数的汇编代码

可以发现,同一条指令lea esp,[ecx-4]在不同的变量中其栈指针的差异竟然不同,因此你们不必为这个出错提示而在意。

这段代码多起来一个push操作:pushdword ptr [ecx-4]。它是做哪个用的,我没法做如下解释:

首先先知道一下call指令的操作(前面终于看到过,即使是main函数,实际只是被另一个函数使用call调用),call指令将下一条指令的地址(即eip寄存器中的值,就是函数返回地址)压入栈中(即push eip),然后将控制转移到目的地址(即eip=目的地址)。

回忆栈帧的可以得知,栈中函数返回地址标志着调用帧的结束,而建立栈帧第一步压入栈的ebp标志着被调用帧的起初,它们之间产生一条分界线。如右上图黑色的粗线:

而如今在pushebp之前要进行16字节的对齐操作,栈中压入的eip与ebp就或许存在一个空隙,也许是为了确保栈帧格式的完整性,在对齐操作以后,pushebp之前,重新压入一次返回地址,因此减小了一条push dword ptr [ecx-4],ecx-4指向的就是返回地址。注意,我说的也是显然,因为我几次测试也没有发现为什么要压入这个返回地址,只能做这种的猜测。

Mac OS X10.6.4+x64+gc.2.1

main函数的汇编代码main函数的汇编代码

最后分析mac下的状况,如左上图右图(优先级为O0,O1),出现了没有见过的rbp,rsp;其实这是64位的寄存器,它们的作用分别与32位的解释器ebp,esp相对应;其实以前的通用e系列通用寄存器都有与之对应的r系列寄存器,此外intel64位模式还新增了8个通用寄存器(r8-r15),以后有机会我会测试mac下的gcc有没有针对64位作出专门的改进,使用这种新增的寄存器。

右上图为优先级O2、O3的结果,用异或操作代替了传送操作,很简单。

这篇文章展现了一些常见环境下main函数的汇编码,并简单的进行探讨,内容非常粗浅。其实即使能完全理解栈帧的概念,不管未来遇到什么样的变量汇编码,都能轻松突破各种混乱的操作,找到其关键内容,这也正是本文的本意。今后经过进一步的学习,我还将尝试完整地解读各个操作系统的可执行文件的内容,而不仅仅只是一个空的main函数。


本文来自电脑杂谈,转载请注明本文网址:
http://xinshanjie.com/a/jisuanjixue/article-122499-1.html

    相关阅读
    发表评论  请自觉遵守互联网相关的政策法规,严禁发布、暴力、反动的言论

    热点图片
    拼命载入中...