共用方式為



2017 年 12 月

第 32 卷,第 12 期

本文章是由機器翻譯。

C++ - 堆疊式緩衝區保護的 Visual C++ 支援

Hadi Brais |2017 年 12 月

當軟體執行某個動作,它不應該執行根據其功能的規格時,它具有稱為有缺失或 bug。該規格中的規則,決定當存取和修改資料和其他資源應該允許共同構成安全性原則。安全性原則基本上定義是什麼意思是安全的該軟體,並特定脫離時應視為安全性問題,而非只是另一個的 bug。

提供各種不同的威脅,從世界各地,安全性是更重要的是比以往現今和,因此,必須是軟體開發生命週期 (SDL) 中不可或缺的一部分。這包括選項如何儲存資料,C/c + + 執行階段 Api 使用,例如,以及哪些工具可以協助讓軟體更為安全。C + + 核心指導方針 (bit.ly/1LoeSRB) 撰寫正確、 可維護的程式碼本質上可幫助。此外,Visual c + + 編譯器可提供許多的安全性功能,可輕易存取透過編譯器參數。這些都能夠分類為其中一個靜態或動態安全性分析。靜態的安全性檢查的範例包括使用 /Wall 和分析交換器和 c + + 核心指導方針西洋棋 /。這些檢查會以靜態方式執行,並不會影響所產生的程式碼,但它們會增加編譯時間。相反地,動態檢查是由編譯器或連結器插入發出可執行檔的二進位檔中。特別是一個動態安全性分析選項,也就是 /GS,以防止堆疊型緩衝區溢位,我將討論在本文中。將說明該交換器的電源已開啟的程式碼的轉換方式且時可以或不能保護您的程式碼。使用 Visual Studio 社群 2017年。

您可能會懷疑,為何不只開啟所有這些編譯器參數,然後再利用它完成。一般情況下,您應該採用所有建議的參數,不論是否您了解其運作方式。  不過,了解特定技術的運作方式的詳細資料可讓您判斷可能對您的程式碼以及如何進一步讓影響使用它。例如,考慮緩衝區溢位。編譯器會提供參數,以處理這類瑕疵品,但它會使用強制執行程式時,緩衝區溢位偵測到損毀偵測機制。不會改善安全性?其實全得視情況而定。首先,當所有緩衝區溢位錯誤時,並非所有都安全性弱點,因此不一定表示已發生入侵。即使它,損毀可能會有已完成所觸發偵測機制的時間。此外,根據您的應用程式的設計方式突然損毀的程式可能不適合,因為它無法以本身是阻絕服務 (DoS) 安全性漏洞或導致可能較差的情況涉及資料遺失或損毀。  會說明本文中,只是合理的做法是以這種損毀,而不是停用或變更保護機制,讓應用程式具有恢復功能。

我在編譯器最佳化 MDSN 雜誌上書寫的發行項的數字 (您可以找到第一個位於msdn.com/magazine/dn904673)。目標主要是以改善執行時間。安全性也可以檢視作為目標的編譯器轉換。也就是說,而不被最佳化執行時,安全性會最佳化減少潛在安全性問題數目。此檢視方塊很有用,因為建議,當您指定多個編譯器參數,以改善執行時間和安全性時,編譯器可能會有多個可能有衝突的目標。在此情況下,它必須以某種方式之間取得平衡,或排定這些目標的優先順序。我將討論 /GS 對您的程式碼,在某些方面的影響特別速度、 記憶體耗用量,以及可執行檔的大小。這是要了解這些參數執行您的程式碼的另一個原因。

在下一步] 區段中,我會提供控制流程攻擊,具有特別強調堆疊緩衝區溢位的簡介。我將討論如何發生,則攻擊者可以利用它們。然後會搜尋在詳細 /GS 會影響您的程式碼和要它可以減輕此類攻擊的程度。最後,我將示範如何使用 BinSkim 二進位的靜態分析工具來執行數項重要的驗證檢查,指定可執行檔的二進位檔上,而不需要的原始程式碼。

控制流程攻擊

緩衝區是記憶體的用來暫時儲存資料處理區塊。從執行階段堆積,執行緒堆疊,直接使用 Windows VirtualAlloc 應用程式開發介面,或指定為全域變數,就可以配置的緩衝區。緩衝區可以從堆積配置額外執行階段使用 C 記憶體配置函式 (例如 malloc) 或 c + + 的新運算子。可以從使用自動陣列變數或 _alloca 函式的堆疊配置緩衝區。最小緩衝區大小可以是零個位元組,最大的大小取決於最大可用區塊的大小。

