type
status
date
slug
summary
tags
category
icon
password
glibc2.23的利用
这道题用到了3种手法,
- house of orange-用来解决没有free函数泄露libc
- Unsorted Bin Attack-向_IO_list_all中写入heap地址
- FROP-劫持IO程序执行流拿到shell
前两种做法我在之前的文章中都有写过,这里不加赘述,只做补充
这里要注意我们在生成unsortedbin后,可以直接申请一个大小0x400的heap,这样我们可以直接在这个heap中中找到libc地址和heap的地址。因为这个heap过大,从unsortedbin上切割后会先初始化为largebin在分配给我们。
这里就是把Unsorted Bin的fd指针修改为0,bk改为要修改的地址的-0x10。
然后把这个bin无论怎么做只要能消除,就能向bk中的地址+0x10中写入程序中
main_arena+88
的地址数据。FSOP:
FSOP的核心是去篡改_IO_list_all和_chain,来劫持IO_FILE结构体。让IO_FILE结构体落在我们可控的内存上。然后在FSOP中我们使用_IO_flush_all_lockp来刷新_IO_list_all链表上的所有文件流,也就是对每个流都执行一下fflush,而fflush最终调用了vtable中的_IO_overflow而前面提到了,我们将IO_FILE结构体落在我们可控的内存上,这就意味着我们是可以控制vtable的,我们将vtable中的_IO_overflow函数地址改成system地址即可,而这个函数的第一个参数就是IO_FILE结构体的地址,因此我们让IO_FILE结构体中的flags成员为/bin/sh字符串,那么当执行exit函数或者libc执行abort流程时或者程序从main函数返回时触发了_IO_flush_all_lockp即可拿到shell下面是链表的正常结构 下面是FSOP的布局,首先篡改_IO_list_all为main_arena+88这个地址(因为这片内存是不可控的),chain字段是首地址加上0x68偏移得到的。因此chain字段决定了下一个IO_FILE结构体的地址为main_arena+88+0x68,这个地址恰好是smallbin中size为0x60的数组,如果我们能将一个chunk放到这个small bin中size为0x60的链上,那么篡改_IO_list_all为main_arena+88这个地址后,small bin中的chunk就是IO_FILE结构体了,将其申请出来后,我们就可以控制这块内存了,从而伪造vtable字段进行布局最终拿到shell。
以上是对SROP的详细解释,这下面的是这道题的基本过程。
再程序中的正常情况下会出现这样的执行流程,首先程序中有个地址名叫
_IO_list_all
里面记录整要执行的第一个IO流IO_2_1_stderr

程序便会从
_IO_list_all
这个地址里寻找到第一个IO流IO_2_1_stderr
的其实地址,然后遍历这个结构体,此时正常情况下这个结构体的内容如下
再遍历完这个结构体后还会再去遍历下一个
IO_2_1_stdout
结构体,而这个结构体的地址就在IO_2_1_stderr
+104
这个地址,也是再上面我们打印的_chain
这里又IO_2_1_stdout
结构体的起始地址。于是程序会再遍历
IO_2_1_stdout
这个结构体
程序再遍历完这个结构体后又会再次寻找
_chain
地址的值,此时这个地址里存放这的为IO_2_1_stdin
结构体的起始地址,于是程序就会再次遍历这个结构体,
再遍历完这个结构体之后由于此时这个结构体的
_chain
为0,那程序便不会再遍历其他结构体,而是寻找到此时结构体中的vtable
地址,里面记录着的是_IO_file_jumps
这个结构体的起始地址。
而程序此时便会根据需要去跳转到需要执行的函数。

以上就是我们在IO中经常提到的有关_IO_list_all的chain遍历。
当然这是正常的情况下会发生的事,
于是我们就有了对这种程序的执行流的劫持。而这种就是我们对这道题的SROP构造过程。在一开始我们提到程序中有个地址名叫
_IO_list_all
里面记录着要执行的第一个IO流IO_2_1_stderr
我们就可以直接把这个地址里的内容修改为其他地址,并把这个新地址当作IO_2_1_stderr
遍历,这样就能直接控制程序遍历的地址为我们修改过的地址。就能出现如下的情况,

此时我们将
_IO_list_all
的地址修改为0x7eb5c77c4b78 <main_arena+88>
与是程序就会把
0x7eb5c77c4b78 <main_arena+88>
这个地址及其之后大的数据当作IO_2_1_stderr
形成如下的结构体
由于之前我们的构造此时程序中的
_IO_read_end
和_IO_read_ptr
这里存放着的地址指向/bin/sh字符串(这个再后面会被当作参数传入函数中)。_chain
中的地址为堆的地址,着也是我们要构造好的地址,并把它当作下一个IO_2_1_stdout
结构体遍历构造结果如下
程序遍历这个结构体中数据,重点在于此时
_IO_write_ptr
地址中的数据为1,至于为什么是1,我暂时不理解,但我感觉可能是与之前的_IO_read_ptr
和_IO_read_end
都指向/bin/sh的地址有关。
但现在程序遍历到我们伪造的这个结构体时发现不对这个结构体的
_chain
还是它本身,于是在遍历一遍之后就要跳转vtable
中值,此时vtable
也被我们修改了
此时的程序会把
vtable
中的值当作_IO_jump_t
这个结构体去在其中寻找__overflow
跳转执行,但是此时我们把__overflow
的地址刚好修改为system
的地址。于是程序在这里就会直接跳转执行
system
函数,并且因为之前_IO_read_ptr
和_IO_read_end
都指向/bin/sh的地址,同时_IO_write_ptr
地址中的数据为1,程序就会直接执行system(/bin/sh)
从而拿到shell。以上就是这道题中的_IO链利用,当然在这道题中对堆的布局也是很巧妙的

