0

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

    2023.07.20 | admin | 136次围观

    参考自《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);
    }
    

    版权声明

    本文仅代表作者观点。
    本文系作者授权发表,未经许可,不得转载。

    发表评论