PWN堆house-of-atum
- 参考博客:Glibc堆利用之house of系列总结 - roderick - record and learn!
- 参考博客:[House Of Atum-原理 | Pwn进你的心](https://ywhkkx.github.io/2023/03/10/House Of Atum-原理/)
- 参考博客:原创]BCTF 2018 House of Atum分析-Pwn-看雪-安全社区|安全招聘|kanxue.com
前置知识
-
在
glibc2.31版本之前,tcache的检查机制比较不太严格,在glibc2.31的时候才出现了key机制,所以在glibc2.31之前,实现tcache double free利用是非常容易的。甚至我们还可以把同一个chunk连续释放8次,第8次释放就会将该chunk放入fastbin中,当然此时该chunk本身还存留在tcache中。 -
这时我们甚至可以利用
UAF漏洞,连续释放同一个chunk8次,使得该chunk被放入fastbin中,该chunk被放入fastbin的同时本身还是留在tcache_bin中。 -
house of atum的适用范围:glibc2.26——glibc2.30。 -
house of atum利用的前提条件:- 需要在
glibc对应版本中。 - 需要存在
UAF漏洞,这里的UAF漏洞需要满足两点,其一是:可以进行double free利用;其二是:edit after free,即该堆块被释放后还可以向这个堆块写入内容。
- 需要在
-
进行
house of atum这个利用方式之前,我们先要了解tcache_bin中的运行机制,而tcache_bin这个运行机制就不在这里多说明了,已经单独写一篇博客了。 -
其实最好是先学习一下
tcache double free这个理由再来学习这个,但是问题不大。
实验
实验1
- 这个利用方式在
how2heap这个仓库中没有理由的代码,这时我们就自己写一个程序来动态调试一下程序的运行情况。 - 该程序的编译的
gcc版本为gcc (Ubuntu 7.5.0-3ubuntu1~18.04) 7.5.0,但是由于我使用的docker环境中,glibc2.27已经打了补丁,所以我将该编译好的程序复制到WSL中,然后使用glibc-all-in-one这个项目,配合patchelf,这样就能得到实验的环境。 - 实验的源码如下:
1 |
|
patchelf完之后就可以开始进行动态调试了。我们就先申请一个堆块。


- 然后我们先释放一次堆块,我们所申请的堆块就被放入了


- 这时我们再释放
6次这个堆块,这时我们tcachebins中存放0x20大小的堆块就会被放满一样的堆块,这时我们再释放一个堆块,这个堆块就会被放入fastbin中。

- 现在我们第
8次释放堆块,查看bins的状态,此时这个chunk的fd指针已经被置0,并且该堆块除了已经放入到tcachebin中,也会被放入到tcachebin中。

实验2
- 接下来我们一步一步完善我们的利用方式。
1 |
|
- 这时我们将这个代码编译后
patchelf,进行调试并且画图理解。我们先来查看堆块释放7次,将tcachebin填满之后就会出现如下图所示。


- 然后当我们再释放一次堆块,此时
tcachebin对应存储着0x20的size已经被填满了,这时这个堆块就会被放入fastbin中,如下图所示,并且此时该堆块的fd指针会被设置为0


- 这时我们再申请与之前释放的相同大小的堆块,此时就会从
tcachebin中去取空闲的堆块。取完之后,tcachebin这个数组元素的值就会被设置为0,但是tcachebins有一个count标志用于表明该tcachebin中存放空闲chunk的数量,但是这时tcachebin相当于没有空闲的堆块。 - 但是我们看到
fastbin数组中还有空闲的堆块。


- 这时我们修改我们申请过来的堆块的
fd指针,将该指针的值设置为chunk_pre_size_addr - 0x10如下图所示。


- 这时我们申请一个堆块,此时我们还会申请到
0x55731847a250这个位置,但是0x55731847a240会被链到tcachebin链表上,这样我们



- 然后我们再次申请一个堆块,此时
malloc()返回的地址对应的就会是fake_fd这边,对应着我们前面两次申请的堆块的prev_size_addr,这时我们就可以通过这次申请过来的堆块写入数据,从而达到修改chunk的prev_size、和chunk的size、P、N、M这三个标志位。从而进行后续的利用操作。
利用方式
- 利用条件:
- 需要存在
UAF漏洞,并且可以使用tcachebin double free。 - 需要泄露堆的地址。
- 需要泄露
- 需要存在
- 申请
chunk A,大小在fastbin范围内 - 释放
A,连续释放8次,此时,A的fd被清0,A也被放置到了fastbin里面 - 申请一个
chunk,将其fd修改为A - 0x10,此时tcache中的counts为6 - 再申请一个
chunk,从fastbin里面取,但是会把fastbin里面剩余的一个chunk链入到tcachebin - 再次分配就会分配到地址
A-0x10处,就可以修改原来A的presize/size等
house of atum_level_1
- 题目来源:BCTF2018-house-of-atum
- 题目附件:搜索一下就能找到
- 涉及知识点:
house of atum、tcachebin下触发unlink、unsortedbin泄露libc地址、tcachebin_UAF申请任意地址、简单的__free_hook的利用,总的来说也是一套组合利用,由于tcachebin下触发unlink、unsortedbin_attack这两个利用还没怎么了解,所以利用完house of atum就卡住了。 - 拿到附件之后我们需要使用
glibc-all-in-one项目对应的glibc2.27版本进行patchelf,这样该程序才能正常运行。
level_1分析1
- 我们先运行一下这个程序看一下这个程序的基本运行逻辑。我们运行一下这个程序发现一开始就会弹出一个菜单。基本上就是
增、删、改、查这一字面意思。

