vtable 劫持分为两种,一种是直接改写 vtable 中的函数指针,通过任意地址写就可以实现。 另一种是覆盖 vtable 的指针指向我们控制的内存,然后在其中布置函数指针
直接修改的需要知道 FILE 的位置在哪. 对于 stdin, stdout, stderr 来说, 它们在 libc 的数据段. 如果程序有取消缓存区, 那么在程序的 bss 节上能够找到指向它们的指针. 对于其他的程序使用 fopen 打开的文件, FILE 结构在堆上 (使用 malloc 分配).
需要注意的是, 由于 IO_FILE_plus 结构中的 vtable 是 const 修饰的, 默认的在 libc 只读数据段, 所以 vtable 中的函数指针不能直接更改. 于是, 只能考虑修改 vtable 这个指针, 使其指向一块我们能够控制的内存, 在块内存上伪造 vtable.
fread 是标准 IO 库函数,作用是从文件流中读数据,函数原型如下
size_t fread ( void *buffer, size_t size, size_t count, FILE *stream) ;
buffer 存放读取数据的缓冲区。size:指定每个记录的长度。count: 指定记录的个数。stream:目标文件流。返回值:返回读取到数据缓冲区中的记录个数
fread 的代码位于 / libio/iofread.c 中,函数名为_IO_fread,但真正的功能实现在子函数_IO_sgetn 中。
_IO_size_t
_IO_fread (void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp)
{
_IO_size_t bytes_requested = size * count;
_IO_size_t bytes_read;
CHECK_FILE (fp, 0);
if (bytes_requested == 0) return 0;
_IO_acquire_lock (fp);
bytes_read = _IO_sgetn (fp, (char *) buf, bytes_requested);
_IO_release_lock (fp);
return bytes_requested == bytes_read ? count : bytes_read / size;
}
在_IO_sgetn 函数中会调用_IO_XSGETN,而_IO_XSGETN 是_IO_FILE_plus.vtable 中的函数指针,在调用这个函数时会首先取出 vtable 中的指针然后再进行调用。
_IO_size_t
_IO_sgetn (fp, data, n)
_IO_FILE *fp; void *data;
_IO_size_t n;
{
return _IO_XSGETN (fp, data, n);
}
在默认情况下函数指针是指向_IO_file_xsgetn 函数的,
if (fp->_IO_buf_base
&& want < (size_t) (fp->_IO_buf_end - fp->_IO_buf_base))
{
if (__underflow (fp) == EOF)
break;
continue;
}
fwrite 同样是标准 IO 库函数,作用是向文件流写入数据,函数原型如下
size_t fwrite(const void* buffer, size_t size, size_t count, FILE* stream);
buffer: 是一个指针,对 fwrite 来说,是要写入数据的地址;size: 要写入内容的单字节数;count: 要进行写入 size 字节的数据项的个数;stream: 目标文件指针;返回值:实际写入的数据项个数 count。
fwrite 的代码位于 / libio/iofwrite.c 中,函数名为_IO_fwrite。 在_IO_fwrite 中主要是调用_IO_XSPUTN 来实现写入的功能。
根据前面对_IO_FILE_plus 的介绍,可知_IO_XSPUTN 位于_IO_FILE_plus 的 vtable 中,调用这个函数需要首先取出 vtable 中的指针,再跳过去进行调用。