在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
1 字符串
1.1 字符串基础 字符串提供命令行参数、环境变量、控制台输入、文本文件及网络连 接,提供外部输入方法来影响程序的行为和输出,这也是程序容易出错的地方。字符串是一个概念,并不是C/C++内置类型,标准C语言库支持类型为char的字符串和类型为wchar_t的宽字符串。 字符串由一个以第一个空(null)字符作为结束的连续字符序列组成,并 包含此空字符(所以sizeof和strlen会差1)。一个指向字符串的指针实际指向该字符串的起始字符。目标大小,指sizeof(array)大小,注意与元素个数区分。
数组大小。数组带来的问题之一是确定其元素数量,例如下面的例子: void clear(int array[]) { for (size_t i = 0; i < sizeof(array) / sizeof(array[0]); ++i) { array[i] = 0; } }
void dowork() { int dis[12];
clear(dis); /* ... */ } array是一个参数,所以它的类型是指针。因此,sizeof(array)等于sizeof(int*),在x86 32机中,sizeof(array) / sizeof(array[0])计算结果都是1。
字符串字面值:简而言之就是在双引号中的值,在C中,字符串字面值的类型是一个char数组,但在C++中,它是一个const char数组。所以在C中可以修改字面值,但是程序如果试图去修改,该行为是未定义的。不要试图修改字符串字面值,编译器有时会把多个相同的字符串字面值存储在相同位置,例如只读存储器(ROM)中,看下面例子: const char *s1 = "abc"; const char *s2 = "abc";
char *s3 = "abc"; char *s4 = "abc";
char s5[] = "abc"; char s6[] = "abc"; 比较地址会发现s1,s2,s3,s4相同,用这4个指针去改变字符串字面值是会出问题的。s5,s6值不同 字符数组初始化:不要指定一个用字符串字面值初始化的字符数组的界限 const char s[3] = "abc"; //不安全写法,少一个'\0' const char s[] = "abc"; //推荐初始化方式
1.2 C++中的字符串 C++标准类模板std::basic_string。简单来说就是string(basic_string<char>) 和wstring(basic_string<wchar_t>),basic_string的类的模版特化更不容易出现错误和安全漏洞,需要强调的是大多数C++字符串对象被视为不可分割的整体(通常按值传递和引用传递),内部字符串不一定是以空字符结束(大多数实现是以空字符结尾),C的库函数都接受以空字符结尾的字符序列指针。
1.3 字符类型 char 是 signed char 还是 unsigned char 可由编译器的配置项设定 当char有符号时,由unsigned char[]转换为const char * 当char无符号时,由singned char[] 转换为const char * 如果不强制转换会有警告,建议使用普通的char
1.4 字符串的长度 混淆概念容易在C和C++中导致严重的错误, wchar_t wide_str1[] = L"0123456789"; wchar_t *wide_str2 = (wchar_t*)malloc(strlen(wide_str1) + 1); if(wide_str2 == NULL) { /*处理错误*/ } free(wide_str2); wide_str2 = NULL; 对一个以空字符结尾的字节字符串,strlen()统计终止空字节前面的字符数量。然而,宽字符可以包含空字节,所以计算结果会出问题。 使用wcslen可以计算宽字符串的大小 wchar_t wide_str1[] = L"0123456789"; wchar_t *wide_str2 = (wchar_t*)malloc(wcslen(wide_str1) + 1); if(wide_str2 == NULL) { /*处理错误*/ } free(wide_str2); wide_str2 = NULL; 注意此长度没有乘sizeof(wchar_t),所以还是不对,下面值最终正确写法: wchar_t wide_str1[] = L"0123456789"; wchar_t *wide_str2 = (wchar_t*)malloc((wcslen(wide_str1)+1)*sizeof(wchar_t)); if(wide_str2 == NULL) { /*处理错误*/ } free(wide_str2); wide_str2 = NULL;
2 常见的字符串操作错误
2.1 无界字符串复制 void get_y_or_n() { char response[8]; puts("Continue? [y] n:"); gets(response); if(response[0] == 'n') exit(0);
return; } 其实gets()函数在C99中以废弃并在C11中淘汰。它没有提供方法指定读入的字符数的限制。这种限制在此函数的如下一致实现中是显而易见的: char *gets(char *dest) { int c = getchar(); char *p = dest;
while(c != EOF && c != '\n') { *p++ = c; c = getchar(); } *p = '\0';
return dest; } 如果输入超出8个字符,那么会导致未定义的行为。不要从一个无界源复制数据到定长数组中,禁止这种方法。 2.1.1 复制和连接字符串 例如strcpy(), strcat(), sprintf(), 容易执行无界操作。例如: int main(int argc, char *argv[]) { /*argc参数个数,argv参数数组*/ } 当argc大于0,按照惯例,argv[0]指向的字符串是程序名。若argc > 1,则argv[0]~argv[argc-1]引用的就是实际程序参数。 当分配的空间不足以复制一个程序的输入,就会产生漏洞。攻击者可以控制argv[0]的内容 int main(int argc, char *argv[]) { /*argc参数个数,argv参数数组*/ char prog_name[128]; strcpy(prog_name, argv[0]); /* ... */ } 输入一个大于128个字节的字符,栈溢出,即缓冲区溢出漏洞。 标准的写法应该是: int main(int argc, char *argv[]) { /* 不要假设argv[0]不许为空 */ 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 { /* 复原 */ } } 其实还有一种方法可以避免溢出,通过设置域宽可以消除gets()的缺陷 char buf[12]; std::cin::width(12); std::cin >> buf; std::cout << buf << std::endl;
2.2 差一错误 简而言之就是从源字符串拷贝内容到目的字符串,刚好最后的'\0'没有 拷贝到目的字符串中,在这之后对目的串调用C语言库的函数可能会出问题,即空字符结尾错误,其余的还有字符串阶截断误差,越界操作等。
2.3 字符串漏洞及其利用 大体上就是缓冲区溢出(详细的可以自己网上查,有很多资料详细介 绍),栈溢出的话,可以把目标代码或者数据覆盖到栈里面,关于栈为什么会溢出,其实是因为在编译后,栈的大小就固定了。这种攻击方式也称注入,这里涉及到汇编以及底层的结构,不做详细解释,不过解决方法也有很多,要么做边界检查,要么动态的分配内存,还有更简单的那就是直接使用std::basic_string。当然使用string也会出问题,例如迭代器失效。 char input[]; string email; string::iterator loc = email.begin(); //复制到string对象,同时把";" 转换成" " for (size_t i = 0; i < strlen(input); ++i) { if(input[i] != ";") email.insert(loc++, input[i]); else email.insert(loc++, ' '); } 第一次insert之后,loc就已经失效,后面的insert都将产生未定义行为。正确的写法应该是 char input[]; string email; string::iterator loc = email.begin(); //复制到string对象,同时把";" 转换成" " for (size_t i = 0; i < strlen(input); ++i) { if(input[i] != ";") loc = email.insert(loc, input[i]); else loc = email.insert(loc, ' '); ++loc; } 当然在编程的时候引用边界之外的元素会抛出一个异常std::out_of _range。另外std::string.c_str()函数可以返回一个以空字符结尾的字符,const值,所以调用free()或者delete()会出错,需要修改则只能修改副本。 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论