Skip to main content

glibc 2.31 malloc相关源码初探

为了看这个,我特意学习了一下vscode(之前我都是0配置vim来阅读的,想着用点现代一点的工具hhh)

__libc_malloc函数

此函数是_int_malloc函数的包装函数,该函数考虑了hook函数,tcache(前两个不向系统申请获取空间),单线程,多线程,(后两个在不同线程下情况选择不同的arena来调用_int_malloc,使得_int_malloc函数可以专注于分配逻辑)

__libc_free函数

此函数是_int_free的包装函数,该函数考虑了hook函数,mmap区域释放(前两个无需_int_free),寻找到内存区域所在的arena,将他和chunk指针一起传给_int_free函数,在看完这里我有点奇怪,为什么没有tcache的释放

_int_malloc函数和_int_free函数

此函数我数了一下共有600+行,再算上对其他函数的引用,RTFSC的压力真大,好在过年这几天也没什么事

由于图片太长无法上传,我将注释版放在了github上,链接如下malloc_chinese_comment/malloc.c at master · yizishun/malloc_chinese_comment (github.com)

写的不好欢迎提pr😊

相关检测

众所周知,ptmalloc中很多的检测是阻止我们攻击的障碍,正所谓“知己知彼,百战不殆”,我们应该知道有哪些检测并选择合适的方法绕过这些检测来达到我们攻击的目的,所以接下来我将会列出大多是libc2.31(可能还会有少量其他版本的检测)中的一些相关检测,若是我写题时遇见了针对特定检测的绕过方法,我也会写出来,这个内容我会不断增加的。

Tcache相关检测

1.tcache in malloc

(-libc2.31):

没有任何检测

(libc2.32-):

检测报错信息malloc(): unaligned tcache chunk detected
原因:尝试malloc一个特定地址的tcache chunk时,检测entries的地址是否是对齐的(因为是LIFO,所以检测开头),因为2.32引入了safe-linking机制,导致真正在单链表中的指针值是经过加密的,若是随意更改了next的指针,经过解密到entries会导致地址不对齐,从而无法从tcache中拿出来(tcache_get)(记住只有entries的地址是经过解密的,单链表的其他指针都是加密的)
防护的攻击:tcache poisoning
绕过方式:safe-ling绕过:在aslr中泄露heap地址,从而得到存放next指针的地址,将加密过后的指针写入next域,实现poisoning

2.tcache in free

(ALL):

检测报错信息free(): double free detected in tcache 2
原因:tcache中的double free很难绕过,因为他在每一个tcache的结构体紧挨next域有一个key域专门用来检测double free的,每一个free过的chunk key域都是tcache的地址,so tcache一般打malloc,fastbin一般打double free
防护的攻击:tcache double free
绕过方式:一般来说需要打double free的都是没有edit函数直接篡改fd或者next的,所以没必要打tcache的double free,可以把tcache填满打fastbin的double free,fastbin malloc一个之后会将其他同大小的的带到tcache中(记得fastbin也需要考虑safe-linking(libc2.32之后))

(libc2.32-):

检测报错信息:free(): unaligned chunk detected in tcache 2
原因:遍历所有tcahce时会检测tcache中所有chunk的next域的解密情况,每个解密都需要正确对齐的
防护的攻击:tcache poisoning
绕过:safe-linking绕过,见上

(?-libc2.35-)

检测报错信息:free(): too many chunks detected in tcache
原因:遍历所有tcache时会顺便记录数量,若发现数量小于mp_.tcache_count,报错退出,虽然不知道这个具体的攻击方式,但是可以看到这个很明显是为了防止对tcache->counts[tc_idx]的篡改

Fastbin相关检测

1.fastbin in malloc

(ALL)

检测报错信息:malloc(): memory corruption (fast)
原因:检测即将malloc出来的那个chunk的size域
防护的攻击:fastbin poisoning?(估计是针对没有tcache的防护)
绕过方法:高版本肯定想办法在tcache bin中malloc出来(因为从fastbin中malloc第一个后会把其他相同大小的chunk带到tcache bin中,只要确保需要malloc到目标地址的时候该chunk存在于tcachebin即可),低版本的话只能使用字节错位(即在目标地址附近寻找一个可以充当size域的数据地址,即伪造size域绕过检测)

(libc2.32-)

检测报错信息:malloc(): unaligned fastbin chunk detected 1/2/3
原因:fastbin是单链表所以也有safe-linking,这个就是检测将fd解密之后的地址是否对齐
防护的攻击:poisoning更改单链表
绕过方法:safe-linking绕过,(所以可以看到,2.32之后都是需要泄露heap地址的)

2.fastbin in free

(ALL)

检测报错信息:free(): invalid next size (fast)
原因:是对free的此chunk的下一个chunk(靠近top的)的size域进行上下界检测,暂时没遇到过

