C/C++安全编码-字符串
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()会出错,需要修改则只能修改副本。
C/C++安全编码-字符串的更多相关文章
- 中文字符串转换为十六进制Unicode编码字符串
package my.unicode; import java.util.regex.Matcher; import java.util.regex.Pattern; public class Uni ...
- SQL Server获取下一个编码字符串的实现方案分割和进位
我在前一种解决方案SQL Server获取下一个编码字符实现和后一种解决方案SQL Server获取下一个编码字符实现继续重构与增强两篇博文中均提供了一种解决编码的方案,考虑良久对比以上两种方 ...
- 普通字符串与Hex编码字符串之间转换
import java.io.UnsupportedEncodingException; import org.apache.commons.codec.binary.Hex; public clas ...
- 2017计算机学科夏令营上机考试-B编码字符串
B:编码字符串 总时间限制: 1000ms 内存限制: 65536kB 描述 在数据压缩中,一个常用的方法是行程长度编码压缩.对于一个待压缩的字符串,我们可以依次记录每个字符及重复的次数.例如,待 ...
- 中文字符串和UTF-8编码字符串相互转换
中文字符串和UTF-8编码字符串相互转换 //UTF字符转换 var UTFTranslate = { Change: function(pValue) { ) { ).replace(/(%u)(\ ...
- c# 实现获取汉字十六进制Unicode编码字符串
1. 汉字转十六进制UNICODE编码字符串 /// <summary> /// //// /// </summary> /// & ...
- 图片和base64编码字符串 互相转换,图片和byte数组互相转换
图片和base64编码字符串 互相转换 import sun.misc.BASE64Decoder; import sun.misc.BASE64Encoder; import java.io.*; ...
- Base64编码字符串时数据量明显变大
那就是当把byte[]通过Convert.ToBase64String转换成Base64编码字符串时数据量明显变大 Base64编码的思想是是采用64个基本的ASCII码字符对数据进行重新编码.它将需 ...
- 将图片转化为base64编码字符串
pom依赖 <dependency> <groupId>org.ops4j.base</groupId> <artifactId>ops4j-base- ...
- ZUFE 1035 字符宽度编码(字符串)
Time Limit: 1 Sec Memory Limit: 128 MB Description 你的任务是编写一个程序实现简单的字符宽度编码方法.规则如下:将任何2~9个相同字符的序列编码成2 ...
随机推荐
- ASP.NET中Request.ApplicationPath、Request.FilePath、Request.Path、.Request.MapPath
1.Request.ApplicationPath->当前应用的目录 2.Request.FilePath->对应于iis的虚拟目录 如 URL http://mockte.com/1 ...
- mschedule 简单linux进程管理(树莓派)
树莓派是神奇的机器,CPU和内存都少的可怜,但体积小功耗低,在上面搞些动搞些西其实也挺有意思,挺好玩的.装的是pidara,基本服务没有精简多少,先cat一下CPU和RAM. [able@raspi ...
- 转: 如何用linux命令修改linux主机ip网关子网掩码
linux一般使用ifconfig命令修改linux主机的ip.网关或子网掩码. 1.命令格式: ifconfig [网络设备] [参数] 2.命令功能: ifconfig 命令用来查看和配置网络设备 ...
- 从Profile中窥探Unity的内存管理
刨根问底U3D---从Profile中窥探Unity的内存管理 这篇文章包含哪些内容 这篇文章从Unity的Profile组件入手,来探讨一下Unity在开发环境和正式环境中的内存使用发面的一些区别, ...
- jQuery的选择器中的通配符[id^='code'] 【转】
JQuery 1.选择器 (1)通配符: $("input[id^='code']");//id属性以code开始的所有input标签 $("input[id$='cod ...
- Java进阶代码
本文重在温习……不过初学以及进阶高手不可错过 1. public static void arraycopy(全小写)(object src,int srcPos,object dest,int d ...
- Ubiquitous Religions
http://poj.org/problem?id=2524 这道题就是并查集. #include<cstdio> #include<cstring> #include< ...
- poj 3308 Paratroopers
http://poj.org/problem?id=3308 #include <cstdio> #include <cstring> #include <algorit ...
- 【HDOJ】4403 A very hard Aoshu problem
HASH+暴力. /* 4403 */ #include <iostream> #include <cstdio> #include <cstring> #incl ...
- Niagara解决设备连接应用的软件框架平台技术。
Niagara 是Tridium公司所研发的设计用于解决设备连接应用的软件框架平台技术. Niagara是一种应用框架,或者说是软件框架,特别设计用于应对智能设备所带来的各种挑战,包括设备连接到企业级 ...