PWN-off by one
Off-By-One
介绍
严格来说 off-by-one 漏洞是一种特殊的溢出漏洞,off-by-one 指程序向缓冲区中写入时,写入的字节数超过了这个缓冲区本身所申请的字节数并且只越界了一个字节。
原理
这种漏洞的产生往往与边界检验不严和字符串操作有关。当然也不排除写入的size正好就之多了一个字节的情况。
边界检验不严包括
- 使用循环语句向堆块写入数据的时,循环的次数设置错误
- 字符串操作不合适
off-by-one所利用的缓冲区包括栈、bss段等等,但是在CTF中堆更常见一点。
利用思路
- 溢出字节为可控制任意字节
- 通过修改大小造成块结构之间出现重叠,从而泄露其他块数据,或是覆盖其他块数据。也可使用NULL字节溢出的方法
- 溢出字节为NULL字节
- size为0x100的时候,溢出NULL字节可以使得prev_inuse位被清,这样前块就会被认为是free块
- 选在unlink方法进行处理
- prev_size被启用,可以伪造prev_size,从而造成块之间发生重叠。此方法的关键在于 unlink 的时候没有检查按照
prev_size找到的块的大小与prev_size是否一致。(此方法在2.28版本以后行不通)
- size为0x100的时候,溢出NULL字节可以使得prev_inuse位被清,这样前块就会被认为是free块
示例1
int my_gets(char *ptr,int size)
{
int i;
for(i=0;i<=size;i++)
{
ptr[i]=getchar();
}
return i;
}
int main()
{
void *chunk1,*chunk2;
chunk1=malloc(16);
chunk2=malloc(16);
puts("Get Input:");
my_gets(chunk1,16);
return 0;
}
我们自己编写的 my_gets 函数导致了一个 off-by-one 漏洞,原因是 for 循环的边界没有控制好导致写入多执行了一次,这也被称为栅栏错误
建造一条直栅栏(即不围圈),长 30 米、每条栅栏柱间相隔 3 米,需要多少条栅栏柱?最容易想到的答案 10 是错的。这个栅栏有 10 个间隔,11 条栅栏柱。
分配两个大小为0x10的chunk
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <=== chunk2
0x602030: 0x0000000000000000 0x0000000000000000
执行my_gets进行输入后,数据覆盖到了prev_size中。
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000021 <=== chunk2
0x602030: 0x0000000000000000 0x0000000000000000
示例2
int main(void)
{
char buffer[40]="";
void *chunk1;
chunk1=malloc(24);
puts("Get Input");
gets(buffer);
if(strlen(buffer)==24)
{
strcpy(chunk1,buffer);
}
return 0;
}
strlen在计算字符串长度的时候不把结束符 \x00 计算在内
但是strcpy在复制字符串时会拷贝结束符 \x00
这就导致了我们向chunk1中写入了25个字节
0x602000: 0x0000000000000000 0x0000000000000021 <=== chunk1
0x602010: 0x0000000000000000 0x0000000000000000
0x602020: 0x0000000000000000 0x0000000000000411 <=== next chunk
在我们输入 ‘A' * 24后执行 strcpy
0x602000: 0x0000000000000000 0x0000000000000021
0x602010: 0x4141414141414141 0x4141414141414141
0x602020: 0x4141414141414141 0x0000000000000400
由于小端存储,所以覆盖掉了低字节。这种也叫做NULL byte off-by-one
在libc-2.29之后
加入check代码
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
但是,只要满足被unlink的chunk和下一个chunk相连,所以仍然可以伪造fake_chunk

浙公网安备 33010602011771号