您的位置:首页 > 文旅 > 旅游 > FSOP,glibc-2.23攻击IO_list_all

FSOP,glibc-2.23攻击IO_list_all

2024/10/6 12:25:04 来源:https://blog.csdn.net/yjh_fnu_ltn/article/details/141271506  浏览:    关键词:FSOP,glibc-2.23攻击IO_list_all

文章目录

  • FSOP
    • 介绍:
    • FOSP链执行流程:
      • 源码调试过程

FSOP

介绍:

  1. FSOP 是 File Stream Oriented Programming 的缩写,根据前面对 FILE 的介绍得知进程内所有的 _ IO_FILE 结构会使用 _ chain 域相互连接形成一个链表,这个链表的头部由_IO_list_all 维护。

  2. FSOP 的核心思想就是劫持_IO_list_all 的值来伪造链表和其中的_IO_FILE 项,但是单纯的伪造只是构造了数据还需要某种方法进行触发。FSOP 选择的触发方法是调用_IO_flush_all_lockp,这个函数会刷新_IO_list_all 链表中所有项的文件流,相当于对每个 FILE 调用 fflush,也对应着会调用_IO_FILE_plus.vtable 中的_IO_overflow。

  3. 关键函数**_IO_flush_all_lockp**:

    int
    _IO_flush_all_lockp (int do_lock)
    {...fp = (_IO_FILE *) _IO_list_all;while (fp != NULL){...if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base))&& _IO_OVERFLOW (fp, EOF) == EOF){result = EOF;}...}
    }
    

FOSP链执行流程:

malloc中unsorted bin出错会调用malloc_printerr 输出错误:

image-20240813114500591

malloc_printerr 函数:

image-20240812174218150

跟进__libc_message函数,最后也调用了abort函数:

/* Abort with an error message.  */
void
__libc_message (int do_abort, const char *fmt, ...)
{va_list ap;int fd = -1;va_start (ap, fmt);#ifdef FATAL_PREPAREFATAL_PREPARE;
#endif/* Open a descriptor for /dev/tty unless the user explicitlyrequests errors on standard error.  */const char *on_2 = __libc_secure_getenv ("LIBC_FATAL_STDERR_");if (on_2 == NULL || *on_2 == '\0')fd = open_not_cancel_2 (_PATH_TTY, O_RDWR | O_NOCTTY | O_NDELAY);if (fd == -1)fd = STDERR_FILENO;struct str_list *list = NULL;int nlist = 0;const char *cp = fmt;while (*cp != '\0'){/* Find the next "%s" or the end of the string.  */const char *next = cp;while (next[0] != '%' || next[1] != 's'){next = __strchrnul (next + 1, '%');if (next[0] == '\0')break;}/* Determine what to print.  */const char *str;size_t len;if (cp[0] == '%' && cp[1] == 's'){str = va_arg (ap, const char *);len = strlen (str);cp += 2;}else{str = cp;len = next - cp;cp = next;}struct str_list *newp = alloca (sizeof (struct str_list));newp->str = str;newp->len = len;newp->next = list;list = newp;++nlist;}bool written = false;if (nlist > 0){struct iovec *iov = alloca (nlist * sizeof (struct iovec));ssize_t total = 0;for (int cnt = nlist - 1; cnt >= 0; --cnt){iov[cnt].iov_base = (char *) list->str;iov[cnt].iov_len = list->len;total += list->len;list = list->next;}written = WRITEV_FOR_FATAL (fd, iov, nlist, total);if (do_abort){total = ((total + 1 + GLRO(dl_pagesize) - 1)& ~(GLRO(dl_pagesize) - 1));struct abort_msg_s *buf = __mmap (NULL, total,PROT_READ | PROT_WRITE,MAP_ANON | MAP_PRIVATE, -1, 0);if (__glibc_likely (buf != MAP_FAILED)){buf->size = total;char *wp = buf->msg;for (int cnt = 0; cnt < nlist; ++cnt)wp = mempcpy (wp, iov[cnt].iov_base, iov[cnt].iov_len);*wp = '\0';/* We have to free the old buffer since the application mightcatch the SIGABRT signal.  */struct abort_msg_s *old = atomic_exchange_acq (&__abort_msg,buf);if (old != NULL)__munmap (old, old->size);}}}va_end (ap);if (do_abort){BEFORE_ABORT (do_abort, written, fd);/* Kill the application.  */abort ();}
}

