发现自己的基础还是薄弱。参加TQLCTF一道题也不会做,看了wp,发现需要这种攻击方法。于是才过来学。本篇包含了tcache结构图。
原理
回想一下之前的tcache_stashing_unlink_attack,最重要的特征是calloc,需要能够越过tcache获取块。这里的fastbin-reverse-into-tcache不需要calloc,但是也和堆块被重新放入tcache紧密相关。这里主要基于2.31和how2heap学习。
我们先malloc14个堆块,放7个填满tcache。free一个victim堆块(我们可以修改bk指针的),之后再free1到6个堆块(这里个数不太重要,可以根据题目限制free,个数只和栈上数据相关。如果我们要分配到的栈地址上偏移为0x8的位置为0(也就是NULL)那么我们只需要free1个即可。否则将会向前追溯栈上的bk位置,导致segmentation fault
在how2heap中,由于栈上数据被填充成垃圾数据,free了7个堆块。下图为攻击前正常的堆块。
接下来,我们修改victim的fd位置为栈(注意,由于victim第一个进入fastbin,因此他被放在fastbin的末尾)。并且malloc7次,拿出来tcache中的所有堆块。如下图
关键点在这里。此时如果我们试图malloc一个0x48的堆块,会从fastbin中取出一个,之后fastbin中剩下的堆块就会被放回到tcache(这里和tcache_stashing_unlink_attack很相似)由于放回的顺序是从fastbin头部取出,放到tcache头部,因此顺序是反的。也就是我们的栈将会被放在tcache的头部。如下图
由于tcache不检查大小,我们相当于完成了一次任意地址分配。
要求2.31
- 可以修改位于fastbin中chunk的bk指针
- 可以释放至少8个chunk,如果target地址有垃圾数据(例如栈)则需要14次分配释放
效果
一个任意地址堆块的分配(任意地址写入)
写入一个堆地址(上图0x7fffab165ce0位置上被写入堆地址)
简化流程
- 填满tcache,第八个块记作victim。
- 再根据要求释放6个块或者1个(是否有垃圾数据,一般就6个块)到fastbin
- 从tcache中取出所有bins
- 将victim的bk改为想要分配的地址(应该是位于fastbin的末尾)
- (检查此时:tcache为空,fastbin有7个正常堆块,最后一个为目标堆块)
- malloc一个fastbin的,此时tcache将被填满,第一个堆块是目标堆块
例子
这里例子就采用tqlctf2022的一道unbelievable_write来练习。下载以及exp
这个题目的libc2.31的,因为dockerfile里面写的是20.04.
查看保护发现没有开启PIE,并且要求我们往target写入任意一个数值改掉原来的就算成功
看一下给定的操作有什么。
c1函数允许我们分配一个任意大小的堆块,然后就会立即释放。这里立即释放有些困难,因为释放就会导致数据被清除,并且无法控制bins。当时就是在这里卡住不会了。
c2允许我们任意释放一个地址。注意v1可以为负,也就是说我们可以free掉tcache管理块。
思路
由于没有限制任意free的chunk位置,可以free掉tcache管理块,构造管理块数据,先伪造tcache已满,再伪造fastbin,再使用上述攻击方法。不能直接往tcache里面写这个堆块的地址。因为malloc之后就要进行free,会检查堆块size位置,这里因为没有size会直接结束。因此要用能够写入一个大数的方法。这里采用的是fastbin reverse into tcache。
这里比较关键的一步是获取tcache管理块的写权限。也就是给定的c3函数。
1 | delete(-0x290) # 释放tcache管理块 |
注意
这里一个特别关键的地方:fastbin的大小范围是0x20到0x90,我们在把堆块放入fastbin的时候,需要tcache size位置满,这一点可以用tcache管理块在bins中后面加上一块chunk来做到(也就是写了tcache管理块的fd位置,这里也正是fastbin chunk个数所在的位置)。如下图就是通过0x290chunk的fd控制了0x20位置的个数。
接下来通过malloc管理chunk,修改对应bins中最低地址(其中最低地址预先写为0x21)来伪造拿到0x21的块,接着释放就到了fastbin中。
但是还有一点:我们在reversing攻击的时候,要求tcache的0x20位置个数为0,不然无法放入Bins。又需要把0x290后面的chunk拿出来。但是程序刚malloc完就free,看似无法做到?
实际上可以在malloc出来0x290的块的时候,直接写入0x290位置为0(拿出来管理chunk之后,上图中0x155e2c0就会被放到管理块0x290位置上,此时设置为空,再free的时候,0x290的后面就只剩下自己一个了)
1 | fake_t = p16(0)*8*4+p16(0)*8*4+p64(0)*40 # reset tcache to null |
这里顺便记录一下tcache结构。下图和pwndbg中看到的结构一样。顺序看起来很奇怪是因为地址的原因。不熟悉的话可以自己调试一下。(保存在tcache结构.xlsx中)
还有一点需要注意:为了修改victim的bk,我们需要一个越界写,这个可以通过将第一个free的chunk的size改大而不是0x21就可以做到。如下,低字节释放0x401对应的chunk,就可以再拿回来写后面chunk的bk位置。
1 | pay_large = p64(0)*3+p64(0x401)+(p64(0)+p64(0x21))*0x7 |
这题知道这些差不多就能做了。但是还有一个比较坑的地方,就是这里puts是没有初始化buffer的。导致后面输出flag的时候报malloc的错(也是很无语)要在之前输出一次no,初始化了buffer即可。
如下为完成了攻击
以下为改掉之后的target
完整exp
注释比较多,懒得删掉了
1 | from pwn import * |