前言

本来是在做tcache attack的例题的,但是wiki上的challenge2考察的重点不仅仅是在tcache。题目程序中没有输出的功能,所以无法像往常一样去泄露libc,这个时候就需要进行IO_FILE利用了。我也是第一次遇到这种情况,啃了几天,决定这部分还是需要单独一片文章来讲解一下。这篇文章主要说明的是利用_IO_2_1_stdout泄露libc的方法,当然IO_FILE还有劫持虚表等利用方式,之后也会陆续写。这篇文章更多的是为下一篇tcache attack的challenge2例题服务的,所以其中提及的针对性

往期回顾:
(补题)LCTF2018 PWN easy_heap超详细讲解
好好说话之Tcache Attack(3):tcache stashing unlink attack
好好说话之Tcache Attack(2):tcache dup与tcache house of spirit
好好说话之Tcache Attack(1):tcache基础与tcache poisoning
好好说话之Large Bin Attack
好好说话之Unsorted Bin Attack
好好说话之Fastbin Attack(4):Arbitrary Alloc
(补题)2015 9447 CTF : Search Engine
好好说话之Fastbin Attack(3):Alloc to Stack
好好说话之Fastbin Attack(2):House Of Spirit
好好说话之Fastbin Attack(1):Fastbin Double Free
好好说话之Use After Free
好好说话之unlink

特别鸣谢povcfe、R4bb1t、raycp、NoOne、yichen、r0se几位师傅的博客与指导

编写不易,如果能够帮助到你,希望能够点赞收藏加关注哦Thanks♪(・ω・)ノ

这部分的内容会引用很多的源码,我也不想这样,但是一些执行流程如果不以源码为载体讲解的话会有歧义,希望能够耐心看完

FILE结构

FILE在linux系统的标准IO库使用来描述文件结构,称之为文件流。这里提及的”流“其实是一种抽象的概念,无论是硬件还是软件其实都没有”流“一说,只是人们为了便于描述数据的流向而创造的名称。比如说当我们要输出磁盘中记录的数据,那么在计算机中首先会将磁盘中的数据加载进内存,那么磁盘–>内存这种流向就被抽象叫做”流“

FILE结构在程序执行fopen函数时会自动进行创建,并分配在堆中。我们常定义一个纸箱FILE结构的指针来接收这个返回值

FILE结构定义在glibc/libio/libio.h中,结构源码如下

看着很长是吧,没有关系,后面会讲解利用_IO_2_1_stdout泄露libc时主要构造的几个成员变量。回来继续,进程中的FILE结构会通过_chain域彼此连接形成一个链表,链表头部用全局变量_IO_list_all表示,通过这个值可以遍历所有的FILE结构,大致的链表结构如下图:

在标准I/O库中,每个程序启动时有三个文件流是自动打开的:stdinstdoutstderr。这里插一句个人观点,我认为stderr其实并不算是”流“,因为他的作用是在程序运行时发生异常或中断时的告警,并没有流向的动作,所以这个地方我自己是有疑问的,如果看到这的你了解的话还请在评论区留言指点一下。好的回来,因为会自动打开,所以在初始状态下,_IO_list_all指向了一个有这些文件流构成的链表,但是需要注意的是这三个文件流位于的是libc.so的数据段

_IO_FILE_plus结构

事实上_IO_FILE结构外包裹着另一种结构_IO_FILE_plus,其中包含了一个重要的指针vtable(虚表)指向了一系列函数指针:

在 libc2.23 版本下,32 位的 vtable 偏移为 0x94,64 位偏移为 0xd8。位置为glibc/libio/libioP.h

这里说明一下vtable(虚表)是个什么东西,我们在学C语言的时候会学到一个叫做虚函数的东西,具有虚函数的类都会有一张vtable(虚表),其中记录了本类中所有虚函数的函数指针,也就是说是个函数指针数组的起始位置,通常虚表在编程中所具有的作用是为了标识父类。需要注意的是虚表中值班韩虚函数的指针,没有函数体,虚函数表既有继承性又有多态性

