House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

admin 2023年7月5日21:41:48评论35 views字数 6158阅读20分31秒阅读模式

前言

众所周知,glibc 高版本逐渐移除了__malloc_hook/__free_hook/__realloc_hook 等等一众 hook 全局变量,ctf 中 pwn 题对 hook 钩子的利用将逐渐成为过去式。而想要在高版本利用成功,基本上就离不开对 IO_FILE 结构体的伪造与 IO 流的攻击。之前很多师傅都提出了一些优秀的攻击方法,比如 house of pig、house of kiwi 和 house of emma等。

其中,house of pig 除了需要劫持 IO_FILE 结构体,还需要劫持 tcache_perthread_struct 结构体或者能控制任意地址分配;house of kiwi 则至少需要修改三个地方的值:_IO_helper_jumps + 0xA0 和_IO_helper_jumps + 0xA8,另外还要劫持_IO_file_jumps + 0x60 处的_IO_file_sync 指针;而 house of emma 则至少需要修改两个地方的值,一个是 tls 结构体的 point_guard(或者想办法泄露出来),另外需要伪造一个 IO_FILE 或替换 vtable 为 xxx_cookie_jumps 的地址。

总的来看,如果想使用上述方法成功地攻击 IO,至少需要两次写或者一次写和一次任意地址读。而在只给一次任意地址写(如一次 largebin attack)的情景下是很难利用成功的。

largebin attack 是高版本中为数不多的可以任意地址写一个堆地址的方法,并常常和上述三种方法结合起来利用。本文将给出一种新的利用方法,在仅使用一次 largebin attack 并限制读写次数的条件下进行 FSOP 利用。顺便说一下,house of banana 也只需要一次 largebin attack,但是其攻击的是 rtld_global 结构体,而不是 IO 流。

上述方法利用成功的前提均是已经泄露出 libc 地址和 heap 地址。本文的方法也不例外。

利用条件

使用 house of apple 的条件为: 1、程序从 main 函数返回或能调用 exit 函数 2、能泄露出 heap 地址和 libc 地址 3、 能使用一次 largebin attack(一次即可)

利用原理

原理解释均基于 amd64 程序。

当程序从 main 函数返回或者执行 exit 函数的时候,均会调用 fcloseall 函数,该调用链为:

  • exit

    • _IO_cleanup

    • _IO_OVERFLOW

    • _IO_flush_all_lockp

    • fcloseall

最后会遍历_IO_list_all 存放的每一个 IO_FILE 结构体,如果满足条件的话,会调用每个结构体中 vtable->_overflow 函数指针指向的函数。

使用 largebin attack 可以劫持_IO_list_all 变量,将其替换为伪造的 IO_FILE 结构体,而在此时,我们其实仍可以继续利用某些 IO 流函数去修改其他地方的值。要想修改其他地方的值,就离不开_IO_FILE 的一个成员_wide_data 的利用。

amd64 程序下,struct _IO_wide_data *_wide_data 在_IO_FILE 中的偏移为 0xa0

我们在伪造_IO_FILE 结构体的时候,伪造_wide_data 变量,然后通过某些函数,比如_IO_wstrn_overflow 就可以将已知地址空间上的某些值修改为一个已知值。

分析一下这个函数,首先将 fp 强转为_IO_wstrnfile * 指针,然后判断 fp->_wide_data->_IO_buf_base != snf->overflow_buf 是否成立(一般肯定是成立的),如果成立则会对 fp->_wide_data 的_IO_write_base_IO_read_base_IO_read_ptr 和_IO_read_end 赋值为 snf->overflow_buf 或者与该地址一定范围内偏移的值;最后对 fp->_wide_data 的_IO_write_ptr 和_IO_write_end 赋值。

也就是说,只要控制了 fp->_wide_data,就可以控制从 fp->_wide_data 开始一定范围内的内存的值,也就等同于任意地址写已知地址

这里有时候需要绕过_IO_wsetb 函数里面的 free

_IO_wstrnfile 涉及到的结构体如下:

其中,overflow_buf 相对于_IO_FILE 结构体的偏移为 0xf0,在 vtable 后面。

而 struct _IO_wide_data 结构体如下:

换而言之,假如此时在堆上伪造一个_IO_FILE 结构体并已知其地址为 A,将 A + 0xd8 替换为_IO_wstrn_jumps 地址,A + 0xc0 设置为 B,并设置其他成员以便能调用到_IO_OVERFLOWexit 函数则会一路调用到_IO_wstrn_overflow 函数,并将 B 至 B + 0x38 的地址区域的内容都替换为 A + 0xf0 或者 A + 0x1f0

