函数栈分析.docx
- 文档编号:10040194
- 上传时间:2023-02-08
- 格式:DOCX
- 页数:9
- 大小:78.24KB
函数栈分析.docx
《函数栈分析.docx》由会员分享,可在线阅读,更多相关《函数栈分析.docx(9页珍藏版)》请在冰豆网上搜索。
函数栈分析
∙首先看看《Computer Systems》一书中关于函数调用的描述:
IA32programsmakeuseoftheprogramstacktosupportprocedurecalls.Thestackisusedtopassprocedurearguments,tostorereturninformation,tosaveregistersforlaterrestoration,andforlocalstorage.Theportionofthestackallocatedforasingleprocedurecalliscalledastackframe.Figure3.16diagramsthegeneralstructureofastackframe.Thetopmoststackframeisdelimitedbytwopointers,withregister%ebpservingastheframepointer,andregister%espservingasthestackpointer.Thestackpointercanmovewhiletheprocedureisexecuting,andhencemostinformationisaccessedrelativetotheframepointer.
SupposeprocedureP(thecaller)callsprocedureQ(thecallee).TheargumentstoQarecontainedwithinthestackframeforP.Inaddition,whenPcallsQ,thereturnaddresswithinPwheretheprogramshouldresumeexecutionwhenitreturnsfromQispushedonthestack,formingtheendofP’sstackframe.ThestackframeforQstartswiththesavedvalueoftheframepointer(i.e.,%ebp).followedbycopiesofanyothersavedregistervalues.
ProcedureQalsousesthestackforanylocalvariablesthatcannotbestoredinregisters.Thiscanoccurforthefollowingreasons:
1.Therearenotenoughregisterstoholdallofthelocaldata.
2.Someofthelocalvariablesarearraysorstructuresandhencemustbeaccessedbyarrayorstructurereferences.
3.Theaddressoperator‘&’isappliedtooneofthelocalvariables,andhencewemustbeabletogenerateanaddressforit.
Finally,Qwillusethestackframeforstoringargumentstoanyproceduresitcalls.
Asdescribedearlier,thestackgrowstowardloweraddressesandthestackpointer%esppointstothetopelementofthestack.Datacanbestoredonandretrievedfromthestackusingthepushlandpoplinstructions.Spacefordatawithnospecifiedinitialvaluecanbeallocatedonthestackbysimplydecrementingthestackpointerbyanappropriateamount.Similarly,spacecanbedeallocatedbyincrementingthestackpointer.
下面进入测试过程,我的测试代码如下:
#include
void test(int i)
{
char output[8];
strcpy(output, "abcdefg");
}
int main()
{
test(10);
return 0;
}
∙
今天看了ProgrammingfromtheGroundUp的函数(Page53)调用一章,对汇编语言函数调用有了一些了解。
在汇编语言中需要调用函数时要call这个函数名,函数的执行过程如下:
准备执行
在主程序中每次调用函数时,先依次把各参数以相反的顺序入栈;
然后callfunc_name,这里call要做两件事:
一是把函数的返回地址入栈,二是让指令执行指针%eip指向函数开始处。
开始执行
现在函数要开始执行了,但它执行函数代码前还要做一点小事,首先把原来的基地址寄存器%ebp值入栈,因为在程序执行中%ebp要另作它用,接着堆栈指针%esp的值复制给%ebp,此后在函数执行中%ebp一直保持不变,可以由此寻址获得函数参数。
pushl%ebp
movl%esp,%ebp
下面开始执行函数代码了。
函数先要把它的局部变量保存在栈中,这很简单。
比如要保存一个long型数据,只要把%esp指针向下移动4个字节(因为栈增长方向是由高地址到低地址),再根据%esp把该数据移入.下面是保存两个局部变量long后的堆栈内容:
Parameter#N<---N*4+4(%ebp)
...
Parameter2<---12(%ebp)
Parameter1<---8(%ebp)
ReturnAddress<---4(%ebp)
Old%ebp<---(%ebp)
LocalVariable1<----4(%ebp)
LocalVariable2<----8(%ebp)and(%esp)
从上可以看出通过%ebp基地址寻址可以访问所有的函数参数和局部变量.当然也可以不用%ebp而用其它的寄存器进行同样的基地址寻址。
但对于x86结构使用%ebp寄存器可能会更快一点。
执行结束:
现在函数执行要结束了,在它返回之前,还要做下面几件事:
1.把函数的返回值存放在通用寄存器%eax中,供外部使用
2.把%esp指向函数开始执行的位置,即movl%ebp,%esp
3.在函数返回ret之前,要还原ebx,即popl%ebp
movl%ebp,%esp
popl%ebp
ret
从上也可以看出,当%esp指向函数开始执行的位置后,局部变量也就没有意义了(因为此时esp指向的栈地址高于那些局部变量的地址)。
函数执结束ret返回后,要把call时push的所有函数参数也pop出来(或者直接在%esp上加参数的个数的4倍,如果不需要再使用这些参数值的话)。
addl$8,%esp #因为有两个参数8个字节
下面是一段代码power.s,计算2^3+5^2
#====================asmcodebegin===================
#PURPOSE:
Programtoillustratehowfunctionswork.Thisprogramwillcompute
#thevalueof2^3+5^2
.section.data
.section.text
.global_start
_start:
pushl$3
pushl$2
callpower #调用power(2,3)函数
addl$8,%esp #函数结束后,堆栈指针%esp要返回到参数之前,即加上4*2
pushl%eax #把第一次计算的power(2,3)的结果(保存在%eax中),入栈保存
pushl$2
pushl$5
callpower #调用power(5,2)
addl$8,%esp #函数结束后,堆栈指针%esp要返回到参数之前,即加上4*2
popl%ebx #从堆栈中取出第一次计算的power(2,3)结果,放在%ebx中
addl%eax,%ebx #两者相加保存在%ebx中
movl$1,%eax
int$0x80
#系统调用结束,调用号保存在%eax中(为1),返回值保存在%ebx中(为计算结果33)
#functionlongpower(longa,longb) =a^b
.typepower,@function
power:
pushl%ebp #%ebp入栈
movl%esp,%ebp #%ebp中保存%esp值,用作下面的基地址寻址
subl$4,%esp
movl8(%ebp),%ebx #把参数a的值赋给%ebx
movl12(%ebp),%ecx #把参数b的值赋给%ecx
movl%ebx,-4(%ebp) #下面是对a做b次循环计算,把中间结果存放在-4(%ebp)中
power_loop_start:
cmpl$1,%ecx
jeend_power
movl-4(%ebp),%eax
imull%ebx,%eax
movl%eax,-4(%ebp)
decl%ecx
jmppower_loop_start
end_power:
movl-4(%ebp),%eax #返回值给%ebx
movl%ebp,%esp #%esp回到函数执行开始位置
popl%ebp #ret前要还原函数执行初的%ebp值
ret
#=================asmcodeend====================
#aspower.s-opower.o
#ldpower.o-opower
#./power
#echo$?
33
函数调用约定描述了函数传递参数方式和栈协同工作的技术细节。
不同的操作系统、不同的语言、不同的编译器在实现函数调用时的原理虽然基本相同,但具体的调用约定还是有差别的。
这包括参数传递方式,参数入栈顺序是从右向左还是从左向右,函数返回时恢复堆栈平衡的操作在子函数中进行还是在母函数中进行。
表4-1-1列出了几种调用方式之间的差异。
4.1.5 函数调用约定与相关指令
函数调用约定描述了函数传递参数方式和栈协同工作的技术细节。
不同的操作系统、不同的语言、不同的编译器在实现函数调用时的原理虽然基本相同,但具体的调用约定还是有差别的。
这包括参数传递方式,参数入栈顺序是从右向左还是从左向右,函数返回时恢复堆栈平衡的操作在子函数中进行还是在母函数中进行。
表4-1-1列出了几种调用方式之间的差异。
表4-1-1 调用方式之间的差异
C
sysCall
StdCall
BASIC
FORTRAN
PASCAL
参数入栈顺序
右→左
右→左
右→左
左→右
左→右
左→右
恢复栈平衡操作的位置
母函数
子函数
子函数
子函数
子函数
子函数
表4-1-2 函数调用约定
调用约定的声明参数入栈顺序恢复栈平衡的位置
__cdecl右→左母函数(C++/C函数默认的调用规范,参数从右向左一次传递并压入堆栈)
__fastcall右→左子函数(快速调用,该规范修饰的函数的实参将被直接传递到CPU的寄存器中而不是内存堆栈中)
__stdcall右→左子函数(WINAPI调用规范)
如果要明确使用某一种调用约定,只需要在函数前加上调用约定的声明即可,否则默认情况下,VC会使用__stdcall的调用方式。
本篇中所讨论的技术在不加额外说明的情况下,都是指这种默认的__stdcall调用方式。
除了上边的参数入栈方向和恢复栈平衡操作位置的不同之外,参数传递有时也会有所不同。
例如,每一个C++类成员函数都有一个this指针,在Windows平台中,这个指针一般是用ECX寄存器来传递的,但如果用GCC编译器编译,这个指针会作为最后一个参数压入栈中。
注意:
同一段代码用不同的编译选项、不同的编译器编译链接后,得到的可执行文件会有很多不同。
因此,请您在进行后续实验前务必注意实验环境的描述,否则所得结果可能会与实验指导有所差异。
函数调用大致包括以下几个步骤。
(1)参数入栈:
将参数从右向左依次压入系统栈中。
(2)返回地址入栈:
将当前代码区调用指令的下一条指令地址压入栈中,供函数返回时继续执行。
(3)代码区跳转:
处理器从当前代码区跳转到被调用函数的入口处。
(4)栈帧调整:
具体包括。
保存当前栈帧状态值,已备后面恢复本栈帧时使用(EBP入栈)。
将当前栈帧切换到新栈帧(将ESP值装入EBP,更新栈帧底部)。
给新栈帧分配空间(把ESP减去所需空间的大小,抬高栈顶)。
对于__stdcall调用约定,函数调用时用到的指令序列大致如下。
调用前push参数3;假设该函数有3个参数,将从右向左依次入栈push参数2push参数1call函数地址;call指令将同时完成两项工作:
a)向栈中压入当前指令在内存中的位置,即保存返回地址。
b)跳转到所调用函数的入口地址函;数入口处pushebp;保存旧栈帧的底部movebp,esp;设置新栈帧的底部(栈帧切换)subesp,xxx;设置新栈帧的顶部(抬高栈顶,为新栈帧开辟空间)
上面这段用于函数调用的指令在栈中引起的变化如图4.1.7所示。
图4.1.7 函数调用时系统栈的变化过程
题外话:
关于栈帧的划分,不同参考书中有不同的约定。
有的参考文献中把返回地址和前栈帧EBP值做为一个栈帧的顶部元素,而有的则将其做为栈帧的底部进行划分。
在后面的调试中,您会发现OllyDbg在栈区标示出的栈帧是按照前栈帧EBP值进行分界的,也就是说,前栈帧EBP值既属于上一个栈帧,也属于下一个栈帧,这样划分栈帧后,返回地址就成为了栈帧顶部的数据。
出于前后概念一致的目的,在本书中将坚持按照EBP与ESP之间的部分做为一个栈帧的原则进行划分。
这样划分出的栈帧如图4.1.7最后一幅图所示,栈帧的底部存放着前栈帧EBP,栈帧的顶部存放着返回地址。
划分栈帧只是为了更清晰地了解系统栈的运作过程,并不会影响它实际的工作。
类似地,函数返回的步骤如下。
(1)保存返回值:
通常将函数的返回值保存在寄存器EAX中。
(2)弹出当前栈帧,恢复上一个栈帧。
具体包括:
在堆栈平衡的基础上,给ESP加上栈帧的大小,降低栈顶,回收当前栈帧的空间。
将当前栈帧底部保存的前栈帧EBP值弹入EBP寄存器,恢复出上一个栈帧。
将函数返回地址弹给EIP寄存器。
(3)跳转:
按照函数返回地址跳回母函数中继续执行。
还是以C语言和Win32平台为例,函数返回时的相关的指令序列如下。
addesp,xxx;降低栈顶,回收当前的栈帧popebp;将上一个栈帧底部位置恢复到ebp,retn;这条指令有两个功能:
a)弹出当前栈顶元素,即弹出栈帧中的返回地址。
至此,;栈帧恢复工作完成。
b)让处理器跳转到弹出的返回地址,恢复调用前的代码区
按照这样的函数调用约定组织起来的系统栈结构如图4.1.8所示。
题外话:
Win32平台下有很多寄存器,Intel指令集中的指令也有很多,现在立刻一一介绍它们无疑相当于给已经满头雾水的您再浇一桶冷水。
虽然这里仅仅列出了3个寄存器和几条指令的作用,但只要您完全理解它们,就一定能顺利理解本书的后续章节,因为它们是栈溢出利用的关键,也是计算机架构的核心所在。
当然,入门以后要想提高到一个新的层次,用《IBMX86汇编》或者《Win32汇编》恶补一下汇编知识是非常必要的。
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 函数 分析
![提示](https://static.bdocx.com/images/bang_tan.gif)