C 和 c + + 的程式語言真正區分它們與其他語言,例如 C# 中,兩個特定功能包括:

  • 您可以執行任意的指標算術。
  • 您已成功 anytime 取值任何指標,只要它會指向配置的記憶體 (從觀點來看的 os),但如果它不會指向其擁有的記憶體,應用程式的行為可能會未定義。

這些功能的語言功能非常強大,但它們在同一時間造成絕佳威脅。特別是,有可以用來存取或緩衝區內容的反覆查看的指標可能會不慎或惡意修改,讓它所指向的緩衝區來讀取或寫入相鄰或其他記憶體位置的界限之外。寫入緩衝區的最大的位址超出稱為緩衝區溢位。寫入緩衝區 (這是緩衝區的位址) 的最小的位址呼叫之前緩衝區反向溢位。

堆疊型緩衝區溢位的弱點可能會發現最近的軟體 (將不會命名) 非常受歡迎的片段中。這會產生不安全地,使用 sprintf 函式,如下列程式碼所示:

sprintf(buffer, "A long format string %d, %d", var1, var2);

已從執行緒堆疊配置的緩衝區以及固定的大小。不過,要寫入緩衝區的字串大小取決於表示兩個指定的整數所需的字元數。緩衝區的大小不足以容納最大可能字串,指定大整數時,導致緩衝區溢位。當發生溢位時,相鄰的記憶體位置堆疊中更上層損毀的原因。

若要示範為什麼這是很危險,請考慮從堆疊配置的緩衝區會通常位於何處 standard x86 呼叫慣例和列入考量編譯器最佳化,以根據宣告的函式的堆疊框架示圖 1

一般 x86 堆疊框架
圖 1 一般 x86 堆疊框架

首先,呼叫端將推入至以特定順序堆疊暫存器未傳遞任何引數。接著,x86 呼叫指令推送至堆疊的傳回位址,並跳至被呼叫端中的第一個指令。如果框架指標省略 (FPO) 最佳化不會發生,被呼叫端會將目前的框架指標推送至堆疊。如果被呼叫端使用尚未被最佳化改變了任何例外狀況處理建構,例外狀況處理框架會接著將放置至堆疊。該範圍內包含的指標和例外狀況處理常式中被呼叫端定義的其他資訊。非靜態區域變數,您尚未被最佳化改變了,無法保留在暫存器中或,spill 從暫存器會從以特定順序堆疊配置。接下來,任何被呼叫端儲存的暫存器由被呼叫端必須儲存在堆疊上。最後,使用 _alloca 配置動態調整大小的緩衝區會放置在底部的堆疊框架。

任何資料的項目在堆疊上可能會有特定的對齊需求,因此填補區塊可能配置視需要而定。被呼叫端的設定 (除了引數) 的堆疊框架中的程式碼片段會呼叫的初構。要傳回其呼叫端函式時,呼叫終解程式碼的部分是負責解除配置的堆疊框架時,最多並包括傳回地址。

在 x86/x64 和 ARM 呼叫慣例之間的主要差異是傳回地址和框架指標會保留在專用的暫存器 ARM 而不是在堆疊上。然而,堆疊緩衝區超出範圍的存取並構成嚴重的安全性問題在 ARM 上因為堆疊上的其他值可能會是指標。

堆疊緩衝區溢位 (超出上限的緩衝區寫入) 可能會覆寫任何會儲存在緩衝區上述的程式碼或資料指標。(寫入緩衝區的下限下方) 堆疊緩衝區反向溢位可能會覆寫被呼叫端儲存暫存器,也可能是程式碼或資料指標的值。任意超出範圍寫入將會導致應用程式損毀或未定義的方式運作。不過,惡意的攻擊可讓攻擊者控制應用程式或整個系統的執行。這可藉由覆寫 (例如地址) 的程式碼指標,使其指向一段程式碼執行攻擊者的意圖。

GuardStack (GS)

