|
过 |
去很长时间以来,在网页中使用的脚本是明文的,这不仅造成了脚本的知识产权无法保护,也带来了一些安全问题:一些没有经验的ASP(指Active Server Pages,下同)管理员可能会把一些关键的代码直接写入ASP页面中,而这些代码一旦由于某种原因被居心不良的用户看到,其后果可想而知。针对这种情况,Microsoft公司在推出Microsoft Internet Explorer的同时在其Scripting虚拟机中引入了一种新的技术:Script Encoding。这种技术通过加密叫本来避免上面提到的那些问题。
然而有意思的是,Microsoft在推出Script Encoder的同时,在其文档中又特别提到:
|
注意,这种编码只能防止别人在无意中查看到您的代码,并不能防止蓄意黑客查看您的编码内容及其方法。 |
这句话显然是说,如果愿意的话,破解经过编码的脚本并不需要花费太大的力气。我们不妨推测一下Microsoft引入编码脚本的目的:防止那些初级用户看到代码,而对于那些有能力破解经过编码的脚本的用户而言,与其破解编码脚本,不如自己去写。这意味着,事实上如果有人想看到经过Script Encoder加密的脚本的原始内容,并不需要花费太多的功夫,尽管根据Microsoft提供的资料,经过加密的脚本是由单独的脚本引擎执行的。
考虑到Microsoft所声称的:Script Encoder可以防止脚本被随意篡改——即使微小的变化也可以导致整个编码代码块无法运行——以及“并不阻止有预谋的黑客查看脚本”,我们不妨作一个这样的假设:Microsoft使用了一种很简单的加密算法(几乎可以肯定不是非对称加密算法,因为那样会很慢,而且也确实没有必要),而这种算法还能够检验数据的完整性(或者配合数据完整性检验算法,如MD5或SHA1等等)。
我们不妨以Microsoft提供的例子[2]做一下编码操作:
|
<SCRIPT LANGUAGE="JScript"> //Copyright? 1998. ZYX Productions. All rights reserved. //**Start Encode** //Your script here </SCRIPT> |
结果:
|
<SCRIPT LANGUAGE="JScript.Encode"> //Copyright? 1998. ZYX Productions. All rights reserved. //**Start Encode**#@~^GgAAAA==@#@&z&IW!DPkmMrwDP4+M+@#@&tAYAAA==^#~@</SCRIPT> |
(由于排版原因发生折行,黑体字部分实际上是一行)
根据Microsoft的技术文档,Jscript.Encode是由单独的虚拟机[3]执行的,也就是说,并不存在解密的过程。
进行一番实验:
|
编码前 |
编码后 |
|
Foo |
#@~^CwAAAA==@#@&sGK@#@&UgEAAA==^#~@ |
|
Fou |
#@~^CwAAAA==@#@&sG!@#@&WAEAAA==^#~@ |
|
FooFoo FooFoo |
#@~^GAAAAA==@#@&sGKsKW@#@&sKGsKW@#@&1QQAAA==^#~@ |
经过观察,不难看出,@#@&代表回车这一结论(并且,由于实际使用的是Windows文本而不是Unix或Mac文本,我们可以暂时假定@#代表CR,@&代表LF)。并且,文字所处的位置对于编码的结果有影响(注意到第一个FooFoo变成了sGKsKW,而第二次它变成了sKGsKW)。
为了进一步了解编码算法,我们编码200个小写字母a,其结果是[注,折行仅仅是为了排版,正常编码中没有折行]:
| #@~^zAAAAA==CmlCmlmllmlmClmlClmlCCmllmClmllmCClmlmlClCCml |
仔细观察上面的文字,可以看出a对应3种形式,C,m,l. 并且,这形式和文字的位置有关系,并且,如上面用下划线标出的,这密文每64字节重复一次。经过试验发现,Script Encoder并不编码中文,对于那些“特殊字符”,也只是做类似CR、LF的编码方式的处理。这样,Script Encoder编码处理的脚本中,只有ASCII 32~ASCII 126,以及ASCII 9(TAB符)被编码,这编码与一个长度为64的线性表有关。个人推测,对于ASCII 9的编码的目的在于加强编码的强度,因为通常脚本编写人员写的代码中ASCII 9的出现频率很高。这样,无论Microsoft Script Encoder采用了什么样的加密算法,我们都可以用替换编码法来等效。
令C=1, m=2, l=0, 我们可以得到a的编码表:
|
1, 2, 0, 1, 2, 0, 2, 0, 0, 2, 0, 2, 1, 0, 2, 0, 1, 0, 2, 0, 1, 1, 2, 0, 0, 2, 1, 0, 2, 0, 0, 2, 1, 1, 0, 2, 0, 2, 0, 1, 0, 1, 1, 2, 0, 1, 0, 2, 1, 0, 2, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 1, 0, 2 |
通过实验发现,事实上对于所有的字符,编码表是完全相同的。基于以上的事实,我们可以构造下面的程序:
|
char EncoderTable[64] = {1, 2, 0, 1, 2, 0, 2, 0, 0, 2, 0, 2, 1, 0, 2, 0, 1, 0, 2, 0, 1, 1, 2, 0, 0, 2, 1, 0, 2, 0, 0, 2, 1, 1, 0, 2, 0, 2, 0, 1, 0, 1, 1, 2, 0, 1, 0, 2, 1, 0, 2, 0, 1, 1, 2, 0, 0, 1, 1, 2, 0, 1, 0, 2}; // 替换索引 char SubTable[128][3] = {....}; // 替换表 int Encode (char c) { static int nPosition=0; nPosition++; nPosition%=64; if(!IsSpecial(c)){ return (SubTable[c][EncoderTable[nPosition]]); } else {return ProcessSpecial(c);} } |
用int作为返回类型的主要原因是有时需要返回两个字节。ProcessSpecial函数用来处理那些特殊字符。SubTable可以通过对整个字符集编码得到。
通过将4个字符加上用于凑齐64字节的某个特定字符,容易得到Microsoft Script Encoder的编码集:(ASCII 9,32-126)
|
d7i P~, "Ze JEr a:[ ^yf ]Yu ['L BvE `cv #b* eMC _Q3 ~SB OR R c z&J !TZ Fq8 +y &f2 c*W *Xl v+ G{F %0R ,1O )l= iIp @!@!@! 'x{ @*@*@* g_Q @$@$@$ b)z A$~ Z/; f9G 23A sow M!V Cu_ q(& 9Bx |Fn SJd H\t 1Hg r6} nKh p}5 I]" ?j U KP: ji` .#j q (po 5eI }t\ $,] -w' TDY 7?% {m| =|# lCm 48( m^1 N[9 +n 0W6 oLT t44 krb L%N 3V0 Vs^ :hs xU WGK w2a ;5$ D.M /dk YOD E;! \-7 hAS 6aX XzH y". `P uk- 8N) U=? |
从这个编码集我们可以看出,@ < >也像CR、LF这些特殊字符一样进行了特殊的编码处理(这不难理解,因为@用于表示特殊字符)。而< >这两个字符进行特殊编码,个人猜想是为了便于脚本虚拟机的设计。
以上就是Microsoft Script Encoder使用的加密算法。从这个编码算法来看,它本身并没有防止篡改的功能。加密后文本的前12字节和最后12字节是为脚本虚机提供信息的。经过分析发现,对于已经试验过的脚本来说,开头的#@~和最后的#~@都是一样的。根据这个情况,我们可以推测它们是脚本开始、结束的实际标志。经过试验发现,#@~后面的字符随脚本的长度变化,而最后12字节中#~@前面的内容随脚本的内容而变化。据此可以推测头部的12字节包含的是脚本的长度信息,而最后12字节包含的是脚本的校验信息。这些信息的编码方法还有待研究。
通过分析Microsoft Script Encoder的加密机制,可以得到下面的结论:
(1) 如Microsoft在文档中提到的那样Microsoft Script Encoder使用的加密算法并不是一种强加密算法。因此,它并不能提供特别可靠的安全。但对于一般的应用来说,由于算法简单、效率高,可以满足应用。这提示我们,实现一个系统的时候,应该选择最符合实际需要的算法,而不一定是最“安全”或者“高效”的算法。
(2) 通过结合长度、校验和信息,可以增强本来没有反篡改能力的替换算法的性能,使其具有反篡改能力。这种方法,在实际设计应用系统的时候无疑是非常有用的。
(3) 根据本文提供的加密算法,可以很容易地写出Microsoft Script Encoder的解密程序。限于篇幅,本文对于解密算法不再赘述。
