『CTF』IO_FILE利用入门(2)

admin 2024年7月1日23:43:38评论1 views字数 3761阅读12分32秒阅读模式
『CTF』IO_FILE利用入门(2)

点击蓝字 关注我们

『CTF』IO_FILE利用入门(2)
日期:2024.07.01
作者:H4y0
介绍:Linux_io_FILE基础知识。

0x00 前言

_IO_FILE利用已成为CTFPWN题的常规利用思路,逐渐成为基础知识,上篇文章已经介绍了_IO_FILE的结构及标准I/O流相关内容,本文将介绍vtable及与vtable相关的劫持程序流的方法。

『CTF』IO_FILE利用入门(2)

0x01 伪造vtable

借用Wiki上的例子:

#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    memcopy(fp,"sh",3);    vtable_ptr[7]=system_ptr //xsputn    fwrite("hi",2,1,fp);}

使用glibc-2.23进行编译,glibc2.23 版本下,位于 libc 数据段的vtable是不可以进行写入的,但是可以通过伪造vtable并且修改_IO_FILE_plus->vtable这个指针来实现劫持程序流程。

简单介绍一下fwrite函数。

_IO_size_t    _IO_fwrite (const void *buf, _IO_size_t size, _IO_size_t count, _IO_FILE *fp){      _IO_size_t request = size * count;      _IO_size_t written = 0;      CHECK_FILE (fp, 0);      if (request == 0)        return 0;      _IO_acquire_lock (fp);      if (_IO_vtable_offset (fp) != 0 || _IO_fwide (fp, -1) == -1)        written = _IO_sputn (fp, (const char *) buf, request);      _IO_release_lock (fp);      /* We have written all of the input in case the return value indicates         this or EOF is returned.  The latter is a special case where we         simply did not manage to flush the buffer.  But the data is in the         buffer and therefore written as far as fwrite is concerned.  */      if (written == request || written == EOF)        return count;      else        return written / size;    }    libc_hidden_def (_IO_fwrite)

fwriteC标准库中用于将数据块写入文件的函数,它通常是对底层文件 I/O函数的封装之一。_IO_sputn函数与 fwrite的实现息息相关。在GNU C库中,fwrite函数通常会调用一系列与文件 I/O相关的函数,其中包括 vtable(虚函数表)函数。

  • _IO_fwrite 函数调用了 vtable 的 _IO_new_file_xsputn 。

  • _IO_new_file_xsputn 函数调用了 vtable 中的 _IO_new_file_overflow 实现缓冲区的建立以及刷新缓冲区。

  • vtable 中的 _IO_new_file_overflow 函数调用了 vtable 的 _IO_file_doallocate 以初始化输入缓冲区。

  • vtable 中的 _IO_file_doallocate 调用了 vtable 中的 __GI__IO_file_stat 以获取文件信息。

  • new_do_write 中的 _IO_SYSWRITE 调用了 vtable_IO_new_file_write 最终去执行系统调用write。

也就是说,fwrite函数会用到xsputnvtable_ptr[7]=system_ptr //xsputn这行代码将虚函数表中的第7个函数指针(通常对应 xsputn 函数,可见上篇文章_IO_jump_t)替换为 system 函数的地址,从而在运行fwrite时运行system函数,实现劫持程序流程。

0x02 IO_validate_vtable

IO_validate_vtableGNU C库中的一个函数,用于验证文件结构体中的虚函数表是否有效。

static inline const struct _IO_jump_t *IO_validate_vtable(const struct _IO_jump_t *vtable) {    /* Fast path: The vtable pointer is within the __libc_IO_vtables       section.  */    uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;    uintptr_t ptr = (uintptr_t) vtable;    uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables;    if (__glibc_unlikely (offset >= section_length))        /* The vtable pointer is not in the expected section.  Use the           slow path, which will terminate the process if necessary.  */        _IO_vtable_check();    return vtable;}void attribute_hidden _IO_vtable_check(void) {#ifdef SHARED    /* Honor the compatibility flag.  */    void (*flag)(void) = atomic_load_relaxed (&IO_accept_foreign_vtables);#ifdef PTR_DEMANGLE    PTR_DEMANGLE (flag);#endif    if (flag == &_IO_vtable_check)        return;    /* In case this libc copy is in a non-default namespace, we always       need to accept foreign vtables because there is always a       possibility that FILE * objects are passed across the linking       boundary.  */    {        Dl_info di;        struct link_map *l;        if (!rtld_active()            || (_dl_addr(_IO_vtable_check, &di, &l, NULL) != 0                && l->l_ns != LM_ID_BASE))            return;    }    ...}static inline bool rtld_active (void) {  /* The default-initialized variable does not have a non-zero     dl_init_all_dirs member, so this allows us to recognize an     initialized and active ld.so copy.  */  return GLRO(dl_init_all_dirs) != NULL;}int _dl_addr(const void *address, Dl_info *info, struct link_map **mapp, const ElfW(Sym) **symbolp) {    const ElfW(Addr) addr = DL_LOOKUP_ADDRESS (address);    int result = 0;    /* Protect against concurrent loads and unloads.  */    __rtld_lock_lock_recursive (GL(dl_load_lock));    ...

其中rtld_active 函数通过检查通过检查 dl_init_all_dirs 变量是否非空来判断 ld.so是否处于活动状态。如果返回true,则调用_dl_addr,进而执行__rtld_lock_lock_recursive (GL(dl_load_lock))。而这个宏和exit hook对应的宏一致,所以和exit hook的思路一样,通过改写指针就可以实现劫持程序流。当然,同样在glibc2.34之后__rtld_lock_lock_recursive__rtld_lock_unlock_recursive进行了修复,该劫持程序流的方式和exit hook一同失效。

0x03 总结

本文主要介绍了两种和虚表相关的劫持程序流的方式,可以通过伪造vtable劫持程序流,也可以通过IO_validate_vtable使用和exit hook相似的方式劫持程序流。

期推荐

『CTF』IO_FILE利用入门(2)

『CTF』IO_FILE 利用入门

『CTF』堆入门 

『CTF』pwnable 学习笔记 

免责声明:本文仅供安全研究与讨论之用,严禁用于非法用途,违者后果自负。

点此亲启

原文始发于微信公众号(宸极实验室):『CTF』IO_FILE利用入门(2)

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月1日23:43:38
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   『CTF』IO_FILE利用入门(2)https://cn-sec.com/archives/2906018.html

发表评论

匿名网友 填写信息