<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    weidagang2046的專欄

    物格而后知致
    隨筆 - 8, 文章 - 409, 評論 - 101, 引用 - 0
    數(shù)據(jù)加載中……

    VC++2005快速構(gòu)建安全的應(yīng)用程序

      一、 簡介

      微軟的Visual C++2005發(fā)布版本對于有志于輕松、迅速地編寫安全可靠的應(yīng)用程序的編程愛好者來說是正確地選擇。正如你所聽到的那樣,Visual C++中語言和庫的新特點使開發(fā)安全、可靠的應(yīng)用程序比以前更容易。它即提供了功能強大并且靈活的標(biāo)準(zhǔn)C++,又提供了適于.NET框架下編程的最強大的開發(fā)語言。

      本文中,我主要探討Visual C++2005發(fā)布版本中部分語言和庫的新特色,無論是對于教學(xué)項目還是大的應(yīng)用工程,這都將幫助你在編寫安全可靠的代碼時提高工作效率。

      二、C運行時庫的安全特點

      如果你正在使用Visual C++創(chuàng)建使用C運行時庫的應(yīng)用程序,你將非常欣慰地了解到現(xiàn)在你所依賴的許多庫函數(shù)都有了更安全的版本。對于需要一個或多個緩沖作為輸入的函數(shù)來說,已經(jīng)添加了長度參數(shù),以此讓函數(shù)來確信不會超越緩沖的邊界。現(xiàn)在更多的函數(shù)開始對參數(shù)進行合法性檢查,必要時將調(diào)用無效參數(shù)處理器。讓我們來看一些簡單的例子:

      C運行時庫中最不可靠的是gets函數(shù),它從標(biāo)準(zhǔn)輸入中讀取一行。思考下面的一個簡單的例子:

    char buffer[10] = { 0 };
    gets(buffer);

      第一行代碼聲明了一個緩沖變量,并將緩沖區(qū)中的字符初始化設(shè)置為0。為避免意外情況發(fā)生將變量初始化為一個眾所周知的值是一個非常好的主意。緊接著,看似清白無辜的gets函數(shù)從標(biāo)準(zhǔn)的輸入流中讀取一行并且寫入到buffer緩沖區(qū)內(nèi)。這有什么錯誤嗎?對于函數(shù)來說C類型的數(shù)組不能實現(xiàn)值傳遞,而是傳遞了指向數(shù)組第一個元素的指針。所以在函數(shù)看來,char[ ]相當(dāng)于char*指針,并且是一個不附帶可以決定所指向的緩沖區(qū)大小尺寸的任何額外信息的原始指針。那么gets函數(shù)是怎么作的呢?它假設(shè)緩沖區(qū)無限大(UINT_MAX 是有精確尺寸的),并將持續(xù)地從輸入流中拷貝字符到緩沖區(qū)內(nèi)。攻擊者可以輕易地使用這個弱點,這種不廣為人知的類型錯誤被稱為緩沖溢出。

      很多最初的C運行時庫函數(shù)遭受同樣的與參數(shù)確認(rèn)有關(guān)的問題,并且現(xiàn)在因此受到抨擊。一定要牢記對于當(dāng)前所要寫的應(yīng)用程序來說,性能處于次要地位,我們現(xiàn)在生活在一個安全第一的世界。每一個受到批評的函數(shù)已經(jīng)被一個提供同樣函數(shù)功能,但添加了安全特點的函數(shù)所代替。當(dāng)然,根據(jù)你在已經(jīng)存在的代碼中所使用的舊的庫函數(shù)的多少,你可能需要花費一些時間來代碼更替到新的、更安全的版本。這些新的函數(shù)有一個_s后綴,例如,gets函數(shù)被gets_s函數(shù)代替;遭受抨擊的strcpy函數(shù)被strcpy_s函數(shù)代替。這里有一個例子:

    char buffer[10] = { 0 };
    gets_s(buffer, sizeof (buffer) / sizeof (buffer[0]));

      gets_s函數(shù)有一個額外的參數(shù),用來顯示可以寫入的最大字符數(shù)量,這里包括一個NULL終結(jié)符。我使用了sizeof操作符,它能決定數(shù)組的長度,因為編譯器在編譯時決定sizeof操作符返回的結(jié)果。記住,sizeof返回操作數(shù)的長度是以字節(jié)為單位的,所以用數(shù)組長度來除以數(shù)組中第一個元素的長度將返回數(shù)組中元素的個數(shù)。這種簡單的方法可以移植到Unicode編碼下使用_getws_s的情況,這個函數(shù)也需要得知以字節(jié)為單位的緩沖區(qū)長度。

      正如我所提到的,另外一個接受安全檢查的常用函數(shù)strcpy函數(shù),就象gets函數(shù)一樣,它沒有方法來保證有效的緩沖區(qū)尺寸,所以它只能假定緩沖足夠大來容納要拷貝的字符串。在程序運行時,這將導(dǎo)致不可預(yù)料的行為,正如我所提到的,為了安全需要避免這些不可預(yù)料的行為,這有一個使用安全的strcpy_s函數(shù)的例子。

    char source[] = Hello world!;
    char destination[20] = { 0 };
    strcpy_s(destination, sizeof (destination) / sizeof (destination[0]), source);

      有很多原因來喜歡這個新的strcpy_s函數(shù)。最明顯的區(qū)別是的額外的、以字節(jié)為單位的參數(shù),它用來確認(rèn)緩沖區(qū)大小。這允許strcpy_s函數(shù)可以進行運行時檢查,以確定寫入的字符沒有超過目標(biāo)緩沖區(qū)的邊界。還有一些其它的檢查方法來確定參數(shù)的有效性。在調(diào)試版本中這些檢測方法,包括顯示調(diào)試報告的斷言(assertions)方法,如果它們的條件沒有滿足,它們將顯示調(diào)試報告。無論是調(diào)試還是發(fā)行版本,如果一個特定的條件沒有得到滿足,一個無效的參數(shù)管理器將被調(diào)用,它默認(rèn)的行為是拋出一個訪問沖突來終止應(yīng)用程序。這非常好的實現(xiàn)了讓你的應(yīng)用程序持續(xù)運行,而不會產(chǎn)生不可預(yù)期的結(jié)果。當(dāng)然,這種情況完全可以通過確保類似于strcpy_s的函數(shù)不調(diào)用無效參數(shù)來避免。

      前一個例子可以通過新的_countof宏來進一步簡化,這個宏移拋開了對有錯誤傾向的sizeof操作符的需要。_countof宏返回C類型數(shù)組的元素數(shù)量。這個宏本身對應(yīng)了一個模版,如果傳遞一個原始指針的話,它將無法通過編譯。這有一個例子:

    strcpy_s(destination, _countof(destination), source);



    三、使用C++標(biāo)準(zhǔn)庫

      已經(jīng)看了C運行時庫新增強的安全特性,讓我們來看一下如何使用C++標(biāo)準(zhǔn)庫來進一步減少你的代碼中的相似錯誤。

      當(dāng)你從C運行時庫轉(zhuǎn)向C++的標(biāo)準(zhǔn)庫,讓你從C++開始受益的一個最有效的方法是使用庫中的矢量類(Vector class)。矢量類是C++標(biāo)準(zhǔn)庫中的一個模仿一維T數(shù)組的容器類,這里T可以是事實上的任何類型,你的代碼中使用緩沖區(qū)的地方都可以用矢量對象來代替。讓我們來考慮上一節(jié)的例子,第一個例子我們使用gets_s函數(shù)來從標(biāo)準(zhǔn)輸入中讀取一個行,考慮用下面的代碼代替:

    std::vector<char> buffer(10);
    gets_s(&buffer[0], buffer.size());

      最值得注意的一個區(qū)別是緩沖區(qū)變量現(xiàn)在是一個帶有可用方法和操作符的矢量對象,這個矢量對象初始化為10個字節(jié)長度,并且構(gòu)造函數(shù)將每個元素都初始化為0,表達(dá)式&buffer[0]用于得到矢量對象的第一個元素的地址,向期待一個簡單緩沖區(qū)的C函數(shù)傳遞一個矢量對象是一種正確的方法。與sizeof操作符不同的是,所有的容器的尺寸測量是基于元素的,而不是基與字節(jié)的。例如,矢量的size方法返回的是容器的元素數(shù)量。

      在上節(jié)的第二個例子里,我們使用strcpy_s函數(shù)從源緩沖區(qū)向目標(biāo)緩沖區(qū)拷貝字符。應(yīng)該清楚矢量對象是如何代替原始的C類型的數(shù)組,為了更好的說明這一點,讓我們來考慮另外一個非常有用的C++標(biāo)準(zhǔn)庫的容器。

      提供的basic_string類使得字符串在C++中可以作為正常的類型來操作。它提供了各種各樣的重載操作符,為C++程序開發(fā)人員提供了自然的編程模式。由于優(yōu)于strcopy_s及其它操作字符串的函數(shù),你應(yīng)該首選basic_string函數(shù)。basic_string以字節(jié)為單位的T類型容器。這里T是字符類型。C++標(biāo)準(zhǔn)類庫對于常用的字符類型提供類型定義。string和wstring中的元素類型分別被定義為char和wchar類型。下面的例子說明basic_string類是多么簡單和安全:

    std::string source = Hello world!;
    std::string destination = source;

      basic_string類也提供了你所希望的、常用的字符串操作的方法和操作符,象字符串聯(lián)合及子串的搜索。

      最后,C++標(biāo)準(zhǔn)庫提供了一個功能非常強大的I/O庫,用來安全、簡單地與標(biāo)準(zhǔn)輸入輸出、文件流進行交互操作。雖然對于gets_s函數(shù)來說使用矢量對象比使用C類型的數(shù)組更好,但你可以通過使用定義的basic_istream 和 basic_ostream類進一步簡化。實際上,你可以書寫簡單并且類型安全的代碼從流中來讀取包括字符串在內(nèi)的任何類型。

    std::string word;
    int number = 0;
    std::cin >> word >> number;
    std::cout << word
    << std::endl
    << number
    << std::endl;

      cin被定義成一個basic_istream流,從標(biāo)準(zhǔn)的輸入中提取字符類型的元素。wcin是用于wchar_t元素。另一方面,cout被定義為一個basic_ostream流,用于向標(biāo)準(zhǔn)的輸出流寫入操作。正如你能想象的,這種模式比起gets_s和puts函數(shù)來可以無限的擴展。但是,真正的價值是在于它非常難以產(chǎn)生讓你的應(yīng)用程序出現(xiàn)安全裂痕的錯誤。

      四、C++標(biāo)準(zhǔn)庫中的邊界檢查

      默認(rèn)情況,C++標(biāo)準(zhǔn)庫中大量的容器對象和迭代對象沒有提供邊界檢查。例如,矢量的下標(biāo)操作符通常是一個比較快,但有潛在的危險性的操作單獨元素的方法。如果你正在尋找得到確認(rèn)檢查的操作方法,你可以轉(zhuǎn)向at方法。安全性的增加是以犧牲性能為代價的。當(dāng)然,絕大情況下性能的降低是可以忽略不計的,但是對于性能要求第一位的代碼來說,這可能是非常有害的,思考一下下面的簡單函數(shù):

    void PrintAll(const std::vector<int>& numbers)
    {
     for (size_t index = 0; index < numbers.size(); ++index)
     {
      std::cout << numbers[index] << std::endl;
     }
    }
    void PrintN(const std::vector<int>& numbers, size_t index)
    {
     std::cout << numbers.at(index) << std::endl;
    }

      PrintAll函數(shù)使用了下標(biāo)操作符,因為索引由函數(shù)控制,并且可以確認(rèn)是安全的。另一方面,PrintN函數(shù)不能保證索引的有效性,所以它使用了更安全的at方法來代替。當(dāng)然,并不是所有的容器的存取操作都象這么簡潔明了。

      在保證C++標(biāo)準(zhǔn)庫的安全特性的同時,Visual C++2005繼續(xù)堅持并在很多情況下改進了C++標(biāo)準(zhǔn)庫的運行特性,同時提供了調(diào)節(jié)C++標(biāo)準(zhǔn)庫安全性的特色。一項受人歡迎的改進是在調(diào)試版本中添加了范圍檢查,這對你的發(fā)行版本性能并不構(gòu)成影響。但這確實幫助你在調(diào)試階段捕獲越界錯誤,甚至是使用傳統(tǒng)上不安全的下標(biāo)操作符的代碼。

      不安全的函數(shù),象vector的下標(biāo)操作算子,和其他的函數(shù),象它的front函數(shù),如果不恰當(dāng)?shù)恼{(diào)用,通常會導(dǎo)致不明確的行為。如果你幸運的話,它將很快導(dǎo)致一個存取沖突,這將使你的應(yīng)用程序崩潰。如果你不那么走運的話,它可能默默地持續(xù)運轉(zhuǎn)并導(dǎo)致不可預(yù)知的副效應(yīng),這將破壞數(shù)據(jù)并可能被進攻者利用。為了保護你的發(fā)行版本的應(yīng)用程序,Visual C++2005引入了_SECURE_SCL符號,用來給那些非安全的函數(shù)添加運行時檢查。象下面的代碼那樣在你的應(yīng)用程序中簡單地定義這個符號可以添加額外的實時檢查并阻止不確切的行為。

    #define _SECURE_SCL 1

      緊記定義這個符號對你的程序沖擊很大,大量的合法的,但是具有潛在非安全的操作將在編譯時將無法通過,以避免在運行時出現(xiàn)潛在BUG。思考下面的使用Copy運算的例子:

    std::copy(first, last, destination);

      其中,first和last是定義拷貝范圍的迭代參數(shù),destination是輸出迭代參數(shù),指示了目標(biāo)緩沖區(qū)的位置,這個位置用來拷貝范圍之內(nèi)的第一個元素。這里有一個危險是destination所對應(yīng)的目標(biāo)緩沖區(qū)或容器不足夠大,無法容納所要拷貝的元素。如果Destination是一個需要安全檢查的迭代參數(shù),類似的錯誤將被捕獲。但是,這僅僅是一個假設(shè)。如果destination是一個簡單的指針,將無法保證copy運算函數(shù)正確運轉(zhuǎn)。這時當(dāng)然會想到_SECURE_SCL符號來避免這一問題,這種情況下,代碼甚至是不能編譯,以此避免任何可能的運行時錯誤。就象你想象的那樣,這將需要重寫更完美有效的代碼。所以,這是一個更好的理由支持C++標(biāo)準(zhǔn)庫容器,避免使用C類型數(shù)組。


    五、編譯器的安全特點

      雖然對于Visual C++2005來說并不是全新的,但大量編譯器特色仍然需要了解。與以前版本的顯著區(qū)別是編譯器的安全檢查當(dāng)前默認(rèn)情況下是打開的,讓我們來看一下編譯器的特點及在某些情況下它們是如何阻止在某些情況下受到攻擊。

      Visual C++編譯器很久以前就開始提供嚴(yán)格的運行時安全檢查選項,包括棧校驗,下溢和上溢檢查以及未初始化變量的識別。這些運行時檢查由編譯器的/RTC選項來控制。雖然在早期的發(fā)展中捕獲錯誤非常有用,但是對于發(fā)布版本性能上的損失卻是不能接受的。微軟的Visual C++.NET引入了/GS編譯開關(guān),對于發(fā)行版本來說它添加了有限的運行時安全檢查。/GS開關(guān)在編譯開關(guān)中插入代碼,通過檢測函數(shù)的棧數(shù)據(jù)來檢測通常基于棧的緩沖溢出。如果發(fā)現(xiàn)問題,應(yīng)用程序?qū)⒈唤K止。為了減少運行時檢查對性能的影響,編譯器辨別哪個函數(shù)易于攻擊并且僅針對這些函數(shù)來進行安全檢查。安全檢查涉及到在函數(shù)的棧框架上增加一個cookie,在緩沖溢出的情況下它將被重寫。函數(shù)指令的前后都添加了匯編指令。在函數(shù)執(zhí)行以前,源自cookie模塊的函數(shù)cookie先執(zhí)行計算。當(dāng)函數(shù)結(jié)束但在棧空間被收回前,cookie的棧拷貝被檢索以判斷它是否被更改。如果cookie未被更改,函數(shù)結(jié)束并繼續(xù)執(zhí)行程序的下一步,如果cookie被更改了,一個安全錯誤句柄將被調(diào)用,它將結(jié)束應(yīng)用程序。
    為了在Visual C++ 2005發(fā)布版本中控制這些編譯選項,打開工程的屬性頁,單擊C/C++標(biāo)簽,在代碼發(fā)生屬性頁中,你將發(fā)現(xiàn)兩個屬性對應(yīng)于我剛剛描述的特點。Basic Runtime Checks屬性對應(yīng)于開發(fā)時/RTC編譯選項,在編譯版本中應(yīng)設(shè)置為BOTH。Buffer Security Check屬性相當(dāng)于編譯器的/GS選項,對于發(fā)布版本應(yīng)設(shè)置為YES。

      對于使用Visual C++ 2005的開發(fā)人員來說,這些編譯特點在默認(rèn)情況下打開,這意味著你可以確信編譯器正在盡其可能阻止你代碼中的漏洞。然而,這并不意味著我們可以完全不關(guān)心安全問題。開發(fā)人員需要繼續(xù)為正確的代碼而努力,并且要考慮各種不同的、可能發(fā)生的安全威脅。編譯器僅僅可以阻止部分類型的錯誤發(fā)生。

      要牢記這些編譯器提供的特殊的安全檢查僅適用于本地代碼,幸運的是,托管代碼很少犯此類的錯誤。這里甚至于有更好的消息,Visual C++ 2005引進了C++/CLI設(shè)計語言,它提供了.NET框架下最強有力的開發(fā)語言。

      六、新的C++編程語言

      Visual C++ 2005發(fā)布版本提供了C++/CLI設(shè)計語言的一流的實現(xiàn)。C++/CLI是為.NET設(shè)計的系統(tǒng)編程語言。相對于其他語言來說,它在創(chuàng)建和使用.NET模塊和匯編上有更多的控制。C++/CLI對于C++開發(fā)人員來說更精細(xì)和自然,無論你是否熟悉C++或.NET框架,你將發(fā)現(xiàn)使用C++書寫托管代碼是對ANSI C++自然文雅的擴展,學(xué)習(xí)起來非常容易。

      對于開發(fā)應(yīng)用程序來說,有許多強制性的原因讓你來選擇托管代碼而不是本地C++。兩個最重要的原因是安全性和效率。通用運行時語言(CLR)給你的代碼提供了一個安全的運行環(huán)境。作為一個程序開發(fā)人員,你不需要關(guān)心緩沖區(qū)溢出及因為你在使用前未初始化變量等問題。安全問題沒有完全消失,但是使用托管可以避免通常發(fā)生的一些錯誤。

      另外一個使用托管的原因是.NET框架下豐富的類庫。雖然標(biāo)準(zhǔn)C++庫更適合于C++類型編程,但是.NET框架包含了一個功能強大的函數(shù)庫,這是標(biāo)準(zhǔn)C++庫所無法比擬的。.NET框架包括很多有用的集合類、一個強大的數(shù)據(jù)操作庫、執(zhí)行很多流行的通訊協(xié)議的類,從SOCKETS到HTTP和網(wǎng)絡(luò)服務(wù)等等。雖然本地C++程序開發(fā)人員可以以各種形似使用這些服務(wù),但通過使用.NET框架獲取的生產(chǎn)力主要因為它的統(tǒng)一性和連貫性。無論你是用System::Net::Sockets還是用System::Web名字空間,你將面對同樣的類型,描述廣泛應(yīng)用的概念,例如流和字符串。這是.NET框架具有開發(fā)高效率的最主要的原因。這讓程序人員更快速地書寫更強有力的應(yīng)用程序,同時代碼更可靠。

      Visual C++ 2005自然地準(zhǔn)許你在一個工程中混合本地與托管代碼,你可以繼續(xù)使用已經(jīng)存在的本地函數(shù)及類的同時,開始使用越來越多的.NET框架下的類庫,甚至是寫你的托管類型。你可以將你的托管類型定義為一個引用類型或一個值類型,雖然Visual C++編譯器允許你為了方面選擇使用棧語法或是為了控制管理資源使用通常的作用域規(guī)則,但值類型在棧上而引用類型位于CLR的托管堆上。

      通過在你定義的class 和 struct 前添加ref來形成一個關(guān)鍵詞,定義了一個引用類型。獲取和釋放資源按通常的方式完成,通過使用構(gòu)造和析構(gòu)函數(shù),正如這里說明的:

    ref class Connection
    {
     public: Connection(String^ server)
     {
      Server = server;
      Console::WriteLine(Aquiring connection to server.);
     }
     ~Connection()
     {
      Console::WriteLine(Disconnecting from server.);
     }
     property String^ Server;
    };

      編譯器負(fù)責(zé)Connection引用類型的IDisposable接口的實現(xiàn),所以使用類似C#、Visual Basic.NET的開發(fā)人員可以使用任何對他們可用的資源管理結(jié)構(gòu)。對于C++開發(fā)人員,有著與以前一樣的選擇。為了簡化資源管理,并書寫異常安全代碼,你可以簡單地在棧上聲明一個Connection對象。當(dāng)一個對象超過其作用范圍后,執(zhí)行Dispose方法的析構(gòu)函數(shù)將被調(diào)用。下面是一個例子:

    void UseStackConnection()
    {
     Connection connection(sample.kennyandkarin.com);
     Console::WriteLine(Connection to {0} established!,
     connection.Server);
    }

      這個例子中,通過在函數(shù)返回調(diào)用前調(diào)用析構(gòu)函數(shù)來關(guān)閉這個Connection,這正如你在C++希望的那樣。如果你希望自己控制對象的生命期,僅僅需要使用gcnew這個關(guān)鍵詞來獲取connection對象的句柄。這個指針可以看作通常的指針(不含有通常的缺陷),并且這個對象的析構(gòu)函數(shù)可以簡單地通過delete操作來調(diào)用。這個例子代碼如下 :

    void UseHeapConnection()
    {
     Connection^ connection = gcnew Connection(sample.kennyandkarin.com);
     try
     {
      Console::WriteLine(Connection to {0} established!,
      connection->Server);
     }
     finally
     {
      delete connection;
     }
    }

      正如你所看到的,從本地C++到托管代碼,Visual C++ 2005帶來了簡單靈活的資源管理方式,可以書寫強壯的資源管理代碼對于書寫正確、安全的代碼是非常重要的。

      七、小結(jié)

      無論是對于一個小的程序還是一個大的應(yīng)用,Visual C++ 2005發(fā)布版本都是一個功能強大的開發(fā)工具,C運行時庫和C++標(biāo)準(zhǔn)庫提供了一個強大的工具集,來發(fā)布功能強大的、強壯的本地應(yīng)用程序,同時,對用C++書寫托管代碼有著一流的支持,Visual C++ 2005在微軟的Windows開發(fā)平臺上是獨一無二的強大的開發(fā)工具。

    from: http://www.97t.org/ArticleView/2005-9-4/Article_View_241.Htm

    posted on 2006-11-09 23:24 weidagang2046 閱讀(352) 評論(0)  編輯  收藏 所屬分類: C/C++

    主站蜘蛛池模板: 免费精品视频在线| 亚洲精品久久无码av片俺去也 | 亚洲综合无码精品一区二区三区| 亚洲色无码专区一区| 国产精品无码免费播放| 亚洲夂夂婷婷色拍WW47| 四虎影视大全免费入口| 亚洲国产AV无码一区二区三区| 成年女性特黄午夜视频免费看| 亚洲色大成网站www| 狼友av永久网站免费观看| 亚洲a无码综合a国产av中文| 四虎在线播放免费永久视频 | AV激情亚洲男人的天堂国语| 国产jizzjizz视频全部免费| 看成年女人免费午夜视频| 免费v片在线观看品善网| 一个人晚上在线观看的免费视频| 亚洲精品国自产拍在线观看| 九九99热免费最新版| 亚洲第一成年男人的天堂| 久久久久久精品免费免费自慰| 亚洲中字慕日产2020| 日韩电影免费在线| 一级成人毛片免费观看| 亚洲国产成人精品不卡青青草原| 99视频在线免费看| 亚洲精品国产suv一区88| 亚洲片一区二区三区| 久久久久久成人毛片免费看| 亚洲人成网站在线观看播放青青| 在线观看人成网站深夜免费| 五月天婷婷精品免费视频| 久久久久久a亚洲欧洲aⅴ| 久草免费在线观看视频| 黄页网址在线免费观看| 亚洲韩国—中文字幕| 狠狠久久永久免费观看| 免费91最新地址永久入口| 亚洲色欲色欱wwW在线| 亚洲国产精品一区二区久久hs|