前言

仅记录自己调试程序的一个double free漏洞的问题

Tcachebins

对于稍微了解linux堆内存管理的人来说,fastbins这个词一定不陌生,而在libc2.26之后,一个新的机制 Tcachebins被引入了,Tcachebins和fastbins很像,但优先级在fastbins之前。

被free的适当大小的内存块会优先被放入Tcachebins,且Tcachebins的每一个链上可以最多链接7个堆块,只有当Tcachebins满了之后,再free的内存块才会被放入fastbins及unsorted bins

在malloc时,也会优先取Tcachebins中的内存块

Tcachebins和fastbins一样,仅基于fd指针构造单链表,且遵循LIFO的原则

ptmalloc堆结构

如下图所示:

heap

在图中,prev_size以及size构成了chunk头:

1
2
1.prev_size    只有在前一个堆块是空闲时才会有值,用来指示前一个堆块的大小
2.size 当前chunk的大小(包括chunk header的长度)。由于chunk大小是8的整数倍,所以此size的后3 bit被用于存储其他信息

prev_size与size分别占据8字节的长度,即chunk头为0x10长.

prev_size所占据的8个字节在prev chunk处在in-use的情况下是无用的,可以被prev chunk使用

size的后三bit有特殊用途,被用于存储其他信息,如下图所示:

chunkheader

这三位的作用是:

1
2
3
1.NON_MAIN_ARENA     这个堆块是否位于主线程
2.IS_MAPPED 记录当前 chunk 是否是由 mmap 分配的
3.PREV_INUSE 记录前一个 chunk 块是否被分配

如果当前chunk处于未被使用状态,则mem前8 bytes被用来存储其他信息,如下:

1
2
fd:     下一个未被使用的chunk的地址
bk: 上一个未被使用的chunk的地址

一般我们使用void *x = malloc(1),获得的指针地址指向的是fd的开头,chunk header为ptmalloc自动分配

另外,在malloc时,根据操作系统的位数,有一个最小分配空间的概念,在64位上=16字节,32位=8字节,即哪怕malloc(0),也会按照最小值分配

Start

下断点至第一次free

firstfree

此时,tcache如下图所示

firstfree-tcache

moduleNameBuff指向的地址内容如下所示:

firstfree-mnb

第一次free后

afterfirstfree-code

tacache如下所示:

afterfirstfree-tcache

原moduleNameBuff所指向的地址(0x55555575ecd0),这里-0x10是为了指向chunk头

afterfirstfree-mnb

可以看到对应上面提到的堆结构,fd指针已经指向了tcachebins中的下一块未被使用的堆地址(0x000055555575ed70)

下断点并运行至第二次free

secondfree-tcache

此时tcache如下图所示:

secondfree-tcache

地址0x55555575ecd0-0x10如下所示:

secondfree-mnb

此时可以看到,由于4-5步之间我还进行了其他的操作,所以导致tcache链上增加了其他的几个空闲块,且此时将要被double free的内存块并未有异常

第二次free后

aftersecondfree-code

此时tcache如下所示:

aftersecondfree-tcache

此时地址0x55555575ecd0-0x10如下所示:

aftersecondfree-mnb

可以看到,tcache显示loop detected,并且被double free的0x55555575ecd0这一堆块的fd指针指向了0x000055555575edd0,即tcache中显示的下一个空闲堆块

而0x000055555575edd0所指向的堆块如下所示:

aftersecondfree-another

可以看到此时0x000055555575edd0所指向的堆块中fd为0x55555575ecd0,那么这也就可以解释loop出现的原因,0x55555575ecd0与0x000055555575edd0中的fd互相指向,形成了一个循环,这是不应当的

跳至“第一块”0x55555575ecd0被分配

此时double free的问题还没有显现出来

first-ecd-code

此时tcache如下所示:

firstalloc-ecd-tcache

0x000055555575edd0和0x55555575ecd0堆块如下所示:

firstalloc-ecd-twoheap

可以看到,断点下到了即将执行malloc的代码处,申请的堆空间为4字节,加上头长为0x14,故而分配0x20size的堆块,此时tcache显示下一个即将被分配的0x20size的堆块为被我们double free的”第一块”0x55555575ecd0

准备填入数据

当“第一块”0x55555575ecd0被分配给data_len后,我准备向这个堆块中填入数据

rec-data-first-ecd-code

此时0x000055555575edd0和0x55555575ecd0堆块如下所示:

rec-data-first-twoheap

tcache如下所示:

rec-data-first-tcache

可以看到,我准备向0x55555575ecd0指向的堆块中写入四个字节的数据,而且此时0x000055555575edd0和0x55555575ecd0堆块以及tcache并没有变动

填入数据

rec-data-first-done-code

此时tcache:

rec-data-first-done-tcache

此时0x000055555575edd0和0x55555575ecd0堆块:

rec-data-first-done-twoheap

可以看到,我向0x55555575ecd0指向的堆块中的低四字节写入了0x00000001,而此时由于0x55555575ecd0被tcache误认为仍是空闲堆块(还存在在tcache的链上),故而在tcache看来,0x55555575ecd0所指向的堆块的头八个字节应当是fd,即指向下一个空闲堆块,故而误将我写入后的四字节+原本的四字节当作了下一个空闲堆块的地址,即0x0000555510000000,而这个地址是无法访问的,从tcache也可以看出,其误将0x0000555510000000作为了“第二个”0x55555575ecd0后的空闲堆块,由于无法访问这一个地址,故而显示[Corrupted chunk at 0x555510000000]

实际上我这里还有一个错误,就是在data_len使用完后并没有立即释放,故而0x55555575ecd0所指向的堆块在之后的程序中会一直呈现被占用的状态,不会再有指针去修改其中的数据,这也代表着Corrupted chunk像一个定时炸弹一样,等待其被分配的那一刻

不过就算我立即free了data_len,随后仍有可能会有其他的指针获得0x55555575ecd0指向的堆块并修改其中的数据,将0x0000555510000000改为其他的值,继续维持异常的状态,导致同样的崩溃

触发

跳至等到所有排在Corrupted chunk之前的堆块都被分配后

boooom-code

此时tcache:

boooom-tcache

此时0x000055555575edd0和0x55555575ecd0堆块:

boooom-twoheap

可以看见即将要执行malloc(4)的操作,对应0x20size的堆块,而在tcache中显示下一块即将被分配的0x20size的堆块为Corrupted chunk,故而在此句程序结束后,程序崩溃,并抛出了错误Program received signal SIGSEGV, Segmentation fault.

总结

实际上,这样的漏洞进一步可以导致write-anything-anywhere问题,而且当写入的内容可以由外部决定时(例如从socket上获取数据)尤为严重,配合其他的漏洞就有可能形成攻击链。