vtable 是IO_jump_t 类型的指针,IO_jump_t中保存了一些函数指针,在后面我们会看到在一系列标准 IO 函数中会调用这些函数指针。也就是说,如果使用_IO_FILE_plus去定义一个结构体指针的话,我们既可以使用IO_FILE中的结构体成员变量,也能使用IO_jump_t中的函数指针

void * funcs[] = {1 NULL, // "extra word"2 NULL, // DUMMY3 exit, // finish4 NULL, // overflow5 NULL, // underflow6 NULL, // uflow7 NULL, // pbackfail8 NULL, // xsputn  #printf后面讲解执行流程章节会用到此处9 NULL, // xsgetn10 NULL, // seekoff11 NULL, // seekpos12 NULL, // setbuf13 NULL, // sync14 NULL, // doallocate15 NULL, // read16 NULL, // write17 NULL, // seek18 pwn,  // close19 NULL, // stat20 NULL, // showmanyc21 NULL, // imbue
};

_flags规则

那么通过上述对IO_FILE和_IO_FILE_plus结构的了解,这里我们将深入的讲解一下IO_FILE结构体中的第一个成员变量_flag,这个成员变量在利用_IO_2_1_stdout泄露libc的时候具有至关重要的作用。

先简单介绍一下_flag的规则,_flag的高两位字节是由libc固定的,不同的libc可能存在差异,但是基本上都一样:0xfbad0000。高两位字节其实就是作为一个标识,标志这是一个什么文件。而低两位字节的位数规则决定了程序的执行状态,低两位的规则如下:

一般在执行流程中会将_flag和定义常量进行按位与运算,并根据与运算的结构进行判断如何执行。后面_IO_2_1_stdout泄露libc章节,我们会一起走一遍输出函数执行流程,在其中就会运用到此处的内容

puts()函数执行流程

由于第二部分的例题中会用到puts()函数,所以这里我们拿puts()函数举例子,其实类似的输出函数比如fwrite函数等执行流程都差不多,区别在于由libc库中运行各个输出函数的.c文件不一样,但是流程都相似,并且都会殊途同归进行输出系统调用

_IO_puts --> _IO_new_file_xsputn

puts()函数在源码中的表现形式为_IO_puts,我们一起来看一下源码位置在:glibc/libio/ioputs.c

这里可以看到_IO_puts在过程当中调用了一个叫做_IO_sputn函数(_IO_fwrite也会调用这个),_IO_sputn其实是一个,它的作用就是调用_IO_2_1_stdout_中的vtable所指向的_xsputn,也就是_IO_new_file_xsputn函数

_IO_new_file_xsputn --> _IO_OVERFLOW

_IO_new_file_xsputn函数源码位置在:glibc/libio/fileops.c

由于_IO_new_file_xsputn函数的源码过长,这里就不大篇幅的贴图了。这里简单的描述一下这个函数的执行过程,在关键部分展示代码:首先进入函数之后判断输出缓冲区还有多少空间,这里是由_IO_write_end - _IO_write_base得来的,这两个是FILE结构体中的两个成员变量,分别是输出结束地址和其实输出地址,由于stdout也是FILE结构,所以后面就直接使用成员变量名称来描述了。接下来如果缓冲区有空间,则先把数据载入输出缓冲区并计算目标输出数据是否还有剩余

经过上述最后一步的判断,如果还有剩余则说明输出缓冲区未建立或者空间已满,那么就需要通过_IO_OVERFLOW函数来建立或清空缓冲区,这个函数主要是实现刷新缓冲区或建立缓冲区的功能。在vtable中为__overflow

_IO_new_file_overflow --> _IO_do_write

_IO_new_file_overflow函数的部分源码如下,位置在:glibc/libio/fileops.c


