<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
    數據加載中……

    VC++2005快速構建安全的應用程序

      一、 簡介

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

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

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

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

      C運行時庫中最不可靠的是gets函數,它從標準輸入中讀取一行。思考下面的一個簡單的例子:

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

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

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

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

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

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

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

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

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

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



    三、使用C++標準庫

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

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

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

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

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

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

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

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

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

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

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

      四、C++標準庫中的邊界檢查

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

    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函數使用了下標操作符,因為索引由函數控制,并且可以確認是安全的。另一方面,PrintN函數不能保證索引的有效性,所以它使用了更安全的at方法來代替。當然,并不是所有的容器的存取操作都象這么簡潔明了。

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

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

    #define _SECURE_SCL 1

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

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

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


    五、編譯器的安全特點

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

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

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

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

      六、新的C++編程語言

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

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

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

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

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

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

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

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

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

    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帶來了簡單靈活的資源管理方式,可以書寫強壯的資源管理代碼對于書寫正確、安全的代碼是非常重要的。

      七、小結

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

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

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

    主站蜘蛛池模板: 亚洲sss综合天堂久久久| 亚洲视频一区在线播放| 日本系列1页亚洲系列| 成人午夜免费福利| 一区二区亚洲精品精华液| 在人线av无码免费高潮喷水| 亚洲欧洲视频在线观看| 日韩免费一区二区三区在线 | 久久影视国产亚洲| 中文字幕永久免费视频| 亚洲综合色成在线播放| a在线观看免费视频| 亚洲欧洲精品无码AV| 99精品视频免费观看| 亚洲一区动漫卡通在线播放| 毛片高清视频在线看免费观看| 亚洲精品天堂成人片AV在线播放| 国产在线播放免费| 国产免费一区二区三区免费视频| 国产亚洲综合成人91精品| 91香蕉国产线观看免费全集| 亚洲高清有码中文字| 免费观看国产精品| av永久免费网站在线观看 | 久久aⅴ免费观看| 亚洲国产精品xo在线观看| 免费视频淫片aa毛片| 一区在线免费观看| 亚洲美女视频一区| 国产成人在线免费观看| 18禁超污无遮挡无码免费网站| 亚洲av片不卡无码久久| 亚洲高清无码在线观看| 无码av免费一区二区三区| 亚洲一卡2卡3卡4卡5卡6卡| 亚洲色欲色欲www在线丝| 精品久久久久久久久免费影院| 高清免费久久午夜精品| 久久精品国产亚洲av麻豆色欲| 免费亚洲视频在线观看| 一级特黄aa毛片免费观看|