JavaScript高手进阶:详解Eval加密

在JavaScript编程中,涉及到代码加密,在混淆加密时代之前,用的最多的应该是种Eval加密。
加密后的特征是以:eval(function(p,a,c,k,e,r)为代码开始,相信很多人都见过这种代码。

Eval加密效果例程

w2sfoot - JavaScript高手进阶:详解Eval加密 - 图1
这是一种非常古老的技术。早在约2004年,一名南非的JavaScript程序员dean.edwards发明了这种加密技术:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图2
本文将探索该加密技术的实现原理,并给出解密方法。
首先,直接上源码,该源码为Eval加密的变种,加密效果一样。

Eval加密完整源码示例

  1. a=62;
  2. function encode(js_code) {
  3. var code = js_code;
  4. code = code.replace(/[\r\n]+/g, '');
  5. code = code.replace(/'/g, "\\'");
  6. var tmp = code.match(/\b(\w+)\b/g);
  7. tmp.sort();
  8. var dict = [];
  9. var i, t = '';
  10. for(var i=0; i<tmp.length; i++) {
  11. if(tmp[i] != t) dict.push(t = tmp[i]);
  12. }
  13. var len = dict.length;
  14. var ch;
  15. for(i=0; i<len; i++) {
  16. ch = num(i);
  17. code = code.replace(new RegExp('\\b'+dict[i]+'\\b','g'), ch);
  18. if(ch == dict[i]) dict[i] = '';
  19. }
  20. return "eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}(" + "'"+code+"',"+a+","+len+",'"+ dict.join('|')+"'.split('|'),0,{}))";
  21. }
  22. function num(c) {
  23. return(c<a?'':num(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36));
  24. }
  25. console.log(encode("var str ='jshaman.com'; console.log(str); var str ='jshaman.com'; console.log(str);"));

执行,看加密效果。

执行效果

w2sfoot - JavaScript高手进阶:详解Eval加密 - 图3
运行加密后的代码,以测试正确性:
在NodeJS环境中运行:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图4
正确输出,与源代码“var str =’jshaman.com’; console.log(str); var str =’jshaman.com’; console.log(str);”实现的效果一致。
接下来,详解加密代码,剖析其加密原理。

Eval加密详细剖析

用于测试的代码仅一行:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图5
在之前展示的代码中增加console.log()用于调试分析:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图6
用于去除回车换行,以及变单引号为斜杠加单引号的两句正则表达式:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图7
此时输出:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图8
Match用于在字符串中查找指定字符,返回为数组。
正则表达式中的\b匹配一个单词边界,也就是指单词和空格间的位置,或换行以后的起始位置。
\w匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图9
运行到这里,输出:[‘var’, ‘str’, ‘jshaman’, ‘com’, ‘console’, ‘log’, ‘str’, ‘var’, ‘str’, ‘jshaman’, ‘com’, ‘console’, ‘log’, ‘str’],可以理解为将代码进行了分词。
接下来的for循环,用于除重,可以达到压缩代码的目的:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图10
重点是dict.push,用的很精妙。(啊?
运行时输出:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图11
可以看到,原本14个数组成员,去重后,缩减成了6个成员。
接下来,是加密的重点部分:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图12
在for循环中,使用正则表达式,将原代码中的关键字替换。
替换的内容来自于num函数的返回值:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图13
这个num函数,返回的是:空格或参数除以62的整数结果加参数除以62的除数大于35时数字编码对应的字符或以36为基数的toString()字符。这也就是:Base62算法!
接下来再用正则表达式,结合base62算法,替换代码中每个字符串部分为dict中的数组序号。
看这时输出,在循环中原始代码已逐渐变为加密形式:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图14
最后,再与Eval语句拼接,实现解密并运行:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图15
这便得到了最终形态:

  1. eval(function(p,a,c,k,e,d){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)d[e(c)]=k[c]||e(c);k=[function(e){return d[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('5 4 =\'2.0\'; 1.3(4); 5 4 =\'2.0\'; 1.3(4);',62,6,'com|console|jshaman|log|str|var'.split('|'),0,{}))

w2sfoot - JavaScript高手进阶:详解Eval加密 - 图16
到此,已经完成加密,生成了加密代码。
接下来,再对解密方法进行说明,将加密代码还原。

解密方法

解密之前,先对加密代码进行美化,手动换行、增加缩进,增加代码可读性:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图17
美化后的代码,可以看到大体分为几部分:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图18

  1. E函数,是base62的解码部分。
  2. while循环是关键字替换,还原出原始代码。
  3. 最下方的字符是加密后的base62编码,以及字符串数组等,是做为参数传递给匿名function(p,a,c,k,e,d)。

为了理解的更清晰,如上图,增加console.log语句,打印出各参数。
执行这段代码,输出如下:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图19
在图中可以看到,变量p中存储的便是原始代码。
到此,等于已经完成解密。
另有一个更简单的方法是,见到此类代码,将起始处的eval改为console.log或document.write或alert都可直接输出源始代码(我之前刚好碰到混淆的JS,用的是“document.write”搭配调试的控制台查看)
如下图,源码被输出:
w2sfoot - JavaScript高手进阶:详解Eval加密 - 图20
可见此种加密方法,虽然看起来够吓人,但破解十分容易。
在之前的文章《JavaScript黑暗技巧:变异的Eval》中,曾讲过对这种eval加密增强的办法:采用变异eval名称、用JShaman对加密代码二次混淆加密,可以极大的提升保护强度,实现JS代码的不可逆加密。