文章來源:javascriptOnlineObfuscator">http://www.BizStruct.cn/JavascriptOnlineObfuscator
Javascript 代碼混淆的目的
Javascript 是一種解釋執行的指令碼語言,主要應用於 Web 領域的用戶端的瀏覽器中;由於 Javascript 解釋執行的特性,代碼必須明文下載到用戶端,並且可以很容易的進行調試,使得 Javascript 代碼的保護非常困難;
不同的人對 Javascript 代碼的保護有不同的看法;有的人辛苦努力的代碼,卻可以被競爭者輕易獲得,他們就非常希望能有保護 Javascript 代碼的方案,但現有的方案可能無法滿足他們的要求;很多人認為 Javascript 語言很簡單,Javascript 代碼沒有保護的價值,可能是他們的代碼確實簡單,或者他們並不瞭解 Javascript 語言強大的功能;還有的人認為現在都開源了,還保護代碼幹什麼,當然開源的人是值得敬佩的,但對別人的代碼的開源要求,卻不是合理的。
為了提高使用者的體驗,出現了 Web 2.0 技術,並隨著 AJAX 和富介面技術的發展,Javascript 在 Web 應用程式上的重要性越來越高,Javascript 代碼的複雜性、功能和技術含量也越來越高,對Javascript 代碼保護的需要也越來越迫切。
Javascript 線上混淆器的目的是為 Javascript 代碼保護的需求,提供一種全新的綜合解決方案,包括編碼規則和免費的線上混淆器。
混淆和加密的區別
很多人將這兩者混在一起討論,實際上兩者的目的有一定的區別,採取的手段也有很大的不同。加密主要是為了防止未經授權的使用,對這種情況即使破解了加密,也只能非法使用,並不一定能獲得軟體的代碼邏輯;但對於指令碼來說,防止對代碼進行訪問的措施,也屬於加密,對這種情況,破解了加密,就獲得了代碼;而混淆是在無法阻止他人擷取代碼的情況下,採取的保護代碼的邏輯不被他人理解的措施;對於混淆的代碼,他人很難理解,無法進行修改和重新應用;
對於產生機器碼的語言,比如 C 語言,只需要考慮未經授權的訪問,幾乎不需考慮代碼的保護;因為對編譯後的軟體,只能反組譯碼為組合語言代碼,幾乎無法分析出代碼的邏輯。
對於產生中間代碼的語言,比如 Java 和 C#,即需要考慮未經授權的訪問,又需考慮代碼的保護;;因為對編譯後的軟體,可以很容易的反編譯為較進階的語言,從而瞭解到代碼中的邏輯,並較容易的破解加密。而混淆後,即難於理解代碼的邏輯,也不易找到加密點所在。
對於指令碼語言,比如 Javascript,只能混淆,難以加密;因為指令碼都是明文存在的,很容易調試的,通過跟蹤可以較容易的破解上面兩種目的的加密。但是混淆後的代碼是難於理解代碼的邏輯的。
我們只涉及到對 Javascript 指令碼進行混淆,而不涉及加密;對於涉及到 Javascript 的系統的加密,我們建議不要將加密點放在 Javascript 指令碼內,而是放在服務端的編譯器內,因為編譯器的加密可以採用更多的保護方式,加密的強度也更高。
我們首先要分析 Javascript 語言和混淆相關的特點,和現有的混淆產品的不足,然後再提出我們對 Javascript 代碼混淆的解決方案,最後是我們的 Javascript 線上混淆器。
Javascript 語言和混淆相關的特性
Javascript 是一種解釋執行的指令碼語言,相對編譯類型的語言有很多自身的特性,而其中一些特性會對代碼混淆帶來很大的困難。
無法定義類的屬性和方法的名稱是否需要被混淆
Javascript 是一種基於原型的語言,沒有嚴格的類型定義。在自訂的類中,對於需要外部存取的屬性和方法,不能進行混淆;對於內部訪問的屬性和方法,需要進行混淆;但Javascript 語言本身,無法對屬性和方法進行這樣的區分。為此我們要尋找一種變通的機制來識別屬性和方法的名稱是否需要混淆。
存在大量的系統定義的核心的和用戶端的方法和屬性不能被混淆
Javascript 語言本身定義了大量的核心的類、方法和屬性;瀏覽器中也定義了大量的用戶端的類、方法和屬性;這些類、方法和屬性都不能夠被混淆,然而這些類、方法和屬性的數量太大,無法通過枚舉來避免混淆;為此我們需要尋找一種方法來標識這些類、屬性和方法。
無法定義全域變數是否需要被混淆
全域變數是 window 對象的屬性,局部變數是函數對象的屬性;所有的局部變數都是可以和應該被混淆的,而全域變數有的需要混淆,有的不能混淆;但全域變數和局部變數的表現形式是一樣的,難以區分;而且全域變數本身更無法定義是否需要被混淆。為此我們要找到一種方法來區分不能混淆的全域變數,和需要混淆的全域變數及局部變數。
Javascript 語言的這些特點,都對代碼的混淆帶來了很大的困難,如果不解決這幾個問題,Javascript 代碼的混淆就缺少實用的價值。
現有 Javascript 混淆產品的問題
當我們需要混淆 Javascipt 代碼的時候,首先考察了市面上現有的產品,和一些論壇裡對混淆的思路,但這些產品和思路都不能滿足我們的要求。
有一個商品化的 Javascript 混淆產品,採用了和一種 C# 混淆工具相似的混淆方式,分析了代碼裡所有的標識符,對一些系統預設的標識符不混淆,對其他的進行混淆,同時提供使用者對標識符的混淆進行選擇和配置;這個產品的功能很多很複雜,但有一個很大的問題,就是預設的標識符有限,對於代碼中用到的大量的系統定義的屬性和方法,會進行混淆,為此需要自己手工配置,避免對這些屬性和方法的混淆,這對於大型的系統幾乎是一個不可能完成的任務。
有一些論壇裡也討論到混淆的思路,包括一些樣本,這些思路更多的是改變標識符的表現形式,有的是用編碼字串的關聯陣列替換屬性,比如將 xx.dd 替換為 xx["\x64\x64"];更複雜的是把 "\x64\x64" 之類儲存到字串數組,然後調用字串數組作為關聯陣列的下標;這種思路可以避免上面的問題,但有一個更大的問題,就是混淆是可逆的,被混淆的標識符僅僅是被轉換成了16進位的形式,可以很容易的恢複。
正是現有產品的不足,促使我們不得不研究自己的解決方案。我們的解決方案也是經過了幾個版本,一開始的版本要複雜的多,花費了很大的工作量,但結果並不理想;幾經修改才找到現有的解決方案;雖然開始的大量工作,最後幾乎都廢棄了,但沒有前面的工作,也就沒有後面的結果;所以即使您可能會認為我們的方案簡單,那也只是我們努力的結果,而不是過程;而且簡單的東西,往往是有效。
Javascript 代碼混淆綜合解決方案
通過前面對 Javascript 的特性和相關混淆產品的分析,使我們認識到如果僅僅是在混淆器上下功夫是不夠的;因為 Javascript 語言本身對混淆的功能有很大的限制,無法解決。為此我們設計了一個綜合的解決方案,就是 Javascript 線上混淆器規則,只要是按照規則編寫的 Javascipt 代碼,都能使用 Javascript 線上混淆器混淆進行混淆。
Javascript 線上混淆器的規則並不複雜,但能夠解決 Javascript 語言本身的特性和其他混淆產品遇到的問題。
規則一、所有用 window 約束的類、變數和函數都不混淆,其他的類、變數和函數都混淆。
全域的類、變數和函數本身都是 window 的屬性,用不用 window 約束,從邏輯的角度是一樣的。但我們可以借用 window 的約束來區分對全域的類、變數和函數是否需要進行混淆。
用 window 的約束必須是前後一致的,不但包括類、變數和函數的定義,也包括類、變數和函數的調用。
局部的類、變數和函數,因為沒有 window 約束,所以都是混淆的。
類型 |
混淆 |
不混淆 |
類定義 |
function Class1(){...} |
window.Class1 = function(){...} |
函數定義 |
function Method1(){...} |
window.Method1 = function(){...} |
變數定義 |
var Param1 = 1; |
window.Param1 = 1; |
產生類的執行個體 |
var object1 = new Class1(); |
var object1 = new window.Class1(); |
函數調用 |
Method1(); |
window.Method1() |
變數引用 |
var newParam = Param1; |
var newParam = window.Param1; |
規則二、所有以小寫字元開頭的屬性和方法都不混淆,以其他字母開頭的屬性和方法都混淆,用 window 約束的屬性和方法應用規則一。
JavaScript 核心和用戶端中有大量的系統定義的方法和屬性不能被混淆,而這些方法和屬性絕大多數都是以小寫字母開始的,本規則保證了系統定義的方法和屬性不被混淆。在 Javascript 用戶端中僅有極少數的系統定義的以大寫字元起始的方法和屬性,對於這種情況,可以採用關聯陣列的方式避免被混淆,比如 object1["Method1"]();此方法也適用於第三方控制項中可能會有的以大寫字元起始的方法和屬性的情況。
此規則也使我們可以在自訂的類中標識方法和屬性是否被混淆,對於需要外部調用不能混淆的方法和屬性,採用小寫字母起始,對於內部的方法和屬性,採用其他字母起始。
類型 |
混淆 |
不混淆 |
類方法定義 |
Class1.Method1 = function(){...} |
Class1.method1 = function(){...} Class1["Method1"] = function(){...} |
對象方法定義 |
Class1.prototype.Method1 = function(){...} |
Class1.prototype.method1 = function(){...} Class1.prototype["Method1"] = function(){...} |
類屬性定義 |
Class1.Prop1 = 1; |
Class1.prop1 = 1; Class1["Prop1"] = 1; |
對象屬性定義 |
object1.Prop1 = 1; |
object1.prop1 = 1; object1["Prop1"] = 1; |
類方法調用 |
Class1.Method1(); |
Class1.method1 (); Class1["Method1"](); |
對象方法調用 |
object1.Method1(); |
object1.method1 (); object1["Method1"](); |
Javascript 線上混淆器的核心規則就是以上兩點,另外還有幾點說明。
標識符的混淆採用 Hash 演算法,無法復原
Hash 演算法是無法復原的,所以不能根據混淆後的標識符,來直接推出混淆前的標識符;但 Hash 演算法依賴於 .Net 系統的實現,大多數的時候,.Net 的 Hash 演算法是不變的,就是同一個標識符的混淆結果是一樣的;如果能夠枚舉足夠多的標識符,仍然可能根據相同的混淆結果,知道混淆前的標識符。
如何調用混淆後的類、方法和屬性
對於混淆代碼的內部調用,只要採用相同的規則,要麼都混淆,要麼都不混淆,就能正確的調用。
對於混淆代碼的外部調用,可以有兩種方式,一種是不混淆,代碼內部採用不混淆的規則,外部採用不混淆的可理解的標識符調用;另一種是混淆,代碼內部採用混淆的規則,外部也採用混淆後的不可理解的標識符調用,但此方式依賴於 .Net Hash 演算法的實現,在不同版本的 .Net 實現中的 Hash 演算法有可能不同,以至混淆後的標識符不一致,從而導致重新混淆後,需要替換原來混淆的標識符。
為何有“清除空格,保留分號後的斷行符號”的選項
Javascript 文法要求全域函數的結尾必須有分號或斷行符號,如果遺漏了分號,而又清除了所有的斷行符號,總是提示第一行缺少分號,無法定位錯誤所在;採用本選項可以有助於尋找缺少的分號的位置。
以下 Javascript 語言的保留字不混淆
break, case, catch, continue, debugger, default, delete, do, else, false, finally, for, function, if, in, instanceof, new, null, return, switch, this, throw, true, try, typeof, var, while, with
混淆器預定義了一些 window 的屬性和方法
Javascript 核心類和函數是不能被混淆的,他們實質上都是 window 的屬性和方法,按照規則應該用 window 約束,以避免被混淆;但對於 Object、Array、Date、ActiveXObject 等核心類,混淆器已經預定義不會混淆,不需要再用 window 約束。對於 alert 等 window 常用的方法和 document 等 window 常用的用戶端屬性,也有預定義。其他需要預定義的類和方法,我們會逐步添加;沒有預定義的全域類和函數,如果不能混淆,必須用 window 約束。
以下全域的類、變數和方法不混淆
ActiveXObject, alert, Array, Boolean, Date , document, Math, Number, Object, RegExp, String, window
Javascript 線上混淆器
請訪問 http://www.BizStruct.cn/JavascriptOnlineObfuscator/JavascriptOnlineObfuscator.aspx 。