技術文章 > 防止對 Visual Basic .NET 或 C# 代碼進行反相工程

防止對 Visual Basic .NET 或 C# 代碼進行反相工程

2020-02-28 10:08

文檔管理軟件,文檔管理系統,知識管理系統,檔案管理系統的技術資料:

Gabriel Torok和 Bill Leach
本文假設您熟悉 .NET 與 C#
摘要
.NET 體系結構的優勢之一在于,利用該體系結構構建的程序集包含很多有用的信息,使用中間語言反匯編程序 ILDASM 即可恢復這些信息。但是這樣會帶來另一個問題,就是可以訪問您的二進制代碼的人能夠以非常近似的手段恢復原始源代碼。作者將在文中介紹程序模糊處理,該處理可作為一種阻止反相工程的手段。此外,他們還將討論可用的不同類型的模糊處理技術,并示范 Visual Studio .NET 2003 中包含的新模糊處理工具。
本頁內容
反匯編
反編譯
深入了解模糊處理
重命名元數據
刪除非基本元數據
其他技術
使用 Dotfuscator Community Edition
檢查映射文件
模糊處理程序的缺陷
小結

迄今為止,從減輕部署和版本控制的負擔,到自描述二進制數據所實現的豐富 IDE 功能,您可能已經熟悉了這些元數據豐富的 Microsoft® .NET Framework 體系結構帶來的所有好處。您可能不知道元數據的這種易用性帶來的一個目前對于大多數開發人員來說還沒有注意到的問題。為公共語言運行庫 (CLR) 編寫的程序更易于進行反相工程。不管怎么說,這并不是 .NET Framework 設計中的缺陷;它只是一種現代的、中間編譯語言(Java 語言應用程序具有同樣的特征)的現實狀況。Java 和 .NET Framework 都使用內嵌在可執行代碼中的豐富元數據:在 Java 中是字節碼,在 .NET 中是 Microsoft 中間語言 (MSIL)。由于比二進制機器碼要高級很多,可執行文件充滿了可以輕松破解的信息。
借助于諸如 ILDASM 這樣的工具(隨同 .NET Framework SDK 一同發布的 MSIL 反匯編程序)或者類似于 Anakrino 和 Reflector for .NET 這樣的反匯編程序,任何人都可以輕松地查看您的程序集并將其反相工程為可讀的源代碼。黑客可以搜索安全缺陷,以探究、竊取獨特的創意并破譯程序。這足以使您猶豫不決。
盡管如此,請不必擔心。有一種模糊處理解決方案可幫助您防止反相工程。模糊處理是為程序集中的符號提供無縫重命名的一項技術,它還提供了其他技巧以阻止反匯編程序。如果運用適當,模糊處理可以大幅度提升應用程序防止反相工程的能力,同時保持應用程序的完整無缺。模糊處理技術通常用于 Java 環境中,多年來已幫助了眾多公司保護他們的基于 Java 技術產品的知識產權。
現在已有多家第三方通過創建 .NET代碼的模糊處理程序來予以響應。通過與我們公司 PreEmptive Solutions 合作,Microsoft 在 Visual Studio?.NET 2003 中包含了 Dotfuscator Community Edition,而 PreEmptive Solutions 也推出了多種模糊處理程序軟件包。
通過使用 Dotfuscator Community Edition,本文將向您講授與模糊處理有關的所有內容(同時簡要介紹一下反匯編)、通常可用的模糊處理的類型以及利用模糊處理程序工作時需要解決的一些問題。
為演示反編譯和模糊處理,我們將使用經典的 Vexed 游戲的一個開放源代碼實現。Vexed.NET 由 Roey Ben-amotz 編寫,可從 http://vexeddotnet.benamotz.com 獲得。這是一個益智類游戲,您的目標就是將相似的塊移到一起,從而使它們消失。下面是Vexed.NET 源代碼中的一個簡單方法:
public void undo() {
if (numOfMoves>0) {
numOfMoves--;
if (_UserMoves.Length>=2)
_UserMoves = _UserMoves.Substring(0, _UserMoves.Length02);
this.loadBoard(this.moveHistory[numOfMmoves -
(numOfMoves/50) * 50]);
this.drawBoard(this.gr);
}
}
反匯編

