使用C语言你必须知道的常见的字符串错误

参考自《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语言你必须知道的常见的字符串错误_第1张图片
在复制和连接字符串中,由于一些函数执行无界复制的操作,可能出现越界操作。

在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中字符串无效。
使用C语言你必须知道的常见的字符串错误_第2张图片
通过上图我们发现,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);
}

调试图如下:
使用C语言你必须知道的常见的字符串错误_第3张图片
可以发现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);
}

使用C语言你必须知道的常见的字符串错误_第4张图片

转载请注明出处。

你可能感兴趣的:(软件安全)