- 然后我们就输入选择,我们选择
1后,程序会要求用户输入内容。

- 然后我们再选择
2看看,我们选择2后,程序会要求我们输入要内容对应堆块的索引值idx,之后让我们输入新的内容。

- 我们先来选择第
4个选项,发现程序要求我们输入索引,输入后会输出对应索引的堆块。

- 最后我们选择第
3个选项,发现就是清除用户指定索引的堆块。

- 运行完一遍程序后我们就可以查看一下这个程序的保护机制了,发现保护全开。

- 大致了解程序的运行逻辑之后,我们就使用
IDA对这个程序进行逆向一下。结合之前我们运行的程序,我们大致可以分析出main函数其实就是一个类似于菜单的。

- 查看一下
initialize()函数,我们发现这个函数其实就是一个输入、输出初始化的一个函数。

- 查看一下
menu()这个函数,这个函数会输出菜单,输出菜单后还会调用getint() - 而
getint()这个函数实现的功能就是字面意思,也就是获取用户的选项输入。


- 接下来就是看按照
增、删、改、看这个顺序查看一下菜单对应的各个函数。 - 先查看
alloc()函数- 该函数首先会遍历一下
已经申请并且存放在notes数组中的堆块个数,我们会发现notes这个是一个全局变量,并且是指针数组类型。 - 如果已经有两个堆块了,就会告诉用户
太多notes,并且不能再申请堆块了 - 如果还没有申请两个堆块,程序接下去就会申请一个
0x48大小的堆块。 - 并且让用户向里面输入内容。
- 该函数首先会遍历一下

- 接下来查看
改即(delete()这个函数),主要就是让用户输入对应的notes数组的索引,然后直接释放对应索引的堆块。 - 程序会提示是否要清除堆块,如果输入
y就会将notes对应的索引的元素设置为0。注意这里就存在UAF漏洞,我们可以选择不clear对应索引的堆块

- 之后查看
edit这个函数,这个函数就先会让用户输入对应的idx,然后向对应的堆块输入新的内容。

- 最后再查看
show(),这个函数就是输出对应堆块的内容。注意这个函数可以配合UAF漏洞使用,从而泄露出堆的地址

- 程序分析完之后我们再来查看一下全局变量,看看是否有全局变量被我们遗漏的。发现并没有全局变量被我遗漏的。只有一个
notes这个全局变量。

level_1分析2
- 接下来我们进行动态调试,查看一下程序的基本逻辑,我们先写好基本的交互脚本。
1 | from pwn import * |
- 到这里我们先思考一个问题,这边我们的
notes只能是申请两个堆块,这时我们就要先仔细思考一下,我们只申请一个堆块,就只对这个堆块进行利用是否可以。我们会发现当我们只申请一个堆块,就来进行house of atum的利用时,虽然我们可以修改到我们所申请堆块的prev_size位和size位,但是这对后续就会出现死路一条。 - 首先我们一开始申请的
0x250这个堆块是与输入输出缓冲区有关的,而在我们申请堆块相邻高地址处就是top_chunk了。 - 这时就会出现一个问题,我们没办法修改相邻的两个堆块中的内容,这时如果我们修改了
中间chunk的prev_size和size位,进行一些unlink等操作就会报错,因为我们没办法修改其他堆块从而绕过检查。

- 这个时候我们就需要先申请两个堆块了,其中
idx=0是我们要进行house of atum利用的堆块。而idx=1对应的堆块是帮助我们绕过检查的堆块。 - 然后我们将
idx=1对应的堆块,在fd偏移0x30到0x40这个地址处写入payload = p64(0)*7+p64(0x11),因为tcache一开始设计就是为了提高堆分配的效率,所以当我们释放idx=1对应的堆块时,它被放入tcachebin中,但是里面写入的数据还是存在的。所以我们就可以利用这个一特性先写入payload = p64(0)*7+p64(0x11),这样我们在之后修改它相邻低地址堆块对应的prev_size和size位时就可以绕过unlink这个检查。此时堆中内容如下:
1 | from pwn import * |

- 在做完绕过准备后,我们先要泄露出堆块的地址,这样我们再能进行
house of atum这个利用,这时我们先连续释放2个idx=0对应的堆块,释放之后,这个堆块对应的fd指针,就是指向自己堆块结构中fd指针的地址。如下图所示



