在libc2.35及以后,glibc将许多的hook都给移除了,例如malloc_hook,free_hook等,导致一些常见的许多堆攻击利用方式几乎都失效了。 高版本后,_IO_FILE 的伪造和对 IO 流的劫持基本上是堆工具的主流思路,但是菜鸡一直看不懂,最近沉下心来看了roderick 佬的文章貌似看懂了点于是小记一下。
利用条件
能控制程序执行 IO 操作,包括但不限于:从 main 函数返回、调用 exit 函数、通过__malloc_assert 触发
已知 heap 地址和 glibc 地址
能控制_IO_FILE 的 vtable 和_wide_data,一般使用 largebin attack 去控制
利用原理
stdin/stdout/stderr 这三个_IO_FILE 结构体使用的是_IO_file_jumps 这个 vtable。
1 | pwndbg> p *(struct _IO_FILE_plus *) stdin |
2.24以后的glibc中,这个 vtable会有一个IO_validate_vtable函数对其指向的地址进行检测,检测代码如下:
1 | /* Perform vtable pointer validation. If validation fails, terminate |
stdin/stdout/stderr 这三个_IO_FILE 结构体中有个 ``_wide_data`成员指向一个和FILE结构体十分相像的wide_data结构体。
1 | pwndbg> p *(struct _IO_wide_data*) 0x7f3cb081cb80 |
两个结构体,都有一个虚表,但是_IO_wfile_jumps 没有进行检查保护,这意味着我们可以把它劫持到我们伪造的虚表,从而控制执行流。
利用_IO_wfile_overflow 函数控制程序执行流
对 fp 的设置如下:
_flags设置为~(2 | 0x8 | 0x800),如果不需要控制rdi,设置为0即可;如果需要获得shell,可设置为sh;,注意前面有两个空格vtable设置为_IO_wfile_jumps/_IO_wfile_jumps_mmap/_IO_wfile_jumps_maybe_mmap地址(加减偏移),使其能成功调用_IO_wfile_overflow即可_wide_data设置为可控堆地址A,即满足*(fp + 0xa0) = A_wide_data->_IO_write_base设置为0,即满足*(A + 0x18) = 0_wide_data->_IO_buf_base设置为0,即满足*(A + 0x30) = 0_wide_data->_wide_vtable设置为可控堆地址B,即满足*(A + 0xe0) = B_wide_data->_wide_vtable->doallocate设置为地址C用于劫持RIP,即满足*(B + 0x68) = C函数的调用链如下:
1
2
3
4_IO_wfile_overflow
_IO_wdoallocbuf
_IO_WDOALLOCATE
*(fp->_wide_data->_wide_vtable + 0x68)(fp)
例子思路
- 利用largebin attack向IO_list_all里面写入一个可控的堆地址
- 在这个堆块里面同时伪造一个_IO_list_all结构体和IO_wide_data结构体,以及他们对应的vtable指针
- _IO_list_all结构体的vtable指针指向 _IO_wfile_jumps来绕过检查,而 _wide_data的结构体指向我们伪造的虚表即可
模版:
1 |
|
exp
1 | #!/usr/bin/env python |