简单写一个 demo 程序进行验证:

输出结果如下:

从输出中可以看到,已经成功修改了 sdterr->_wide_data 所指向的地址空间的内存。

利用思路

从上面的分析可以,在只给了 1 次 largebin attack 的前提下,能利用_IO_wstrn_overflow 函数将任意地址空间上的值修改为一个已知地址,并且这个已知地址通常为堆地址。那么,当我们伪造两个甚至多个_IO_FILE 结构体,并将这些结构体通过 chain 字段串联起来就能进行组合利用。基于此,我总结了 house of apple 下至少四种利用思路。

思路一:修改 tcache 线程变量

该思路需要借助 house of pig 的思想,利用_IO_str_overflow 中的 malloc 进行任意地址分配,memcpy 进行任意地址覆盖。其代码片段如下:

利用步骤如下:

  • 伪造至少两个_IO_FILE 结构体

  • 第一个_IO_FILE 结构体执行_IO_OVERFLOW 的时候,利用_IO_wstrn_overflow 函数修改 tcache 全局变量为已知值,也就控制了 tcache bin 的分配

  • 第二个_IO_FILE 结构体执行_IO_OVERFLOW 的时候,利用_IO_str_overflow 中的 malloc 函数任意地址分配,并使用 memcpy 使得能够任意地址写任意值

  • 利用两次任意地址写任意值修改 pointer_guard 和 IO_accept_foreign_vtables 的值绕过_IO_vtable_check 函数的检测(或者利用一次任意地址写任意值修改 libc.got 里面的函数地址,很多 IO 流函数调用 strlen/strcpy/memcpy/memset 等都会调到 libc.got 里面的函数)

  • 利用一个_IO_FILE,随意伪造 vtable 劫持程序控制流即可

因为可以已经任意地址写任意值了,所以这可以控制的变量和结构体非常多,也非常地灵活,需要结合具体的题目进行利用,比如题目中_IO_xxx_jumps 映射的地址空间可写的话直接修改其函数指针即可。

思路二:修改 mp_结构体

该思路与上述思路差不多,不过对 tcachebin 分配的劫持是通过修改 mp_.tcache_bins 这个变量。打这个结构体的好处是在攻击远程时不需要爆破地址,因为线程全局变量、tls 结构体的地址本地和远程并不一定是一样的,有时需要爆破。

利用步骤如下:

  • 伪造至少两个_IO_FILE 结构体

  • 第一个_IO_FILE 结构体执行_IO_OVERFLOW 的时候,利用_IO_wstrn_overflow 函数修改 mp_.tcache_bins 为很大的值,使得很大的 chunk 也通过 tcachebin 去管理

  • 接下来的过程与上面的思路是一样的

思路三:修改 pointer_guard 线程变量之 house of emma

该思路其实就是 house of apple + house of emma

利用步骤如下:

  • 伪造两个_IO_FILE 结构体

  • 第一个_IO_FILE 结构体执行_IO_OVERFLOW 的时候,利用_IO_wstrn_overflow 函数修改 tls 结构体 pointer_guard 的值为已知值

  • 第二个_IO_FILE 结构体用来做 house of emma 利用即可控制程序执行流

思路四:修改 global_max_fast 全局变量

这个思路也很灵活,修改掉这个变量后,直接释放超大的 chunk,去覆盖掉 point_guard 或者 tcache 变量。我称之为 house of apple + house of corrision

利用过程与前面也基本是大同小异,就不在此详述了。

其实也有其他的思路,比如还可以劫持 main_arena,不过这个结构体利用起来会更复杂,所需要的空间将更大。而在上述思路的利用过程中,可以选择错位构造_IO_FILE 结构体,只需要保证关键字段满足要求即可,这样可以更加节省空间。

例题分析

这里以某次市赛的题为例,题目为 pwn_oneday,附件下载链接在这里

这个题目禁止了 execve 系统调用,能分配的 chunk 的大小基本是固定的,并且只允许 1 次读和 1 次写,最多只能分配 0x10 次,使用的 glibc 版本为 2.34

题目分析

initial

首先是初始化,开启了沙盒:

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

main

main 函数必须选一个 key,大小在 6-10。也就是说,分配的 chunk 都会属于 largebin 范围。

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

add

限制了只能分配 key+0x10key+0x202 * key + 0x10 大小的 chunk

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

dele

存在 UAF,没有清空指针。

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

read

只给 1 次机会读。

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

write

只给一次机会写,并只泄露出 0x10 个字节的数据。

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

利用过程

