zoukankan      html  css  js  c++  java
  • hctf2016 fheap学习(FreeBuf发表的官方解法)

    目录

    如何在二次释放前修改函数指针

             修改函数指针流程

    如何获得进程的加载基址

             puts函数的调用

    如何获取system函数地址

             说一下用DlyELF函数

    如何调用system函数

             ROP需要的栈布局

             read函数的妙用

             参数”/bin/sh”如何传递过去

    参考资料

    官方提供的解法还是有一些意思的。对于我来说又多了一种见识。


    如何在二次释放前修改函数指针

    修改函数指针流程

    分配2个0x4字节长度的字符串后内存布局

    图1 分配2个0x4字节长度的字符串后内存布局

    删除3次字符串后内存布局,3次,而不是3个

    图2 删除3次字符串后内存布局

    是删除3次,而不是删除3个字符串,str0被删除了两次,这就让str0有了一个回指str1的指针,再被分配出去之后,str1依然能够在fastbin中链接上str0的空间。

    如何修改release指针

    图3 如何修改release指针

    让人惊讶的事情发生了,不是吗。由于多删除了一次str0,使得glibc以为fastbin队列里面有三个空闲块,它才不管这三个块是不是同一个呢,只要指针能引用就


    如何获得进程的加载基址

    puts函数的调用

      我们都知道,puts能够输出一个字符串到屏幕,那么如果我们能调用它,并传给它一个指针,它就会认为这个指针指向一个字符串,然后打印给我们看。如果我们给的这个指针指向的地址包含了一个函数地址,那么我们自然能获得函数地址了啊。

      作者是如何做的呢?

      调用puts的方法就是修改release指针,用str1的数据形式将release_ptr最低字节修改为0x2d。为什么只修改最低一个字节就可以了呢?我们看原始函数的地址和调用puts的指令地址就知道了。

    图4 原始释放函数的地址

    图5 调用puts的指令地址

      可以看到,两个地址只有最低一个字节不同。

      然后,我们就delee(0)可以看到puts把str0打印出来了,将release_ptr的值当做字符串打印出来了。

      然后减掉0xd2d,就可以得到进程的加载基址了。


    如何获取system函数地址

      作为分析来说直接用本地libc库来定位比较方便,目前与上篇文章一样。

      说一下用DlyELF函数。

      DlyELF是pwn库提供的一个函数,用来定位库函数在进程内的地址。

      那么如何使用呢?

        1.  首先我们要提供一个leak(addr)函数,这个函数要能够实现:

          1)  能够读取addr处至少一个字节数据

          2)  能够多次调用不至于崩溃

        2.  要将程序中的信息传递给DlyELF

        3.  使用方式:

          DynELF(leak_addr, elf=ELF('./fheap'))

        4.  leak_addr函数的实现:

    #参考FlappyPig的做法,结合官方提供脚本中的函数
    def leak(leak_addr):
        create(4, 'x00')
        index0 = 9
        #addr是进程基址
        printf_plt = 0x9d0 + addr
        #print "printf_plt:", hex(printf_plt)
        payload = ""
        payload += ("%%%d$s--..--"%(index0)).ljust(0x18, 'a')
        payload += l64(printf_plt)[:3] + "x00"
        create(0x20, payload)
        target.recvuntil('quit')
        target.sendline('delete ')
        target.recvuntil('id:')
        target.sendline('0')
        target.recvuntil('sure?:')
    
        padding = "yes.aaaa"
        padding += l64(leak_addr)
        target.sendline(padding)
    
        data = target.recvuntil('--..--')[:-6]
        data += "x00"
        delete(1)
        return data

    如何调用system函数

      先delete(1),然fastbin回到图2状态。以便我们修改函数指针。

      这次作者要调用0x11dc处的指令,我们来看看,这个地址的指令能做什么事。

    图6 0x11DC处地址的指令

      加上ret_addr,一个5个参数,为什么要弹5个参数呢?

      我们想一想,当修改release_ptr直接调用0x11DC偏移处的指令时候,rbp和rsp并没有回复到进函数时候的初始值。初始值是怎么样的呢?

    图7 delete_string函数的初始栈布局

      rsp指向偏移120,buf指向偏移110,在删除字符串进行确认时候,输入的字符串前八个字节是”yes aaaa”,再加上调用release_ptr用的是call方式压栈一个,正好会弹出4个参数,然后”yes aaaa”之后的值会被当做ret_addr给rip。

      那么作者下一步调用的是哪个函数呢?

      来看看作者构造的ROP需要的栈布局。

    ROP需要的栈布局

    图8 ROP链的构造

        rip从0到6依次执行

    read函数的妙用

        参考图8,read将system读入到atoi的got表项,而这个表项随后会被调用。起始就调用了我们写入的system地址

    参数”/bin/sh”如何传递过去

      参考图8,“/bin/sh”这个参数如何传递过去呢?作者调用了getint函数,这个函数是做什么的呢?将字符创转化成数字。

      对于我们而言,这个函数有什么用呢?它会读入“/bin/sh”保存到一个地方,然后传递给atoi函数。


    运行结果

    图9 运行结果


    总结

      作者调用system的方式真是让我这个新手大开眼界,回味无穷。

      而且构造能覆盖release_ptr的fastbin队列的手法也让我惊叹。

      收获颇多。


    参考资料 

      [1]  Hctf官方解题说明:

    http://www.freebuf.com/articles/web/121778.html

  • 相关阅读:
    剑指offer39-平衡二叉树
    剑指offer37-数字在排序数组中出现的次数
    剑指offer36-两个链表的第一个公共结点
    剑指offer31-整数中1出现的次数
    剑指offer30-连续子数组的最大和
    剑指offer28-数组中出现次数超过一半的数字
    剑指offer26-二叉搜索树与双向链表
    剑指offer21-栈的压入、弹出序列
    剑指offer16-合并两个排序的链表
    C#-杂碎
  • 原文地址:https://www.cnblogs.com/shangye/p/6156391.html
Copyright © 2011-2022 走看看