PWN堆house of spirit-1
- 参考博客:好好说话之Fastbin Attack(2):House Of Spirit_fastbin attack house of spirit-CSDN博客
- 参考书籍:杨超的《CTF竞赛权威指南——pwn篇》
- 用到的仓库:github的how2heap仓库
前言
-
学习了
UAF
、double_free
、unlink
、off-by-one
,之后基本上对堆稍微有点了解。 -
个人感觉,堆漏洞的成因就是
UAF
和堆溢出
,而栈的漏洞的成因基本上就是栈溢出
,其他的就是格式化字符串漏洞
,漏洞的成因感觉就是这几种。但是利用方式很多,所以学来学去都是在学被人挖掘出来的利用方法。在学这些利用方法的时候会不由感慨,那些开创漏洞利用的人脑洞真大。所以怎么感觉pwn到最后变misc
那些脑洞题了… -
初步了解了一些漏洞成因和漏洞利用后,就可以开始学习
house of
系列的利用方法了。目前打算主线直接学习house of
系列,按照Glibc堆利用之house of系列总结 - roderick - record and learn!这篇文章的顺序学习,学到哪些利用,之前没涉及到的再分出支线去学习。 -
堆的漏洞利用可以进行以下三种分类:这些分类相互有交集,并不互相独立,目前我做的是使用
house of
系列进行堆利用的分类,等house of
系列学习完之后,有时间再进行其他的分类glibc
版本对应的漏洞利用,具体可以看how2heap
这个github仓库shellphish/how2heap: A repository for learning various heap exploitation techniques.house of
分类的漏洞利用bins_attack
以及其他的attack
分类利用
house-of-spirit介绍
house-of-spirit
是堆利用house-of
系列的一种方法。这种方法目前只了解到是针对fast_bin
的attack和tcache_bin
的攻击,所以这个利用方法将被分成两个部分,第一个部分是介绍glibc2.26
之前的还没有引入tcache_bin
的攻击。第二部分是介绍glibc2.26
之后引入了tcache_bin
后针对tcache_bin
的攻击- 在学习
house-of-spirit
之前首先要对Linux
下的堆管理器即ptmalloc2
有一定的了解,还要知道UAF
漏洞、double_free
漏洞以及off-by
的两种漏洞,能简单利用UAF
和double
、这个两个漏洞对fast_bin
进行攻击和利用。这样就可以进一步学习house-of-spirit
。 - 对于
house-of-spirit
这个漏洞,与对堆管理器中unlink
的攻击关系其实不大。可以根据自己的节奏去选择学习顺序。 - 本次介绍的是对
glibc2.26
之前的版本还没引入tcache_bin
,针对的是fast_bin
的攻击。
利用方式
- 先来比较直观和简单地介绍一下
house-of-spirit
的利用方式。这个利用方式是double_free
和UAF
漏洞的进一步利用。 - 首先
house-of-spirit
的堆利用方式,是一种用于获得某块内存区域控制权的技术(即你可以实现对该块内存任意写入数据)。 house-of-spirit
是通过伪造一个堆块fake_chunk
,并且有一个指针ptr
指向该fake_chunk
,加上我们可以使用free()
函数,将指向这个fake_chunk
的ptr
指针给free
掉,这时该fake_chunk
的就会被放入fast_bin
对应大小堆块的链表头部。当我们再次使用malloc函数使用一个堆块,这时fake_chunk
就会被申请出来,被用户使用,这样我们就可以对该fake_chunk
进行写入或者修改数据。- 接下来通过图片来直观感受一下
house-of-spirit
的这个利用方式。 - 如图
fast_bin
链表这边有两个空闲的真chunk
,并且都放在了fast_bin
中。
- 这时我们在某个内存空间中(可能是
.bss
段、栈上
或者其他内存空间)伪造了一个chunk,称为fake_chunk
,并且有一个指针ptr
指向该fake_chunk
- 恰好这个程序中的
free
函数能释放ptr
这个指针。所以当我们释放ptr
这个指针后,就会将这个fake_chunk
放入fast_bin
这个链表中(该链表采用头插法)
- 当我们再次使用
malooc
函数申请适当大小的内存,就可以将这个fake_chunk
申请给用户写入数据。
- 以上就是
house_of_spirit
的利用过程,现在来总结一下house_of_spirit
的利用方式,以及house_of_spirit
与fast bin double free
的区别houser_of_spirit
是通过伪造一个堆块fake_chunk
(该堆块并不是通过malloc申请回来的),利用free
的机制将该fake_chunk
放入fast_bin
链表之中,再次通过malloc
将该fake_chunk
申请,从而实现任意内存读写- 而
double freee
是将本来通过malloc申请的堆块chunk1
(该堆块是合法的),通过两次释放chunk1
,将同一个堆块即chunk1
放入两次到fast_bin
中,这样我们第一次申请chunk1
,就可以修改chunk1
中的fd
指针,使其指向任意内存地址,当我们再次申请chunk1
时,这个任意内存就会被放入我们的fast_bin
链表之中,再次使用malloc
申请,我们就可以申请到任意内存并对该内存进行读写。 - 区别:
house_of_spirit
是构造fake_chunk
想办法放入bin
中链表,通过再次申请达到任意地址读写。而double
是通过合法chunk的两次释放和两次申请,从而在第三次申请chunk时能申请到任意地址,对任意地址读写
伪造条件
-
由于
free
的时候会对堆块进行检查,即验证堆块的合法性,所以我们在伪造堆块的时候就要满足一定的条件,这样才能绕过检查,继续进行house_of_spirit
的利用。 -
接下来说明一下伪造需要满足的条件:
fake_chunk
标志位、fake_chunk
的内存地址对齐、fake_chunk
的size大小、fake_chunk
的next_chunk
的大小、fake_chunk
的连续释放(即double_free的错误) -
ptmalloc
所申请的堆块,数据结构如下:
fake_chunk的标志位:
- 在伪造一个
fake_chunk
的时候,由于堆块结构中的size
部分的最低三位是标志位,所以这些标志位要满足一下条件:P
标志位设置为1
:该标志位表示的是该堆块物理地址上相邻的前一个堆块是否处于空闲状态,1
表示处于使用状态。当标志位设置为0
的时候就会在free
的时候会触发unlink
机制,导致合并一个不存在的地址空间,会引发程序的崩溃。有些题目好像不满足也可以,对P标志位要求没那么严格M
标志位设置为0
:M
标志位表示的是IS_MAPPED
,记录当前chunk是否由mmap分配。当这个位置的标记设置为1
,就表示这个堆块内存时调用mmap
分配的,在处理这个chunk的时候就会单独处理。N
标志位设置为0
:一般攻击的都是主线程下的堆(目前还没有打过多线程的堆题),所以该标志位应该被设置为0
fake_chunk的内存对齐:
- 堆内存在申请的时候都是8字节对齐或者16字节对齐的,也就是说
fake_chunk
的prev_size
的最低位地址、P
标志位对应的地址应该为0xXXXXXXX0
。也就是需要16字节对齐。(32位程序下是0xXXXXXXX8
,也就是8字节对齐) - 在
free
的时候会对内存是否对齐进行检查,如果检查到所free的堆块内存没有对齐就会出现报错,或者是程序无法运行下去。
fake_chunk的size大小:
fake_chunk
是要放入fast_bin
中的,所以我们的size
大小就要满足挂入fast_bin
链表的要求,即size
的大小要小于0x80
字节- 如果要放入
tcache_bin
中就要满足放入tcache_bin
中的size
大小 - 还需要注意一点就是
size
的值也必须是16字节对齐
,即满足0x10
的整数倍(32位程序要满足0x8
字节对齐)
fake_chunk的next_chunk的大小:
- 所谓
fake_chunk
的next_chunk
指的就是与fake_chunk
物理地址上相邻的且地址高于fake_chunk
的一段内存空间(因为是伪造的堆块,所以该段内存空间其实不是堆块) - 当我们释放
fake_chunk
的时候,会有一个检查机制,ptmalloc2
会通过,如下计算确定next_chunk
的地址,并对其size
进行检查
1 | next_chunk_address = fake_chunk_address + fake_chunk->size |
- 所以
next_chunk
的size
大小应该为满足以下两个条件:size
要满足0x10
字节对齐,即必须为16字节的整数倍(32位系统则是0x8
字节对齐)size
还要满足小于128kb
- 这样设置的目的就是在
chunk
连续释放的时候,能够保证伪造的fake_chunk
在释放后能够挂在fast_bin
中的main_arena
的前面。这样我们再次使用malloc
申请适当大小的堆块时,就会直接申请到fake_chunk
fake_chunk的连续释放:
- 在
double_free
中如果直接连续free
相同的堆块,程序就会因为检测到double_free
而无法运行,所以我们在进行double_free
利用的时候就释放一次目标堆块后,要释放另一个堆块,之后才能再次释放目标堆块。这就是需要我们绕过double_free
的检查机制 - 所以
fake_chunk
在释放完一次后,不能马上再进行释放,这样就会导致double_free
检查不通过,导致程序无法运行。
总结:所以我们要构造的fake_chunk
要满足如下条件
实验
- 本实验的代码时在
how2heap
中glibc2.23
版本的house-of-spirit
,并通过阅读程序,模拟出攻击流程 - 我将其源码进行汉化,源码如下:
源码
1 |
|
1 |
|
- 该程序先调用了
malloc(1)
开辟了一个堆块内存,初始化了一下堆块内存。如果没有malloc(1)
在gdb动态调试的时候就会出现堆块没有初始化的提示。
- 使用
malloc
后在栈上创建了一个多的数据,进行堆块的伪造。
- 然后根据输出提示,设置了伪造堆块的相关数据。
- 使用gdb进行动态调试后就会看到栈上的数据是这样分布的,并且现在
fast_bin
是空的
- 之后我们通过
free(a)
即free(&fake_chuns[1])
,就把伪造的堆块放入到了fastbin链中。这样就完成了house of spirit
的利用。
题目
house_of_spirit_level_1
- 题目来源:lctf2016_pwn200,在buuctf上有相应的环境,附件也可以从那边下载。这边我使用patch的方法,使得我在
wsl
的环境下使用glibc2.23
进行动态调试,这样就可以避免glibc
版本过高的原因从而导致出现tcache_bin
的问题
level_1分析1
- 现在先使用
IDA
对该程序进行静态分析,在静态分析的时候运行程序,这样能更好的理清楚程序的运行逻辑。先check
一下该程序的保护机制。发现保护机制开的很少。
- 然后使用IDA对该程序进行逆向分析。先来先查看
main
函数,这里的main
函数比较简单先是对输入输出进行初始化,然后再调用一个func
函数。
- 之后查看
func
函数,结合程序运行的结果看,这个函数会先输出提示who are u
,然后逐个字节的读取用户的输入,知道遇到换行符。还会将用户输入打印出来,并且提示输入id
,之后就是调用两个目前还不知道什么功能的函数。
- 之后将
sub_4007DF
这个函数重新命名为func2
,将sub_400A29
这个函数重新命名为func3
,再进入func2
进行查看函数的具体操作。- 该函数先是逐个字节读取用户的输入,一共读取4个字节或者遇到换行符结束读取。
- 在读取的时候还会对读入
id
进行检查,检查该id
,如果输入的id
不在十进制的数字里面,那么就不会执行接下去的代码。 - 之后还会使用
atoi
函数,该函数将我们输入的id
原本是字符的形式,将其转换为整型的形式,并返回给v2,还会将该值作为返回值返回。
- 之后再来查看
func3
的函数,执行的具体功能。该函数实现以下功能:- 开辟一个
0x40
大小的堆内存,将malloc
返回的地址给dest
变量 - 输入提示字符串
give me money~
,提示我们输入0x40
字节大小的数据,这里并不存在栈溢出 - 之后我们会把输入的数据原来存储在
buf
中,现在复制到dest
这边,即复制到malloc
开辟的堆内存这边。 - 最后将
dest
的值(即堆块的地址)赋值给ptr
这个指针,注意ptr
这个指针是存储在.bss
段上的全局变量这个可能有用 - 之后会调用
sub_4009C4
这个函数,将该函数命名为func4
- 开辟一个
- 接下来查看
func4
函数里面的运行过程,发现该函数就是一个经典菜单的形式。- 先会调用
func2
这个功能,实现用户对选项的选择。 - 输入
1
的时候就调用check_in
这个函数 - 输入
2
的时候就调用check_out
这个函数 - 输入
3
就退出该程序
- 先会调用
- 接下来先查看
check_out
这个函数,该函数实现的功能是释放掉全局变量指针ptr
所指向的内存地址,并将该ptr
指针置零,从而避免UAF
的漏洞利用。
- 接下来查看
check_in
函数- 该函数会先检查
ptr
是已经指向一个指针,如果已经指向一个指针,那么程序就不会执行后续的语句 - 通过
func2
读取数字,并判断读取的数字是否在0-128
范围内 - 之后使用
malloc
函数申请一个前面所读取数字大小的堆块。 - 最后可以向所申请的堆块写入数据。
- 该函数会先检查
level_1分析2
- 现在对该程序进行动态调试,查看该程序是否有溢出点或者其他的漏洞。
- 对于
func
函数这边,存在一个off-by-one
的利用,可以通过将v2
这个数组填满48个字节,然后就可以避免换行符,这样这边就没有\x00
对进行截断,这时printf
就会将rbp
所指向的栈地址的存储的数据泄露出来,从而知道了栈的地址。
- 接下来看
func3
这个函数,这个函数在会使用read
函数在栈上写入0x40
个数据,但是buf
只有56
字节(即0x38)的栈上存储空间,这时最后的0x8
字节就会把dest
这个指针给覆盖掉。- 所以
func3
存在一个溢出,可以修改dest
的值,并且之后会把dest
赋值给ptr
这个全局变量的指针。 - 在分析1中可以知道
free()
函数free
的是ptr
指针所指向的地址。
- 所以
level_1分析3
- 这个时候我们即泄露了栈地址,又能通过溢出修改
dest
这个指针,从而间接修改ptr
指针。现在思路就比较明显,既然可以泄露栈地址,那么就可以利用栈伪造一个fake_chunk
。通过house-of-spirit
这个对利用技术,将栈上的堆放入到fast_bin
中,在申请回来这样就可以对比较大块的栈地址进行写入。一般就可以修改返回地址。接下来要查看一下如何伪造这个堆块。栈上有什么数据可以提供我们伪造堆块。 - 对于
func()
函数这个输入点进行分析,发现并不能使用func()
函数这个输入点进行堆块的伪造,这是因为在堆块的伪造过程中必然会出现\x00
这个截断操作。这就导致我们在使用printf
函数进行格式化输出的时候栈上的地址泄露不出来。
- 对于
func2
可写入栈上的数据有限,也没办法完成对堆块的伪造,所以先来分析一下func3
是否能在修改指针的时候对堆块进行伪造,发现这是可以行的,这时我们在栈上进行堆块的伪造,并不会影响我们ptr
指针的修改
- 所以我们选择该此处对堆块的伪造,然后我们再来分析一下栈上高地址处是否可以有
0x10
整数倍的,并且小于128kb
大小的,还要满足prev_size
的最低位为0xXXXX0
这样我们就可以伪造出fake_chunk
并且成功的把堆块给链到fastbin
上。- 查看栈后发现这个地方明显是可以被控制的,而控制这个栈数据的也就只能是
func
或者func2
的执行过程。 - 我们发现这个数据是存储在
func
函数输入的更低地址,所以应该是在func
这个函数控制的。
- 查看栈后发现这个地方明显是可以被控制的,而控制这个栈数据的也就只能是
- 我们查看
func
的变量时,发现逐字节输入,控制循环的变量i
最终的结果为0x30
(存储位置是位于更低地址的栈那边),但是这个0x30
我们希望用来伪造size
,可是这个栈地址是0xXXXXX0
开始的,这并不能满足堆块内存对齐的问题
- 这时还有一个
0x30
是怎么来的,对于反编译的代码是没有体现出来的。我们需要去看func
的汇编代码,这时我们发现调用完func2
后,存储返回值的寄存器rax
会有一个操作,也就是将func2
的返回值复制到栈上。 - 而
func2
返回的也就是我们输入数字对应的整数。所以我们要伪造该堆块,在func
调用func2
时,就要像这边输入48
即0x30(或者其他符合要求的数据。)
- 这样我们就满足了伪造堆块的条件,现在我们要通过栈地址计算我们所申请堆块的
size
大小,和我们要伪造堆块的size
- 还有我们要修改的指针是指向
0x7ffcddf7d710
,但是我们泄露的指针是泄露到更高地址。所以我们还要计算偏移,所以我们最后会将指针修改为leak_addr-0x110+0x60
- 通过伪造堆块我们就可以释放该堆块,将该堆块放到
fast_bin
链上,这时编写部分exp
进行动态调试查看fake_chunk
是否被添加到fast_bin
链表上
1 | from pwn import * |
- 这时我们就发现我们伪造的
fake_chunk
被加到了fastbin
上
- 这时我们再使用
malloc
申请回来,我们就可以对该栈进行写,并且还可以修改返回地址。之前在查看保护机制时,有注意到栈是具有可执行权限的,所以我们就向我们申请的堆块写入shellcode
,并修改返回地址到栈上我们写入shellcode
的开头。这样就可以getshell
了
level_1_exp
1 | from pwn import * |
house_of_spirit_level_2
- 题目来源:2014 hack.lu oreo,再次挑战该题,被这题薄纱了俩次QAQ
- 题目在该链接的github仓库有:https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/fastbin-attack/2014_hack.lu_oreo