稍微深入研究過一點 java 的同學,恐怕都知道什么叫做 “反編譯” 。也就是說,隨便拿一個 class 文件,找一個 jad 來,所有的 “智慧結晶” 就全都 “真相大白” 了,跟原先的 source code 相比,區別只是沒有注釋而已。
對于開源軟件開發者來說,這本是無所謂的事,但對于商業開發者而言,這簡直就是噩夢。在 java 的世界,道高一尺魔高一丈(及其反復迭代)的結果是,這件事最終演變得比較詭異,以至于專門誕生了一個名叫 “代碼混淆” 的產業。在我上一次關注的時候,這個領域的最新進展是可以 “混淆” 程序執行的流程,以至于正常的人類閱讀反編譯出來的源碼,將會導致嚴重的腦殘。不過,傳說又出了個叫做 “流程優化器” 的東東……(這個故事未完待續)。
其實,這件事困擾的不僅只是 java ,幾乎所有 “有源代碼” 的程序都有這個煩惱。比如,飽受折磨的還有 php, asp 以及 .net。不知道有沒有高人能從 “機器碼” 反編譯出 C 和 C++ 的源程序呢,反正我挺好奇的。不過,話說回來, “沒有源代碼” 的程序,恐怕還真的沒有。保護源代碼,在我們現如今 “處處是山寨,遍地是豺狼” 的產業現狀之下,似乎仍然是個不得不認真對待的事情。
在源代碼保護的問題上,Erlang 的表現又會如何?今天體驗了一把,應該說,設計得很細致,至于說這樣的設計是否能夠完全杜絕源代碼的泄露,這個問題恐怕仍然需要留給 “專家” 們去研究。好吧,口水就噴到這里,下面上干貨。
目前這個階段,對 Erlang 源代碼的保護,主要是在 debug_info 上做手腳,因為,在 debug_info 里面有完整的源代碼,可以極其輕松的從中 “找回” 源碼(兩個語句而已,在官方文檔之中都有例子)。
先看如何從 Erlang 的 beam 文件獲取源代碼。象這樣的一個簡單程序:
-module(a).
-export([test/0]).
test() ->
io:format("source code.~n", []).
帶 debug_info 編譯,并運行之。
$ erlc +debug_info a.erl
$ erl -s a test -s c q -noshell
source code.
$
我們可以這樣還原它的源碼:
$ erl
1> {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(code:which(a), abstract_code]).
{ok,{a,[{abstract_code,
{raw_abstract_v1,
[{attribute,1,file,{"./a.erl",1}},
{attribute,1,module,a},
{attribute,3,export,[{test,0}]},
{function,5,test,0,
[{clause,5,[],[],[{call,6,{remote,...},[...]}]}]},
{eof,7}]}}]}}
2> io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).
-file("./a.erl", 1).
-module(a).
-export([test/0]).
test() -> io:format("source code.~n", []).
ok
3>
看,和源碼幾乎完全一致。
那么,如果我們編譯的時候不帶 debug_info 呢?是的,完全可以。不過,如果你想要在這樣的 beam 上執行 debugger 或者 xref 之類的動作,那么,沒有 debug_info 就做不了。天知道我們會不會有需要做 “現場調試” 的時候呢。有沒有既保留 debug_info 又阻止其他人通過 debug_info 來得到源碼的辦法呢?有,那就是——加密 debug_info 。
首先建立一個 ~/.erlang.crypt 文件,內容如下:
$ cat ~/.erlang.crypt
[{debug_info, des3_cbc, [], "my_source_code_secret_key"}].
這里的 “my_source_code_secret_key” 就被用來生成對 debug_info 加密的密鑰。用 encrypt_debug_info 參數編譯,并運行之。
$ erlc +encrypt_debug_info a.erl
$ erl -s a test -s c q -noshell
source code.
現在拿掉 ~/.erlang.crypt (模擬生產機環境),看看能否正常運行。
$ mv ~/.erlang.crypt ~/.erlang.old.crypt
$ erl -s a test -s c q -noshell
source code.
運行沒問題。此時,是否還能還原源碼呢。
$ erl
1> beam_lib:chunks(code:which(a), [abstract_code]).
{error,beam_lib,
{key_missing_or_invalid,"./a.beam",abstract_code}}
這正是我們想要的。
比如說,假如某日我們需要在這臺生產機上做 “現場調試”,那就再加上 ~/.erlang.crypt 文件。作為驗證,我們再執行一次還原源碼的操作。
$ mv ~/.erlang.old.crypt ~/.erlang.crypt
$ erl
1> {ok,{_,[{abstract_code,{_,AC}}]}} = beam_lib:chunks(code:which(a), abstract_code]).
{ok,{a,[{abstract_code,
{raw_abstract_v1,
[{attribute,1,file,{"./a.erl",1}},
{attribute,1,module,a},
{attribute,3,export,[{test,0}]},
{function,5,test,0,
[{clause,5,[],[],[{call,6,{remote,...},[...]}]}]},
{eof,7}]}}]}}
2> io:fwrite("~s~n", [erl_prettypr:format(erl_syntax:form_list(AC))]).
-file("./a.erl", 1).
-module(a).
-export([test/0]).
test() -> io:format("source code.~n", []).
ok
3>
看 debug_info 還原出來了。
我們藏在 debug_info 中的源碼是被 des3_cbc 算法保護起來的,有興趣的童鞋可以去 wiki 百科了解它的加密強度,解開它的關鍵是 ~/.erlang.crypt 文件,只要它不泄露,那么在生產環境下,我們的代碼就仍然是安全的,也就是說,就算這臺機器被黑掉了,也還原不出源碼(如果我說錯了,請糾正我),而且只要你持有 .erlang.crypt 文件,(在需要的時候)仍然可以進行調試。
實驗之前,確實沒想到 Erlang 還設計了這么一個機制,挺細致的。需要說明的是,上述方案是對 beam 中的 debug_info 進行了加密,從而阻止其他人從中獲取源碼,至于是否還有其他的還原源碼的可能,目前還不是很清楚。比如,理論上,是否有可能通過 beam 之中的 op code 反編譯出原始的 source code 呢?對于這個話題,如果有童鞋知道,請不吝賜教。
posted on 2009-09-07 16:18
暗夜教父 閱讀(707)
評論(0) 編輯 收藏 引用