offbyone
严格来说 off-by-one 漏洞是一种特殊的溢出漏洞,off-by-one 指程序向缓冲区中写入时,写入的字节数超过了这个缓冲区本身所申请的字节数并且只越界了一个字节。
先来认识堆叠,因为off by系列的目的之一,就是可以用来做“堆叠',那先要明确目的(其实,先从目的讲或者先从过程讲,都不是很完美)。在堆叠中,一般有向前叠,或者向后叠两种。堆叠使用前向合并的逻辑比较多,后向合并的逻辑,模板化的利用,我倒是暂时还没见过。原因是,做堆叠,有个难点,是前后向合并逻辑中的unlink宏。这个宏,会检查堆块的size和下一个堆块记录的prev size是否相等,不相等的话,unlink这个堆块,就会报错。
offbyone|原理
off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的 size 正好就只多了一个字节的情况。其中边界验证不严通常包括
-
使用循环语句向堆块中写入数据时,循环的次数设置错误(这在 C 语言初学者中很常见)导致多写入了一个字节。
-
字符串操作不合适
一般来说,单字节溢出被认为是难以利用的,但是因为 Linux 的堆管理机制 ptmalloc 验证的松散性,基于 Linux 堆的 off-by-one 漏洞利用起来并不复杂,并且威力强大。 此外,需要说明的一点是 off-by-one 是可以基于各种缓冲区的,比如栈、bss 段等等,但是堆上(heap based) 的 off-by-one 是 CTF 中比较常见的。我们这里仅讨论堆上的 off-by-one 情况。
offbyone|利用思路
-
溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用 NULL 字节溢出的方法
-
溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得
prev_in_use
位被清,这样前块会被认为是 free 块。(1) 这时可以选择使用 unlink 方法(见 unlink 部分)进行处理。(2) 另外,这时prev_size
域就会启用,就可以伪造prev_size
,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照prev_size
找到的块的大小与prev_size
是否一致。
最新版本代码中,已加入针对 2 中后一种方法的 check ,但是在 2.28 及之前版本并没有该 check
/* consolidate backward */
if (!prev_inuse(p)) {
prevsize = prev_size (p);
size += prevsize;
p = chunk_at_offset(p, -((long) prevsize));
/* 后两行代码在最新版本中加入,则 2 的第二种方法无法使用,但是 2.28 及之前都没有问题 */
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
}