上图即是_IO_new_file_overflow函数的部分代码,我们想要利用的就是最后红色框中的_IO_do_write (f, f->_IO_write_base,f->_IO_write_ptr - f->_IO_write_base),图片上可能会有点看不清,_IO_do_write就是我们需要执行的目标函数,这个函数执行后会调用系统调用write输出输出缓冲区,传入_IO_do_write函数的参数为:stdout结构体_IO_write_base(输出缓冲区起始地址)和size(_IO_write_end - _IO_write_base计算得来)

如果我们事先在stdout的_IO_write_base的位置部署要输出的起始地址,那么在去利用_IO_do_write函数,即可打印部分内存地址,打印出来的内容就包含我们所需要泄露的libc

如果我们想要利用_IO_do_write函数的话是需要绕过_IO_new_file_overflow函数的检查的,就是上图中蓝色框中的判断条件,我们一步一步的分解_IO_new_file_overflow函数的这两个判断条件:

首先我们来看第一个判断条件,这里判断_flags的标志位是否包含_IO_NO_WRITES,将_flags和_IO_NO_WRITES进行一个按位与的操作,我们可以向前翻一下flag规则的章节,_flag与_IO_NO_WRITES各自定义的常量为:

#define _IO_MAGIC 0xFBAD0000 /* 魔数 */
#define _IO_NO_WRITES 8 /* 不可写 */

可以看到_flag魔数的常量为0xfbad0000,_IO_NO_WRITES不可写标志位的常量为8,我们返回上图的程序中,如果进行按位与操作之后的结果为真,则返回为错误。一旦返回的是错误,那么后续我们想要利用的_IO_do_write函数就不会再被执行了,所以我们要将此处的与运算为假:

#define _IO_MAGIC 0xFBAD0000
#define _IO_NO_WRITES 8
_flags & _IO_NO_WRITES = 0
_flags = 0xfbad0000

这样一来判断条件中与运算就会为假,就不会执行判断中的语句了。接下来我们看一下第二个判断条件:

第二个判断是为了检查输出缓冲区是否为空,如果为空则进行分配空间,并且会初始化指针。一旦进行初始化操作,那么就会覆盖掉我们事先在stdout的_IO_write_base的数据,这样一来我们其实是无法完全掌控的。所以这个判断条件分支尽可能的也不进入,那么我们将if判断条件的值为假即可

if ((f->_flags & _IO_CURRENTLY_PUTTING) == 0 || f->_IO_write_base == NULL)

我们拆开来看这个判断条件,由两部分组成,并用或连接。先看后半部分f->_IO_write_base == NULL,这里由于会在_IO_write_base中部署数据,所有后半部分的条件判断一定为假。那么这样一来我们将前半部分也为假,即f->_flags & _IO_CURRENTLY_PUTTING = 1,则整个判断就为假:

#define _IO_MAGIC 0xFBAD0000
#define _IO_CURRENTLY_PUTTING 0x800
f->_flags & _IO_CURRENTLY_PUTTING = 1
_flags = 0xfbad0800

_IO_new_do_write --> new_do_write

经过前面的_flags的处理,即可顺利执行到_IO_do_write函数,跟进_IO_do_write函数后将会进入_IO_new_do_write函数,我们来看一下这个函数的源码,位置在glibc/libio/fileops.c

可以看到_IO_new_do_write并没有做太多的操作,就调用了new_do_write函数,new_do_write函数的参数其实是和传入的参数是一样的,一参stdout结构体,二参输出缓冲区起始地址,三参输出长度

new_do_write --> _IO_SYSWRITE

我们一起来看一下new_do_write函数中的源码,位置在glibc/libio/fileops.c

可以看到上图即是new_do_write函数中的源码了,红色框中的_IO_SYSWRITE函数即是IO_FILE的最终目标,执行系统调用write。但是同时我们还能看到前面又经过了两次判断,其中第一次if判断与_flags相关,else if中的内容同样需要仔细斟酌一下。由于if和else if是幸福二选一,如果两个判断条件都不满足的话,是不会继续执行到518行的系统调用的