超出範圍降低堆疊為基礎的存取,您可以手動加入必要框住檢查 (新增 if 陳述式來檢查指定的指標的範圍內) 或使用執行這些檢查 (例如,snprintf) API。不過,弱點可能仍然保存不同的原因,例如不正確的整數算術或型別轉換用來判斷緩衝區的界限,或是執行繫結檢查。因此,動態的安全防護功能機制,以避免或降低的可能性是漏洞的必要的。

一般的防護技術包括隨機化的位址空間,並使用非可執行檔堆疊。專用的防護技術可以根據目標是否以避免超出範圍分類存取發生擷取之前發生,或超出範圍偵測存取在某個時間點之後發生。兩者都是可行的但防止增加可觀的效能負擔。

Visual c + + 編譯器提供兩種偵測機制,類似,但有不同用途和不同的效能成本。第一種機制是使用 /rtc 參數可啟用的執行階段錯誤檢查的一部分。第二個是 GuardStack (稱為緩衝區安全性檢查文件中) 和安全性檢查在 Visual Studio 中,可以使用 /GS 參數來啟用它。

與 /rtc,編譯器會配置其他小的記憶體區塊從堆疊以交錯方式使堆疊上的每個本機變數 sandwiched 兩個這類區塊之間。每個其他區塊會填入一個特殊值 (目前,0xCC)。這是由被呼叫端初構中處理。終解中,在執行階段函式呼叫來檢查是否任何這些區塊已損毀,並回報可能的緩衝區溢位或反向溢位。此偵測機制將效能和堆疊空間方面的一些額外負荷,但它的設計是要用於偵錯,並確保程式的正確性,不只是做為防護功能。

GuardStack,相反地,設計用來更低額外負荷,並做為防護功能,可以在生產環境中,可能是惡意環境運作。因此 /rtc 應該用於偵錯組建,GuardStack 適用於這兩個組建。此外,編譯器不允許您使用 /rtc 編譯器最佳化 GuardStack 相容,而且不會干擾編譯器最佳化。根據預設,同時會啟用在偵錯組態只 GuardStack 啟用在 Visual c + + 專案的 [發行] 組態時。在本文中,我只會詳細討論 GuardStack。

呼叫堆疊看起來一般 x86 時 GuardStack 已啟用,如中所示圖 2

一般 x86 使用 GuardStack 保護堆疊框架 (/ GS)
圖 2 有一般 x86 使用 GuardStack 保護的堆疊框架 (/ GS)

有三個差異相較於所示的堆疊配置圖 1。首先,為特殊值,稱為 cookie 或 canary,會配置正上方的本機變數。更容易呈現溢位的第二個、 本機變數配置優先於所有其他的區域變數。第三,某些特別容易發生緩衝區溢位的引數會複製到低於本機變數的區域。當然,若要進行這些變更,不同的初構和終解使用,現在,我將討論。

受保護的函式的初構大約會包含下列的其他指示,在 x64 上:

sub         rsp,8h
mov         rax,qword ptr [__security_cookie] 
xor         rax,rbp 
mov         qword ptr [rbp],rax

會從堆疊配置額外的 8 個位元組,並初始化為值的全域變數 XOR RBP 中保存的值與登錄 __security_cookie 複本。當指定 /GS 時,編譯器會自動連結從 gs_cookie.c 原始程式檔所建立的物件檔案。這個檔案 __security_cookie 做為型別 uintptr_t,x64 和 x86、 64 位元或 32 位元全域變數會分別定義。因此,使用 /GS 編譯每個可攜式執行檔 (PE) 映像會包含初構和終該映像的函式的使用該變數的單一定義。在 x86、 程式碼會是相同的 32 位元暫存器和 cookie 之外使用。

使用安全性 cookie 背後的基本概念是偵測,只在函式傳回前,是否 cookie 的值有不同的參考 cookie (全域變數)。這表示可能發生緩衝區溢位入侵嘗試或只是無害的 bug 所造成。相當重要的 cookie 具有非常高的熵進行很難被攻擊者猜中。如果攻擊者可以判斷特定的堆疊框架中使用的 cookie,GuardStack 就會失敗。要探討更多有關哪些 GuardStack 可以和無法稍後再進行這一節。

參考 cookie 映像,就會發出編譯器時指定任意的常數值。因此,它必須仔細初始化,基本上會執行任何程式碼。最新版本的 Windows 可辨識的 GuardStack,並將在載入時間初始化的高熵值的 cookie。/GS 啟用時,未在 EXE 或 DLL 的進入點的第一件事是藉由呼叫 __security_init_cookie gs_support.c 中定義和宣告中 process.h 初始化 cookie。如果它尚未適當地初始化 Windows 載入器,此函式會初始化映像的 cookie。

