通过春秋杯的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 * |