光明與黑暗 – 談程式碼保護

保護與破解就像是矛與盾,本文著重與討論程式碼保護技術,也就是如何保護你的程式碼不被竊取及竄改

光與黑、矛與盾

     自古以來,光明與黑暗都是對立的,這世界上沒有所謂的完全光明,也沒有完全的黑暗,恩…..再掰下去就變RPG了…….

  本文著重與討論程式碼保護技術,也就是如何保護你的程式碼不被竊取及竄改,所以相對地,某程度上我也會展示一點黑技巧,放心,都是很初階的黑技巧,所以別說我教壞小朋友哦。

 

以健康心態看待保護與破解

  所謂的保護,指的是在有限程度上阻止惡意行為,注意,是有限程度,因為所有的保護都基於過往的經驗及些許的預知,當未預期行為發生時,保護就會被破解。  所謂的破解,指的是運用特定的技巧,非常規,以時間來交換取得想要的結果。

  所以,別以為你做了什麼就可以高枕無憂,但,你的程式碼被100個人看過跟被3個人看過是有差別的。

 

The Managed Language

  通常,Managed Language所產出的程式檔是最容易被竊取的,因為他們是在執行時期解譯,所以必須有個機制來轉換,例如C#編譯為IL,CLR讀入IL解譯為機器碼執行,這裡的C#到IL這段注定了C#很容易被反組譯的命運,因為既然可以由C#到IL,那麼必然也能由IL到C#,而IL相對於機器碼又是一個很容易理解的指令集,能輕易被看光也不訝異了。同理,Java也是一樣的情況,Java到Byte Code,VM讀取後解譯執行,這兩個語言的反組譯工具到處都是,所以是最需要進行防護的,至於非編譯性的如JavaScript,那就是明碼大家看囉。

  那麼如果C#跟Java都直接編譯出機器碼,那麼反組譯就很難了是吧?是的,雖然還是可以反組譯,但基本上機器碼的特性會讓兩者有極大的差異,我指的是反組譯結果的漂亮度(當然,如果你本來就寫的很亂,就沒差),不過這樣做的代價有兩個,對於Java來說,一個是失去產出品的可攜性,一個是必須在編譯時期花比較多時間,對於C#來說,除了這兩個之外,還會失去跨語言的特性,眾所皆知C#是.NET平台中的一個語言,IL的出現可以讓所有.NET語言共用一組指令集,並且讓產出物可互通的特性。

  Managed Language好處很多,包含產出物大小,互通,例外處理,記憶體管理,執行時期資訊等等,所以不能以難以防護來一句話打死。

 

欲練神功、必先自宮,喔,是要維護光明就得先懂黑暗

  那麼一個沒有防護的C#產出品有多脆弱呢?讓我們看看下圖。

所以我從來不怕自己的程式碼不見,因為只要有產出物(.exe、.dll),就能拿回漂亮的程式碼,還是原裝的哦。

看到又怎麼樣? 呃…..其實也不怎麼樣,只是可以用ildasm反組譯改改再編回去,想怎麼改就怎麼改(其實ildasm算笨的,這些反組譯工具都有能力直接產生出.cs檔.....)。

然後會得到一個.il檔案。

接著用下面的命令就可以編譯。

最後。

接著我不喜歡那個字。

所以改他。

接著編一編。

然後就….沒有然後了。

其實就算沒有ildasm這些工具,直接修改.exe檔內容也可以達到同樣效果,不過你得先了解.NET的Assembly結構及特性,要避免這點的最快途徑是利用Strong Name Signed機制,這可以保護檔案不被竄改,但還是那句話,任何保護都是可被破解的。

 

光明在哪裡?

  看了黑暗,自然要回到光明,要防止這種情況發生的方法很多,Managed Language有反組譯工具,自然也會有反反組譯工具,模糊器是最簡單的方式,我比較常用的是Smart Assembly,由Reflector工具作者所做的(矛與盾?),透過模糊器,可以把程式碼及字串模糊化。

這是用最大模糊化的結果,這通常是兩面刃,一是不一定可以正常執行,二是效能會受到影響,一般情況下會取中間模糊化選項,而代價就是比較容易被猜出,不管如何,有法必有破。

 

帶我到充滿愛的地方

 一般來說,像Smart Assembly這種模糊化工具的結果已經可以被接受,畢竟能突破的人很大機率也具備突破另一層防護的能力,不過我們還是聊一下好了(硬要講),要更進一步防護就是殼上加殼(俄羅斯娃娃?),.NET有個機制可以讓我們直接給IL就能執行,這意味著IL檔案可以不是實體檔案,既然不是實體檔案,那麼侵入者就必須先取得IL,這就是殼上加殼,要做到這點,必須把你想保護的部分放到另一個Class Library中。

記得不要用Reference,直接把ClassLibrary1.dll嵌入執行檔中,想達到這個目的最簡單也最脆弱的是運用Resource。

再透過AppDomain來讀入並呼叫,這裡使用Reflection,比較好的方式是透過共用Interface。

缺點是,真的很脆弱。

不過你得到重點了吧,只要把這個dll進行加工,例如運用.zip加密壓縮,於程式中再解密,接著搭配模糊化工具,就達到了殼上加殼的目的,當然,還是那句話,能解開模糊化工具的人,很可能也具備了由被模糊化程式碼中取出解密字串的能力。

另外,使用這種方式Load進來的Assembly最好是很單純的,盡量不要有太多的相依性,不然你就得面對AssemblyResolve議題,這通常不難,因為關鍵的東西就那幾樣。

 

這還有沒有天理啊?

 

  如果這還不夠,剩下來的就是進入機器碼的層級,你可以把重要的部份用C/C++撰寫(好吧,喜歡的話Assembly也不錯),再用P/Invoke呼叫,這可以達到多一層防護,這樣也不是高枕無憂,基本上破解一開始就是從機器碼開始的,所以要讀懂你的C/C++產出物也不是很難,一般來說,我們會在產出物上進行Packer,不過,Unpacker也很流行,但是只要是自己做的Packer,最大程度進行防護也不難。

 

 

你涅槃了嗎?

 

  攻防戰基本上就是比誰氣長,你的殼越多,對手就越容易放棄,舉現今最有名的denuvo來說,殼自然是不用說,隨處插上check checksum,加上由硬體產生的獨一hashcode,要穿過去就得面對數萬次的check checksum,如果想繞過去就得做出完美的checksum validation simulator,這麼難的東西都被處理掉了,我們這算是小孩子扮家家酒等級(我是不是不小心把流程說出來了…),但是啊,被100個人弄跟被3個人弄是有差別的好嗎?