跟进abort函数,其中调用了fflush函数:

/* Cause an abnormal program termination with core-dump.  */
void
abort (void)
{struct sigaction act;sigset_t sigs;/* First acquire the lock.  */__libc_lock_lock_recursive (lock);/* Now it's for sure we are alone.  But recursive calls are possible.  *//* Unlock SIGABRT.  */if (stage == 0){++stage;if (__sigemptyset (&sigs) == 0 &&__sigaddset (&sigs, SIGABRT) == 0)__sigprocmask (SIG_UNBLOCK, &sigs, (sigset_t *) NULL);}/* Flush all streams.  We cannot close them now because the usermight have registered a handler for SIGABRT.  */if (stage == 1){++stage;fflush (NULL);}
······
}

跟进fflush函数,fflush是一个宏定义,调用了IO_fflush函数,且参数是NULL:

image-20240812174952791

继续跟进IO_fflush(NULL),由于传入的参数为NULL,所以会调用_IO_flush_all函数:

int
_IO_fflush (_IO_FILE *fp)
{if (fp == NULL)return _IO_flush_all ();else{int result;CHECK_FILE (fp, EOF);_IO_acquire_lock (fp);result = _IO_SYNC (fp) ? EOF : 0;_IO_release_lock (fp);return result;}
}

跟进_IO_flush_all函数,_IO_flush_all_lockp调用了_IO_flush_all_lockp(1):

int
_IO_flush_all (void)
{/* We want locking.  */return _IO_flush_all_lockp (1);
}

跟进_IO_flush_all_lockp(1),而 _IO_flush_all_lockp就是这条FILE终点:

image-20240812175836473

int
_IO_flush_all_lockp (int do_lock)
{int result = 0;struct _IO_FILE *fp;int last_stamp;#ifdef _IO_MTSAFE_IO__libc_cleanup_region_start (do_lock, flush_cleanup, NULL);if (do_lock)_IO_lock_lock (list_all_lock);
#endiflast_stamp = _IO_list_all_stamp;fp = (_IO_FILE *) _IO_list_all;//这里fp取到了_IO_list_all 这里fp直接指向了_IO_2_1_stderr_首地址while (fp != NULL)//进入循环{run_fp = fp;if (do_lock)_IO_flockfile (fp);if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T|| (_IO_vtable_offset (fp) == 0&& fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr> fp->_wide_data->_IO_write_base))
#endif)&& _IO_OVERFLOW (fp, EOF) == EOF)//这里经过前面的判断后调用了_IO_OVERFLOW(fp,EOF)result = EOF;if (do_lock)_IO_funlockfile (fp);run_fp = NULL;if (last_stamp != _IO_list_all_stamp){/* Something was added to the list.  Start all over again.  */fp = (_IO_FILE *) _IO_list_all;last_stamp = _IO_list_all_stamp;}elsefp = fp->_chain;//这里使用FILE结构中的_chain来更新fp,直到fp为空才退出循环,所以会刷新_IO_list_all 链表中所有项的文件流}#ifdef _IO_MTSAFE_IOif (do_lock)_IO_lock_unlock (list_all_lock);__libc_cleanup_region_end (0);
#endifreturn result;
}

查看_IO_OVERFLOW(fp, EOF)定义,以及最后的:

//libc_2.23 的定义
define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
define _IO_JUMPS_FUNC(THIS) (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (THIS) + (THIS)->_vtable_offset))//结合传入的参数转化后如下:相当于调用了fp的__overflow函数
define _IO_OVERFLOW(FP, CH) JUMP1 (__overflow, FP, CH)
define JUMP1(__overflow, FP, CH) (_IO_JUMPS_FUNC(FP)->__overflow) (FP, CH)
define _IO_JUMPS_FUNC(FP) (*(struct _IO_jump_t **) ((void *) &_IO_JUMPS_FILE_plus (FP) + (FP)->_vtable_offset))//在libc_2.24后:_IO_JUMPS_FUNC的宏定义变化
define JUMP1(FUNC, THIS, X1) (_IO_JUMPS_FUNC(THIS)->FUNC) (THIS, X1)
define _IO_JUMPS_FUNC(THIS) (IO_validate_vtable (_IO_JUMPS_FILE_plus (THIS)))/* Check if unknown vtable pointers are permitted; otherwise,terminate the process.  */
void _IO_vtable_check (void) attribute_hidden; //提前声明/* Perform vtable pointer validation.  If validation fails, terminatethe process.  */
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_vtablessection.  */uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables;const char *ptr = (const char *) vtable;uintptr_t offset = ptr - __start___libc_IO_vtables;if (__glibc_unlikely (offset >= section_length))/* The vtable pointer is not in the expected section.  Use theslow 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_DEMANGLEPTR_DEMANGLE (flag);
#endifif (flag == &_IO_vtable_check)return;/* In case this libc copy is in a non-default namespace, we alwaysneed to accept foreign vtables because there is always apossibility that FILE * objects are passed across the linkingboundary.  */{Dl_info di;struct link_map *l;if (_dl_open_hook != NULL|| (_dl_addr (_IO_vtable_check, &di, &l, NULL) != 0&& l->l_ns != LM_ID_BASE))return;}#else /* !SHARED *//* We cannot perform vtable validation in the static dlopen casebecause FILE * handles might be passed back and forth across theboundary.  Therefore, we disable checking in this case.  */if (__dlopen != NULL)return;
#endif__libc_fatal ("Fatal error: glibc detected an invalid stdio handle\n");
}

最后找函数地址时,使用了_vtable_offset 即 _IO_FILE 结构体的 vtable 指针,而vtable 指针指向的是一个虚表,所以相当于最后调用到了下面的_IO_file_overflow函数,并且传入的参数是fp指针,即文件的地址:

image-20240812181423518

image-20240812180745597

随意最后IO_FILE链为:malloc报错 ==> malloc_printerr ==> __libc_message ==> abort ==> fflush ==> IO_fflush ==> _IO_flush_all ==> _IO_flush_all_lockp ==> _IO_OVERFLOW(最后使用vtable 指向的虚表中的指针),

最后在_IO_flush_all_lockp中时有两个判断条件需要绕过,才能调用到_IO_OVERFLOW :

  • fp->_mode <= 0
  • fp-> _IO_write_ptr > fp->_IO_write_base

image-20240816210105503

所以,在unsorted bin中构造的IO_FILE要满足这两个条件即可,最后伪造虚表,并用system地址覆盖掉_OVERFLOW指针,并在vtable位置伪造指针 ,指向这个虚表即可 。

源码调试过程

  1. 下面结合题目来调试这个过程,题目解析参考的这篇文章:House of Orange-CSDN博客,脚本和这篇文章完全一样,直接到最后一部调试malloc,直接断点到malloc:

  2. 此时堆上的布局如下,并且顺利进入malloc函数:

    image-20240816210302070

  3. 经过free_hook检查后进入到 _ int_malloc中分配chunk,传入的参数为main_arena地址,和申请的chunk大小:

    image-20240816210519531

  4. 进入 _int_malloc函数,先转化size的大小:

    image-20240816210815961

    随后检查实际分配的大小与get_max_fast(0x80)比较,先访问fastbin:

    image-20240816211101226

    fatbin中没有剩余的chunk,接下来就访问small bin:

    image-20240816211317124

    small bin中也没有剩余的chunk,下面进入到unsorted bin中查询:

    image-20240816212435339

    在unsorted bin中找到空闲chunk:

    image-20240816213402495

    size != nb,所以先放入到small bin中:

    image-20240816214425524

    image-20240816215039109

    修改后的main_arena,覆盖的IO_list_all的file中的_chain正好衔接到fake_chunk

    image-20240816220849827

    查看到fake_chunk中伪造的file结构

    image-20240816221330642

  5. 下面会因为unsorted bin的完整性报错 ,从而调用malloc_printerr函数:

    image-20240816222223675

  6. 再调用**__libc_message**函数:

    image-20240816222513599

  7. 再调用abort函数:

    image-20240816223308411

  8. 调用发flush(NULL),这里传入的参数是NULL:

    image-20240816223639380

  9. 成功进入到_IO_flush_all_lockp函数,来刷新所有文件流:

    第一个文件流,被我们覆盖掉IO_list_all后,移动到了main_arena_88:

    image-20240816224156379

    通过_chain取到第二个文件,即为我们伪造的fake_chunk:

    image-20240816224646684

    image-20240816224827261

    image-20240816225023786

  10. 最后成功拿到flag:

    image-20240816225050904

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com