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=128kb
glibc2.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
、itemlist
num
:统计着我们申请堆块的次数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的博客!