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的博客!

