天天看点

JavaScript高手进阶:详解Eval加密

作者:修丹道的程序猿

在JavaScript编程中,涉及到代码加密,在混淆加密时代之前,用的最多的应该是种Eval加密。

加密后的特征是以:eval(function(p,a,c,k,e,r)为代码开始,相信很多人都见过这种代码。

Eval加密效果例程:

JavaScript高手进阶:详解Eval加密
这是一种非常古老的技术。早在约2004年,一名南非的JavaScript程序员dean.edwards发明了这种加密技术。
JavaScript高手进阶:详解Eval加密

本文将探索该加密技术的实现原理,并给出解密方法。

首先,直接上源码,该源码为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);"));

执行,看加密效果。

执行效果:

JavaScript高手进阶:详解Eval加密

运行加密后的代码,以测试正确性:

在NodeJS环境中运行:

JavaScript高手进阶:详解Eval加密
正确输出,与源代码“var str ='jshaman.com'; console.log(str); var str ='jshaman.com'; console.log(str);”实现的效果一至。

接下来,详解加密代码,剖析其加密原理。

Eval加密详细剖析:

用于测试的代码仅一行:

JavaScript高手进阶:详解Eval加密

在之前展示的代码中增加console.log()用于调试分析:

JavaScript高手进阶:详解Eval加密

用于去除回车换行,以及变单引号为斜杠加单引号的两句正则表达式:

JavaScript高手进阶:详解Eval加密

此时输出:

JavaScript高手进阶:详解Eval加密

Match用于在字符串中查找指定字符,返回为数组。

正则表达式中的\b匹配一个单词边界,也就是指单词和空格间的位置,或换行以后的起始位置。

\w匹配包括下划线的任何单词字符。等价于“[A-Za-z0-9_]”。

JavaScript高手进阶:详解Eval加密

运行到这里,输出:['var', 'str', 'jshaman', 'com', 'console', 'log', 'str', 'var', 'str', 'jshaman', 'com', 'console', 'log', 'str'],可以理解为将代码进行了分词。

接下来的for循环,用于除重,可以达到压缩代码的目的:

JavaScript高手进阶:详解Eval加密

重点是dict.push,用的很精妙。

运行时输出:

JavaScript高手进阶:详解Eval加密

可以看到,原本14个数组成员,去重后,缩减成了6个成员。

接下来,是加密的重点部分:

JavaScript高手进阶:详解Eval加密

在for循环中,使用正则表达式,将原代码中的关键字替换。

替换的内容来自于num函数的返回值:

JavaScript高手进阶:详解Eval加密

这个num函数,返回的是:空格或参数除以62的整数结果加参数除以62的除数大于35时数字编码对应的字符或以36为基数的toString()字符。这也就是:Base62算法!

接下来再用正则表达式,结合base62算法,替换代码中每个字符串部分为dict中的数组序号。

看这时输出,在循环中原始代码已逐渐变为加密形式:

JavaScript高手进阶:详解Eval加密

最后,再与Eval语句拼接,实现解密并运行:

JavaScript高手进阶:详解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,{}))
JavaScript高手进阶:详解Eval加密

到此,已经完成加密,生成了加密代码。

接下来,再对解密方法进行说明,将加密代码还原。

解密方法:

解密之前,先对加密代码进行美化,手动换行、增加缩进,增加代码可读性:

JavaScript高手进阶:详解Eval加密

美化后的代码,可以看到大体分为几部分:

JavaScript高手进阶:详解Eval加密

E函数,是base62的解码部分。

while循环是关键字替换,还原出原始代码。

最下方的字符是加密后的base62编码,以及字符串数组等,是做为参数传递给匿名function(p,a,c,k,e,d)。

为了理解的更清晰,如上图,增加console.log语句,打印出各参数。

执行这段代码,输出如下:

JavaScript高手进阶:详解Eval加密

在图中可以看到,变量p中存储的便是原始代码。

到此,等于已经完成解密。

另有一个更简单的方法是,见到此类代码,将起始处的eval改为console.log或document.write或alert都可直接输出源始代码。

如下图,源码被输出:

JavaScript高手进阶:详解Eval加密

可见此种加密方法,虽然看起来够吓人,但破解十分容易。

在之前的文章《JavaScript黑暗技巧:变异的Eval》中,曾讲过对这种eval加密增强的办法:采用变异eval名称、用JShaman对加密代码二次混淆加密,可以极大的提升保护强度,实现JS代码的不可逆加密。有兴趣的朋友,可以翻看那篇文章。

继续阅读