JavaScript高手进阶:详解Eval加密

2022-04-05
1057

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

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

Eval加密效果例程:

95e16b0bdad24d92848da12fc7fdd61f.png

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

cef58c37330b45968975866a385d7809.png

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

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

执行,看加密效果。

执行效果:

20eac1123a324ad7a81d6e8e183e8e97.png

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

在NodeJS环境中运行:

5ef3c86176234a28a4dc02257dc4c9b2.png

正确输出,与源代码“var str ='jshaman.com'; console.log(str); var str ='jshaman.com'; console.log(str);”实现的效果一至。

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

Eval加密详细剖析:

用于测试的代码仅一行:

9f2c938bbe6941f0ade2eb3a5f10889c.png

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

2fd34c49c49e41e7b196c0af37aafb69.png

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

fd84a690524e470f90c0ce8534f634c8.png

此时输出:

583f649578404dc0ad1ef44c49af4a05.png

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

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

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

9d59c6091f374992ab4094069570a20a.png

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

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

67cb6d2373014fc4bf6ffbc1f3dc0f1a.png

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

运行时输出:

882f1bab21ed45859bfaac8ab6309617.png

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

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

303f4a95c54347dbb6174f737f5a4a0d.png

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

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

1fd592c172a6427d9da35acc73b21510.png

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

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

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

a98c5e14bebc463790cf76089675af33.png

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

76597e7bfcbf48f1bff34a64550bfbab.png

这便得到了最终形态:

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,{}))

4603d0ef8da34f62a9c3533f62a6584c.png

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

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

解密方法:

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

861dd6b27ee446b89468f5ed2d955fbd.png

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

d9eb23d874bd458db685a89111be0219.png

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

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

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

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

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

e076840090d342708c9649cca5a66b13.png

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

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

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

如下图,源码被输出:

c32463c292574a339409f9a2b222f2f4.png

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

转载时必须以链接形式注明原始出处及本声明

扫描关注公众号