JavaScript高手进阶:详解Eval加密
在JavaScript编程中,涉及到代码加密,在混淆加密时代之前,用的最多的应该是种Eval加密。
加密后的特征是以:eval(function(p,a,c,k,e,r)为代码开始,相信很多人都见过这种代码。
Eval加密效果例程:
这是一种非常古老的技术。早在约2004年,一名南非的JavaScript程序员dean.edwards发明了这种加密技术。
本文将探索该加密技术的实现原理,并给出解密方法。
首先,直接上源码,该源码为Eval加密的变种,加密效果一样。
Eval加密完整源码示例:
a=62; function encode(js_code) { var code = js_code; code = code.replace(/[\r\n]+/g, ''); code = code.replace(/'/g, "\\'"); var tmp = code.match(/\b(\w+)\b/g); tmp.sort(); var dict = []; var i, t = ''; for(var i=0; i<tmp.length; i++) { if(tmp[i] != t) dict.push(t = tmp[i]); } var len = dict.length; var ch; for(i=0; i<len; i++) { ch = num(i); code = code.replace(new RegExp('\\b'+dict[i]+'\\b','g'), ch); if(ch == dict[i]) dict[i] = ''; } 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,{}))"; } function num(c) { return(c<a?'':num(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36)); } console.log(encode("var str ='jshaman.com'; console.log(str); var str ='jshaman.com'; console.log(str);"));
执行,看加密效果。
执行效果:
运行加密后的代码,以测试正确性:
在NodeJS环境中运行:
正确输出,与源代码“var str ='jshaman.com'; console.log(str); var str ='jshaman.com'; console.log(str);”实现的效果一至。
接下来,详解加密代码,剖析其加密原理。
Eval加密详细剖析:
用于测试的代码仅一行:
在之前展示的代码中增加console.log()用于调试分析:
用于去除回车换行,以及变单引号为斜杠加单引号的两句正则表达式:
此时输出:
Match用于在字符串中查找指定字符,返回为数组。
正则表达式中的\b匹配一个单词边界,也就是指单词和空格间的位置,或换行以后的起始位置。
\w匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。
运行到这里,输出:['var', 'str', 'jshaman', 'com', 'console', 'log', 'str', 'var', 'str', 'jshaman', 'com', 'console', 'log', 'str'],可以理解为将代码进行了分词。
接下来的for循环,用于除重,可以达到压缩代码的目的:
重点是dict.push,用的很精妙。
运行时输出:
可以看到,原本14个数组成员,去重后,缩减成了6个成员。
接下来,是加密的重点部分:
在for循环中,使用正则表达式,将原代码中的关键字替换。
替换的内容来自于num函数的返回值:
这个num函数,返回的是:空格或参数除以62的整数结果加参数除以62的除数大于35时数字编码对应的字符或以36为基数的toString()字符。这也就是:Base62算法!
接下来再用正则表达式,结合base62算法,替换代码中每个字符串部分为dict中的数组序号。
看这时输出,在循环中原始代码已逐渐变为加密形式:
最后,再与Eval语句拼接,实现解密并运行:
这便得到了最终形态:
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,{}))
到此,已经完成加密,生成了加密代码。
接下来,再对解密方法进行说明,将加密代码还原。
解密方法:
解密之前,先对加密代码进行美化,手动换行、增加缩进,增加代码可读性:
美化后的代码,可以看到大体分为几部分:
E函数,是base62的解码部分。
while循环是关键字替换,还原出原始代码。
最下方的字符是加密后的base62编码,以及字符串数组等,是做为参数传递给匿名function(p,a,c,k,e,d)。
为了理解的更清晰,如上图,增加console.log语句,打印出各参数。
执行这段代码,输出如下:
在图中可以看到,变量p中存储的便是原始代码。
到此,等于已经完成解密。
另有一个更简单的方法是,见到此类代码,将起始处的eval改为console.log或document.write或alert都可直接输出源始代码。
如下图,源码被输出:
可见此种加密方法,虽然看起来够吓人,但破解十分容易。
扫描关注公众号