.NET Framework SDK 附帶有一個名為 ILDASM 的反匯編實用工具,該工具允許您將 .NET Framework 程序集反編譯為 IL 匯編語言語句。為了啟動 ILDASM,您必須確保安裝了 .NET Framework SDK,并在命令行中輸入 ILDASM,然后是您希望進行反匯編的程序名。在當前例子中,我們將輸入“ILDASM vexed.net.exe”。這樣將啟動 ILDASM UI,它可用于瀏覽任何基于 .NET Framework 的應用程序的結構。圖 1 顯示了經過反匯編的 undo 方法。
返回頁首
反編譯

如果您認為只有少數真正了解 IL 匯編語言的人才會看到并理解您的源代碼,請牢記反編譯并不會到此為止。我們可以使用反編譯程序來創建實際的源代碼。這些實用工具可以直接將 .NET 程序集反編譯為如 C#、 Visual Basic .NET 或 C++ 這樣的高級語言。讓我們看一下由 Anakrino 反匯編程序生成的 undo 方法:
public void undo() {
if (this.numOfMoves > 0) {
this.numOfMoves =
this.numOfMoves - 1;
if (this._UserMoves.Length >= 2)
this._UserMoves =
this._UserMoves.Substring(0, this._UserMoves.Length - 2);
this.loadBoard(
this.moveHistory[this.numOfMoves -
this.numOfMoves / 50 * 50]);
this.drawBoard(this.gr);
}
}
您可以看到,結果幾乎與原始代碼完全相同。稍后,我們將回到這里來查看經過模糊處理后的結果。
返回頁首
深入了解模糊處理

模糊處理是使用一組相關技術來實現的。模糊處理的目的是隱藏程序的意圖而又不改變其運行時行為。它不是加密,但對于 .NET 代碼而言,它比加密可能更勝一籌。您可以加密 .NET 程序集,使它們完全不可讀。但是,該方法會造成一個典型的進退兩難的局面,這是因為運行庫必須執行未加密代碼,解密密鑰必須與已加密程序保存在一起。因此,可創建一個自動化的實用工具來恢復密鑰、解密代碼,然后以 IL 的原始形式將其寫到磁盤上。一旦發生這種情況,程序就完全暴露在反編譯之下了。
我們可以將加密比作是將包含六道菜的套餐鎖到箱子里。只有預定的用餐者(在本例中為 CLR)才有鑰匙,而且我們不想讓任何其他人知道他或她將要吃什么。遺憾的是,到了用餐時間所有食物將完全處于眾目睽睽之下。模糊處理工作更像將包含六道菜的套餐放到一個攪拌機中,然后將它放到一個塑料袋中送給用餐者。當然,每個人都可以看到正在遞送的食物,但是除了碰巧可以看到一顆完整的豌豆或一些牛肉色的粘糊東西之外,他們并不知道原來的食物是什么。用餐者仍然獲得了他想要的東西,而且這次用餐仍然提供了與以前一樣的營養價值(幸運的是,CLR 對口味并不挑剔)。模糊處理程序的竅門就是把窺探者搞糊涂,同時該程序仍然能向 CLR 提交同樣的產品。
當然,模糊處理(或加密)并不是百分之百地堅不可摧。即使編譯后的 C++ 也可能被反匯編。如果黑客堅持不懈,他就可以重新產生您的代碼。

圖 2 模糊處理過程

模糊處理是一個應用于編譯后的 .NET 程序集而不是源代碼的過程。模糊處理程序決不會讀取或更改您的源代碼。圖 2 顯示了模糊處理過程的流程。模糊處理程序的輸出是另一組程序集,功能上與輸入程序集等同,但是該程序以一種阻礙反相工程的方式進行了轉換。現在我們將討論 Dotfuscator Community Edition 用來實現該目標所使用的兩項基本技術:重命名和刪除非基本元數據。
返回頁首
重命名元數據

