house of water 和 stdout输出
house of water 和 stdout 输出
house of water
发现了一个很神奇的堆打法,可以做到无读写在 tcachebin 上踩至少一个 glibc 地址出来,非常好用
使用条件
- uaf
- 任意大小堆块分配
原理
- 如果程序运行到第一个 malloc,会初始化
main_arena
,并且在 2.31 之后的版本,会分配 0x290 大小的结构体存储 tcachebin 的内容,该结构体如下:
- 而 house of water 的做法就是在这个上面留下 libc 地址,从而可以直接在 tcachebin 中取出,导致在 libc 上分配堆
how2heap 中的 house of water demo:
原理
- 释放的 0x3e0 和 0x3f0 两个堆块是为了构造一个 0x10001 这样的大小块,这是因为 tcache_perthread_struct 第一个值会标记每个 tcachebin 内的堆块数量,这样就构造出了一个 size 样式的东西,方便我们进行 fake chunk 的创建。
- 19-22 行:创建 7 个 chunk,很明显是为了填满 tcachebin
- 24-33 行:间隔创建三个 chunk,并且增加间隔防止合并,这三个 chunk 全部在 unsortedbin 的位置。然后创建了一个巨大的 0xf000 的 chunk,用来填充到 0x10001,目的是为了让最开始讲的 tcache_perthread_struct 那个 0x10001 作为 size 是合法的。
- 35-37 行:创建 0x20 大小的 chunk,并且伪造 prev_size 和下一个 chunk 的 size:0x20;
- 39-41 行:填满 tcachebin
- 43-45 行:在 unsorted_start 的上面设置了一个 0x31 的堆块并且释放,释放掉之后由于进入 tcachebin 会加入一个验证的 key,这个 key 会覆盖掉原本 unsorted_start 的 size,所以得还原。
- 47-49 行:同理
- 在刚刚两个步骤下,我们可以知道在 tcache_perthread_struct 中,0x20 大小的会在 tcachebin 的第一个位置,而 0x30 大小的会在 tcachebin 的第二个位置,于是就造成了 0x10001 这个值下面刚好是这么两个地址,这样的话,也就是说假设 0x10001 进入 bin,那么它的 fd 指针将指向 unsorted_end,而 bk 指针将指向 unsorted_start
- 51-53 行:释放了三个 chunk,他们仨会进 unsorted bin,这里 unsorted bin 里会变成:
unsorted_start->unsorted_middle->unsorted_end
- 55-56 行:这一步是将之前讲到的 0x10001 这个堆块链接上去,替换掉 unsorted_middle。,可以看到将 unsorted_start 的 fd 指针变成了 fake_chunk,unsorted_end 的 bk 指针也变成了 fake_chunk,而刚刚提到了,fake_chunk 的 fd 指针是 unsorted_end,bk 指针是 unsorted_start,和 unsorted_middle 是一样的,所以完全可以过 unsorted bin 检测。在这一步之后,unsorted bin 变成了
unsorted_start->fake_chunk_unsorted_end
。很明显,如果这时候我们对 fake_chunk 里面踩一个 libc 地址,它会默认跑到 tcachebin 中去。 - 59 行:这一步的想法是进行切割,如果 unsorted bin 里面没有合适大小的块,则它会按顺序分配到 smallbin 或者 largebin 中,然后再进行切割,很明显这里会把 unsorted_start 和 unsorted_end 放入 small bin,而 fake chunk 进入 large bin。所以只要选择一个小于 0x10000 的块,这样在放入各自的 bin 之后,由于只有 fake chunk 进入了 large bin,它一定会在某两个位置出现 libc 地址,而这两个位置会变成 tcache bin 的两个。在此之后,如果申请相应大小的 tcache bin 的 chunk,则会在 libc 上建立相应的堆块。
house of water 一般会紧接着攻击 stdout,从而泄露 libc 地址,来进行下一步攻击
stdout 的攻击
stdout 是个 IO_FILE,其结构体如下:
定义:
其中 _IO_2_1_stdout_
的 _flags
是这样的:_IO_MAGIC|_IO_IS_FILEBUF|_IO_CURRENTLY_PUTTING|_IO_LINKED|_IO_NO_READS | _IO_UNBUFFERED |_IO_USER_BUF
我们在使用的时候,可以将 _flags 调成 0xfbda18**,从上面可以找到含义,然后将 _IO_write_base
改成想要泄露的位置,再将 _IO_write_ptr
改为结束位置,就可以在下一次调用 puts 或者 printf 的时候泄露出来想要的东西了。
一般如果想要泄露 libc 的话 payload 会写 p64(0xfbad1800)+p64(0x0)*3+'\x00'
这个也可以用 fastbin attack 来做,因为 stdout 的上面是 stderr,里面会有 0x7f。