现在是还没有进行布局的堆,我们刚刚申请了一个0x410的chunk使用,接下我们就要从这个0x410的chunk处开始写入数据,布局好我们要的IO链。
板子
很明显我们布局的重点在与之前的那个
unsortedbin
里面,我们就来重点看一下这里面
在这里我们把这个chunk的
prev_size
的内容修改为/bin/sh
size
修改为0x61,这里就是为了在上面提到的吧这个chunk放入smallbins中后会让这个chunk的头地址进入main_arena+88+0x68
,从而满足我们对IO的劫持fd和bk就改为满足
Unsorted Bin Attack
的条件,bk为_IO_list_all-0x10

之后再这个chunk头地址+0x68的位置写上这个chunk的头地址,就是满足
_IO_FILE_plus
中_chain
的位置为这个chunk的头地址。还有再这个chunk头地址+0xd8的位置写上这个地址的本身的位置,这里是为构造
vtable
的地址为它本生,程序就会从这里开始选择要跳转的地址再完成上面的修改之后就可以满足这样的条件

这样完美构造一个
_IO_FILE_plus
结构体再heap中。最后一步我们构造
vtable
的+0x18的位置上写入system函数的地址,就可以满足这样的条件
于是程序再跳转
vtable
后会,寻找__overflow
的地址执行。这样我们就完成了heap中的构造。
当我们通过申请chunk,使得之前修改的0x60的chunk进入smallbins后,
_IO_list_all
会被修改为main_arena+88
的地址
此时程序中的
_IO_list_all
会变为如下情况
此时因为我们的0x60的chunk被放入smallbins中,于是会被放入
main_arena+88+0x68
的位置,刚好就是此时_IO_list_all
中的_chain
地址
刚好就可以让我们跳转其中。然后具体的流程就和上面的一样。
exp
写的总感觉有点问题,很多地方不是很清楚,学的还是不足,这里就先当一个板子用吧。后面把具体流程理解了再来详细写写。
glibc2.24到glibc2.27以下的利用
在FROP的利用中之前我们里提到的那种方法适用glibc2.23的版本中,并且还得在题目中能够知道heap的libc的地址才能构造出完整的伪造IO链
在glibc2.24的版本中这种构造手法就已经不能在使用,因为我们之前的构造中有个点是我们要把IO中的
vtable
修改到heap的地址上,从而让其执行system
函数。而在glibc2.24的版本中程序加上了一个对
vtable
地址的检查,于是我们之前的手法就不能执行,必须要寻找到新的链构造新的IO链。感谢Jelasin师傅的博客,我现在对IO的利用学习就在跟随师傅的文章来,师傅nb
这种新的利用链,我看了几天,发现实在是难以理解,还是功力不足。于是打算这种手法先当做一种板子来记录在这里,遇到题可以直接套用。具体的利用过程,待我在看一看源码理解了利用过程再来详解。
hctf2017 babyprintf

这道题的话逆向比较简单,就是想输入大小,程序按照这个大小生成对应的chunk,但要注意chunk的大小必须要在0x1000一下。
程序在生成malloc之后通过gets函数向其中读入数据,这个函数存在堆溢出漏洞。
然后程序会通过
__printf_chk
函数将刚刚输入的内容打印出来,于是这里就存在一个格式化字符串漏洞,但这里要注意,这个函数并不直接等同于printf函数,其区别再与包含%n
的格式化字符串不能位于程序内存中的可写地址。
当使用位置参数时,必须使用范围内的所有参数。所以如果要使用%7$p
,你必须同时使用1
2
3
4
5
和6
。
这里其实就已经断绝我们利用格式化字符串直接修改地址内容的方法,但这里我们依然可以通过这个漏洞把libc的地址泄露出来。
然后之后我们有堆溢出就可以先利用
house of orange
手法产生unsortedbin,然后再利用
Unsorted Bin Attack
这手法,向_IO_list_all
中写入main_arena+0x88
的地址,同时再heap中构造出FSOP的利用,这里直接给了吧板子
重点在于stream的那里,其注入程序后会形成如下情况,这里这个chunk的大小也要修改为0x61,原因和上面的一样。


就会形成上面的情况。
简单的理解:(有误直接加我qq喷我)
我们之前再
vtable
里写入_IO_str_jumps
的地址,这里当程序遍历完我们伪造再heap上的IO之后就会来到vtable
里的_IO_str_jumps
里寻找函数地址执行
此时程序会找到高亮的这一行,去执行
__GI__IO_str_overflow
函数再这个函数就会把刚遍历的IO转换为寄存器中的值,并且其中有一行为
此时rbx中值为之前通过遍历的伪造IO的heap的起始地址。于是我们再之前伪造的IO中距离heap的起始地址0xe0的位置上写入system函数地址
于是程序就会直接跳转执行这个函数。

当然这里也可以直接写ogg直接拿到shell
exp
就这样吧,目前实在是不理解具体的过程。直接当板子用吧,这里给几篇文章感觉比较好的
- 作者:wgiegie
- 链接:https://tangly1024.com/article/1543ecc9-5160-8019-8cc1-cee1c602f82c
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章