模糊處理的第一道防線就是將有意義的名稱重命名為一個無意義的名稱。如您所知,從精心選擇的名稱中可得出許多有價值的線索。它們有助于使您的代碼自我文檔化,并充當了揭示它們所表示的項目的有價值的線索。CLR 不關心一個名稱的描述性如何,因此模糊處理程序可以自由地修改這些名稱,通常是修改為類似于“a”這樣的單字符名稱。
顯然,模糊處理程序對一個特定程序所能執行的重命名次數是有限制的。通常來說,有三種常見的重命名方案。
如果您的應用程序由一個或多個獨立的程序集組成(即未模糊處理的代碼不依賴于任何程序集),那么模糊處理程序可以自由地對程序集進行重命名,而無論名稱的可見性如何,只要名稱和對名稱的引用在程序集組中是一致的。Windows 窗體應用程序就是一個很好的例子。作為一個反面的極端例子,如果您的應用程序設計目的就是供未經過模糊處理的代碼使用,那么模糊處理程序將無法更改那些對客戶可見的類型或成員的名稱。這種類型的應用程序的例子,包括共享類庫、可重用組件等諸如此類的東西。位于二者之間的是那些打算插到現有的未做模糊處理的框架中的應用程序。在這種情況下,模糊處理程序可以對它運行時所在的環境不訪問的任何東西進行重命名,不管可見性如何。ASP.NET 應用程序是這種類型應用程序的很好的例子。
Dotfuscator Community Edition 使用一種稱為為“重載歸納”的專利重命名技術,這項技術可以向重命名添加扭曲。在詳盡分析作用域之后,方法標識符得到了最大限度的重載。重載歸納技術并不是將舊名稱替換為一個新名稱,而是盡可能地將很多方法重命名為相同的名稱,從而迷惑那些試圖理解反編譯代碼的人。
此外,作為一個好的副作用,由于程序集中所包含的字符串堆變小,應用程序的大小也隨之而變小了。例如,如果您有一個長度為 20 個字符的名稱,將其重命名為“a”將節省 19 個字符。此外,通過節約字符串堆項以不斷地重新使用重命名來節省空間。將每一項都重命名為“a”意味著“a”只存儲了一次,每個被重命名為“a”的方法或字段都可以指向它。重載歸納增強了這種效果,原因是最短的標識符會得到不斷的重新使用。通常,一個重載歸納項目將有高達 35% 的方法被重命名為“a”。
要了解重命名對反編譯代碼的影響,請查看重命名處理后的 undo 方法:
public void c() {
if (this.p > 0) {
this.p = this.p - 1;
if (this.r.Length >= 2)
this.r = this.r.Substring(0, this.r.Length - 2);
this.a(this.q[this.p - this.p / 50 * 50]);
this.a(this.e);
}
}
您可以看出來,在沒有經過任何模糊處理,這個方法就已經比較難于理解了。
返回頁首
刪除非基本元數據

在編譯過的、基于 .NET 的應用程序中,并非所有的元數據在運行時都會得到使用。其中的一些數據將由其他工具使用(例如,設計器、IDE和調試器)。例如,如果您在 C# 中的類型上定義了一個名為“Size”的屬性,則編譯器將省略屬性名“Size”的元數據,并將該名稱與實現 get 和 set 操作的那些方法關聯起來(它們被分別命名為“get_Size”和“set_Size”)。當您開始編寫設置 Size 屬性的代碼時,編譯器將始終生成一個對方法“size-Size”本身的調用,并且決不會通過其名稱引用該屬性。事實上,屬性的名稱供 IDE 和使用您的代碼的開發人員使用;CLR 從不訪問它。
如果只是由運行庫而不是其他工具使用您的應用程序,那么模糊處理程序刪除這種類型的元數據就是安全的。除了屬性名,事件名和方法參數名也在這個范疇之列。Dotfuscator Community Edition 在它認為這樣做安全時會刪除所有這些類型的元數據。
返回頁首
其他技術

