前言
最近碰到一道使用glibc 2.35版本的堆题,由于以前也没有真正接触过高版本glibc的堆利用,所以借着这次机会学习了如何通过控制tls_dtor_list来进行流程控制。
前置条件
1.任意地址写
2.堆基址泄露
3.pointer_guard泄露
4.exit函数退出
须知
劫持流程
exit
--->
__run_exit_handlers
--->
__call_tls_dtors
__call_tls_dtors函数
void
__call_tls_dtors (void)
{
while (tls_dtor_list)
{
struct dtor_list *cur = tls_dtor_list;
dtor_func func = cur->func;
#ifdef PTR_DEMANGLE
PTR_DEMANGLE (func);
#endif
tls_dtor_list = tls_dtor_list->next;
func (cur->obj);
atomic_fetch_add_release (&cur->map->l_tls_dtor_count, -1);
free (cur);
}
}
tls_dtor结构
*(struct dtor_list*)fake_dtor = {
func = 0x83485355fa1e0ff3,
obj = 0x1ad48f1d8b4808ec,
map = 0xed85482b8b486400,
next = 0x441f0f664374
}
劫持tls_dtor_list原理
通过__call_tls_dtors源码可知,只要我们将tls_dtor_list改为可控区域,即可控制func(cur->obj)。
然而这里需要注意的是 PTR_DEMANGLE (func) 会将func解密,如下是 PTR_DEMANGLE 宏定义
# define PTR_DEMANGLE(reg) ror $2*LP_SIZE+1, reg; \
xor %fs:POINTER_GUARD, reg
正常情况解密过程是先将reg循环右移0x11位,然后再将reg与pointer_guard进行异或得到最终的结果。
所以这里加密的过程也就很清晰了,只需要将我们真实地址先与pointer_guard进行异或,然后再循环左移0x11位即可。这也就是为什么我们需要泄露pointer_guard的原因。
其实tls的利用原理还是比较简单的,难的是实现任意地址写与目的地址泄露。
至于任意地址写与目的值泄露不在本博客讨论范围内,毕竟方法还是很多的。
这里还要提一点,就是在题目使用了沙盒,禁止掉execve要怎么办呢?
其实方法可以在hourse of emme中找到,我们也可以仿照emme手法中的栈迁移来实现orw
因为我这次做的题目就出现了这种情况,所以就在这里顺便说一说在能够利用tls_dtor_list劫持程序流程之后,如何进行栈迁移构造orw ROP链。
在讲解之前,首先要知道如下几个gadget
setcontext+61及setcontext+294
可以看到,在setcontext+61处可以通过[rdx+0xa0]来控制rsp的值,以实现栈迁移
那么如何实现控制rdx的值呢?在上述介绍tls劫持流程中我们知道,最后我们可以通过控制cur->obj来控制rdi,那么我们就可以通过[rdi-8]进而控制rdx,最后控制rsp到我们构造好的ROP链之中。
总结
高版本glibc的解题思路基本上都离不开对IO的利用,但是tls另辟蹊径,通过劫持tls_dtor_list来达成流程控制的目的,真的很不可思议(wwwww)。
若读者觉得本文描述的不够详细,可参考一下博客:
house of emma利用手法详解(21湖湘杯实例解析) - FreeBuf网络安全行业门户
glibc 2.35 pwn——house of emma示例程序_ubuntu 22.04 glibc是多少-CSDN博客