PWN堆unsorted_bin_attack2
- 之前的
unsorted_bin_attack
只是一个比较小的技巧,可以用这个技巧泄露libc
的地址,而本篇文章的学习是,利用unsorted_bin
的管理机制的一些缺陷,从而进行堆利用,进而getshell
- 在学习
unsorted_bin_attack
时,我们就要先了解一下unsorted_bin
这个链表的管理机制。
前置知识
- 在学习过
house of lore
其实对unsorted_bin_attack
的利用就简单非常多。虽然house of lore
是针对smallbin
的利用。但是确实有助于理解unsorted_bin_attack
。 - 但是建议还是先从
unsorted_bin_attack
先入手,再去学习house of lore
。
unsorted_bin运行机制
- 我们之前已经了解了利用
unsortedbin
中的堆块泄露libc的地址。这个泄露的原理就是第一次被放入unsortedbin
中的堆块,其fd
指针、bk
指针指向的是main_arena+88
处(其他不同版本的libc
偏移可能不同。) - 而我们查看
main_arena+88
这个位置就会发现,main_arena+88
这个位置其实是一个数组的开头。如下图所示,这个数组里面还有很多元素都还没有被使用的上。这个数组被称为bins
。 - 而其实这个
bins
就是用来管理unsortedbin
、smallbins
、largebins
这个链表的头结点,与图中管理fastbin
链表的头结点数组fastbinsY
相似。 - 但是
bins
这个头结点是双向循环链表,这点与fastbin
单向链表就有所不同。所以我们的bins[0]
就相当于unsortedbin_fd
指针,bins[1]
就相当于unsortedbin_bk
指针。
- 而我们图中的
main_arena
就相当于glibc
中这个结构体的实例。
- 接下来我们来简述一下
unsortedbin
的大致运行机制,之后再画图说明。unsortedbin
也是通过链表进行组织的,并且unsortedbin
是一个双向循环链表的形式。- 与
fastbin
链表不同的是unsorted_bin
链表采用的是FIFO
形式也就是(先进先出的形式)。 - 当有一个chunk被放入
unsorted_bin
这个链表中,这个glibc
将使用头插法将该堆块,插入靠近unsorted_bin
这个头结点的位置(指的是逻辑地址) - 当有一个
chunk
要被取出时,会从最远离unsorted_bin
的堆块开始取(指的是逻辑地址),然后会更新unsorted_bin
中的bk
指针。因此unsorted bin
遍历堆块的时候使用的是bk
指针。
- 接下来画图描述,当没有堆块被放入
unsorted_bin
中时就会呈现出如下形式,即空空如也
- 当有一个堆块被放入
unsorted_bin
中就会出现如下图所示的双向循环列表。
- 当有新的
chunk
被放入到unsortedbin
中时,就会使用头插法
。
- 当有堆块要被取出时就会先从
chunk1
这边取出,然后更新图中的双向循环链表。
相关检查
- 以下图的
chunk1
和chunk2
为例子,这时演示的是我们调用malloc
申请一个堆块时,如果要从unsorted_bin
中取堆块的情况。- 当我们要取出
chunk1
的时候,有一个victim
会指向chunk1
,还有一个bck
指针会指向chunk2
。 - 这时程序先会检查
victim
的size
位,检查size
是否小于等于0x10
、size
是否大于av->system_mem
,经过判断后就会获取victim
所指向堆块的size
。__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
- ``__builtin_expect (victim->size > av->system_mem, 0)`
- 当
malloc
申请的堆块大小属于small bin
范围中,并且unsorted_bin
中的最后一个堆块是unsortedbin
中的唯一chunk
时,就会优先使用这个块,如果满足条件就会进行切割和解链操作。 - 当
malloc
申请的堆块大小超出unsorted_bin
中的最后一个堆块时,则会将victim
所指向的chunk
根据size
位,放入相应的small_bins
中或者large_bins
中 - 并且更新
双向循环链表
中的unsorted_bin_bk
指针和bck->fd
指针。我们对unsorted_bin_attack
的利用重点就是在这里,如果我们可以修改victim
的bk
指针,修改完改指针后,我们再次调用malloc
申请相同大小的堆块,这时unsorted_bin
在更新双向循环链表的时候就会修改bk
指针所指向的位置,从而将某些值
(这个值我们无法控制)写入到我们想要的位置中unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);
- 猜测:由于
unsorted_bin
链表的管理与smallbin_bin
链表的管理差不多,是不是能将house of lore
的利用方式是使用在unsorted_bin
中呢? - 如果之前的条件都不满足,意味着目前的
victim
不能满足用户的需求,需要根据其size
放入small bin
或large bin
的链最后是unlink
将victim
彻底解链。
- 当我们要取出
相关源码
_int_malloc中关于unsorted_bin的源码
1 | while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av)) |
实验
- 还是老样子,使用
how2heap
的例子,进行动态调试然后画图理解利用过程。
源码
1 |
|
- 将源码翻译一遍
1 | #include <stdio.h> |
- 编译后我们先运行一下该程序,运行结果如下,目前的我们对
unsorted_bin_attack
的利用还是不太清楚,接下来我们就来动态调试。并且画图理解。
- 首先我们再栈上定义了一个
unsigned long stack_var=0;
,定义完这个变量之后,先申请了一个大小为400
的堆块。
- 然后为了防止之后释放堆块的时候,堆块与
top_chunk
合并,所以我们再申请了一个500
大小的堆块。
- 之后我们释放第一次申请的堆块,该堆块会被放入
unsorted_bin
中
- 这时我们修改该堆块的
bk
指针,将该指针指向stack_var
这个变量的地址。
- 修改完之后我们再次申请这个堆块,申请后我们的栈上的
stack_var
的值就会变成main_arena+88
的地址,并且stack_var_addr+0x8
会指向我们刚申请回来的堆块。
- 在实际修改
bk
指针的时候,一般fd
指针也会被修改,但是将 unsorted bin 的最后一个 chunk 拿出来的过程中,victim 的 fd 并没有发挥作用,所以即使我们修改了其为一个不合法的值也没有关系
利用方式
- 这个
unsorted_bin_attack
一般也是起到辅助作用,基本上是house of
组合技中的一环。 - 接下来就介绍一下后续的利用方式
- 我们通过修改循环的次数来使得程序可以执行多次循环。控制循环次数的变量一般都是在栈上。
- 我们通过修改
idx
的大小,增加我们申请堆块的次数。 - 我们可以修改 heap 中的
global_max_fast
来使得更大的 chunk 可以被视为 fast bin,这样我们就可以去执行一些 fast bin attack 了。
unsorted_bin_attack_level_1
- 题目来源:HITCON Training lab14 magic heap
- 题目附件:上网搜一下就有
- 直接使用
glibc-all-in-one
项目配合patchelf
- 考点:
堆溢出
、unsorted_bin_attack
level_1_分析1
- 先
check
一下保护机制。发现如下图所示的保护机制。
- 然后运行一下程序查看一下程序的具体逻辑。一开始还是一个经典堆菜单的题目。
add
、edit
、dele
、exit
这三个。
- 我们选择
1
后的具体执行过程。分别输入选择
、大小
、内容
- 选择
2
后的具体执行过程。分别输入选择
、索引
、大小
、内容
- 选择
3
后具体执行的过程。分别输入选择
、索引
- 选择
4
就直接退出
- 接下来我们反编译一下这个程序,查看程序的具体执行逻辑。接下来我们来查看一下这个程序反编译后的
main
函数的具体执行逻辑。 - 该程序先是对输入、输出进行初始化,初始化之后进入两个循环。
- 然后接下去查看,接下去的程序逻辑才是比较重要的。
- 程序先会输出我们之前看到的菜单,然后让用户做出选择。
1
就是创建一个堆块,2
就是修改一个堆块,3
就是删除堆块。- 注意这边我们还看到了一个
4869
选项,这边选项会判断magic
是否大于0x1305
,如果大于就会执行l33t()
函数
- 而
l33t()
这个函数是执行的就是cat flag
的命令操作。
- 接下来我们就查看
add()
、delete()
、edit()
这三个函数。 - 先来查看
add()
这个函数- 这个函数先会判断
heaparray
这个全局变量的数组里面的元素是不是空的,这个数组就是一个指针数组,存放着malloc()
返回的堆地址。 - 然后会让用户输入要申请堆块的大小,申请堆块的大小后程序就会让用户向刚申请的堆块写入数据。这里不存在堆溢出。
- 这个函数先会判断
- 现在我们查看
delete()
函数,这个函数的具体执行逻辑如下。- 程序先会让用户输入要释放的堆块对应的索引。
- 用户输入后,程序会判断是否超出索引范围,并判断这个索引是否存放有堆块。
- 如果存放有堆块就会释放对应索引的堆块,然后将对应索引的位置设置为
0
- 注意:这里不存在
UAF
漏洞
- 最后我们来查看
edit()
函数 - 这个函数基本上也会让用户输入指定索引,然后判断索引是否超出范围
- 注意:这里程序可以让用户指定输入的大小,所以用户就可以指定输入比较大的值,从而造成堆溢出操作
level_1_分析2
- 这题的利用也就比较明显了,我们先申请三个堆块。利用方式其实就是
unsorted_bin_attack
,所以我们就要通过漏洞去修改处于unsorted_bin
中的堆块对应的bk
指针。 - 所以我们一开始需要申请
3
个堆块,按申请的顺序分别命名为chunk0
、chunk1
、chunk2
。- 其中
chunk0
的作用是用于从而修改chunk1的bk
指针。 chunk1
的作用是用于释放,释放后其会被放入到unsorted_bin
中,所以要申请比较大的堆块使得这个堆块并不会被放入fastbin
链表中。- 接下来我们申请的
chunk2
是防止在释放chunk1
时chunk1
与top_chunk
合并。
- 其中
1 | from pwn import * |
- 这时我们再释放
chunk1
,chunk1
就会被放入unsortedbin
中。
- 这时我们就要通过溢出操作修改
chunk1
的bk
指针,在溢出的时候要注意一点就是要保持chunk1
的size
位不变,即size为一定要为0x121
。否则我们再次申请堆块的时候size
不正确可能就会导致程序崩溃。 - 接下来我们修改后再使用
malloc(0x110)
大小的堆块,修改完后的堆块内容如下图。
1 | payload = p64(0)*3+p64(0x121)+p64(0)+p64(0x6020C0-0x10) |
- 之后我们再次申请堆块,申请堆块后我们再查看
magic
这个全局变量的值,这时我们发现magic
的值已经非常大了。
- 之后我们再选择
4869
,这样我们就可以执行cat flag
的命令了。我们就得到flag
了
level_1_exp
- exp如下:
1 | from pwn import * |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 iyheart的博客!