Dotfuscator Community Edition 使用我們剛剛介紹的技術來提供良好的模糊處理,但是您應該知道模糊處理技術在提供更為強大的保護的同時,可能還會阻止反相工程的進行。Dotfuscator Professional Edition 實現了很多其他技術,其中包括控制流模糊處理、字符串加密、增量模糊處理和規模降低。
控制流是一種強大的模糊處理技術,它的目的是隱藏一系列指令的意圖而又不會更改邏輯。更為重要的是,它可用來刪除反編譯程序為了忠實重現高級源代碼語句(比如 if-then-else 語句和循環)而尋找的那些線索。事實上,這項技術試圖破壞反匯編程序的工作。
要查看運行效果,在運用重命名和控制流模糊處理后,再次研究反編譯后的undo方法(請參見圖 3)。您可以看到反匯編程序并沒有生成原始的嵌套 if 語句,而是生成了一個 if 語句、兩個嵌套 while 循環和一些將其捆綁在一起的 goto。標簽 i1 被引用了,但它不是由反編譯程序生成的(我們假定它是一個反編譯程序錯誤)。
字符串加密是一種將簡單加密算法應用到嵌入您的應用程序中的字符串的技術。如上所述,在運行時執行的任何加密(或特殊情況下的解密)從根本上講都是不安全的。也就是說,技術高超的黑客事實上是可以破解它的,但對于應用程序代碼中的字符串而言,這樣做是值得的。我們所面對的事實是,如果黑客希望進入您的代碼,那么他們不會盲目地開始搜索已重命名的類型。他們可能確實會搜索“無效許可證密鑰”,這會將他們直接引導到執行許可證處理的代碼。對字符串進行搜索非常簡單;字符串加密設置有保護,這是因為在編譯的密碼中只存在加密的版本。
增量模糊處理有助于發布修補程序來解決客戶在面對模糊處理時碰到的問題。修復代碼中的錯誤時經常會創建或刪除類、方法或字段。更改代碼(例如,添加或刪除某個方法)可能會導致隨后的模糊處理運行,從而使事物的重命名稍有不同。先前稱為“a”的名稱現在可能稱為“b”。遺憾的是,如何不同地進行重命名和不同地重命名哪些內容卻不容易弄清楚。
增量模糊處理可以解決這一問題。Dotfuscator 將創建一個映射文件以告知您它是如何執行重命名的。但是,這個映射文件在隨后的運行中同樣可用作對 Dotfuscator 的輸入,以指示先前使用的重命名應在任何可能的地方再次使用。如果發布您的產品,然后修補一些類,Dotfuscator 就會以一種模仿其先前重命名方案的方式運行。這樣,您就可以只將修補過的類發布給您的客戶。
減小規模不會嚴格地阻止反相工程,但這里仍值得提一提,因為模糊處理程序幾乎始終必須要在輸入程序集上執行依賴性分析。因此,模糊處理程序不僅可以很好地進行模糊處理,更好的是,它還可以利用對您的應用程序的了解來刪除您的程序沒有使用的代碼。看起來有點奇怪,實際上,刪除未使用的代碼非常容易,那么,是誰編寫了這些不使用的代碼呢?答案是,我們所有的人。此外,我們都使用其他人編寫的、可重用的庫和類型。
可重用代碼意味著存在一些可處理許多用例的隨附代碼;但在任何給定的應用程序中,您通常只使用這些眾多用例中的一種或兩種。高級模糊處理程序可以確定這一點并刪除所有未使用的代碼(而且,是從已編譯的程序集而非源文件中刪除)。結果是,輸出中所包含的正是您的應用程序不再需要的類型和方法。較小的應用程序具有節省計算資源和縮短加載時間等好處。這些好處對于在 .NET Compact Framework 上運行的應用程序或分布式應用程序尤為重要。
返回頁首
使用 Dotfuscator Community Edition

