算是对自己掌握的知识做一个总结吧。毕竟憨憨,经常脑子犯浑,自己把自己绕进去了。就趁着清醒的时候对于手法,原理进行一个记录吧。(慢慢✍吧,我也不知道多久能写完,懒癌选手)
Tcache_bin
tcache在free中并不会被清除inuse标志,所以他们被认为是处于使用状态,不会被合并,LIFO
某要点
这里也点出一点和fastbin的malloc机制所不同的地方
首先来看tcache bin的malloc机制
计算对应的size是否在bin中存在chunk,tcache是否初始化。malloc不存在对size的检测。
fastbin
的malloc机制主要看这一段
会对malloc的chunk的size位与fast bin中chunk的size位进行检测,是否匹配。
照我理解,就是malloc(0x28)的时候,会对这个malloc里的size进行fastbin中chunk获取,获取完后会进行这里的第一重检测,也就是实际chunk中的size与fastbin中取出的chunk的size进行匹配。因为这一重检测,所以我们打malloc_hook的时候经常会-0x23这种操作,来使得size匹配。
tcache_dup
因为tcache bin检查机制的问题,可以前后两次free同一个堆块,检查机制并不会报错。也就是常说的double free技巧。
这种条件适用的前提是存在一个UAF漏洞!或者也可以进行双指针同时指向同一个堆块!
因为double free 是任意地址写的一种技巧,指堆上的某块内存被释放后,并没有将指向该堆块的指针清零,那么,我们就可以利用程序的其他部分对该内存进行再次的free。
tcache House of Spirit
这种手法和fatsbin的House of Spirit很类似。这点我在fastbin的攻击手段中讲述的比较详细了,这里就简要概括一下。
可以说也是一种任意地址写的手段,不过得先在你想要的地方伪造堆块,并控制堆指针指向伪造堆块的mem域,将其释放,再次malloc一个相同大小的堆块就可以达成任意地址写。
fastbin里面这种手法你需要对下一个区域进行堆块伪造(主要是写入正确的size位),而在tcache bin中,你就不需要进行下一个堆块的伪造。
tcache poisoning
也就是常见的修改tcachebin chunk的fd域,实现任意地址写。
这个我寻思,经常和tcache dup配合,double free后,进行fd修改,两种攻击组合,形成任意地址写。
tcache perthread corruption
每个线程通过一个tcache_perthread_struct线程本地变量保存tcache bin以及相关的chunk计数。如果我们能够修改tcache_perthread_struct这个结构体的内容,就可以完全控制malloc的内存分配。
那么如何获得这个地址,并控制他呢?我们来了解下tcache的初始化过程,MAYBE_INIT_TCACHE这个宏,他在没有初始化tcache的情况下会初始化一个tcache,实际上初始化tcache的工作是由内部函数tcache_init完成的
###tcache_init
static void
tcache_init(void)
{
mstate ar_ptr;
void *victim = 0;
const size_t bytes = sizeof (tcache_perthread_struct);
也就是说,tcache_perthread_struct应该保存在堆区的底部,因为他是最先分配的内存空间。它的结构体中保存着0x20-0x90bin中chunk的数量和信息。如果我们将tcache_perthread_struct中bin的信息全部改为0707070707070707也就代表了tcache bin全部被我们填满,接下来的chunk就会free进fast or unsorted里面。
glibc 2.29里的tcache bin
简单来说,新增了一个指针key放在bk位置,当chunk被free放入tcache时,key会被写入tcache_perthread_struct
的地址。chunk被取出时,key会被清空。
在free时,存在一重检测,会检测key是否为tcache_perthread_struct
的地址,然后遍历tcache,检测该chunk是否已经在tcache里。
也就是:
1 | e-key == &tcache_perthread_struct && chunk in tcachebin[chunk_idx] |
由此产生的思路:(抄某爷爷的,地址在这:https://www.secshi.com/39809.html)
- 如果有UAF漏洞或堆溢出,可以修改
e->key
为空,或者其他非tcache_perthread_struct
的地址。这样可以直接绕过_int_free
里面第一个if判断。不过如果UAF或堆溢出能直接修改chunk的fd的话,根本就不需要用到double free了。 - 利用堆溢出,修改chunk的size,最差的情况至少要做到off by null。留意到
_int_free
里面判断当前chunk是否已存在tcache的地方,它是根据chunk的大小去查指定的tcache链,由于我们修改了chunk的size,查找tcache链时并不会找到该chunk,满足free的条件。虽然double free的chunk不在同一个tcache链中,不过不影响我们使用tcache poisoning进行攻击。
关于tcache下对于main_arena泄露的一些个人见解
手法一:
tcache_entry
1 |
|
tcache_perthread_struct
1 | /* There is one of these for each thread, which contains the |
tcache_prethread_struct
是整个 tcache 的管理结构,其中有 64 项 entries。每个 entries 管理了若干个大小相同的 chunk,用单向链表 (tcache_entry
) 的方式连接释放的 chunk,这一点上和 fastbin 很像- 每个 thread 都会维护一个
tcache_prethread_struct
tcache_prethread_struct
中的counts
记录entries
中每一条链上 chunk 的数目,每条链上最多可以有 7 个 chunktcache_entry
用于链接 chunk 结构体,其中的next
指针指向下一个大小相同的 chunk- 这里与 fastbin 不同的是 fastbin 的 fd 指向 chunk 开头的地址,而 tcache 的 next 指向 user data 的地方,即 chunk header 之后
问题呢,就在tcache_entry
这里,众所周知,我们进行常规的tcache duping + tcache poisoning操作时,会对同一个chunk free两次,并malloc相同大小的chunk 三次,以此达到任意地址写的目的。
那么有没有想过,你free两次,entries[]里面只记录了两次,也就是entries[2],你malloc一次,entries[]中的数字就-1,那么你第三次malloc的时候,entries[]就变成了entries[-1],可能很多人看到这个-1就明白过来了。
是的,这个size的tcache bin被你溢出了。那么下次,你再次free 相同size的chunk时,tcache bin中这个size大小的entries[]就会认为本bin已经被填满,chunk就会根据size落入fastbin或者unsortedbin。
此时,根据uaf漏洞,就可以show出main_arena。(如果允许malloc的size小于0x80,那么就拿不到main_arena)
手法二:
当然,如果malloc的size小于0x80,也是基于uaf漏洞的话,还有另外一种方法。
在double free后,因为是单链表结构,bin中chunk的fd会储存下一个chunk的位置信息,double free,也就是把自己的地址写入了fd。如果存在uaf,自然可以在double free后,直接show本堆块,泄露地址。这样,通过uaf漏洞实现任意地址写,讲堆块的size位进行修改,改成大于0x400,free后落入unsortedbin,从而泄露main_arena。
fastbin
fastbin_dup_consolidate
在利用申请一次largebin大小的堆会将fastbin的堆进行合并进入unsortedbin中,这种打法据我所知,一般配合scanf来达成(因为憨憨也只见过配合scanf的)
利用这种特性,可以达成fastbin的doublefree
这里是poc
1 |
|
大名鼎鼎unlink
网上有很多教程,我也就简略点一点自己在学习unlink时遇到的困难
先说利用前提,存在溢出漏洞
unlink实现手段:
前向合并,在chunk1种fake chunk,fake chunk的fd 和bk分别设置为ptr-0x18和ptr-0x10(64位)
32位是ptr - 12 ,ptr-8
修改chunk2 head的prev_size为fake chunk size以及chunk2 的prev_inuse位 置0,free chunk2
这样,最终结果,ptr就指向ptr-0x18的地方。
此时,我们就达成了我们所想要的任意地址写,怎么理解呢,我们来看一下。
1 |
|
1 | target = 0x80e010 before free //这里是运行结果 |
ptr这个指针原本指向的是堆块的地址,但是经过我们的unlink之后,ptr指针就指向了自身地址-0x18的地方,也就是上述所表示的ptr=ptr-0x18。
那么,我们就获得了两个相同的指针。一个指针存在于原本的ptr地址里,另一个就是我们现在控制的chunk指针。
此时,我们修改fake_chunk[3]为要写的地址(也就是上面所见的chunk0[3]) ,因为fake_chunk[3]是一个指针,我们将其进行修改,指向新地址就代表着我们将新地址的内容作为了我们fake_chunk的内容,此时再修改fake_chunk[0] (也就是修改我们新地址的内容) 即达成了一次任意地址写~
后向合并
此时检查chunk1的下下个chunk的flag位,那么此时就在chunk2 faka chunk,并且将chunk3的prev_inuse位 置0,
堆重叠
堆重叠,顾名思义,就是两个堆指针同时指向同一个chunk。这里借助一下unlink操作实现。
poc:
1 |
|
这里,ptr6指针就被预留。
要想实现需要达成两个条件,假设有chunk1,2,3。
一是chunk3的prev_inuse为0,chunk3的prev_size为chunk1+chunk2
二是chunk2的prev_size为chunk1。
检测是这样的,在free chunk3时根据prev_size以为前面有chunk1+chunk2大小的chunk,找到chunk1,找到chunk2的prev_size,符合,unlink。
House of
house of lore
简述一下,这个就是利用small bin来达成任意地址写。如果栈地址可写,那就可用来打栈,控制返回流程进而控制程序。
先来说说前置知识,检测和为什么free后落入unsorted bin,看不到small bin里的数据。
检测:
也就是说,我们取small bin最后一个chunk的时候会进行检测,如果存在空闲堆块,就会进行检测。
检测为获取最后一个堆块的bk(也就是获取倒数第二个堆块的地址),检查倒数第二个堆块的fd是否为最后一个堆块。
那么我们就可以利用这种检测,修改最后一个chunk的bk指向我们想要的地址(fake_chunk的head部,不是mem)
在那个地址构造好fake_chunk,prev_size,size可以直接写0,无对这俩的检测。只需要构造fd和bk,
fd为最后一个堆块地址,bk为fake_chunk2
(注意一点,在打fake_chunk地址的时候,会再进行一次上述检测,所以要在fake_chunk后面再构造一个fake_chunk2,这个fake_chunk2不用构造prev_size,size什么的,只需要注意在fd位置写上fake_chunk地址就好)
说完检测,来说说第二个问题。
不知道你们有没有,反正我学艺不精,在学这个的时候,想的是,我写的poc怎么size是在smallbin范围,free后全在unsortedbin,进不到smallbin里。
此时来了解一下,unsorted bin 可以视为空闲 chunk 回归其所属 bin 之前的缓冲区
在free了small bin大小的堆块后,堆块会被放入到 unsort bin 中去,然后下一次分配的大小如果比它大,那么将从 top chunk 上分配相应大小,而该 chunk 会被取下 link 到相应的 bin 中。如果比它小 (相等则直接返回),则从该 chunk 上切除相应大小,并返回相应 chunk,剩下的成为 last reminder chunk , 还是存在 unsorted bin 中。
ok,前置讲完了,总结打法,在目标地址伪造俩chunk,chunk1的fd指到smallbin的最后一个chunk,bk指向chunk2,chunk2的fd指向chunk1。smallbin的最后一个chunk的bk指向chunk1,剩下就是malloc完事。上图!
贴poc,👴们自己看8,应该没什么要讲的了。
1 |
|
house of force
简单来说,在正常malloc机制中,如果你malloc的size大于bin中存储的chunk的size 或者 是bin为空,就会在top chunk的位置分割top chunk。怎么找到top chunk的呢,是在内存中存在一处指针,指向了top chunk的位置,分割top chunk后chunk的落点也是由那个指针指定。
按照上述意思,只要我们控制了那个top chunk的指针,我们就可以实现任意地址写。
要想达成house of force,就需要以下条件:
1.能够以溢出等方式控制到 top chunk 的 size 域
2.能够自由地控制堆分配尺寸的大小
第一种,将top_chunk地址减小,往上分配
1.利用溢出,将top chunk size改为-1
2.计算距离,注意 ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK限制,也就是你的目标地址-top_chunk-SIZE_SZ-MALLOC_ALIGN_MASK。当然,这个MALLOC_ALIGN_MASK可减可不见,看是否对齐。
3.直接进行堆块分配。
贴poc
1 | int main() |
第二种,将top_chunk地址增大,往下分配
操作和上述无异,就是第二步,直接目标地址-top_chunk就可了。
贴poc
1 | int main() |
largebin_attack
为什么largebin_attack都需要有两个堆块在largebin里,那是因为,注意观察源码,它会存在一个判断
如果fwd不等于bck,意味着large_bin中有空闲chunk存在,所以才会进入下面的