這篇文章的標題所提出的問題的答案是“不可能”。至少對我來說是不可能的。借助適當的工具,我們可以反編譯任何SWF文件。所以,不要將重要的信息置於SWF文件中。SWF文件中不要包含個人的帳號或者密碼。
我將簡要的論述“保護”技術的歷史,和他們是如何失敗的,接着我將說明我們能盡的最大努力。中國古語有雲,“規則只能防君子,不能仿小人”。
公開的文件格式
在討論之前,我們先要知道,SWF的文件格式是公開的。公開的文件格式,意味着SWF文件並不是只能由Flash生成。其他公司也能制作可以在SWF播放器上播放的SWF文件。公開的文件格式意味着從什么位置獲取什么信息是眾所周知的,也就意味着每個字節都是眾所周知的。因此,如果我有時間來一個字節一個字節的檢查SWF文件,我可以了解所有的細節。
當然,對於一個2M大小的SWF文件,我沒有時間來逐個字節的檢查。因此,我就借助軟件來完成這個工作。如果軟件遇到問題,我會暫時接管這個工作,檢查發生問題的字節。修正它,然后繼續。所以,沒有什么東西能夠掩藏的住,其限制只是我的時間和我的耐性。如果反編譯一個SWF文件的酬勞是數百美元的話,我想我會花上數年時間來逐個字節的讀取它。
好了,以下是反編譯和保護技術之間的戰爭歷史。
防止被導入
伴隨着Flash的出現,Macromedia提供給開發者一個“防止導入的口令保護”功能。如果你給SWF文件加上導入口令的話,這個SWF文件就不能被導入了(知道倒入密碼除外)。SWF文件不加保護的話,其中的矢量圖形可以被導入到fla文件中。這種保護沒有什么用處,僅僅是假想的安全。
試想一下,你的SWF被用戶的播放器來播放的,你不可能利用用戶的播放器來保護你的SWF文件。因此,它是如何來保護SWF文件的呢?很簡單,這種保護存在於你所買的Flash開發工具中。Flash開發工具不能導入有(導入)密碼的SWF文件。沒關系,對吧?我可以用十六進制編輯打開那個SWF文件,刪除保護密碼,從而也就移除了保護功能。
如此簡單,所以忘記導入保護功能吧。
轉換成放映機文件並且壓縮
如果我將它轉化成exe格式的放映機文件,還可以被反編譯嗎?答案:是的,SWF文件仍然存在其中。借助軟件可以很容易的將SWF文件從exe文件中釋放出來。壓縮可以使SWF文件不能被十六進制編輯器讀取,壓縮是一種保護措施嗎?壓縮算法類似於zip算法,很容易被破解。
FLASM AND THE P-CODE
在flash5的時代,出現了兩種流行的工具,免費的“Flasm”和商業的“ASV 2.0”。Flasm就是“Flash asm”,它將SWF中的字節碼解釋成可理解的簡短代碼(p-codes)。比如“a=3”顯示為"push 'a', 3", "setVariable";SWF中的字節碼是:"96 08 00 00 61 00 07 03 00 00 00 1D".如果想學習“SWF格式結構”的話,這是個非常有價值的工具。
程序員喜歡用高級語言(比如:C、C++)來開發軟件,但是當講求效率的時候,他們會在其中混合使用低級的匯編語言。因此,有時候開發者會利用Flasm編寫低級別的p-codes來增加效率。所以,Flasm編輯SWF中的actionscript是強有力的。你可以參考例子,了解如何利用這種技術來優化3D代碼,但是懷有惡意的用戶能夠“編輯”SWF文件,SWF中的任何保護措施都可以不費力的移除。我們不需要知道密碼就可以移除保護措施。
這兒有個通用、知名的技術來保護我們的影片不被偷竊並在其它的范圍內顯示。我們編輯腳本來檢查_url屬性,如果_url不是我們(合法)的范圍,就使功能失效並顯示一條“You are thief”的消息。可是,借助Flasm可以很容易刪除這條腳本語句。不需要1分鍾便可以破解這種保護措施。
ACTIONSCRIPT VIEWER AND "void (a)<=b>"c" || 0(!1 && !0)"
ASV(ActionScript Viewer)能夠從SWF中提取出角色,例如::聲音、形狀和位圖等都可以被竊取。
它同樣可以提取actionscript字節碼,ASV 2嘗試將p-codes匹配成高級別的actionscript.當遇到"push 'a', 3", "setVariable";時顯示"a=3"這樣的等同於actionscript的語言。然而我們能夠創造沒有任何模式來匹配的代碼,從而破壞ASV的解析。利用Flasm,可以容易的編寫不同於標准模式的代碼,從而使ASV不能進行匹配工作。擾亂ASV 2工作的一句有名的代碼是“;”,這是一條jung代碼。它不做任何事,但是能搞亂ASV 2的工作。
但是,當保護腳本眾所周知時,ASV的作者(Burakk)當然不會放過它。這種保護技術對於ASV 3來說就失效了。
飛速發展的反編譯工具
之后是MX時代的到來,許多反編譯工具的出現,加快了Flash厄運的速度。
現行版本的ASV 4除了顯示得到匹配的actionscript代碼,得不到匹配的代碼以p-codes形式顯示。如果解釋成p-codes發生問題,將顯示SWF中的字節碼。它同樣能夠顯示代碼在SWF文件中所處的偏移量,這意味着它不會失效。你不可能擾亂它的工作,因為,至少它能顯示SWF文件中的“字節碼”。
更甚的是,Flash MX2004提供通過JavaScript API來生成“fla”文件。那使它能夠建立發布成SWF格式的fla文件。此刻,所有的東西都在那邊了。
更不用說聲音、形狀和位圖了,偷竊者不喜歡這些東西,因為它們套容易取得了。偷竊者喜歡切的actionscript,因為其中隱藏着密碼,因為其中有阻止此影片正常播放的腳本代碼,
如果ASV只能將腳本反編譯成字節碼,那么它對於大多數偷竊者是沒有用處的。因此很多人進他們的最大努力來阻止ASV 4將腳本反編譯成actionscript或者p-codes.實際上,對於大多數反編譯者來說,腳本得不到匹配,反編譯工具就無用了。
這是曾經用過的一些技術,當它們在因特網上發布並且被反編譯組織揭示之后,每種技術的保護效果最終都會變得非常薄弱和氣數將盡。
依據數據尺寸(句子)分塊反編譯
大多數之所以能夠成功的迷惑或者破壞反編譯器,原因在於播放器和反編譯器的不同行為。播放器逐個的執行字節碼,就像現實世界中的讀書一樣,一個單詞,接着下一個單詞。然而反編譯器通常將字節鏈分成有意義的片斷,猶如現實世界中的讀書一樣,一個句子,接着下一個句子。
反編譯器的行為如此簡單的原因在於大多數的p-code都是遵循數據大小規律的。對於字節碼("96 08 00 00 61 00 07 03 00 00 00 1D"),反編譯器遇到代表"push"操作的0x96時會想“push什么呢”?下個字節(0x0008)指示的內容:接下去8個字節中的內容壓入堆棧,即把("00 61 00 07 03 00 00 00")壓入堆棧。所以,通常反編譯器依據數據大小將簡短的片斷切成一塊一塊的,這樣便會解釋成“push something”。因此,("96 08 00 00 61 00 07 03 00 00 00")就成為一個句子。下一個字節是下一個句子的開始,就是代表"setVariable"的0x1D.這樣8個字節的"something",將被更進一步解釋成一個字符串“a”和一個數字“3”。
讓我們來看一下字節碼:("99 02 00 05 00 96")。0x99意味着分支(或者跳轉),在哪里分支呢?接下去的是(0002),因此數據存儲在機下去的兩個字節中,將它在下面兩個字節處截斷。總之,我們知道"99 02 00 05 00"是個句子。接下去的是0x96,代表下個句子的開始。
再看第三個例子,字節碼:("88 08 00 03 00 63 00 62 00 61 00 96 07 00")。0x88代表定義常數,定義的常數內容是什么呢?后續字節(0008),表明常數內容存儲在后繼的8個字節中。所以,句子就是:("88 08 00 03 00 63 00 62 00 61 00")。代表下個句子開始的("96 07 00 ……),意味着將7個字節的數據壓入堆棧。
因此,字節碼砍成單獨的句子。每個句子由命令和數據組成,並且以命令打頭。因此,每個句子都是一個基本的單元。理論上來說,對於這種方法沒有什么錯誤。
使播放器從句子中間開始讀數據
讓我們開始討論“是播放器從句子中間讀數據”的破壞反編譯器的技術。
首先,我舉一個現實世界的例子:
John says good morning. Mary says thank you.現在生成SWF文件:
skip 9 words Tom says John says good morning. skip 3 words back 7 wordsMary says thank you.如果逐個字讀的話,結果和原來一樣,然而反編譯器按句子來讀取,自然發生錯誤。第一,它知道Tom說了什么,但語法不對,匯報出錯;第二,它沒有看到第二個“skip”命令,因為它處於句子中間;第三,當它被迫退回7個字后,感到迷惑,認為應該從“Tom said”開始執行整個句子;第四,這個錯誤使它在第二行和第三行之間陷入無限循環之中。
總之,我們增加了“Tom said”這句垃圾代碼,並提供整個句子長度的錯誤數據大小信息。這個錯誤的長度覆蓋了“skip”命令。
來看個真實的例子,請注意,這些技術需要操作字節碼,純actionscript不能夠實現。
例1 :
向前跳轉的包含無效尺寸數據的死代碼。
push True branchifTrue label2 constants ''label2:push 'a',3setVariable你仔細看的話,會發現"constants ''"這行是垃圾代碼,它不可能被執行到。然而,理論上,當第二行的結果為"not True"時,它將被執行。因此,反編譯器嘗試對它進行反編譯。
讓我們來增大"0x88 - constants"后面的"sentence size",從而包括知道腳本結尾的所有字節。你知道,反編譯器將把字節碼砍成像這樣的3個句子:
push True branchifTrue label2 constants label2: push 'a',3 setVariable如果你試圖反編譯此SWF文件,根據我前面提到的4個錯誤,一些反編譯器將碰壁。仍舊有些反編譯器幸存,但只是顯示:"if(false){};".ASV 3也不能顯示此腳本,但是ASV 4能夠顯示。為了破解這個SWF,我們移除死代碼"constant xxxx", (0x88和隨后兩個字節),然后所有的東西都得到反編譯。
這兒是zip格式的文件,將詳細解釋怎樣制作這樣的受保護文件。
例2:
先后跳轉的包含無效尺寸數據的死代碼。
push 'b' label1:push 'a',3 setVariable branch label2 branch label1
label2:push 'b'是句垃圾代碼,我們將修改它,用來使ASV 4碰壁。讓我們來修改push 'b'的"length of sentence".修改"0x96"后面的2個字節的數據,使句子的長度增長到分支Label1之前。這樣,反編譯器將把字節碼作為3個句子:
push label1: push 'a',3 setVariable branch label2 branch label1 label2:
現在,反編譯器不知道將把什么壓入堆棧,同樣它將在第一個句子和第二個句子之間形成死循環。這種技術將使大部分反編譯器碰壁。Flasm、ASV 4同樣也無效。為了破解這個SWF,我們手工刪除"push b" (0x96和后隨的2個字節),這樣所有的東西都可反編譯了。
當這個技術流傳后,burakk將修改ASV 4,使它能正確處理死循環。這樣下個版本的ASV就可對付這種技術了。
這兒是zip格式的文件,將詳細解釋怎樣制作這樣的受保護文件
非顯示字符和混淆器
除了阻礙反編譯器的技術之外,我們還可以使反編譯的結果不容易讀。你可以瀏覽關於混淆器的網站。
基本上,它是對變量和函數的名字進行重命名。
function -3(-4){trace(-4);} function -1(0,-2){ if(0<-2){-3(1);} }
肯定的是,偷竊者不能僅僅是拷貝、粘貼就能使用這些代碼。編譯器不允許你對一個函數進行這樣的命名。
混淆器的局限是改變函數的名字可能給下面的腳本代碼帶來麻煩。
function myFun(){trace("myFun");} a="my";b="Fun";this[a+b]();
另一個技術是使函數的名字不可顯示。比如,用漢字作為函數名,反編譯器可能就會不能很好的顯示它。然后,我們便會看到:
function ?(){?,?){?。?=?;}
ASV 4使用unicode編碼顯示不可顯示字符,所以,結果是易讀的,只不過增加了點輕微的難度。
自我保護
如果你找到一個很好的方法來保護自己的SWF不備反編譯,不要與別人分享這個方法,至少不要再因特網上公開它。當然,它不能過100%的防反編譯,至少對我來說是這樣。但是,不是所有的人都對SWF的格式了如指掌。許多,偷竊者僅僅只會借助軟件來反編譯SWF.因此,如果你的目的是盡可能少的偷竊者能夠偷竊你的SWF,那么請保護好你的(白虎SWF的)秘密方法。
只是在此提醒你,你的SWF是無保護的。對於熟悉SWF格式的人來說,所有東西都是毫無遮掩的。如果你的目標只是保護“一些函數”的話,它應該是安全的。他們未必會竊取你的函數,他們會編寫自己的函數。
通用保護措施
我們在網站上放了個聯機游戲,不幸的是,許多訪問者只是訪問我們的站點僅僅一次而已,然后利用下載的版本來離線玩。有時,我們甚至發現我們的游戲出現在其他的站點上。
為了避免這種情況發生,下面是可采取的保護措施:
1. 所屬領域檢查
編寫腳本檢查_url,如果_url是""就播放影片,否則不播放或者退出。當離線播放的時候,_url應該像"file://C| someSub/game.SWF";但它在其它domain中時,_url就形如"".因此,這個技術可以恰當的加上一些保護。當然,對於心存不良的反編譯者來說是無用的。那些腳本可以借助Flasm來刪除或者修改為不檢查。盡管在其它公開場合未必能看到被破解的SWF文件,但是離線也能夠播放的SWF就可能發生上述情況。
2. 服務端口令驗證
編寫腳本,並且在開始游戲時從服務端加載密碼。如果密碼為空,則停止游戲或者退出游戲。這很容易被懷有惡意的用戶破解,他們只要編輯SWF文件,將那些腳本刪除即可。哪些腳本不可以被刪除呢?當開始游戲時,從服務端加載的地圖數據是進行游戲必須的,所以心存不良的用戶不可能刪除這些腳本。為了進行游戲,它必須提供這些地圖數據。當然,它可以從緩存在臨時文件夾中的數據中挑選地圖數據,提供給SWF,從而激活游戲。
3.將SWF或者變量置於服務端
這是第二種技術的擴展、延伸,該技術已經廣泛的使用着。起初的游戲(SWF)文件只是個裝載器,當單擊播放(或開始)按鈕后,將加載另一個SWF文件。當需要地圖文件的時候,便從服務端加載地圖數據。當加載數據遇到阻礙時,將從服務段再次加載受到阻礙的SWF文件。新層上的數據也是從服務端傳過來。
從這兒。我們可以看到一個原則:防止反編譯的最好方法是“不給”。
如果一些愚蠢的竊賊只下載game.SWF文件,他不可能玩這個游戲。 他需要在緩存中挑選出所有的SWF文件和變量。打開所有的SWF文件,編輯這些變量的名字,使它與緩存中的變量的名字一致。
如果我們的地圖由CGI隨機產生,那么竊賊只可能擁有一份地圖,他沒有隨機產生地圖的權力。如果是個迷宮游戲的話,充其量,他只能玩一個迷宮,他沒有“動態產生迷宮”的功能。如果心存不良的用戶玩這個游戲將碰到一個新的阻礙,因為在他的緩存中沒有這個新的受到阻礙的SWF文件,所以將無法進行游戲。
因此,許多算法和功能都放在服務端,那個SWF文件只是個界面而已。這是個完美的保護措施,但是缺陷是這件產生一個轉為游戲工作的CGI,而不是Flash.我們正在討論關於SWF的保護問題,這個解決方案是不妥當的,因為SWF文件自身得不到保護
