erlang 的源代码保护机制
稍微深入研究过一点 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}}
这正是我们想要的。
$ 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>
比如说,假如某日我们需要在这台生产机上做 “现场调试”,那就再加上 ~/.erlang.crypt 文件。作为验证,我们再执行一次还原源码的操作。
看 debug_info 还原出来了。
我们藏在 debug_info 中的源码是被 des3_cbc 算法保护起来的,有兴趣的童鞋可以去 wiki 百科了解它的加密强度,解开它的关键是 ~/.erlang.crypt 文件,只要它不泄露,那么在生产环境下,我们的代码就仍然是安全的,也就是说,就算这台机器被黑掉了,也还原不出源码(如果我说错了,请纠正我),而且只要你持有 .erlang.crypt 文件,(在需要的时候)仍然可以进行调试。
实验之前,确实没想到 Erlang 还设计了这么一个机制,挺细致的。需要说明的是,上述方案是对 beam 中的 debug_info 进行了加密,从而阻止其他人从中获取源码,至于是否还有其他的还原源码的可能,目前还不是很清楚。比如,理论上,是否有可能通过 beam 之中的 op code 反编译出原始的 source code 呢?对于这个话题,如果有童鞋知道,请不吝赐教。