pwn漏洞挖掘技术之栈溢出

说点题外话,推荐一本二进制漏洞挖掘奇书:《0day安全:软件漏洞分析技术》,这本书从技术到进阶讲了与漏洞挖掘有关的知识。如果要在ctf的pwn方向或者是在网络安全方向的二进制漏洞挖掘中有所提升,这本书一定要看。下面回归正题。
栈在内存中是以什么形式存在的?首先要对内存有一定的了解。内存按功能大致可划分为代码区、数据区、堆区、栈区。其中栈区是内存中功能执行的一部分。通常来说,栈的操作有两种,压栈(PUSH)和弹栈(POP),遵循后进先出的原则(Last In First Out,简称LIFO)。要挖掘栈溢出漏洞,我们要对栈在内存中的实现细节十分了解。什么时候内存才会用到栈?程序员写一个普通程序,这个程序运行时调用函数的过程中在栈中是以栈帧的形式出现的。一般的小白在这里听得可能会一头雾水,没关系,下面听我娓娓道来。
你可以把栈想象成一摞摞盘子,不同的盘子代表不同的数据。这里有一张空桌面,把桌面表示为栈底。桌面上什么都没有,我们这时放入一个个盘子,叠成一摞,相当于压栈(PUSH)操作,把数据存入栈中。你也可以从桌面上拿出盘子,相当于在函数执行结束时,栈会执行弹栈(POP)操作。很容易想象,盘子的放置与拿出也满足后进先出(Last In First Out,简称LIFO)原则。但是你可能会问?栈跟函数调用有什么关系?没关系,下面继续讲解。
我们以一个程序作为例子。

#include<stdio.h>
void function(int a,int b){
	int c;
	c=a+b;
	printf("%d",c);
}
int main(int argc,char **argv){
	int a=1;
	int b=2;
	function(a,b);
	return 0;
}

当上面程序执行时,栈中发生了什么?
请注意,main函数在该程序中是程序入口,所以栈中首先从main函数开始变化。这里补充一个很多小白可能不知道的知识,那就是不管是什么编程语言,当函数被调用时,栈中会形成一个栈帧,存储该函数的相关数据。而栈在内存中是从高地址向低地址生长(在栈空间内,最高地址称为栈底,最低地址称为栈顶。可以理解为放在桌面上最底下的盘子是高地址,这是栈底;最上面的盘子是低地址,这是栈顶),当程序进入main函数入口时,在栈空间的高地址(也就是栈底上方)形成了一个main栈帧,专门存放main函数的数据。一个函数的调用只对应一个栈帧。那么栈帧中存放的是什么,以什么样的顺序存放呢?通常来说,当程序员调用一个函数时,开辟栈帧后最先进入栈帧的是函数的参数,而且是从右向左依次进入。在这里,main函数的参数有两个,分别是int argc和char **argv,argv最先进入main栈帧,其次是argc。然后是局部变量入栈,将a和b依次入栈(PUSH),什么意思呢?如果a,b已经赋值,就把在main栈帧中开辟两个4个字节的内存空间,将a和b的数据依次入栈,如果a、b没有被赋值,就直接开辟内存空间。
main函数继续执行,接下来EIP指针寄存器指向function()函数(这里补充一下,EIP指针是存储代码区的机器指令地址,即EIP指向下一个要执行的指令,且随着程序的执行不断变化),在main栈帧之上有开辟了一个栈帧,这里我们把它叫做function栈帧,在function栈帧中,同样的,main函数的地址入栈(通常叫做返回地址入栈),b和a依次进入栈帧,然后在function栈帧中为局部变量c开辟4个字节空间,CPU根据c=a+b将c的值运算出来后将c的值入栈(PUSH)。
从程序代码中我们知道,function函数中还有一个函数printf,调用它的时候也要在栈内存空间中开辟栈帧,我们把它叫做printf栈帧。function函数的调用还没结束,就要执行printf函数,所以要在function函数的上方开辟一个printf栈帧,此时,function函数地址入栈,参数c入栈,然后格式控制符“%d”入栈(要注意,格式控制符也属于参数)。
以上就是function函数调用时的一系列压栈(PUSH)操作(即创开辟一系列的栈帧存放数据),function函数在调用完printf函数后就调用完毕了,怎么弹栈(POP)呢?弹栈后数据去哪了呢?
printf函数调用完毕时,要将printf栈帧弹出,弹出后的数据将送往寄存器。在把printf栈帧弹出后,在function栈帧上方(也就是原来printf栈帧下方,有一个返回地址(在本例中,它存放着function函数的地址,有助于printf函数弹栈后将指针指向function栈帧,继续对function栈帧执行弹栈操作),它是栈帧之间的分界线),根据它使指针指向function栈帧,然后function栈帧弹栈。同样的main栈帧和function栈帧之间也有返回地址,指针指向main栈帧,然后main栈帧弹栈。

以上就是程序执行时栈内部栈帧压栈(PUSH)和弹栈(POP)的实现细节。
参考文献《0day安全 软件漏洞分析技术》

猜你喜欢

转载自blog.csdn.net/ConlinderFeng/article/details/106178740
今日推荐