現在讓我們使用 Dotfuscator Community Edition 來模糊處理 Vexed 應用程序。Dotfuscator Community Edition 使用一個配置文件來指定特定應用程序的模糊處理設置。它讓一個 GUI 來幫助您輕松創建和維護配置文件,以及運行模糊處理程序并檢查輸出。此外,Dotfuscator Community Edition 的命令行界面允許您將模糊處理輕松集成到您自動生成過程中。您可以從 Visual Studio .NET 2003 的工具菜單直接啟動 GUI。
要配置 Vexed 以進行模糊處理,您需要在 Dotfuscator Community Edition GUI 中指定 3 項:輸入程序集、映射文件位置和輸出目錄。輸入程序集(Dotfuscator 稱之為“觸發器程序集”)在 Trigger 選項卡上指定。您可以在這里根據所需添加任意多的程序集,但對 Vexed 應用程序來說只需要一個。
在“Rename | Options”選項卡上指定映射文件的位置(請參見圖 4)。映射文件中含有原始名稱和被模糊處理名稱之間的明確名稱映射,這些信息至關重要。重要的一點是,對應用程序進行模糊處理后,要保存該文件;如果沒有它,您就不能輕松地對模糊處理過的應用程序進行查錯。由于其重要性,Dotfuscator 在默認情況下不會改寫現有映射文件,除非您顯式地選中 “Overwrite Map file“ 框。
最后,“Build”選項卡允許您指定放置經過模糊處理的應用程序的目錄。完成上述工作后,就可以對應用程序進行模糊處理了。您可以保存配置文件以備后用,然后可以在“Build”選項卡上按“Build”按鈕,或在工具欄上使用“Play”按鈕。在構建時,Dotfuscator 會在 GUI 的輸出窗格中顯示進度信息。在“Options”選項卡上選擇 Quiet 或者 Verbose,可以控制在這里顯示的信息量。
一旦完成生成,您就可以在 Output 選項卡上瀏覽結果,如圖 5 所示。如您所見,Dotfuscator 顯示了一個與對象瀏覽器類似的應用程序圖形視圖。新名稱位于視圖中原始名稱的正下方。在此圖中,您會看到名為“board”的類被重命名為“h”,具有不同簽名的兩個方法(init 和 ToImage)都被重命名為“a”。
返回頁首
檢查映射文件

Dotfuscator 生成的映射文件是一種 XML 格式的文件,這種文件除包含上述名稱映射外,還包含一些統計數據,這些數據指出重命名過程的有效性。圖 6 匯總了對 Vexed 應用程序進行模糊處理后類型和方法的統計。
映射文件還被用于執行增量模糊處理。此過程允許您從以前的運行中導入名稱,這樣會通知模糊處理程序采用與以前同樣的方式來執行重命名。如果為一個經模糊處理的應用程序發布修補程序(或新插件),您可以使用與原始版本相同的名稱集對更新程序進行模糊處理。這對維護多個相互依賴的應用程序的企業開發小組特別有用。
返回頁首
模糊處理程序的缺陷