我们先看这个比较复杂的else if判断:


这条分支我们尽可能的不碰,原因有两点:

  • 第一,其实只要满足判断中的条件fp->_IO_read_end = fp->_IO_write_base即可绕过这里的判断,使之相等的操作并不是没有可能,但是在实际操作中实现的几率比较小。一般在做这种题的时候都会伴随着随机化保护的开启,进行攻击的时候,我们一般采用的都是覆盖末位字节的方式造成偏移,因为即使随机化偏移也会存在0x1000对齐。但是这时候就会遇到一个很尴尬的情况,_IO_read_end和_IO_write_base存放的地址是由末位字节其他高字节共同组成的,其他高字节由于随机化的缘故无法确定,所以何谈使两个成员变量中的地址相等呢
  • 第二,可以看到else if这条分支中调用了_IO_SYSSEEK系统调用,即lssek函数,如果我们将_IO_read_end的值设置为0,那么_IO_SYSSEEK的二参fp->_IO_write_base - fp->_IO_read_end得出的数值就有可能非常大,这就会导致sleek函数执行不成功导致退出,这是因为载入内存的数据范围可能并不大,但是经过sleek函数修改过大的偏移之后超过了数据范围的边界。一旦Sleek函数执行不成功导致退出,那么就不会到达我们想要的_IO_SYSWRITE系统调用了

所以综上所述,我们无法完全掌控_IO_read_end和_IO_write_base中的数值,导致进入else if的分支后程序执行流程不可控

接下来我们看一下if分支:

if分支相对来说造成的影响就比较小了,内部仅仅将偏移设置为标准值,不会影响后续的输出流程。并且if判断的条件也很容易满足,我们只需要将fp->_flags & _IO_IS_APPENDING = 1即可,只对_flag修改不会影响其他部分:

#define _IO_MAGIC 0xFBAD0000
#define _IO_IS_APPENDING 0x1000
fp->_flags & _IO_IS_APPENDING = 1
_flags = 0xfbad1000

这样就可以到达_IO_SYSWRITE系统调用了!

总结

所以通过上述的讲解,我们只需要满足如下几条对_flags的设定,即可利用_IO_2_1_stdout泄露libc

#define _IO_MAGIC 0xFBAD0000
#define _IO_NO_WRITES 8
#define _IO_CURRENTLY_PUTTING 0x800
#define _IO_IS_APPENDING 0x1000

1、设置_flags & _IO_NO_WRITES = 0
2、设置_flags & _IO_CURRENTLY_PUTTING = 1
3、设置_flags & _IO_IS_APPENDING = 1

_flags = 0xFBAD1800

4、设置_IO_write_base指向想要泄露的位置,_IO_write_ptr指向泄露结束的地址(不需要一定设置指向结尾,程序中自带地址足够泄露libc)

