0.FILE结构体
FILE 在 Linux 系统的标准 IO 库中是用于描述文件的结构,称为文件流。 FILE 结构在程序执行 fopen 等函数时会进行创建,并分配在堆中。我们常定义一个指向 FILE 结构的指针来接收这个返回值。
FILE 结构定义在 libio.h 中,如下所示:
1 | struct _IO_FILE { |
进程中的 FILE 结构会通过_chain 域彼此连接形成一个链表,链表头部用全局变量_IO_list_all 表示,通过这个值我们可以遍历所有的 FILE 结构。
在标准 I/O 库中,每个程序启动时有三个文件流是自动打开的:stdin、stdout、stderr。因此在初始状态下,_IO_list_all 指向了一个有这些文件流构成的链表,但是需要注意的是这三个文件流位于 libc.so 的数据段。而我们使用 fopen 创建的文件流是分配在堆内存上的。
我们可以在 libc.so 中找到 stdin\stdout\stderr 等符号,这些符号是指向 FILE 结构的指针,真正结构的符号是:
1 | _IO_2_1_stderr_ |
(注:以上摘自ctf-wiki。)
1.利用stdout泄露libc地址
这种泄露方法取代了原本的house of roman,只需要爆破4位(半个字节,house of roman需要爆破4096位)。
这种泄露方法通常用在没有show这类函数的堆题上,通过这种方法可以将libc上的地址泄露出来。
主要思路是修改stdout的flags位为0xfbad1800,并将_IO_write_base的地址改小(通常是局部覆写最后一字节为\x00)。这样在程序再次调用puts这类函数的时候,就会输出_IO_2_1_stderr_某偏移处附近的一些libc地址。
为什么要修改flags为0xfbad1800而不是其他别的什么值呢?
来看一段源码:
puts函数再源码中是由_IO_puts实现的,而它内部会调用_IO_sputn,并会执行_IO_new_file_xsputs,最终会执行_IO_overflow。可以看到_IO_do_write是最后调用的函数,而_IO_write_base是我们要修改的目标。
因此我们需要满足一系列的条件使得它进入我们期望的地方。
而要想要修改这个这些,我们最期望的就是能直接访问这些内容进行修改,或者在它们附近伪造一个堆块来进行改写。
那么,以后者为例,在不知道地址的情况下我们怎么才能够改写这些地方呢?
我们知道,当程序里只有一个usortedbin的时候,其fd和bk均指向main_arena+88(以libc2.23为例),巧合的是,这个地址和我们要改写内容的地址仅有后两个字节不同,我们可以让这个地址传入fastbin的fd,再通过局部覆写低两个字节来使得目标地址进入我们的fake chunk。而我们又知道ALSR的随机化程度不高,仅对大于0x1000的部分进行随机化,这也就意味着后一个半字节是不变的,那么我们要获取的实际地址仅有半个字节未知,因此需要爆破这4位。
2.实例探究与offbyone结合的利用
- 附件:noleak
- 环境:Ubuntu16.04 libc2.23
这题是之前的offbyone的改题,保护和漏洞点一样,只是没有show功能的函数。
因此,这题的思路是先利用offbyone漏洞将fake chunk写入fastbin链,修改stdout的flags和_IO_write_base最后一个字节来泄露libc的地址,再次通过offbyone来攻击__malloc_hook以getshell。
因为后半的利用前面讲过了,这里重点来讲一下如何泄露。
我们用pwndbg进行动态调试,获取stdout中存放的地址:
查看这个地址附近的内存:
可以发现就是我们要修改的结构体。
我们再用find_fake_fast指令在其附近找一个能充当fake chunk的内存区域:
能找到一以25dd结尾的fake chunk头,我们的目标就是修改main_arena+88处的地址后两个字节为25dd(5dd是固定的,2是要爆破的数字)。
我们最开始的堆布局如下:我们的思路是利用offbyone使得2、3进入fastbin,1、2、3合并进入unsortbn,再把1申请回来,此时剩下的unsortbin与我们的fastbin产生overlap,unsortbin的指针就传入了fastbin链:1
2
3
4
5add(0,0x68,'A'*0x68) #利用offbyone改写1的size
add(1,0x108,'AAAA') #1必须是unsortdbin大小的堆块
add(2,0x68,'BBBB') #2,3是等大的fastbin chunk
add(3,0x68,'CCCC') #1,2,3是用来合并的
add(4,0x68,'DDDD') #防止被topchunk合并
此后再申请一个unsortbin大小的堆块(注意这个堆块大小不能超过2+3),那么我们申请得到的新堆块就和fastbin链上的freed chunk重叠,我们就可以进行局部改写了:
(注:由于原fasbin链中最先入链的堆块的size会被改写,因此不能直接申请对应大小的堆块,应先将size改回合法的后再申请,否则会报错!)
之后就是爆破(看脸)了,成功的话(概率为1/16)就能泄露出地址信息:
后续的利用就不赘述了,直接贴exp:
1 | from pwn import * |
成功的话就会获取shell: