原理
glibc 在空闲堆块无法满足要求的情况下会对 top chunk 进行操作,从它哪里得到新的满足用户需求的 chunk 块,House of Force的主要目标就是 top chunk,我们如果可以控制 top chunk 的 size 的话就可以使得 top chunk 指向任何位置,实现任意写。
但是 glibc 在分配堆块的时候也存在检查,不过这个检查很好绕过,因为它并不是专门来检测违规操作的,可以通过源码了解一下。
我们可以看出,唯一需要绕过的检查就是 size 的大小检查,我们在做 house of force 的时候必须把 top chunk 的 size 设置为很大的值,不过对于有堆溢出或者其他漏洞的程序来说,这个并不难。
一个很有用的做法就是把 top chunk 的 size 设置为 -1,因为在 malloc 源码中,大部分都是 unsinged 类型的数据,那么-1就会被当做这个类型的最大值从而轻松实现绕过。
从上面代码我们就可以看出,如果我们绕过检查了,我们实际上是可以控制 top chunk 的,进而我们就实现了任意地址写。
不过我们需要注意的是用户输入的 size 和堆分配的 chunk 的大小其实是不一样的,用户输入的 size 会经过一个内部的 checked_request2size 运算:
所以用户输入的 size 要经过运算使得 ((req) + SIZE_SZ + MALLOC_ALIGN_MASK) & ~MALLOC_ALIGN_MASK 得出的结果满足需求,这个结果才是相应的堆块的chunk_size.而且需要绕过REQUEST_OUT_OF_RANGE的检查,这个的意思就是你输入的负值必须比 -2*MINSIZE 小(记得分清负数谁大谁小奥),这个一般都不用担心,同时我们要特别注意的是地址的对齐(32位8字节对齐,64位16字节对齐),因为 free 一个堆块的时候会进行地址对齐的检查。
同时,因为对 top chunk 的 chunk header 的设置,我们会改写指向的目标地址附近的值,这一点可以看一下上面源码里的 set_head。
总结:
程序存在漏洞我们可以控制 top chunk 的 size 域,例如堆溢出,off-by-one 这类
我们要可以自由的控制malloc的大小,因为像got表或者malloc_hook,free_hook,main_ret这些其实都和top chunk相隔甚远。
分配次数不能受限制(其实这个还好,很多ctf题目的这个条件都可以满足)
练习
1
HITCON training lab 11
我们先用 checksec 大体观察一下
可以看到只是开启了 Canary 和 NX,下面分析一下程序。
这个是add_item函数,可以看出堆块存储的数据结构大致是这样的:
然后一个结构体数组存放在bss段就构成了这个程序的主要堆块存取数据结构。
show函数就是通过变量结构体数组来把所有的堆块内容打印出来。
remove函数在free后把指针置0,同时也把size给置0了。
change函数存在明显的溢出漏洞,因为change的时候输入的size可以由用户自由控制,这就存在了堆溢出漏洞。
同时这个程序也存在后门magic()
利用方法
首先做好准备工作,哈哈。
按照上面讲的house of force,你就可以知道,我们只需要修改top chunk的size,然后算出用户申请多少size才能使得top chunk偏移到第一次申请的chunk处,然后我们改写改chunk的内容为为magic函数的地址来实现利用就可以了。
EXP
看到 exp 有人可能会疑惑第二个 malloc 那里为啥多减去一个 0xf 这个其实不是必须的,看上面的源码我们可以知道在计算 chunk size 的时候加上了 MALLOC_ALIGN_MASK,这里其实就是减去这个值,不过不是必须的,因为后面的与操作在对齐 chunk size 的时候就帮我们减去之前加上的这个值了。
另外 MALLOC_ALIGN_MASK = 2 * SIZE_SZ - 1 , 所以在 64 位里面就是 0xf 了。
上面这个算是一个实验,下面来一个题目类型的。
2
2016_bctf_bcloud
就逆向分析而言这个题目并不复杂,下面我只是大致分析一下题目的关键部分。
首先用checksec查看一下大致情况
开启了NX和canary
从new_note()函数我们可以得知堆块存储的大致数据结构,两个bss段的数组分别对应着堆块的mem部分指针和用户输入的size,同时用issync数组来标记是否同步。
delete_note()函数同时对指针和size置0,并且释放堆块。
利用思路
首先我们需要泄露地址,这个地址泄露的漏洞不是很好发现哈。。
第一眼看上去没什么问题,不过在strcpy的时候如果我们写入了0x40个字符,我们在栈上的数据就会和下面的指针连在一起,由于strcpy依赖"\x00"进行截断,所以会把我们数据下紧邻的堆块指针也复制到堆块里面,这样在printf的时候就会造成地址泄露。
这样我们就成功泄露了heap的地址,另一个漏洞在于init_org_host()函数:
这个漏洞类似于上面泄露地址时候的,也是由于strcpy,同样,让我们看一下如果我们输入最大数据量时栈里面的情况:
可以看到aaaa所在的区域的"\x00"截断已经没有了,那么在执行strcpy的时候会在org_ptr处造成溢出,而org_ptr处的下面一个chunk就是top chunk,所以如果我们精心构造第一次和第二次的输入就可以改造top chunk了实现house of force了。
house of force
我们改写top chunk的size为-1,然后算出note_size数组和top chunk之间的偏移使得top chunk指向note_size的上方,然后把note_size和note_list申请出来就可以实现溢出,进而实现利用。
利用思路
改写note_size,和note_list(这个是最主要目标)来实现任意写,实现任意写之后getshell的方式有很多,这里我们改写free@got为puts_plt,其实就是puts函数的地址,这样我们在delete的时候就可以把note_list上面对应的指针所指向的内容读取出来,这里我们可以把atoi@got写到note_list上来实现地址泄露,泄露成功之后我们可以把system的实际地址写入到atoi@got里面,因为在读入的时候都会使用这个函数,同时这个函数的参数是一个指针,那么我们输入的"/bin/sh\x00"就会被当做system函数的参数,这样我们就可以实现getshell了。
EXP
相关参考:
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/glibc-heap/house_of_force-zh/
▼
领取专属 10元无门槛券
私享最新 技术干货