来自手把手教你栈溢出从入门到放弃的学习笔记

15 篇文章 0 订阅

这篇文章用于我个人理解知识点,如有各种纰漏可以在通过各种渠道联系我修正
来自手把手教你栈溢出从入门到放弃(下)的学习笔记

在这里插入图片描述

在我理解看来就是栈在调用函数的时候,被调用的函数(如scanf ,gets 等等),会被暂时的压入栈顶,使栈的原有内容(调用函数的部分)被向下压,在调用结束后,栈顶的函数(如scanf ,gets 等等),会再次从栈中退出,使栈中的原有内容恢复原来的地址。

从下面这张图也可以看出,大致就是讲了这么一个东西,(就是注意callee和caller这两个看起来差不多的单词的区别,callee是指被调用的函数,(gets等),caller是指调用函数的部分(栈中原有的内容,我们这里理解为main函数等可以更具体一些))

在这里插入图片描述

这里介绍了3个寄存器,esp(栈顶),ebp(栈底),eip(当前即将执行的程序指令的地址)
特点是如下,esp在压入栈和退出栈时发生变化。(上文已经解释过)
eip在cpu读取过后随之指向下一条指令。

这样变化的用意,是为了让调用函数的状态保存起来,同时创建被调用函数的状态
简单来讲就是为了让main函数(等)的状态得以保存而不至于丢失,并且创建调用(gets,scanf等)被调用函数的调用状态(上文解释过)

同时调用函数(gets,scanf等)时,如果有参数,那么它们的参数将按照逆序被压入,比如先调用gets,后调用scanf,那么它们的参数会是会是先将scanf的参数压入,然后将gets的参数压入,gets的参数会在scanf的上面
如果没有参数,那么就没有这一步。

在这里插入图片描述

在上一步之后,(将gets()的参数压入栈内后),将main函数的下一条指令地址做为返回地址压入栈内,这样main函数的eip信息得以保存
在这里插入图片描述
再将当前栈底的值压入栈内,并且将栈底的值更新为栈底,这样main的基地址得到保存,同时ebp变为get的栈底。

在这里插入图片描述
将main函数的栈底压入栈内保存之后,并且将当前的的栈顶传递到栈底寄存器内,然后就是将被调用函数(gets等)的局部变量数据压入栈内

在这里插入图片描述

在压入栈的过程中,栈顶的值不断减小,压入栈的数据(gets等函数的参数,返回地址,main函数的栈底,以及局部变量)共同构成了gets函数的状态,在发生调用时,还会将gets函数的指令地址保存到eip中。让程序依次执行。
也就是说函数调用结束的核心是一个将调用过的函数内容丢弃,并且将栈顶恢复回未调用状态的过程。
并且要注意被调用函数(gets等),会从栈内直接被弹出,然后栈顶会指向gets的栈底。
在这里插入图片描述

然后将栈底存储的main函数的栈底从栈内弹出,保存的ebp寄存器内,这样
在这里插入图片描述

来自手把手教你栈溢出从入门到放弃(上)的学习笔记

相较于(上)多介绍了几个寄存器,分为两类,通用寄存器和特殊寄存器
区别是通用寄存器的适用范围较大,而特殊寄存器一般只能被特定的指令适用,而且不能用来存储数据。
通用寄存器有eax,ebx,ecx和edx
每个寄存器都有自己较为固定的独特用途(如下)
例如ebx被称为基地址寄存器,(Base)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

简单介绍必要的汇编指令 ,32x86架构下的汇编语言有intel和AT&T两种格式,
两者的主要差别如下,intel格式 ,寄存器名称是数值前无符号,
AT&T格式,寄存器名称前加%,数值前加$
在这里插入图片描述
最常用的汇编指令如下:
mov :数据传输指令,将SRC传递给DST
写为 mov DST,SRC;

push:压入栈堆指令,将SRC压入栈内
写为 push SRC;

pop: 弹出栈堆指令,将栈顶的数据弹出并保存至DST
写为 pop DST;

lea: 取地址指令,将MEM的地址保存至REG
写为 lea REG,MEM;

add/sub 加/减法指令,将运算结果存至DST,
写为 add/sub DST,SRC

and/or/xor 按位进行与/或/异或,将运算结果保存到DST
写为 and/or/xor DST,SRC;

call 调用指令,将当前的eip压入栈顶,并将PTR存入eip
写为 call PTR

ret 返回指令,操作为将栈顶数据弹出至eip
格式为 ret;

在这里插入图片描述
在这里插入图片描述
接下来介绍ROP方法
意为修改返回地址,让其指向内存中已有的一段指令
相较于(上)中的return2libc,优势在于,有时目标函数在内存无法找到,有时目标操作没有特定的函数用来适配,
这时候我们就可以通过寻找多个指令片段(gadget)拼凑出一系列操作来达成我们的目的。
构造方式如下:padding+address of gadget
在这里插入图片描述
同时,如果你想连续执行若干段指令(也就是多个gadget),那么就需要每个gadget执行之后有连接,能够将控制权交给下一个gadget。
所以这要求我们的每个gadget的最后一步都是ret指令。这样程序的控制权(eip)才能得到切换。

