musl 1.2.2的两道pwn题
musl 1.2.2堆的内存管理与以前学的glibc完全不同,是将一块连续的内存划分为相同的大小的chunk(有点像slab),再去由group、meta、malloc_context这些结构体逐级去管理,推荐大家多看几遍0xRGz师傅的musl源码解析文章,这里主要写的是自己在复现musl pwn题中所学到的一些细节。
前置要点
分配策略
需要记住的是刚释放的chunk不会被立刻使用:
- 在同一的group中,如果avail_mask不为0,如果释放一个该group中的chunk,接下来申请chunk也只会优先申请那些被avail_mask标识的,而不会去使用刚释放的;
- 如果avail_mask 为0,就会去找meta->next所指向的meta,调用activate_group函数更新下一个meta的avail_mask,接着去使用下一个meta中的chunk,同时将下一个meta更新为链表头。
dequeue
1 | |
dequeue 触发条件:
avail_mask 表示只有一个chunk 被使用 ,freed_mask = 0,而free刚好要free 一个chunk;
avail_mask = 0, freed_mask表示只有1个 chunk没被释放,这时释放的chunk就是最后一个chunk;
avail_mask = 0, freed_mask = 0,且继续申请该大小的chunk,这时就会unlink此meta,使用新的meta分配。
queue
1 | |
queue 触发条件:
avail_mask = 0, freed_mask = 0,释放其中的一个chunk(当然该meta已经是dequeue的)。
祥云杯2021 babymull
这里是直接参考的这篇wp。
最主要的就是通过后门函数去泄漏malloc_context的secret,修改chunk的offset,让其找到伪造的group,再通过伪造的group找到伪造的meta,queue伪造的meta,最后修改伪造的meta->mem地址,实现任意地址写。
exp
1 | |
这里引用另一个师傅的文章关于此题的疑问,自己复现时也有过同样的疑问
疑问1:可以直接申请到stdout_FILE是因为avail_mask设置为2,其表示的是第一个chunk已经被标识为不可分配,第二个chunk则是可以分配,所以就直接越过中间0x940,分配第二个chunk;如果avail_mask设置为3或者1,则就可以正常从头开始分配。
疑问2:是绕过free的检查,更详细点就是get_nominal_size函数中关于chunk边界的检查
*CTF2022 babynote
这里是参考xyzmpv师傅的wp
具体细节就不再赘述,需要注意在calloc函数中使用malloc分配内存后会继续调用is_allzero函数,而在is_allzero函数中又存在get_meta函数去检查所分配的内存块,需要先dequeue去将目标内存的group改为正确的内存地址,之后才能正常分配目标地址。
利用dequeue去攻击,除了伪造prev,、next、avail_mask、 freed_mask这些值,freeable和maplen也不能忽视。
只有freeable = 1时,meta才能被dequeue。
当maplen = 0时,说明group不是新mmap 出来的,而是使用其他meta里的group;使用最简单的代码就能证明这一点:
1 | |
在malloc(0x20)之前是没有相应0x30大小的group
在malloc后可以观察到active[2]的group实际上是直接从active[15]中分配的chunk
而在利用dequeue去攻击时,如果设置maplen为0,在dequeue之后就会调用free_group函数,然后就使用get_meta对伪造的group还要做一系列检查,最后再去调用nontrivial_free函数,这就对伪造的group和meta有更多的要求,引起不必要的麻烦。
由于自己本地使用的是musl_1.2.2-4,对这道题给的libc无法正常使用带符号调试(也不确定是不是这个原因导致的),为了方便自己做题就直接拿本地的libc去做了,下面exp仅供参考
1 | |
参考:https://bbs.kanxue.com/thread-269533-1.htm#msg_header_h3_8
https://blog.csdn.net/weixin_45209963/article/details/124423573