不同版本下glibc有关堆的管理和保护
0x00
记录一下不同版本glibc下的堆管理保护机制的变化(开个坑,以后慢慢记录补充
先贴上几篇参考博客
不同版本glibc的堆管理和新增保护机制 - Luexp
glibc各版本的堆保护 | jkilopu’s blog
Glibc中堆管理的变化 | 白里个白
Glibc高版本堆利用方法总结 - LynneHuan - 博客园
然后ubuntu和glibc不同版本的对应关系
1 | ubuntu-libc version |
0x01 关于tcachebins
在glibc2.26+,引入了tcachebins
有两个比较关键的函数tcache_get()
和tcache_put()
(以下为glibc2.28中的源码)
1 | static void |
这两个函数会在函数 __int_free
和 __libc_malloc
的开头被调用,其中 tcache_put
当所请求的分配大小不大于0x408并且当给定大小的 tcache bin 未满时调用。一个 tcache bin 中的最大块数mp_.tcache_count
是7
当程序进行 malloc 操作时,会优先检查 tcache 是否有可用的 chunk,如果有,就直接返回。同样,当进行 free 操作时,如果 chunk 的大小符合要求,并且对应的 tcache bin 还未满(默认每个 bin 可以存放 7 个 chunk),就会把 chunk 放入 tcache。否则,会按照原来的流程,放入 unsorted bin 或者其他的 bin 中
在 tcache_get
中,仅仅检查了 tc_idx,此外,我们可以将 tcache 当作一个类似于 fastbin 的单独链表,只是它的 check,并没有 fastbin 那么复杂,仅仅检查 tcache->entries[tc_idx] = e->next
.
tcachebins并不是由main_arena
(位于libc数据段)管理的,而是由tcache_perthread_struct
(位于堆段)管理的。具体管理机制见另一篇博客
0x02 tcache_key与safe-linking
参考博客
Safe-Linking 机制的绕过 | ZIKH26’s Blog
tcache bin key加密机制
在glibc2.29+,tcache bin引入了tcache的key加密机制,tcache_entry
中新加了一个指针key
1 | struct tcache_entry { |
我们看引入了key之后的tcache_put()
的部分源码(以下为glibc2.32+)
1 | tcache_put (mchunkptr chunk, size_t tc_idx) |
chunk2mem
函数是返回我们free的chunk的实际数据区,也就是去除推头0x10的部分。将其key字段(即fd后的0x8字节)设置为tcache_key。在free一个chunk时候,会检查其key字段的值,如果等于对应tcachebins的tcache_key
,说明已经释放过,则会报错中断,如下
1 | [DEBUG] Received 0x29 bytes: |
要绕过这一机制进行double free,需要能够破坏free的chunk的key值即可。
至于safe-linking,在glibc2.32+引入,在上面源码就可以看到
1 | e->next = PROTECT_PTR (&e->next, tcache->entries[tc_idx]); |
这一段就是对tcache_entry
中的next指针加密。这个宏定义如下
1 |
也就是被释放的chunk在tcache->entry
中的next指针值等于被释放的chunk本身的mem地址右移12位再异或上其要指向的chunk(原本tcachebins中的第一个chunk)的mem地址,这一加密结果也会同步到tcachebins中的chunk的fd指针中。我们自己简单验证一下。在glibc2.35版本中我们可以看到如下
1 | pwndbg> heap |
这是笔者自己写的先申请三个chunk再先后free的程序的调试结果。可以看到第三个chunk的fd为0x55500000c669,并不是其应该指向的0x555555559330。我们经过safe-linking的加密,(0x5555555593c0 >> 12) ^ 0x555555559330=0x55500000c669。
特别的,对于第一个释放的chunk1,其fd值为0x555555559,因为其在释放时对应的tcachebins为空,其本来应该指向的是0,那么加密之后其实就是其本身fd地址右移12位。在这种情况下,其加密其实并没有达到预期效果,只需左移12位便可以恢复其地址,实现绕过
我发现:
1 | pwndbg> print/x (0x55500000c7f9 ^ 0x5555555592a0) << 12 |
在同一页内存的chunk,其fd值异或上其指向的真实值再左移12位便得到了heap_base。(堆内存按页分配,heap_base都是0x1000的整数倍)
例如在一个存在uaf漏洞的程序中,我们申请一个chunk,然后放入tcachebin中,再访问其fd值,得到的fd值左移12位便是heap_base,其实是heap_base =(leak_add ^ 0)<< 12,因为此时对应的tcachebins中只有这一个chunk,其fd指向的真实地址就是0
然后就是关于entries指针数组和对应tcachebins的关系,其实二者指向的是同一块内存,源码中在操作时候对e->key,e->next的赋值其实也就是对tcachebins中相应chunk的key字段和fd域的赋值。
在safe-linking引入后,tcache_perthread_struct
的entries数组中存放的依然是对应tcachebins的第一个chunk的真实的mem地址(也就是fd地址)(索引方式见我的另一篇博客),所以我们劫持tcache_perthread_struct
后仍然可以直接覆盖entries数组的元素为我们的目标地址。
然而在safe-linking保护下,我们使用tcache poisoning(类似fastbin attack)修改tcachebins中的chunk的fd域的时候,就需要将目标地址进行加密操作再覆盖。
例如我们已经泄露了heap_base,要修改tcachebins中的第一个chunk的fd,便要计算val =((heap_base+0x2a0) >> 12) ^ target_add,用这个val来投毒。target_add便是我们想利用tcache poisoning申请的地址,至于heap_base+0x2a0便是我们想要修改fd域的chunk的mem地址(0x290是在glibc2.32+下的tcache_perthread_struct的大小,那么heap+0x290+0x10便是我们申请的第一个chunk的mem地址,也就是此例子中我们要进行投毒的chunk。具体的地址要具体调试分析,此处只是为了方便以申请的第一个chunk为例。)
然而在实际上,右移12位后,16进制下地址的后三位都被移出,不会产生影响。也就是说,对和heap_base同一页(单位0x1000)下的chunk进行tcache poisoning时候,只需要直接使用(heap_base>>12)^target_add即可。
0x03 关于hooks
各种hooks在glibc2.34+都被移除了。在glibc2.34之前的版本中对hooks的劫持时是控制程序执行流的重要方法
在malloc.c中可以看到几个全局钩子
1 | /* 钩子指针声明 */ |
这几个钩子也正是pwn中劫持的重点关照对象。这几个hook大多都能在libc中直接查到偏移位置。(例如libc.sym['__free_hook']
)
关于钩子的触发,以__malloc_hook
为例,源码如下
1 | void *__libc_malloc(size_t bytes) { |
可以看到在执行__libc_malloc
函数时候会先检查__malloc_hook
,如果钩子非空,则直接执行指向的函数(或被篡改的可执行的地址)。其他几个全局钩子也是类似的,调用对应的函数时候,都会先检查对应的hook,不空则直接执行。
比如打__free_hook
,改为system函数然后在chunk中写入'/bin/sh\x00'
,直接free对应chunk即可。我们看源码
1 | void __libc_free(void *mem) { |
因为__free_hook
函数会以传入__libc_free
的对应chunk的mem地址为参数。而打__malloc_hook
时候则更多需要one_gadget的辅助了
glibc2.34+各类hooks被移除,因此也需要掌握其他方法.