通过春秋杯的houseofSome
做题过程中发现的新读写原语(与出题人交流发现这条write primitive
已经有研究了,但是笔者的read primitive
确实是新的,暂且把这种组合利用称为house of illusion
)
这题使用了patch
过的glibc-2.38
,_wide_data
加入了_vtable_check
,house_of_apple2
系列就都失效了,想要打io
,我们必须用原有的vtable
函数来实现 写或读原语,劫持_IO_list_all
是必要的。
这题只能泄露libc
地址,并且只能将一个字节写成\x00
,我们发现fopen
会将打开的dev
插入_IO_list_all
,并且dev
的地址是堆地址,就可以通过覆写_IO_list_all
低地址,劫持到可控的堆地址,接下来就是实现read/write primitive
write primitive
原先打io
利用的:
1 | vtable->__overflow((FILE *)chain, -1) |
也就是调用了_IO_new_file_overflow
,在这个函数中存在:
1 | if ( ch_0 == -1 ) |
1 | int __fastcall _IO_new_do_write(FILE_0 *fp, const char *data, size_t to_do) |
new_do_write
可以调用_vtables + 0x78
的函数,也就是_IO_new_file_write
1 | size_t __fastcall new_do_write(FILE_0 *fp, const char *data, size_t to_do) |
_IO_new_file_write
就实现了真正的write
1 | ssize_t __fastcall IO_new_file_write(FILE_0 *f, const void *data, ssize_t n) |
从而可以构造write primitive
1 | fake_io_write = fit({ |
read primitive
read primitive
需要知道_IO_file_jumps
长啥样
1 | 00:0000│ 0x7ffff7dbe450 (_IO_file_jumps) ◂— 0x0 |
在write primitive
中我们调用了_IO_new_file_overflow
和其中的_IO_new_file_write
,对应偏移0x18
和0x78
在read primitive
中我们通过将_vtables
减去0x8
,也就能调用_IO_new_file_finish
1 | void __fastcall IO_new_file_finish(FILE_0 *fp, int dummy) |
恰巧里面刚好也有_IO_new_do_write
,由于它是通过_vtables + 0x78
调用函数,所以实际调用_IO_file_read
1 | ssize_t __fastcall _GI__IO_file_read(FILE_0 *fp, void *buf, ssize_t size) |
从而能得出read primitive
:
1 | fake_io_read = fit({ |
conclude
笔者的这两个primitive
,需要leak libc
地址、劫持_IO_list_all
,触发IO
流。
可以通过读原语,将第一个fake FILE
的_chain
指向要写入的libc
地址,再向指向libc
地址写入一连串linked
的fake FILE
,这样可以在_IO_flush_all
完成后续多次利用。
且读和写的地址和长度都由_IO_write_base
和_IO_write_ptr - _IO_write_base
计算得到,实现非常方便。
exp
1 | from pwn import * |