請注意,沒有與 RBP XOR'ing,只遺漏參考 cookie 在任何時間點 (使用超出範圍讀取,例如) 的執行期間足夠破壞 GuardStack。與 RBP XOR'ing 可讓您有效率地產生不同的 cookie,攻擊者將需要知道參考 cookie 和 RBP,找出一個堆疊框架的 cookie。RBP 單獨使用時,不保證有高熵,因為它的值取決於編譯器最佳化程式碼,堆疊使用的空間為止,與執行位址空間配置隨機載入 (ASLR),如果啟用 [隨機的最佳化。

受保護的函式終解大約會包含下列的其他指示,在 x64 上:

mov         rcx,qword ptr [rbp]
xor         rcx,rbp 
call        __security_check_cookie
add         esp,8h

首先,在堆疊上的 cookie 不 XOR 必須產生應該要當成參考的 cookie 相同的值。編譯器會發出指示來確保 RBP 的值會用於初構和終解後相同的 (除非由於某種原因而損毀)。

中 vcruntime.h,宣告 __security_check_cookie 函式,編譯器連結,其目的是要驗證是在堆疊的 cookie。這是主要是藉由比較參考 cookie 的 cookie。如果檢查失敗,程式碼會跳到定義於 gs_report.c __report_gsfailure 函式。在 Windows 8 和更新版本中,函式呼叫 __fastfail 結束處理程序。在其他系統中,函式會藉由移除任何潛在的處理常式後呼叫 UnhandledExceptionFilter 終止處理序。無論如何,錯誤會記錄由 Windows 錯誤報告 (WER) 和它包含的資訊中的堆疊框架的安全性 cookie 損毀。

當 /GS 引進 Visual c + + 2002年中時,您可以藉由指定的回呼函式來覆寫失敗的堆疊 cookie 檢查的行為。不過,因為堆疊是未定義的狀態,因為某些程式碼已經有執行之前偵測溢位,所以沒有幾乎會提供可靠地即可在該點。因此,開始使用 Visual c + + 2005年更新的版本會刪除這項功能。

GuardStack 的額外負荷

為了減少額外負荷,編譯器會考慮容易遭受這些函式會受到保護。不同版本的編譯器可能會使用不同未記載的演算法來決定是否函式不會有弱點,但一般而言,如果函式定義陣列或大型資料結構,並取得這類物件的指標,很可能將會是視為易受攻擊。您可以指定藉由套用 __declspec(safebuffers) 至其宣告,都不受到特定函式。不過,這個關鍵字會被忽略套用至函式的內嵌在受保護的函式,或受保護的函式是內嵌在其中。您也可以強制編譯器將保護一或多個使用 strict_gs_check pragma 的函式。安全性開發週期 (SDL) 檢查時,使用 /sdl 啟用指定嚴格 GuardStack 所有原始程式檔和其他動態安全性檢查。

GuardStack 會將有弱點的參數複製到下列本機變數的安全位置中,以便如果溢位,就會發生,將更難以了這些參數會損毀。為指標或 c + + 參考參數可能會成為弱點的參數。/GS 文件,如需詳細資訊,請參閱。

我所進行實驗使用 C/c + + 實際執行應用程式,以判斷效能和映像大小相關的額外負荷的數的字。我已套用 strict_gs_check 上所有原始程式檔,因此結果無關的哪些編譯器認為容易受到函式 (我 refrained 使用 /sdl,因為它可讓其他安全性檢查,它有自己的額外費用)。我收到的最大的效能負擔是 1.4%,而最大的影像大小負擔是 0.4 百分比。最糟的狀況,就會發生在程式中花費在大部分的時間呼叫受保護的函式執行非常少的工作。設計良好的實際程式不會呈現這類行為。請注意也 GuardStack 帶來的潛在非微不足道的堆疊空間負擔。

GuardStack 的效用

GuardStack 旨在減輕弱點的特定類型,也就是堆疊緩衝區溢位。更重要的是,使用 GuardStack 單獨使用針對此漏洞進行可能不會提供較高程度的保護因為有攻擊者移周圍的方式:

  • 偵測到損毀的 cookie 才會發生此函數會傳回。許多程式碼可能取得 cookie 已損毀的時間之間偵測到損毀的時間執行。該程式碼可能會使用從堆疊的上方或下方 cookie,其他的已覆寫值。這會建立有機會讓攻擊者取得的應用程式執行 (部分) 的控制權。在此情況下,偵測可能甚至完全不接受的位置。
  • 不覆寫 cookie 可能仍然會發生緩衝區溢位。會溢位最具危險性的情況下使用 _alloca 配置的緩衝區。即使受保護的引數和被呼叫端儲存的暫存器可以覆寫在此情況下。
  • 它可能遺漏的某些 cookie 超出範圍使用記憶體讀取。因為不同的映像檔使用不同的參考 cookie,因為 cookie RBP XOR 一樣,它可以是讓攻擊者更具有挑戰性的遺漏 cookie 使用。不過,Windows 子系統 Linux (WSL) 可能會引進另一種方式流失 cookie。WSL 提供的 Linux 系統呼叫的分岔模擬所建立的新處理序的重複項目父處理序。如果應用程式正在攻擊分岔新處理序來處理內送的用戶端要求時,惡意用戶端可以發出要求,以判斷安全性 cookie 的值相當小的數目。
  • 已經建議一些技術,來猜出在某些情況下的映像的參考 cookie。我不知道任何攻擊成功的實際所在參考 cookie 被猜到,但其成功的機率不夠小,無法關閉它。與 RBP XOR'ing 加入防禦機制以防止此類攻擊的另一個非常重要的圖層。
  • GuardStack 減輕的弱點可能會造成潛在的弱點可能會不同,特別是,DoS,資料遺失。偵測到損毀的 cookie 時,會突然終止應用程式。伺服器應用程式,攻擊者可能會造成伺服器當機,可能遺失或損毀寶貴的資料。

因此,務必您第一次盡量撰寫正確且安全的程式碼靜態分析工具的協助。然後,遵循深度防禦策略中,採用 GuardStack 和 Visual c + + (其中有許多預設會啟用 「 版本 」 組建中) 所提供您在出貨的程式碼中其他動態防護功能。

/GS /ENTRY 與

預設進入點函式 (* CRTStartup) 指定編譯器,當您編譯來產生 EXE 或 DLL 檔不在順序中的四個項目: 初始化參考的安全性 cookie。初始化的 C/c + + 執行階段。呼叫您的應用程式; 的主要功能和終止應用程式。您可以使用 /ENTRY 連結器參數來指定的自訂項目點。不過,結合 /GS 效果的自訂項目點可能會導致有趣的案例。

自訂進入點和其所呼叫的任何函式是保護的候選項目。若 Windows 載入器適當地初始化 cookie,則任何受保護的函式會使用副本在其初構和終相同參考 cookie。因此會不發生任何問題。

如果 Windows 未適當地初始化 cookie 和自訂的項目點並第一件事就是呼叫 __security_init_cookie,所有受保護的函式會使用正確的參考 cookie 除外的進入點。前文提過,一份參考 cookie 進行終解中。因此,如果進入點正常地傳回,cookie 將簽入其終解檢查將會失敗,造成誤判。若要避免這個問題,您應該呼叫的函式而不是傳回正常終止的程式 (例如結束)。

如果 Windows 未適當地初始化 cookie,並進入點未呼叫 __security_init_cookie,所有受保護的函式會使用預設參考 cookie。幸運的是,由於此 cookie 是 RBP XOR 一樣,使用 cookie 的 entropy 不是零。因此您仍可以獲得某些保護,尤其是使用 ASLR。不過,建議由呼叫 __security_init_cookie 正確地初始化參考 cookie。

若要確認 GuardStack 使用 BinSkim

BinSkim 是二進位淺色、 靜態分析工具,用來驗證某些使用給定的 PE 二進位檔中的安全性功能的使用方式的正確性。BinSkim 支援的其中一項特定功能是 GuardStack。BinSkim 是開放原始碼 (github.com/Microsoft/binskim) 下 MIT 授權,完全以 C# 撰寫。它支援 x86、 x64 和 ARM Windows 二進位檔案會編譯以最新版 Visual c + + (2013年 +)。您可以使用它當做獨立的工具,或更多有趣的是,包括 (的一部分) 在您的程式碼。比方說,如果您有支援 PE 外掛程式的應用程式,您可以使用 BinSkim 來驗證外掛程式利用建議的安全性功能,並拒絕否則載入它。如何使用 BinSkim 當做獨立的工具,我將討論這一節中。

GuardStack 是而言,該工具會確認指定的二進位檔符合下列四個規則:

  • EnableStackProtection:檢查對應會儲存在相關聯的 PDB 檔案的旗標。如果找不到旗標,此規則將會失敗。否則,它會傳遞。
  • InitializeStackProtection:若要尋找 __security_init_cookie 函式和 __security_check_cookie 相關聯的 PDB 檔案中所定義,會逐一查看全域函式的清單。如果找不到兩者,此工具會考慮 /GS 未啟用。在此情況下,EnableStackProtection 應該會失敗。如果未定義 __security_init_cookie,規則就會失敗。否則,它會傳遞。
  • DoNotModifyStackProtectionCookie:查閱參考 cookie 使用的映像的載入組態資料的位置。如果找不到位置,此規則將會失敗。如果載入組態資料表示,cookie 定義,但其位移無效,此規則將會失敗。否則,此規則會傳遞。
  • DoNotDisableStackProtectionForFunctions:若要判斷是否有與 __declspec(safebuffers) 屬性對其套用的任何函式,會使用相關聯的 PDB 檔案。如果找到任何,規則將會失敗。否則,它會傳遞。使用 __declspec(safebuffers) 不允許 Microsoft SDL。

若要使用 BinSkim,先從 GitHub 儲存機制下載的原始程式碼並建置專案。若要執行 BinSkim,請在您最愛的介面中執行下列命令:

binskim.exe analyze target.exe --output results.sarif

若要分析多個映像,您可以使用下列命令:

binskim.exe analyze myapp\*.dll --recurse --output results.sarif --verbose

請注意,您可以使用萬用字元的檔案路徑。--Recurse 參數會指定 BinSkim 應該太分析子目錄中的映像。--Verbose 參數會告知 BinSkim 来包含在結果檔案中傳遞的規則 — 不只是失敗。

結果檔案是在靜態分析結果交換格式 (SARIF)。如果您在文字編輯器中開啟它,您會看到如下所示的項目圖 3

圖 3 BinSkim 分析結果檔案

{
  "ruleId": "BA2014",
  "level": "pass",
  "formattedRuleMessage": {
    "formatId": "Pass ",
    "arguments": [
      "myapp.exe",
    ]
  },
  "locations": [
    {
      "analysisTarget": {
        "uri": "D:/src/build/myapp.exe"
      }
    }
  ]
}

每個規則的規則識別碼。規則識別碼 BA2014 是 DoNotDisableStackProtectionForFunctions 規則的識別碼。Microsoft SARIF SDK (github.com/Microsoft/sarif-sdk) 包含原始程式碼的 Visual Studio 擴充功能,以在 Visual Studio 中檢視 SARIF 檔案。

總結

GuardStack 動態技巧是極為重要偵測基礎緩和堆疊緩衝區溢位弱點。中的偵錯的預設會啟用它,Visual Studio 中的發行組建。它是設計用來使其廣泛用於可忽略的額外負荷,大部分的程式。不過,它不提供這類弱點的終極解決方案。緩衝區溢位通從堆疊配置的緩衝區,但它們也會發生的任何配置的記憶體區域中。最以突顯的方式,堆積以緩衝區溢位是只為趟。基於這些理由,是非常重要,若要使用其他 Visual c + + 和控制流程防護 (CFG)、 位址空間配置隨機載入 (ASLR)、 資料執行防止 (DEP)、 安全結構化例外狀況處理 (/SAFESEH Windows 所提供的防護技術),以及結構化例外狀況處理覆寫保護 (SEHOP)。所有這些技術可以形成工作來強化您的應用程式。如需有關這些技術和其他人的詳細資訊,請參閱bit.ly/2iLG9rq


Hadi Brais是教育部登記學者在印度 Institute 的技術 Delhi,研究編譯器最佳化,電腦架構和相關的工具和技術。他的部落格上hadibrais.wordpress.com且可在連線hadi.b@live.com

這點受惠檢閱本文章下列技術專家:  Shayne Hiet 區塊 (Microsoft),Mateusz Jurczyk (Google)、 Preeti Ranjan 貓熊 (IITD),Andrew Pardoe (Microsoft)


MSDN Magazine 論壇中的這篇文章的討論