前言
仅记录自己调试程序的一个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堆结构
如下图所示:
在图中,prev_size以及size构成了chunk头:
1 | 1.prev_size 只有在前一个堆块是空闲时才会有值,用来指示前一个堆块的大小 |
prev_size与size分别占据8字节的长度,即chunk头为0x10长.
prev_size所占据的8个字节在prev chunk处在in-use的情况下是无用的,可以被prev chunk使用
size的后三bit有特殊用途,被用于存储其他信息,如下图所示:
这三位的作用是:
1 | 1.NON_MAIN_ARENA 这个堆块是否位于主线程 |
如果当前chunk处于未被使用状态,则mem前8 bytes被用来存储其他信息,如下:
1 | fd: 下一个未被使用的chunk的地址 |
一般我们使用void *x = malloc(1),获得的指针地址指向的是fd的开头,chunk header为ptmalloc自动分配
另外,在malloc时,根据操作系统的位数,有一个最小分配空间的概念,在64位上=16字节,32位=8字节,即哪怕malloc(0),也会按照最小值分配
Start
下断点至第一次free
此时,tcache如下图所示
moduleNameBuff指向的地址内容如下所示:
第一次free后
tacache如下所示:
原moduleNameBuff所指向的地址(0x55555575ecd0),这里-0x10是为了指向chunk头
可以看到对应上面提到的堆结构,fd指针已经指向了tcachebins中的下一块未被使用的堆地址(0x000055555575ed70)
下断点并运行至第二次free
此时tcache如下图所示:
地址0x55555575ecd0-0x10如下所示:
此时可以看到,由于4-5步之间我还进行了其他的操作,所以导致tcache链上增加了其他的几个空闲块,且此时将要被double free的内存块并未有异常
第二次free后
此时tcache如下所示:
此时地址0x55555575ecd0-0x10如下所示:
可以看到,tcache显示loop detected,并且被double free的0x55555575ecd0这一堆块的fd指针指向了0x000055555575edd0,即tcache中显示的下一个空闲堆块
而0x000055555575edd0所指向的堆块如下所示:
可以看到此时0x000055555575edd0所指向的堆块中fd为0x55555575ecd0,那么这也就可以解释loop出现的原因,0x55555575ecd0与0x000055555575edd0中的fd互相指向,形成了一个循环,这是不应当的
跳至“第一块”0x55555575ecd0被分配
此时double free的问题还没有显现出来
此时tcache如下所示:
0x000055555575edd0和0x55555575ecd0堆块如下所示:
可以看到,断点下到了即将执行malloc的代码处,申请的堆空间为4字节,加上头长为0x14,故而分配0x20size的堆块,此时tcache显示下一个即将被分配的0x20size的堆块为被我们double free的”第一块”0x55555575ecd0
准备填入数据
当“第一块”0x55555575ecd0被分配给data_len后,我准备向这个堆块中填入数据
此时0x000055555575edd0和0x55555575ecd0堆块如下所示:
tcache如下所示:
可以看到,我准备向0x55555575ecd0指向的堆块中写入四个字节的数据,而且此时0x000055555575edd0和0x55555575ecd0堆块以及tcache并没有变动
填入数据
此时tcache:
此时0x000055555575edd0和0x55555575ecd0堆块:
可以看到,我向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之前的堆块都被分配后
此时tcache:
此时0x000055555575edd0和0x55555575ecd0堆块:
可以看见即将要执行malloc(4)的操作,对应0x20size的堆块,而在tcache中显示下一块即将被分配的0x20size的堆块为Corrupted chunk
,故而在此句程序结束后,程序崩溃,并抛出了错误Program received signal SIGSEGV, Segmentation fault.
总结
实际上,这样的漏洞进一步可以导致write-anything-anywhere
问题,而且当写入的内容可以由外部决定时(例如从socket上获取数据)尤为严重,配合其他的漏洞就有可能形成攻击链。