PWN堆house-of-force
house of force的利用有一个glibc的适用范围,该范围在glibc2.23到glibc2.29。在glibc2.29之后增加了相关的检测,导致house of force基本失效。- Glibc堆利用之house of系列总结 - roderick - record and learn!
house of force不需要伪造堆块,这个堆利用比较好理解。
前置知识
- top_chunk也有用上堆块的隐式链表技术
top_chunk运行机制
-
top_chunk是一个比较特殊的chunk,这里就先简单叙述一下top_chunk的运行机制- 当一个程序一次
malloc都没有进行的时候,它并不会被分配top_chunk,此时的堆并没有被初始化。 - 当程序第一次使用
malloc的时候,先会通过系统调用向操作系统申请内存,这个申请过来的内存会放入top_chunk这边,此时av->top(top是一个指向chunk的指针)会指向top_chunk(准确的来说是指向top_chunk的起始位置,即指向top_chunk的prev_size位),然后会从top_chunk中切割一个一块chunk给用户使用。(切割后会更新av->top,此时top指向的是更新后的top_chunk的起始地址)**** - 然后当程序再次使用
malloc申请一个堆内存的时候- 先会判断
bins中是否有符合要求的空闲堆块,如果没有就从Top chunk中切割一块出来,切割出来后会更新main_arena中top指针。 - 如果我们申请的堆块大小大于
Top_chunk现有的大小,Top_chunk还会与bins中空闲的chunk合并,查看合并后的top_chunk的大小是否满足我们malloc所申请的堆块大小。(合并后的Top_chunk可能会放入unsorted_bin中,这个机制将在house of orange中利用) - 以上都不满足的话就会通过
mmap或者brk这两个系统调用,向操作系统申请额外的内存,扩展到Top_chunk中。
- 先会判断
- 当一个程序一次
-
这里还有一个要注意的地方就是,
glibc2.27即以前,在第一次调用malloc时,通常会分配给程序中的top_chunk分配128KB左右的大小,这与这个定义有关EFAULT_MMAP_THRESHOLD=128kbglibc2.28开始,在第一次调用malloc时,就会给top_chunk分配256KB左右的大小- 还有一点要注意的就是,不一定每次分配都是
128KB大小,还会因为页对齐等会导致申请过来的top_chunk大小在128KB左右浮动,可能是132KB等。
-
以上就是
top_chunk的运行机制,接下来给一个示例程序进行动态调试看看。
1 |
|
- 接下来我们对该程序进行动态调试,这样来查看
top_chunk的具体流程,当我们一次malloc都没调用的时候就会出现以下情况- 程序的堆空间还没有被初始化
main_arena中的top指针还是空的

- 接下来我们调用一次malloc后就会出现如下情况
- 这个时候我们就已经申请了一个
0x20字节的堆块 - 此时
top_chunk也有大小了。(top_chunk的大小可以算一下看看,是不是接近128KB) - 此时我们的
top指针也指向了top_chunk的起始地址
- 这个时候我们就已经申请了一个

- 接下来我们再调用一个
malloc函数,这个时候我们又申请了一个0x20010字节大小的堆块,这时会观察到如下情况Top_chunk的size位变成了0xfd1,如果不包括标志位的话,刚好0x20010+0xfd0=0x20fe0- 此时的
top指针仍然指向的是top_chunk的起始位置。 - 此时的
top_chunk的内存足够,还不需要向操作系统申请内存空间

- 接下来再我们调用第3次的malloc函数,并查看堆块
- 这时
top指针还是指向的是堆块的头部 - 但是
top_chunk的size位变成了0x20fc1,这就是向操作系统申请增加内存后的再分配给第3个堆块的结果。
- 这时

