程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> C/C++安全編碼-字符串,編碼字符串

C/C++安全編碼-字符串,編碼字符串

編輯:C++入門知識

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語言 輸入字符串輸出對應字母的ASCII編碼 加密

#include<stdio.h>
#include<string.h>
char pass[101];
int main()
{
int i;
char s[101],key[101];
puts("設定密碼:");
scanf("%s",pass);
puts("輸入源字符串:");
scanf("%s",s);
puts("輸入密碼:");
scanf("%s",key);
while(strcmp(pass,key))
{
/*for(i=0;i<strlen(s);i++)
printf("%d ",s[i]+4);
puts("");*/
puts("密碼錯誤!重新輸入:");
scanf("%s",key);
}
puts(s);
for(i=0;i<strlen(s);i++)
printf("%d ",s[i]);
puts("");
}
 

弱弱的問一句,C語言可以不可以實現字符串的編碼格式轉換 GB2312toUTF-8?

其實 linux 和 windows 的系統函數都是C函數,並且提供了GB2312toUTF-8的函數,所以C語言是可以實現轉碼的。以下是windows的例子:int num = ::MultiByteToWideChar(CP_ACP, 0, "你好", -1, NULL, 0);wchar_t* m_arrayShort = new wchar_t[num];::MultiByteToWideChar(CP_ACP, 0, "你好", -1, m_arrayShort, num); int len = ::WideCharToMultiByte (CP_UTF8, 0, (LPCWSTR)m_arrayShort, num, 0, 0, NULL, NULL);char *tmpPT = new char[len+1];::WideCharToMultiByte(CP_UTF8, 0, (LPCWSTR)m_arrayShort, num, tmpPT, len, NULL, NULL);tmpPT[len] = 0;
 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved