iofile 之 fopen
自己先写一个程序玩玩:
#include<stdio.h>
#include<stdlib.h>
int main() {
FILE* fp = fopen("test", "wb");
fprintf(stderr, "fp is at: %p\n", fp);
char* ptr = malloc(0x20);
return 0;
}
然后:
gcc -g IO_FILE_fopen.c -o IO_FILE_fopen
gdb IO_FILE_fopen
在 main 下断点并跟踪到 fprintf(stderr, “fp is at: %p\n”, fp); 执行完毕,执行 p *fp 可以看见:
_flags 是文件标志,暂时不知道有什么用。
执行 heap,可以看到分配了一个大小为 0x1e0 的堆:
p fp, 也可以看见它指向了:
看来是和堆很像了,执行malloc(0x20)后:
注意到read\write和buf的系列的值都为0x0,我们给源代码添加相应函数。
#include<stdio.h>
#include<stdlib.h>
int main() {
FILE* fp = fopen("test", "rb");
fprintf(stderr, "fp is at: %p\n", fp);
char* ptr = malloc(0x20);
fread(ptr, 1, 20, fp);
return 0;
}
执行完 fread 后,可以看见又分配了一个较大的堆:
应该是 buf 的:
但一般很少会有要求读取 file 的 pwn 题,io_file 更多是与堆结合。
iofile 之 house of orange
先看管理 file 的 _IO_list_all 链表:
有几个关键的:
_chain 指向该链表下一个节点:
查看 vtable:
vtable + 3 是 _IO_OVERFLOW 函数的地址,修改它为 system 的地址,在 unsorted bin 报错的时候就可以 getshell。
unsorted bin 中错误的fd/bk指针,会触发 malloc_printer 函数打印错误信息,malloc_printer 调用 __libc_message,_libc_message 调用 abort(),abort() 调用 _IO_flush_all_lockp。
在 _IO_flush_all_lockp 中,通过对链表结构 _IO_list_all 中每个节点进行遍历,找到符合条件的节点,执行 _IO_OVERWRITE 函数,其中特点是 _ IO_FILE_PLUS 类型的结构体,对函数的查找需要通过 vtable 定位函数表。如果我们可以劫持 IO 表中的 _IO_OVERFLOW 就可以 getshell。
可以看见 _IO_FILE_plus 就是在堆上:
打印一波:
计算可知 28 个 p64 构成一个 _IO_FILE_plus 结构体。
一个简单的利用就是伪造一个 _IO_FILE_plus 结构体,然后把它连接在 _IO_list_all 上。在结构体中,把 vtable 指向一个可控的地址,这个地址 +3 写入 system_addr。
获取到 libc 基地址地址就能获取到 _IO_list_all 的地址。一种改写方式是利用 unsorted bin。
在malloc的过程中,unsorted bin 会从链表上卸下来,将其中最后一个 chunk 取出,并把倒数第二个 chunk 的 fd 设置为 unsortedbin_chunk(av) 的地址其实就是 (&main_arena+88),而此时我们将 unsorted bin 中的 chunk 的 bk 改成 _IO_list_all-0x10,这样从 unsorted bin 中取出它时,就可以成功将 _IO_list_all 改写为 &main_arena+88 了。
将 _IO_list_all 改写为 &main_arena + 88 的地址后,而 fd -> chain 的位置是在 偏移 + 68 处,&main_arena + 88 + 68 刚好是 size 为 0x60 的 small_bin 的 bk,这个可以控制;又只含一个 chunk 时,small_bin chunk 的 bk 指向它本身。因此只要在该 chunk 上伪造 fileplus 结构体即可。
_IO_list_all -> main_arena + 88 -> chain (+68) -> (fake file)(size = 0x60)small_bin chunk
这大概就是 house of orange.