好好说话之IO_FILE利用(1):利用_IO_2_1_stdout泄露libc相关推荐

  1. 好好说话之House Of Einherjar

    前言 又鸽了好久,抱歉哈~ 总的来说House Of Einherjar这种利用方法还是挺简单的,有点像chunk extend/shrink技术,只不过该技术是后向,并且利用top_chunk合并机 ...

  2. 好好说话之64位格式化字符串漏洞

    64位格式化字符串和32位的很相似,做题的步骤也相同,唯一不同的是64位程序对函数参数存储的方式和32位的不同.64为程序会优先将函数的前6个参数放置在寄存器中,超过6个的再存放在栈上,而32位直接存 ...

  3. BUUCTF(pwn)[HarekazeCTF2019]baby_rop2 泄露libc基址,rop,利用gadget

    64位,开了nx保护 运行了一下程序 buf的大小是0x20,但是读入的时候读入的是0x100,会造成溢出,我们要想办法覆盖返回地址为" system('/bin/sh') 利用read函数 ...

  4. 读书笔记之《好好说话》

    作者 主创成员:马东.马薇薇.黄执中.周玄毅.邱晨.胡渐彪.刘京京. 感想 这本书的主创者是<奇葩说>的大牛们,这也是将它纳入书单的原因.  不管在生活中还是在工作中"好好说话& ...

  5. 好好说话之Tcache Attack(1):tcache基础与tcache poisoning

    进入到了Tcache的部分,我还是觉得有必要多写一写基础的东西.以往的各种攻击手法都是假定没有tcache的,从练习二进制漏洞挖掘的角度来看其实我们一直模拟的都是很老的环境,那么这样一来其实和真正的生 ...

  6. 好好说话之Fastbin Attack(1):Fastbin Double Free

    好像拖更了好久...实在是抱歉....主要是fastbin attack包含了四个部分,后面的例题不知道都对应着哪个方法,所以做完了例题才回来写博客.fastbin attack应该也会分四篇文章分开 ...

  7. 好好说话之unlink

    堆溢出的第三部分unlink,这可能是有史以来我做的讲解图最多的一篇文章了累死 .可能做pwn的人都应该听过unlink,见面都要说声久仰久仰.学unlink的时候走了一些弯路,也是遇到了很多困扰的问 ...

  8. 好好说话之Tcache Attack(3):tcache stashing unlink attack

    tcache stashing unlink attack这种攻击利用有一个稍微绕的点,就是small bin中的空闲块挂进tcache bin这块.弯不大,仔细想想就好了 往期回顾: 好好说话之Tc ...

  9. 好好说话之Tcache Attack(2):tcache dup与tcache house of spirit

    这篇文章介绍了两种tcache的利用方法,tcache dup和tcache house of spirit,两种方法都是用how2heap中的例题作为讲解.由于tcache attack这部分的内容 ...

最新文章

  1. 谈谈对数据库中ACID、CAP、BASE的认识
  2. iphone怎么投屏到电脑_手机怎么投屏到电脑?这几步轻松学会
  3. 如何建立一个完整的游戏AI
  4. Vue介绍---vue工作笔记0001
  5. 动态创建TXMLDocument--使用IXMLDocument接口
  6. WebResource.axd引起的问题
  7. Codejock Xtreme ToolkitPro MFC 使用
  8. 基于java的租房系统源代码_基于jsp的租房管理系统-JavaEE实现租房管理系统 - java项目源码...
  9. awz3格式转epub格式转mobi格式
  10. 构建你的Office 365开发环境 - IOS版
  11. 使用vscode利用vue脚手架创建项目每次修改代码都会频繁编译
  12. 现代数据库及大数据管理—常见问题与技术归纳
  13. Mysql之st_distance_sphere计算两坐标点距离
  14. 【动态规划】状态机模型:买卖股票的最佳时机 IV
  15. c#代码实现打印机打印文件
  16. stm32cubeide烧写程序_STM32 Cube IDE 下实现 IAP —— (1) 程序跳转
  17. win10备份为wim_Win10 也能玩转一键还原
  18. LDA 线性判别分析
  19. C++编写MC(含源码)
  20. cpc按点击计算怎么算_什么是CPC(每次点击费用)?

热门文章

  1. android手机如何截屏,安卓手机怎么截图? (全文)
  2. android的aod的功能,一加正式推出氢OS 11:基于安卓11打造 新增「年轮AOD」功能
  3. w ndows7调亮度快捷键,Windows7电脑亮度怎么调?
  4. F - Fairy, the treacherous mailman
  5. Thingworx笔记-创建菜单
  6. QT 主线程子线程互相传值
  7. 《自己动手写CPU》--第九章--学习笔记
  8. 杭州公司java开发工程师常见面试问题
  9. php java扩展模块_php扩展模块装安装
  10. sql server 经典练习题分享二