介绍
严格来说 off-by-one 漏洞是一种特殊的溢出漏洞,off-by-one 指程序向缓冲区中写入时,写入的字节数超过了这个缓冲区本身所申请的字节数并且只越界了一个字节。
漏洞原理
off-by-one 是指单字节缓冲区溢出,这种漏洞的产生往往与边界验证不严和字符串操作有关,当然也不排除写入的 size 正好就只多了一个字节的情况。其中边界验证不严通常包括
- 使用循环语句向堆块中写入数据时,循环的次数设置错误(这在 C 语言初学者中很常见)导致多写入了一个字节。
- 字符串操作不合适
一般来说,单字节溢出被认为是难以利用的,但是因为 Linux 的堆管理机制 ptmalloc 验证的松散性,基于 Linux 堆的 off-by-one 漏洞利用起来并不复杂,并且威力强大。 此外,需要说明的一点是 off-by-one 是可以基于各种缓冲区的,比如栈、bss 段等等,但是堆上(heap based) 的 off-by-one 是 CTF 中比较常见的。我们这里仅讨论堆上的 off-by-one 情况。
利用方法
1.溢出字节为可控制任意字节:通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用 NULL 字节溢出的方法
2.溢出字节为 NULL 字节:在 size 为 0x100 的时候,溢出 NULL 字节可以使得
prev_in_use
位被清,这样前块会被认为是 free 块。(1) 这时可以选择使用 unlink 方法(见 unlink 部分)进行处理。(2) 另外,这时prev_size
域就会启用,就可以伪造prev_size
,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照prev_size
找到的块的大小与prev_size
是否一致。
我们现在先通过heapcreator这一题来看第一种利用情况。用ida查看分析伪代码,在主函数中可以清楚的看到程序提供的主要功能。
switch ( atoi(&buf) )
{
case 1:
create_heap(&buf, &buf);
break;
case 2:
edit_heap(&buf, &buf);
break;
case 3:
show_heap(&buf, &buf);
break;
case 4:
delete_heap(&buf, &buf);
break;
case 5:
exit(0);
return;
default:
v4 = "Invalid Choice";
puts("Invalid Choice");
break;
}
我们对每一个功能逐步进行分析,create_heap功能
unsigned __int64 create_heap()
{
_QWORD *v0; // rbx
signed int i; // [rsp+4h] [rbp-2Ch]
size_t size; // [rsp+8h] [rbp-28h]
char buf; // [rsp+10h] [rbp-20h]
unsigned __int64 v5; // [rsp+18h] [rbp-18h]
v5 = __readfsqword(0x28u);
for ( i = 0; i <= 9; ++i )
{
if ( !heaparray[i] )
{
heaparray[i] = malloc(0x10uLL);
if ( !heaparray[i] )
{
puts("Allocate Error");
exit(1);
}
printf("Size of Heap : ");
read(0, &buf, 8uLL);
size = atoi(&buf);
v0 = heaparray[i];
v0[1] = malloc(size);
if ( !*((_QWORD *)heaparray[i] + 1) )
{
puts("Allocate Error");
exit(2);
}
*(_QWORD *)heaparray[i] = size;
printf("Content of heap:", &buf);
read_input(*((_QWORD *)heaparray[i] + 1), size);
puts("SuccessFul");
return __readfsqword(0x28u) ^ v5;
}
}
return __readfsqword(0x28u) ^ v5;
}
根据以上代码可以分析出程序的存储结构,下图是create了0x20大小的heap的情况,其中heaparry数组存放了chunk1的mem指针,chunk1中存放了heap的size和存储实际内容的chunk2的mem指针,chunk2存放了实际的heap的内容。
![image-20200713154932938](https://tva1.sinaimg.cn/large/007S8ZIlly1ggpe8ks0osj316q0osjuo.jpg)
edit_heap功能根据chunk1中存放的指针对chunk2中的内容进行修改,要注意的是可写入的长度是size+1。
if ( heaparray[v1] )
{
printf("Content of heap : ", &buf);
read_input(*((void **)heaparray[v1] + 1), *(_QWORD *)heaparray[v1] + 1LL);
puts("Done !");
}
show_heap功能根据chunk1中存放的指针对chunk2中的内容进行打印
if ( heaparray[v1] )
{
printf("Size : %ld\nContent : %s\n", *(_QWORD *)</