这道题的限制还是很多的,当然,给的漏洞也很明显。但是程序里面没有使用与 IO 有关的函数,全部使用原始的 read/write 去完成读写操作,并且使用 glibc-2.34 版本,这个版本里面去掉了很多的 hook 变量。

很明显,需要使用一次读泄露出 libc 地址和 heap 地址,然后用一次写做一次 largebin attack

如果用 largebin attack 去劫持_rtld_global 的 link_map 成员,那么还需要一次写去绕过 for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next),否则这里会造成死循环;如果打 l_addr 成员,会发现能分配的堆的空间不足,l->l_info[DT_FINI_ARRAY]->d_un.d_ptr 的值为 0x201d70,而就算每次分配 0xaa0 * 2 + 0x10,再分配 16 次也没这么大。至于劫持别的成员,受限于沙盒,也很难完成利用。

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

由于限制了读写次数为 1 次,就很难再泄露出 pointer_guard 的值,也很难再覆盖 pointer_guard 的值,所以与 pointer_guard 有关的利用也基本行不通。

因此,选择使用 house of apple 劫持_IO_FILE->_wide_data 成员进行利用。

在利用之前,还有一些准备工作需要做。我们需要进行合理的堆风水布局,使得能够在修改一个 largebin chunk A 的 bk_nextsize 的同时伪造一个 chunk B,并需要让 A 和 B 在同一个 bins 数组中,然后释放 B 并进行 largebin attack,这样就能保证既完成任意地址写堆地址,也能控制写的堆地址所属的 chunk 的内容。

对三种大小 chunk 的 size 进行分析,并设 x = key + 0x10y = key + 0x20z = key * 2 + 0x10。那么有:

1
2
2 * y - z = 2 * key + 0x40 - 2 * key - 0x10 = 0x30
2 * y - 2 * x = 2 *key + 0x40 - 2 * key - 0x20 = 0x20

题目中还存在 UAF,于是就可以的构造出如下布局:

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

堆风水步骤为:

  • 释放 chunk 1,并将其置于 largebin 中

  • 利用一次写的机会,修改 chunk 2,此时修改了 chunk1 的 bk_nextsize,并伪造一个 chunk 3

  • 释放 chunk 3,在其入链的过程中触发 largebin attack,即可任意地址写一个堆地址

经过计算,这里选择 key 为 0xa,此时 chunk 1 的大小为 0xab0,伪造的 chunk 3 的大小为 0xa80

基于上面对 house of apple 的分析,首先使用思路三修改 pointer_guard,然后进行 house of emma 利用。由于 pointer_guard 是 fs:[0x30],而 canary 是 fs:[0x28],所以直接找 canary,然后利用 pwndbg 的 search 命令搜索即可,如下所示:

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

此时的利用步骤如下:

  • 利用一次 write 的机会泄露出 libc 地址和 heap 地址

  • 利用堆风水,构造 1 次 largebin attack,替换_IO_list_all 为堆地址

  • 利用 house of apple,修改掉 pointer_guard 的值

  • 利用 house of emma 并结合几个 gadgets 控制 rsp

  • 用 rop 链输出 flag

其 exp 如下:

调试截图如下:

修改掉 pointer_guard

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

然后使用_IO_cookie_read 控制程序执行流:

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

成功劫持 rsp

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

接下来使用思路一,修改 tcache 变量,对于该变量的寻找同样可以使用 search 命令:

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

此时的步骤如下:

  • 使用 house of apple 修改 tcache 变量为可控堆地址

  • 使用_IO_str_overflow 完成任意地址写任意值,由于_IO_str_jumps 区域是可写的,所以我选择覆盖这里

  • 仍然利用一些 gadgets 劫持 rsp,然后 rop 泄露出 flag

exp 如下:

调试截图:

修改掉 tache 变量:

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

然后分配到_IO_str_jumps

House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)

后面的过程就一样了:

最后成功输出 flag:

总结

之前的一些 IO 流攻击方法对_wide_data 的关注甚少,本文提出一种新的方法,劫持了_wide_data 成员并在仅进行 1 次 largebin attack 的条件下成功进行 FSOP 利用。且该方法可通杀所有版本的 glibc

可以看到,house of apple 是对现有一些 IO 流攻击方法的补充,能在一次劫持 IO 流的过程中做到任意地址写已知值,进而构造出其他方法攻击成功的条件。

来源:https://xz.aliyun.com/  感谢【 roderick

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年7月5日21:41:48
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   House of Apple 一种新的 glibc 中 IO 攻击方法 (version 1)https://cn-sec.com/archives/1849447.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息