参考自《C和C++安全编码》。
实验环境:win10 & visual studio 2019
举一个非常简单的例子。
void get_name()
{
char name[8];
puts("Your name?");
gets(name);
return;
}
因为gets()函数的原因,无法对用户的输入长度进行控制。
char *gets(char *dest)
{
int c = getchar();
char *p = dest;
while (c != EOF && c != '\n')
{
*p++ = c;
c = getchar();
}
*p = '\0';
return dest;
}
因而程序员往往会设计一个足够长的数组保证输入,如果不加输入限制的话,就只能期望用户可以老老实实的配合。
对于和善的用户自然是可以很好的工作,对于恶意用户而言,程序就有可能遭重。
同时要注意,字符串的结尾:’\0’也要占一位,所以8位的数组不可以输入8个字符,如下图所示。
在复制和连接字符串中,由于一些函数执行无界复制的操作,可能出现越界操作。
在c语言调用main()函数时,通常声明为:
int main(int argc, char *argv[])
{
;
}
命令行参数作为指向从argv[0]到argv[argc-1]的数组成员并以空字符结尾的字符串指针传入main()
在argc>0的情况下,argv[0]指向的字符串是程序名;
在argc>1的情况下,从argv[0]到argv[argc-1]引用的字符串是实际程序参数。
在任何情况下,argv[argc]的值都应该是NULL。
当分配空间不足以复制一个程序的输入,就会产生漏洞。在下面的程序中,提供一个超过128长度的字符串就会造成漏洞,攻击者可以把argv[0]设置为NULL来调用这个程序。
int main(int argc, char *argv[])
{
char prog_name[128];
strcpy(prog_name, argv[0]);
}
这个程序可以在较早版本的编译器下编译并执行。但现在较新的编译器检查了缓冲区溢出,对对象大小检查失败,直接不予以执行。
使用strlen()函数可以确定argv[0]到argv[argc-1]引用字符串的长度,以便可动态分配充足的内存。
int main(int argc, char *argv[])
{
const char* const name = argv[0] ? argv[0] : "";
char *prog_name = (char *)malloc(strlen(name) + 1);
if (prog_name != NULL)
strcpy(prog_name, name);
else
;
}
在这里只是讲个原理,毕竟在vs的环境下只有替换为strcpy_s才能编译成功。
strcpy()也可替换为更安全的strdup(),接受一个指向字符串的指针,然后返回一个新分配的复制字符串的指针,最后将返回的指针传递给free()以回收内存。
字符串需要以’\0’结尾,为了接下来的实验,添加下条语句:
#define _CRT_SECURE_NO_WARNINGS
#pragma warning( disable : 4996)
之后敲入实验代码:
int main()
{
char a[10];
char b[10];
char c[20];
strncpy(a, "0123456789", sizeof(a));
strncpy(b, "0123456789", sizeof(b));
strcpy(c, a);
}
调试如下图所示,由于c中没有结束字符,所以直接提示c中字符串无效。
通过上图我们发现,a和b地址之间相差20位, 接下来测试下面代码:
int main()
{
char a[10];
char b[10];
strncpy(a, "0123456789", sizeof(a));
strncpy(b, "0123456789", sizeof(b));
a[2] = '\0';
printf("%c\n", b[21]);
printf("%s", b);
}
调试图如下:
可以发现a的地址是0x005cfdec,b的地址是0x005cfdd8,中间相差20位(十进制),其中有10位(十进制)缓冲区。因为b中没有声明字符’\0’,因此在打印b时会直接冲到a的区域。
由于程序员判断边界失误会发生的错误。
例如声明了a[6],循环赋值时从0直接赋到6超了一位。
有一个strncat()函数,功能是在字符串结尾追加n个字符。strncat()会在指定的最大长度之后一字节的位置写入字符串结束符。在C的标准库中,对字符串操作的函数最大长度都不是统一的,所以容易出现差一错误。
int main()
{
char str[5] = "0000";
char buff[10] = "111111";
strncat(buff, str, sizeof(str)-1);
printf("%s", buff);
}
当一个数组不足以容纳一个字符串时,程序员为了防止缓冲区溢出,通常会控制输入,这样会发生数据的丢失,有时也会产生漏洞。
由于c语言中字符串由数组实现,所以一些不使用函数的操作有可能具有安全隐患。
int main(int argc, char* argv[]) {
int i = 0;
char buff[128];
char* arg1 = argv[1];
while (arg1[i] != '\0') {
buff[i] = arg1[i];
i++;
}
buff[i] = '\0';
printf("buff = %s\n", buff);
}
转载请注明出处。