top_chunk源码
- 这里给出
malloc.c中_int_malloc中关于top_chunk的相关源码
1 | use_top: |
- 这里还给出源码中
av->top中的av这个结构体实例对应着的具体的结构体,这边av是一个结构体指针,其指向的就是这个结构体,这个结构体变量名就是我们经常见到的main_arena。 main_arena其实是一个全局变量
1 | struct malloc_state |
实验
- 这个实验也是来自
how2heap中glibc2.23的house-of-force
源码
1 | /* |
- 同样地,为了更好的代码审计,我将该代码进行中文翻译
1 |
|
- 接下来我们进行动态调试,首先字符串
This is a string that we want to overwrite.,是在0x602080地址处 - 由于调试的时候会出现异常情况从而导致退出
gdb调试,并且也没有关闭堆地址随机偏移,这就导致了图片中有些地址会改变 - 我们先执行一次
malloc(0x100),此时实际申请的堆块大小为0x110,此时top_chunk的size位是这样的,这时我们程序就有两个堆块。一个是malloc申请回来可以使用的堆块,是不能使用的待申请的堆块top_chunk


- 接下来我们就修改
Top_chunk的size位,修改size位为-1,这样Top_chunk的size位就会变得非常大,当我们申请一个非常大块的内存时,就不会调用mmap,这个操作。


- 之后我们就计算
malloc要申请的堆块大小。由目的地址 - top_chunk起始地址(即prev_size的地址)-sizeof(long)*4(即0x20字节),由于.bss段的地址比堆地址小,这时申请的目的地址就会是负数-13725872(十六进制为0xFFFFFFFFFF2E8F50) - 所以我们要申请的大小就是
malloc(-13725872),在申请完之后Top_chunk的地址就会变成0x602070其prev_size和size这两个位都在This is a string that we want to overwrite.这个字符串的上方



- 这个来具体介绍一下为什么
Top_chunk的地址会变成0x602070,这里的需要用到整数溢出和堆块的隐式链表技术。这时我们就要查看一下上面top_chunk源码这边,关键点在chunk_at_offset (victim, nb)这边nb这个变量是用户申请的堆块大小,经过对齐等操作后要申请的实际大小。victim这个变量在对top_chunk操作时就是指向top_chunk的起始地址- 我们注意到这个语句
remainder = chunk_at_offset (victim, nb);,它执行的是victim+nb(这就是一个切割top_chunk的操作),所以当我们堆块申请的是负数的时候victim + nb就会降低top_chunk的地址。(利用隐式链表技术更新top_chunk的指针) - 这时就会使得
top_chunk的起始地址在bss段中。 - 这时我们
top_chunk的size位就会发生整数溢出-1-(-13725888) - 这边还要注意一下:我们需要通过申请负值来修改top_chunk的地址为低地址,而负值在malloc中有一处比较会导致非常大,所以必须要溢出修改top_chunk的size位为
-1从而绕过判断检查(这是在之后打level1调试出来体会到的)
1 | if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE)) |
- 这时我们再申请一个堆块,该堆块的
prev_size和size就是top_chunk申请之前的prev_size和size,而用户使用的内存空间的起始地址就为字符串This is a string that we want to overwrite.的起始地址,这样我们就可以对该地址中的内容进行修改。 - 就像图中这样我们使用
malloc申请了100字节大小的堆块,这样我们就可以修改字符串了



利用方式
-
house of force的这个利用方式并不用伪造堆块,而是通过堆溢出对top_chunk的size位进行修改。然后通过计算top_chunk与目标地址的偏移,从而达到申请任意地址,从而可以造成任意地址写的效果。 -
这个漏洞利用方式需要的条件:
堆溢出漏洞,这样就可以修改top_chunk的size位堆地址,我们需要计算偏移就需要堆地址,如果我们要申请到栈上,这时还需要泄露栈上的地址。- 如果申请到
bss段:需要malloc()能传参数为负值,如果是申请到栈段则malloc()就需要传递很大的值
-
我们计算偏移需要这样计算:
1 | The evil_size is calulcated as (nb is the number of bytes requested + space for metadata): |
house-of-force_level1
- 题目来源:hitcontraning_lab11
level1分析1
- 先使用查看一下附件的保护机制。发现是开了
canary保护,没有开启PIE保护。

- 接下啦我们反编译一下程序,还是老样子先来查看一下
main函数- 该程序的
main函数,会先初始化输入输出 - 然后会申请一个堆块,这个堆块是由于存储两个函数地址
- 然后会调用
hello_message函数,输出欢迎用户的信息 - 之后调用就是进入循环经典堆菜单题目
- 这里也给出
hello_message、goodbye_meeage这两个函数输出的具体信息
- 该程序的



- 接下来我们查看一下菜单,这时就表面:
1展示数据2添加数据3修改数据4移除数据5退出

- 现在我们依照
增删改查的顺序查看这个堆块,在做堆题一般是先查看add这样我们会更熟悉申请堆块中堆块位置存储的具体数据 - 我们先来查看一下
add_item():- 首先介绍一下三个全局变量,其中
num、ptr_array、itemlistnum:统计着我们申请堆块的次数ptr_array:是一个指针数组,这个数组存储着malloc返回的堆地址itemlist:是一个int类型的数组,这个数组,存储着每个堆块所申请的大小
- 该程序先对
num进行检查,然后用户可以输入要申请堆块的大小(堆块大小不能为0) - 之后程序就会申请一个堆块,申请完后会让用户输入内容,并在最后添加
\x00 - 最后
num这个全局变量会自增 - 注:这里的
ptr+array+2*i还不知道这个地址如何增加,之后动调看看
- 首先介绍一下三个全局变量,其中

- 接下来查看
remove_item():- 该函数的功能就是释放堆块,输入我们要释放堆块的索引
- 然后释放相应堆块,并且将
ptr_array对应位置设置为0,然后将itemlist对应位置也设置为0 - 最后
num自减

- 接下来我们查看
change_item(),程序的逻辑大致如下:- 我们先要选择我们要修改内容的堆块
- 之后输入我们要修改多少字节(这边可以造成堆溢出)
- 然后调用
read函数对相应堆块内容进行修改

- 接下来查看最后一个函数
show_item(),查看一下具体逻辑:输出我们已经申请的所有堆块的相应和索引

- 这边我们还注意到一个
magic()函数:这边会直接将flag输出

level1分析2
- 这题并不能泄露堆地址,但是一开始我们申请了一个堆块用于存储函数指针,这样我们并不用泄露地址可以直接计算偏移,将
top_chunk的位置给改到第一次我们malloc(0x10)这个堆块。 - 这样我们就可以劫持
goodbye_message()为magic(),这样我们在退出的时候就可以得到flag - 为了
magic()能成功调用,我们就先在当前目录下创建一个flag文件,存储着flag{test_flag} - 接下来我们就来进行动态调试看看,这里我们先动调查看
0x6020C8是如何存储malloc返回的指针,这时我们先创建两个堆块,这时我们就发现反编译的一个错误,其实ptr_array并不存在,只有itemlist存在,按照申请的大小、堆块地址这个顺序存储(但是这个对于我们这题的思路没啥影响)

- 所以我们就再尝试申请一个堆块,看看
top_chunk和des的偏移,这时我们add(0x10,b'aaaa') - 发现我们写入的堆块如下图所示,同时我们可以计算我们要申请的堆块即偏移地址,也就是
des-top_chunk_addr-siezof(long long size)*4 - 即
0x1C06000-0x1C06040-0x20=-0x60

- 这时我们尝试不修改
top_chunk能不能将top_chunk的起始地址修改为0x1c06000,发现会报错

- 所以我们在申请负索引的时候就要先修改
top_chunk的size位,这时我们利用edit()修改了top_chunk的size位

- 这时我们再来申请负值,但是这里出现段错误,也就是这段程序这边出现了问题,这边出现的错误情况是我们申请的堆块太小,导致我们的申请负堆块(如果用无符号整数表示就非常大),这时就会导致失败。
- 还有就是如果我们没有改变
top_chunk的size位,也会导致报错(所以这时就需要改变top_chunk的size位为-1)


- 所以我们现在先申请一个
0x100大小的堆块,然后再修改,计算偏移后再申请负值的堆块,结果就会如下:
1 | add(0x100,b'aaaa') |



- 这个时候就会出现
top_chunk的起始地址会在我们这两个函数指针所在堆块的起始地址 - 这时我们再申请一个
0x10大小的堆块,并修改goodbye_message()这个函数指针即值为0x4008b1将其修改为magic()这个函数的地址0x400D49

- 这时我们再进行退出,这时就会调用
0x400D49这个函数即magic()


- 这时退出就会打印出
flag的值

level1_exp
- exp:
1 | from pwn import * |
house-of-force_level2
level2分析1
level2分析2
level2分析3
level2exp
利用失效
- 在
glibc2.29加入了一个检测,system_mem这个记录程序向堆块申请的堆块总字节大小。 - 这个检查就导致了
house_of_fore基本失效

本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 iyheart的博客!

