基础栈溢出复习 四 之 BROP
当然这个也是《 基础栈溢出及其利用方式的》系列的一部分。
什么是BROP
那么我也只是在这里尽量让大家先明白,什么是BORP,以及BROP的攻击原理,以及在后面放一个最近CTF中,及HCTF –出题人跑路了的PWN题的详细分析。
BROP 原文:Blind Return Oriented Programming (BROP) Website
其核心要义就是,通过ROP的方法,远程攻击一个应用程序,劫持程序控制流程。其难点在于,我们并没有程序的源代码以及二进制程序。
详细的东西,我也不想再继续搬了,mctrain在文章讲得已经非常不错了,我在这里提供我的drop地址,不过大家少用阿,这玩意儿吃流量 Blind Return Oriented Programming (BROP) Attack - 攻击原理
大概总结下
看了 Drops的文章,我们大概可总结一下攻击流程
- 如果有Canary 防护,需要通过brute-force暴力破解或者 作者提出的方法“stack reading”
- 寻找stop gadget或者叫 hang gadget,这gadgaet使得程序进入了无限循环,并且hang,使得攻击者保持连接状态。(如blocking的系统调用 sleep)
- 寻找可以利用的,即potentially useful gadgets。这里指useful指的是具有某些功能,并不会造成crash的gadget
- 远程dump内存,(当然如果有格式化串,可以利用那也简便狠多,可以参考安全客文章《格式化字符串blind pwn详细教程》),如果没有,我们可能需要一个write的系统调用,传入一个socket文件描述符。
write(int sock,void *buf,int len)
转化成4条汇编指令就是依次对应的是1
2
3
4pop %rdi ret
pop %rsi ret
pop %rdx ret
call write ret%rdi
->sock%rsi
->buf%rdx
->len
在栈上构造好这个四个gadget的内存地址,依次执行顺序调用就可以了(这当然是在我们解决掉Canary之后) - 在dump 内存的过程中,
pop %rdx ret
这样的gadget也许不容易找到,所以作者又提出另一种方法,利用 strcmp函数,达到相同效果 - 所以之后的任务是:
- 寻找BROP Gadget(注:什么是BROP Gadget 可在Drops仔细阅读)
- 找到对用PLT项
HCTF 之 出题人失踪了 (brop)
了解了,攻击流程,以及攻击方法,我们就可以尝试做这个题目了。杭电的师傅已经,把源码公开在github上了。我们可以自己拿下来编译一下。 - *已知信息**
比赛的时候,题目给了ip和端口 其他任何信息都没有。但是后面给出了bof的buffer大小作为提示。而且题目没有开Canary防护,所以我们并不需要突破Canary1
2
3
4
5
6gdb-peda$ checksec
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : Partial
经过测试,当输入的字符超过72字节,程序就不会再打印 No password, No game了。
首先寻找 stop gadget
这个地方,muhe师傅交了我一种方法,那就是利用pwntools
的异常处理来检测。因为我们需要return address指向一块代码区域,当程序的执行流跳到那段区域之后,程序并不会crash,而是进入了无限循环,这时程序仅仅是hang在了那里,攻击者能够一直保持连接状态。于是,我们把这种类型的gadget,成为stop gadget,这种gadget对于寻找其他gadgets取到了至关重要的作用。
1 | from pwn import * |
可能会得到多个gadget,找个好用的就可以了。
找useful gadget
由于这个题目实质是调用puts函数,不是write函数,所以我们并不需要三个gadget,只需要1个 pop rdi;ret
就足够了
%rdi,%rsi,%rdx,%rcx,%r8,%r9 用作函数参数,依次对应第1参数,第2参数。。。
那么如何得到一个 pop rdi;ret
呢?我们设想,在64位的ELF中,通常存在一个pop r15;ret 对应的字节码为41 5f c3。后两字节码5f c3对应的汇编为pop rdi;ret。
如果有存在一个地址 addr,满足
1 | Payload1 = 'a'*72 + l64(addr-1)+l64(0)+l64(ret) |
ret是一个返回函数,且有输出信息。那么我们就可以得到addr,即pop rdi;ret
在64位ELF中,通常存在一个pop r15;ret,对应的字节码为41 5f c3。后两字节码5f c3对应的汇编为pop rdi;ret。
如果addr就是指向的5f,那么addr-1就是指向41,Payload1 = ‘a’*72 + l64(addr-1)+l64(0)+l64(0x400711) ,41和5f组成一个指令,pop r15出来,后面接返回地址0x400711,栈平衡满足要求。Payload2 = ‘a’*72 + l64(addr)+l64(0)+l64(0x400711) ,pop rdi出来,也能正常返回。Payload3 = ‘a’*72 + l64(addr+1) +l64(0x400711) ,addr+1指向c3即ret,直接返回后返回0x400711
于是,我先去寻找这么一个ret,返回有输出信息。
1 | def ret_addr(addr): |
有了 ret,于是我可以开始寻找 pop rdi;ret
了。
1 | def get_useful_gadget(addr): |
找到pop rdi;ret
了 ,gadget 的需求我们达到了。
dump 程序
照理,这个时候我们应该可以开始dump程序了,但是紧接着一个问题来了,我们不知道put_plt
的地址。我们知道,puts函数能打印字符串,于是我们设想构造一个payload来验证得到的是不是puts_plt
的地址,例如
1 | payload = 'A'*72 +p64(pop_rdi_ret)+p64(0x400000)+p64(addr)+p64(stop_gadget) |
如果打印前四个\字符为 \x7fELF,则addr为puts_plt
。
我找到的是 pop_rdi_ret = 0x4005d6
有了 gadget 和put_plt,我们就可以着手dump程序了。
首先我们需要构造一个leak的函数:
1 | payload = 'a'*72 + p64(pop_rdi_ret) +p64(addr) + p64(puts_plt) +p64(stop_gadget) |
这样就可以开始leak,但是还有一个问题,如果对一个\x00的地址进行leak,返回是没有结果的,因此如果返回没有结果,我们就可以确定这个地址的值为\x00,所以可以设置为\x00然后将地址加1进行dump。
所以我们需要一个判断:
1 | if data == '': |
基本这样,我们就可以dump文件了,当文件dump下来以后,我们就能很容易的得到一些got信息,那样我们可以更容易的去起shell
只要分别从0x400000和0x600000开始dump就可以。
leak 获取libc
当我们已经获取了got表信息后,那么我就可以进一步去leak函数,用search_Libc或者自己收集的libc 库查找相应的libc。那么我就可以进一步查询偏移,就可以构造payload 起shell了。
leak payload 也是相似的,就不重复了。
当然,我们这里也可以利用Pwntools的工具 Dynelf 来leak查询system地址,然后找一个地址写入/bin/sh\x00
。
最后一步就可以起shell了。
剩下的内容基本和我们一般的leak info 题目是一样的。
与我前面的文章,PlaidCTF 2013: ropasaurusrex的利用方式基本相同,由于篇幅原因就不继续写下去了。
参考链接
- HCTF 源码 https://github.com/zh-explorer/hctf2016-brop/blob/master/main.c
- muhe博客 http://o0xmuhe.me/2017/01/22/Have-fun-with-Blind-ROP/
- 以及队内 师傅的Writeup