有關復雜應用程序的模糊處理(特別是重命名)比較棘手,它對正確配置高度敏感。如果不慎,模糊處理程序就會中斷您的應用程序。在本部分中,我們將討論一些使用模糊處理程序時可能出現的常見問題。
首先,如果您的應用程序包含強名稱程序集,則您需要多做一些工作。強名稱程序集是經過數字簽名的,允許運行庫確定是否已在簽名后改變了程序集。簽名是一個利用 RSA 公鑰/私鑰對的私鑰簽名的 SHA1 哈希值。此簽名和公鑰都被嵌入到程序集的元數據中。因為模糊處理程序將修改程序,所以在模糊處理后進行簽名非常重要。在開發過程中和進行模糊處理之前,您應該對程序集延遲簽名,然后完成簽名過程。有關延遲簽名程序集的詳細信息,請參閱 .NET Framework 文檔。請記住,在測試延遲簽名的程序集時,關閉強名稱驗證。
使用 Reflection API 和動態類加載還將增加模糊過程的復雜性。由于這些實用程序是動態的,所以它們優于大多數模糊處理程序使用的靜態分析。請考慮下列 C# 代碼片段,該代碼片段按名稱獲取類型并動態地將其實例化,然后將類型轉換返回給接口:
public MyInterface GetNewType() {
Type type = Type.GetType( GetUserInputString(), true );
object newInstance = Activator.CreateInstance( type );
return newInstance as MyInterface;
}
類型的名稱來自另一個方法。GetUserInputString 可能要求用戶輸入一個字符串,也可能從數據庫中檢索一個字符串。不論采用哪種方式,代碼中都不顯示靜態分析可恢復的類型名稱,因此,無法了解輸入程序集中的哪些類型可能會采用此方式來進行實例化。在此情況下采用的解決方案是防止對實現 MyInterface 的所有潛在的可加載類型進行重命名(請注意,仍可以執行方法和字段重命名)。因此,手動配置和掌握一些有關要進行模糊處理的應用程序的知識在這里非常重要。Dotfuscator Community Edition 為您提供了多種工具來防止對所選類型、方法或字段進行重命名。您可以挑選和選擇獨特的名稱;或者,您可以使用正則表達式和其他標準(例如作用域的可見性)來編寫排除規則。例如,您可以將所有的公共方法排除在重命名之外。
在您已部署了一個經模糊處理的應用程序并嘗試支持它時,使用模糊處理程序會產生另一個問題。假定應用程序將拋出一個異常(對大多數人而言,都會發生該情形)并且客戶向您發送如下的堆棧轉儲:
System.Exception: A serious error has occurred
at cv.a()
at cv..ctor(Hashtable A_0)
at ar.a(di A_0)
at ae.a(String[] A_0)
顯然,上述堆棧轉儲所含的信息少于來自未模糊處理的程序所含的信息。好的方面是,您可以使用模糊處理期間生成的映射文件將堆棧跟蹤解碼回原始代碼。壞的方面是,堆棧跟蹤中的消息有時不足以明確地從映射文件中檢索出原始符號。例如,請注意在轉儲中省略了方法返回類型。在用增強的重載歸納重命名算法模糊處理的應用程序中,或許只有返回類型不同的方法才會被重命名為相同的名稱。因此,堆棧跟蹤可能是任意的。在大多數情況下,您可以縮小檢索范圍,以便更有把握找到原始名稱。要獲得幫助,Dotfuscator Professional 為您提供了一個工具以自動將堆棧跟蹤轉換回不招人喜歡的原始方法。
返回頁首
小結

您可以防止黑客使用隨處可得的 ILDASM 實用程序對您的應用程序進行惡意處理。您可以用模糊處理程序來保護代碼。模糊處理可以有效阻止反相工程。使用 Visual Studio .NET 2003 產品中提供的 Dotfuscator Community Edition,只需點擊幾下鼠標即可進行良好的模糊處理。
有關文章,請參閱
Inside Microsoft .NET IL Assembler by Serge Lidin (Microsoft Press, 2002)
Dotfuscator FAQ
有關背景信息,請參閱
Ildasm.exe Tutorial
Anakrino
http://vexeddotnet.benamotz.com
http://www.preemptive.com
Gabriel Torok 是 PreEmptive Solutions 的總裁。他與別人合著有 JavaScript Primer Plus 和 Java Primer Plus,這兩本書均由 Macmillan 出版。Gabriel 在世界各地的開發會議上發表演講并講授課程。
Bill Leach 是 PreEmptive Solutions 的首席技術官。他是 Dotfuscator 產品線的架構師和技術主管。Bill 還擔任了軟件開發書籍和文章的技術評論。
轉到原英文頁面
香港马会宝贝心水论坛