在这里插入图片描述

那么我们如何用多个gadget实现我们想要的效果呢,需要解决以下几个问题
常见情况下我们使用ROP是实现一次系统调用,linux对应的汇编指令是 int0x80.当我们执行这条指令时,被调用的函数编号会被存入eax(指eax寄存器),调用的参数应按照顺序存入ebx,ecx,edx,esi,edi中。
例如,编号125所对应的函数
mprotect(void *addr,size_t len,int prot)
可用该函数将栈的属性改为可执行,这样就可以使用shellcode了。加入我们想利用系统调用执行这个函数,exa应当为125,ebx应当为内存栈的分段地址(通过调试工具确定),0x1000(需要修改的空间长度,也许更长也许更短),7(RWX权限,也就是可读可写可执行)

在这里插入图片描述

那么我们如何寻找对应的指令片段(以ret结尾的gadget) ,比如ropgadget,rp++,ropeme等(我用的是ropgadget),甚至可以用grep等文本匹配工具在汇编指令中搜索ret然后进一步筛选
然后是如何传入系统调用的参数(比如/bin/sh)
我们首先将参数传输进寄存器,所以可以先用pop指令将栈顶数据弹入寄存器。如果在内存中能找到直接可用的数据,也可以用mov指令来进行传输,不过写入数据在pop要比先搜索mov来得简单
那么如果使用pop来传输调用参数,就需要在溢出的数据里面包含这些参数。所以

在这里插入图片描述
在调用mprotect()为栈开启可执行权限之后,我们希望能够执行一段shellcode,所以要将shellcode也加入我们的溢出数据,并且将shellcode的开始地址加到int 0x80的gadget之后。但是我们需要确定shellcode在内存中的确切地址,这个问题我们可以使用push esp(将esp压入栈顶)这个gadget(假如可以找到的话)。
在这里插入图片描述

我们假设在内存中找到了以下几条指令,那么我们就可以用如下格式的payload
对于所有包含 pop 指令的 gadget,在其地址之后都要添加 pop 的传输数据,同时在所有 gadget 最后包含一段 shellcode,最终溢出数据结构应该变为如下格式
payload : padding + address of gadget 1 + param for gadget 1 + address of gadget 2 + param for gadget 2 + … + address of gadget n + shellcode
(param)
也就是填充字符+gadget1的地址+gadget1的参数(也就是上文所说的pop的的传输数据)+…+shellcode

在这里插入图片描述

相比较上面的包含多个gadget的溢出数据(修改前)

首先,这里为了简单,假定输入的溢出数据不受\x00字符的影响,所以payload可以直接包含
\x7d\x00\x00\x00(也就是我们传给eax的参数125)。如果希望实现更为真实的操作,可以用多个gadget通过运算得到上述参数。
比如下述三条gadget
pop eax;ret #将栈顶的数据弹出并保存到eax中(栈顶为0x1111118e)
pop ebx;ret #将栈顶的数据弹出并保存到ebx中(栈顶为0x11111111)
sub eax ,ebx;ret #eax减去ebx,结果保存到eax中。
然后我们就可以通过拼接溢出数据,输入到程序中来让程序调用栈开启可执行权限,从而达到执行我们的shellcode的目的,同时,因为ROP方法的灵活性,我们不需要痛苦的试探shellcode的起始地址。

在这里插入图片描述

但是以上都是我们假设的结果,实际过程不会那么顺利能够搜索到这些gadget,所以需要注意以下两个方面
第一,很多时候我们并不能一次性凑齐理想的gadget,这时候我们就要通过数据地址的偏移,寄存器之间的数据传输等方法来完成目的。比如下面这个例子
我们找不到pop ebx;ret #将栈顶的数据弹出并保存到ebx,并且使用ret,将栈顶数据弹出至eip(是指ebx的栈顶)
但是假如可以找到下面的gadget
mov ebx,eax;ret #将eax传递给ebx,并且使用ret指令
然后将其与pop eax;ret #将的栈顶弹出并且保存到eax
把这两条指令结合起来,(后一条指令在前)
先执行pop eax;ret就是将栈顶弹出并且保存到eax,然后执行mov ebx,eax;ret,然后将eax传递给ebx,
所以结合起来就与pop ebx无异。
这里再次强调ret在操作上就是将栈顶数据弹出到eip中,(而eip是什么前面已经解释过了)
pop ebx;ret,就是将栈顶的数据保存到ebx中,然后ret再将ebx的(栈顶)数据弹出到eip中,从而如果ebx的(栈顶)数据使我们要的东西,那么就可以通过执行这条我们要的指令达成目的。
而且这样的结合还可以绕过\x00截断的问题。
在这里插入图片描述

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值