该利用方法通过堆溢出漏洞修改 top chunk 的 size 字段,使得 malloc 一块很大的内存可使用 top chunk 进行分配,当更新 top chunk 的 ptr 时会发生整数溢出,从而控制 top chunk ptr 为指定目标内存地址,如 .bss 段、.data 段和 GOT 表等。当再次使用 malloc 申请内存时将从目标内存处进行分配,之后对该内存进行写操作,即可实现任意地址写数据。
0x01 TOP Chunk
堆内存是从低地址向高地址进行分配的,在堆内存的最高处存在着一块空闲 chunk 称为 top chunk。使用 malloc 分配内存时,若 bins 和 fast bins 中的 chunk 都不能满足分配需要则在 top chunk 中分出一块内存给用户。
top chunk 的大小跟随内存的分配和回收不停变换,如果从 top chunk 分配内存会导致 top chunk 减小,同时 top chunk 的指针增大;如果回收的 chunk 恰好与 top chunk 相邻,那么回收的 chunk 就会合并到 top chunk 中,从而使 top chunk 变大,top chunk 的指针减小。
glibc 中从 top chunk 分配内存的代码如下,首先会检查 top chunk 的大小是否能满足分配需求,同时还要确保分配完后剩余的大小不能小于最小 chunk 大小(MINSIZE),若满足该条件则进行分配。分配内存后需更新 top chunk 的 size 字段为 size - nb(nb 为新分配 chunk 的大小),top chunk ptr 更新为 ptr + nb。
下面以一个例子说明该过程,堆内存初始状态如下,top chunk 的大小为 0x20fe0,ptr为 0x603020,并且 bins 中没有空闲的 chunk。
此时使用 malloc(0x45) 申请一个新 chunk,将会在 top chunk 中分配内存给该 chunk。新分配 chunk ptr 为 0x603020,即原 top chunk 的 ptr,大小为 0x50 = align(0x45 + 0x8),其中 0x8 为 size 字段长度,对齐单位为 16 字节(32 bit 系统中为 8 字节);分配完后,top chunk 的 size 为 0x20f90 = 0x20fe0-0x50,ptr 为 0x603070 = 0x603020+0x50;最后返回给用户的内存为 0x603030 = 0x603020+2*0x8。
0x02 利用方法
在 top chunk 中分配一块很大的内存给新申请的 chunk,使得更新 top chunk 的 ptr 时发生整数溢出,从而控制 top chunk ptr 为指定目标内存地址,如 .bss 段、.data 段和 GOT 表等。当再次使用 malloc 申请内存时将返回目标内存地址,之后对该内存进行写操作,即可实现任意地址写数据。
1. 修改 top chunk 的 size 为大数
从上面的分析可知,在 top chunk 中分配内存需要满足以下条件。
由于 arena 的大小为 132KB,所以 top chunk 的 size 不大于 132KB(0x21000 bytes),因此在正常情况下通过 top chunk 分配的堆不能超过 0x21000 bytes,这导致无法在更新 top chunk 的 ptr 时发生整数溢出。为此,需要先利用堆溢出漏洞修改 top chunk 的 size 为一个大数,通常取 -1(其补码为 0xFFFFFFFFFFFFFFFF),之后便可通过 top chunk 申请一块很大的内存以触发整数溢出。
2. malloc 一块大内存,控制 top chunk ptr
假设该步骤中申请内存时用户请求大小为 request_size;最终需控制的内存地址为 target;top chunk 的 ptr 初始值为 top_old,分配新 chunk 后的 ptr 为 top_new;由上一节中的分析可得到以下等式,其中 SIZE_SZ 在 64 bits 系统中为 8 bytes,32 bits 系统中为 4 bytes。
根据上式可得
需要注意的是 request_size+SIZE_SZ 要遵循块的对齐机制,如果未对齐应进行调整,将 request_size 的计算结果减去一个值(因为对齐时会增大长度使其对齐),使 request_size+SIZE_SZ 能对齐。
malloc 执行完后 top chunk ptr 将会更新,并指向目标内存 target-2* SIZE_SZ 处,即 top chunk 已转移到目标内存地址。
由于计算 request_size 的大小需要知道堆内存中 top_old 的 ptr,所以得借助其他漏洞泄漏堆中 top chunk 的地址。或者可以将 target 指定在堆内存区域,那么通过本地调试可获得 top chunk 的地址,此时使用上式计算所得 request_size 相当于相对地址偏移,当堆基址改变后该值仍适用。
3. 再次 malloc,返回目标内存
此时申请 chunk 将从目标内存处分配,最终成功返回目标内存 target,之后可对该内存写数据,以实现进一步的攻击。
0x03 实例分析
下面以 HITCON-Training 中的 lab11 为例说明 house of force 的利用过程,题目文件和利用脚本也可在 Github 中下载。
1. 漏洞
程序中在修改 item 时调用 change_item() 函数,name 的长度由用户指定,并且没有进行检查。输入过长字符串到 name 中将会导致堆溢出,可覆盖 top chunk。
2. 利用脚本
1)利用思路
利用 house of force 使得 top chunk 转移到 box 结构体所在内存处,使得下次申请内存时从该地址开始进行分配,控制该内存块后可修改 box 结构体中的函数指针为 magic 函数地址;最后调用 goobye_message 函数时就跳转到 magic 函数执行,从而输出 flag。
2)利用脚本
利用脚本如下:
3. 利用过程
1) 添加 item0
添加item0后,堆内存分布如下,0x63000 处的 chunk0 为 box 结构体,结构体中包含 2 个函数指针。0x603020 处的 chunk1 为刚申请用于存放 name 的空间,并且 chunk1 与 top chunk 相邻。
|
|
2) 溢出 name
由于 change_item() 函数中 name 的长度由用户指定,并且程序没有对长度做限制,当指定修改的 name 长度大于 name 的内存大小时,将会导致越界写内存,从而可修改 top chunk 的 size 字段为-1。
3) 添加 item1
程序调用 malloc 在 top chunk 中分配一块大内存给 name ,此时更新 top chunk ptr 将会触发整数溢出,从而控制 top chunk 转移到指定内存。
4) 再次添加 item2
malloc(0x20) 从新的 top chunk 中分配一块内存给 item 的 name,rax 中返回的起始地址为 0x603010,该内存块会包含 box 结构体所在的 chunk。
之后将 magic 函数地址作为 item2 的 name 写入到新分配的 chunk 中,覆盖 box 结构体中的函数指针,进行劫持程序执行流程。
5) 退出程序
退出程序时会调用 goodbye_message 函数,从而执行 magic 输出 flag。
References:
[1] The Malloc Maleficarum Glibc Malloc Exploitation Techniques
[2] House of Force
[3] WhyNot-HEAP-Exploitation
[4] CTF Wiki-house of force
[5] HITCON-training writeup