前置学习
- 【pwn学习】堆溢出(一)
- 【pwn学习】堆溢出(二)- First Fit
- 【pwn学习】堆溢出(三)- Unlink和UAF
off-by-one是一种特殊的溢出漏洞,指只溢出一个字节的情况。
造成原因
造成off-by-one的原因常常是因为边界验证不充分。
循环写入时,循环次数设置错误导致多写入了一个字节;
字符串操作有误;
strlen 是我们很熟悉的计算 ascii 字符串长度的函数,这个函数在计算字符串长度时是不把结束符 '\x00'
计算在内的,但是 strcpy 在复制字符串时会拷贝结束符 '\x00'
。
利用
泄露的字节为可控制任意字节:通过修改大小造成块大小的重叠。
泄露的字节为null字节:可以清楚标志位。下面的检查在2.28版本后添加来针对这种情况。
if (__glibc_unlikely (chunksize(p) != prevsize))
malloc_printerr ("corrupted size vs. prev_size while consolidating");
unlink_chunk (av, p);
栅栏错误通常是由于循环的边界没有控制好导致写入多执行了一次。
// example_2.c
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;
}
示例程序中for循环多循环了一次,因此实际上会读入17个字节导致off-by-one漏洞
用gdb调试,读入前
pwndbg> x/30gx 0x405290
0x405290: 0x0000000000000000 0x0000000000000021 <== chunk1
0x4052a0: 0x0000000000000000 0x0000000000000000
0x4052b0: 0x0000000000000000 0x0000000000000021 <== chunk2
0x4052c0: 0x0000000000000000 0x0000000000000000
0x4052d0: 0x0000000000000000 0x0000000000000411
读入17个字符’a’后
pwndbg> x/30gx 0x405290
0x405290: 0x0000000000000000 0x0000000000000021
0x4052a0: 0x6161616161616161 0x6161616161616161
0x4052b0: 0x0000000000000061 0x0000000000000021
0x4052c0: 0x0000000000000000 0x0000000000000000
0x4052d0: 0x0000000000000000 0x0000000000000411
0x4052e0: 0x75706e4920746547 0x00000000000a3a74
第二种常见的导致 off-by-one 的场景是字符串操作,原因是字符串的结束符计算有误
// example_3.c
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
是我们很熟悉的计算 ascii 字符串长度的函数,这个函数在计算字符串长度时是不把结束符 '\x00'
计算在内的,但是strcpy
在复制字符串时会拷贝结束符 '\x00'
。这就导致了我们向 chunk1 中多写入了一个结束符\x00
。
strcpy
写入前
pwndbg> x/30gx 0x405290
0x405290: 0x0000000000000000 0x0000000000000021
0x4052a0: 0x0000000000000000 0x0000000000000000
0x4052b0: 0x0000000000000000 0x0000000000000411
0x4052c0: 0x75706e4920746547 0x0000000000000a74
0x4052d0: 0x0000000000000000 0x0000000000000000
写入后
pwndbg> x/30gx 0x405290
0x405290: 0x0000000000000000 0x0000000000000021
0x4052a0: 0x6161616161616161 0x6161616161616161
0x4052b0: 0x6161616161616161 0x0000000000000400
0x4052c0: 0x75706e4920746547 0x0000000000000a74
可以发现下一个chunk的size区域的低字节被\x00
覆盖。
其中
mchunk_size
记录了当前chunk的大小,chunk的大小都是8字节对齐,所以mchunk_size的低3位并不用来表示地址。为了充分利用内存空间,这三位被当作了标志位。--------------------------------------------------- |...........................................|A|M|P| ---------------------------------------------------
- A (NON_MAIN_ARENA) : 记录当前chunk是否不属于主线程。1 = 不属于,0 = 属于。
- M (IS_MMAPED): 记录当前chunk是否是由mmap分配的。1 = 是, 0 = 否。
- P (PREV_INUSE): 记录前一个chunk块是否被分配。1 = 是,0 = 否。这里的前一个chunk指的是物理地址上相邻的前一个chunk。
溢出 NULL 字节可以使得 prev_in_use
位被清,这样前块会被认为是 free 块。
例题-1 Asis CTF 2016 b00ks