检测报错信息:double free or corruption (fasttop)
原因:是对fastbin的double free的检测,但是检测机制是只检测是否连续free(因为全部检测影响性能,还不能使用tcache专门的结构体,导致此检测很容易绕过)
防护的攻击:fastbin double free
绕过方法:隔一个再free,不要连续free同一个

检测报错信息:invalid fastbin entry (free)
原因:对free的chunk的size域进行检测
防护的攻击:篡改global_max_fast,导致将错误size的chunk free进fastbin

small bin相关检测

1.small bin in malloc

(ALL)

检测的报错信息:malloc(): smallbin double linked list corrupted
原因:检测当前chunk的bk指针指向的chunk的fd指针是否等于当前chunk
防护的攻击:篡改双向链表的bk指针实现任意地址分配
绕过方法:house of lore(在目标地址构造正确的fd指针,具体还没认真学)

2.small bin in sort

无检测

Unlink相关检测

unlink一般使用在分配largebin时以及合并操作时

(?-libc2.27-)

检测的报错信息:corrupted size vs. prev_size
原因:对当前的chunk的size域和前一个的prev_size域检测是否匹配(物理chunk相邻检测)

(ALL)

检测的报错信息:corrupted double-linked list
原因:检测当前chunk的fd和bk指向的chunk的bk和fd指针是否指向当前chunk(双向链表相邻检测(太狠了))

检测的报错信息:corrupted double-linked list (not small)
原因:检测当前chunk的fd_nextsize和bk_nextsize指向的chunk的bk_nextsize和fd_nextsize是否指向当前chunk(largebin双向链表检测)

large bin相关检测

1.large bin in malloc

见unlink检测↑,似乎没有什么绕过手段了,只能打largebin in sort时的largebin attack。

2.large bin in sort

检测的报错信息:malloc(): largebin double linked list corrupted (nextsize)(中间插入时)
检测的报错信息:malloc(): largebin double linked list corrupted (bk)(中间插入时)
绕过方法:放到largebin时一般是想打largebin attack实现任意地址写chunk地址,绕过这两个检测只需要每次插入的chunk是该largebin中最小的即可。

unsorted bin相关检测

1.unsorted bin in malloc

(unsorted bin一般不在malloc时攻击,因为检测太多)

检测的报错信息:malloc(): invalid size (unsorted)(高版本libc的提示信息)
malloc(): memory corruption(低版本libc的提示信息)
原因:检测chunk的size域的大小是否在上下限内,威胁较少

检测的报错信息:malloc(): invalid next size (unsorted)
原因:检测下一个chunk的size域大小是否在上下限内,威胁较少

检测的报错信息:malloc(): mismatching next->prev_size (unsorted)
原因:检测该chunk的size是否与下一个chunk的prev_size匹配

检测的报错信息:malloc(): unsorted double linked list corrupted
原因:检测该chunk的fd指针

检测的报错信息:malloc(): invalid next->prev_inuse (unsorted)
原因:检测下一个chunk的prev_inuse位

2.unsorted bin in sort and splite

3.unsorted bin in free

TODO

SUMMARY

总结一下,我发现(以我现在的知识)每一个bin的不同的检测方式导致每一个都有每一个的弱点,合理利用每一个的弱点,打出一套组合技才能攻陷目标

比如说tcache的弱点在于malloc出来的检测过少,导致一般打任意地址分配(任意地址写)会先考虑它,即tcache poisoning

fastbin的弱点在于对于double free的检测过少,导致篡改指针的任务会交给fastbin

smallbin的弱点在于malloc时不是用的unlink,检测相对较少,若是tcache和fastbin不能用,用smallbin打house of lore也行,并且他毕竟是一个双向链表,所以泄露地址的任务可以交给他

largebin的弱点在于它分类放入largebin时检测不严格,导致largbin attack,可以实现任意地址写堆地址(这么看来smallbin也行?)

unsorted bin的弱点首先也是可以泄露地址,emm其他的我还不清楚,之后再补充吧

最后的最后,堆攻击首先也是需要泄露地址,之后需要控制程序流来getshell(比如先任意地址分配/写,将system或者其他shellcode的地址写入hook地址(hook之后移除,IO相关指针也行))

我现在的相关经验告诉我,1.一般泄露地址是双向链表的事情(一般需要UAF,或overlapping制造UAF),2.任意地址写一般是获得任意地址的chunk,这件事一般是fastbin和tcache配合(有时没有机会同时获得这两个chunk,比如只有largebin,就可以修改变量获得tcache,或者只有fastbin,就需要字节错位写(不过很少啦))题目一般会给我几个机会,比如给了edit函数那就不需要fastbin就可以任意地址分配了等等

所以基本上堆利用很多时候就是四步

  • 1.泄露地址
  • 2.改写指针到能控制程序流程的指针处(比如hook,got,io)
  • 3.malloc到这个位置
  • 4.改他的地址为system或者其他getshell的地址

不同的bin在不同的领域上是薄弱点,根本原因其实就是各个bin的职责不同,导致相关检测不同,多检测,就少性能,少特性