- 接下来我们就可以进行
house of atum的利用了- 我们先释放
idx=1处的堆块,因为如果idx=1处的堆块没有被释放,我们就不能连续申请两个堆块,就没办法利用house of atum了。 - 先释放
idx=1处的堆块后,再释放5次idx=0处的堆块就行。 - 第
5次释放idx=0处的堆块时,要将notes[0]处的元素设置为0,这样做也是为了后续我们能连续申请两个堆块。
- 我们先释放
1 | from pwn import * |
- 操作所对应的
exp,操作完之后我们再看一下对应的内存,发现堆块的布局如下我们连续释放的堆块已经被放入fastbin中,并且其fd指针已经被设置为0了


- 并且此时我们发现,我们之前写入的
0x11还保留在对应的堆块中。

- 这时我们就可以进行
house of atum的利用
1 | from pwn import * |
- 先申请一个堆块,将这个堆块的
fd指针修改为chunk_addr = chunk_addr -0x10 - 0x20,也就是这个位置

- 然后我们再申请一个堆块,这个堆块的内容随便填一下,这样我们就可以得到如下的
bins,我们会发现0x*****240即上图中对应红框框起来的下面一个位置,已经被放入到了tcachebin中。 - 但是此时我们的
idx=0和idx=1都有堆块存放了,所以我们这时需要释放一个堆块,才能将放入tcachebin中的堆块给申请回来。而释放idx=0或者idx=1其中一个都没什么问题,他们指向的都是0x******260,并且存放0x50大小的堆块对应的tcachebin已经存满7块了,所以我们释放这两个堆块的其中一个,都会被放入fastbin中。我们选择释放掉idx=1


- 释放完
idx=1后,我们就可以将对应的0x******240这个堆块给申请回来,并且就可以从0x******240开始往里面写数据,在写数据的时候可以覆盖到原来我们堆块的prev_size和size位。这时我们就可以保持prev_size=0x0并且size=0x91为我们后续触发unlink操作做准备。我们计算0x91这个位置,刚好就是下图所示的范围。 - 此时我们
idx=1指向的地址为0x*****240,idx=0指向的地址为0x********260

- 接下来我们就要实现在
tcachebin下的如何触发unlink - 首先我们需要让
tcachebin对应的位置存满7个堆块,然后我们第8次释放后就会放入fastbin中,并且查看是否能合并,如果能和其他相邻的堆块合并,就会进行合并操作,合并后的堆块就会被放入unsortedbin这个堆块中。所有我们就要释放idx=0对应的堆块8次 - 这里还要注意下,在第
8次释放的时候,我们要将notes[0]中的数据设置为0。这样我们之后才能申请堆块。
1 | from pwn import * |
- 我们来查看效果,
unlink后的bins和堆内存。我们发现图中的堆块已经成功被触发了unlink操作。

- 并且我们发现放入
unsortedbin中的堆块,fd、bk对应的值为main_arena+96的位置,由于notes[0]处的元素已经被设置为0了这时我们就可以使用show(1)来泄露堆块

- 接下来就是泄露操作了
1 | from pwn import * |
- 但是在进行
show(1)操作时,我们还要对idx=1处的堆块进行编辑。因为0x*****240这边都是空字节,在使用puts函数输出时,并不会将main_arena+96这个地址给泄露出来。所以我们要先填充b'a'*0x20,再调用show(1)进行地址的泄露。

- 并且泄露之后我们还需要将对应地址
0x****260的prev_size、size复原,要不然在下次申请的时候会程序就会报错。 - 泄露的结果如下:

- 接下来我们就要对
unsortedbin这个运行进制进行利用了,我们先来看看目前bins的布局


- 此时利用的是
tcachebin_UAF然后申请到任意地址,我们已经泄露了libc的地址,这时我们就可以劫持hook从而getshell,这里我们选择劫持__free_hook这个函数指针为system('/bin/sh')。所以我们先要将堆块申请到__free_hook这个位置。 - 并且由于
tcachebins在取出堆块的时候并没有对chunk的size位做检测,所以并不要寻找合适的size对应的内存进行申请,直接申请__free_hook-0x20这个位置即可。

- 这时我们还需要修改已经被放入
tcachbin中的size位为0x51,这样我们申请的时候就可以申请到处于0x*****260处的堆块。 - 并且我们还要修改
fd的值为free_hook-0x20,这样在申请完0x*******260这个堆块的时候free_hook-0x20这个地址就会被链到tcachebin上,具体效果如下:

- 但是此时
idx=0和idx=1都已经有堆块了,所以我们申请到0x******260这个堆块后,又要将其释放。但是该堆块会被放入fasbin中,所以我们下次申请时就会申请到__free_hook-0x20这个位置,接下来将/bin/sh先写入到free_hook-0x10的位置,然后将system_addr填入__free_hook中。
1 | payload = p64(0)*0x3 + p64(0x51) + p64(free_hook-0x20) |

- 填入之后再使用
free调用释放这个堆块,这时free(0x7fcd558de8d8),就相当于执行system("/bin/sh"),0x7fcd558de8d8这个内存地址中存储的就是/bin/sh\x00这个字符串。这样就能取得shell了

level_1_exp
- 具体的
exp如下:
1 | from pwn import * |
利用失效
glibc2.31后利用基本失效了
1 | // glibc ≥ 2.30 |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 iyheart的博客!

