转自:http://www.fly63.com/article/detial/45前端
使用js的混淆加密,其目的是为了保护咱们的前端代码逻辑,对应一些搞技术吃饭的公司来讲,为了防止被竞争对手抓取或使用本身的代码,就会考虑如何加密,或者混淆js来达到代码保护。node
在web系统发展早期,js在web系统中承担的职责并很少,只是简单的提交表单,js文件很是简单,也不须要任何的保护。web
随着js文件体积的增大,为了缩小js体积,加快http传输速度,开始出现了不少对js的压缩工具,好比 uglify、compressor、clouser。。。它们的工做主要是chrome
· 合并多个js文件后端
· 去除js代码里面的空格和换行数组
· 压缩js里面的变量名浏览器
· 剔除掉注释安全
压缩后的代码网络
虽然压缩工具出发点都是为了减小js文件的体积,可是人们发现压缩替换后的代码已经比源代码可读性差了不少,间接起到了代码保护的做用,因而压缩js文件成为了前端发布的标配之一。可是后来市面上主流浏览器chrome、Firefox等都提供了js格式化的功能,可以很快的把压缩后的js美化,再加上现代浏览器强大的debug功能,单纯压缩过的js代码对于真正怀有恶意的人,已经不能起到很好的防护工做,出现了"防君子不防小人"的尴尬局面。ide
chrome开发者工具格式化以后的代码
而在web应用愈来愈丰富的今天,伴随着浏览器性能和网速的提升,js承载了更多的工做,很多后端逻辑都在向前端转移,与此同时也让更多的不法分子有隙可乘。在web模型中,js每每是不法分子的第一个突破口。知晓了前端逻辑,不法分子能够模拟成一个正常的用户来实施本身的恶意行为。因此,在不少登陆、注册、支付、交易等等页面中,关键业务和风控系统依赖的js都不但愿被人轻易的破解,js混淆应运而生。
这是一个老生常谈的问题。实际上,代码混淆早就不是一个新鲜的名词,在桌面软件时代,大多数的软件都会进行代码混淆、加壳等手段来保护本身的代码。Java和.NET都有对应的混淆器。黑客们对这个固然也不陌生,许多病毒程序为了反查杀,也会进行高度的混淆。只不过因为js是动态脚本语言,在http中传输的就是源代码,逆向起来要比打包编译后的软件简单不少,不少人所以以为混淆是画蛇添足。
.NET混淆器dotFuscator
其实正是由于js传输的就是源代码,咱们才须要进行混淆,暴露在外的代码没有绝对的安全,可是在对抗中,精心设计的混淆代码可以给破坏者带来不小的麻烦,也可以为防守者争取更多的时间,相对于破解来讲,混淆器规则的更替成本要小得多,在高强度的攻防中,能够大大增长破解者的工做量,起到防护做用。从这个角度来说,关键代码进行混淆是必不可少的步骤。
js混淆器大体有两种:
· 经过正则替换实现的混淆器
· 经过语法树替换实现的混淆器
第一种实现成本低,可是效果也通常,适合对混淆要求不高的场景。第二种实现成本较高,可是更灵活,并且更安全,更适合对抗场景,我这里主要讲一下第二种。基于语法层面的混淆器其实相似于编译器,基本原理和编译器相似,咱们先对编译器作一些基本的介绍。
名词解释
token: 词法单元,也有叫词法记号的,词法分析器的产物,文本流被分割后的最小单位。
AST: 抽象语法树,语法分析器的产物,是源代码的抽象语法结构的树状表现形式。
编译器VS混淆器
编译器工做流程
简单的说,当咱们读入一段字符串文本(source code),词法分析器会把它拆成一个一个小的单位(token),好比数字1 是一个token, 字符串'abc'是一个token等等。接下来语法分析器会把这些单位组成一颗树状结构(AST),这个树状结构就表明了token们的组成关系。好比 1 + 2 就会展现成一棵加法树,左右子节点分别是token - 1 和token - 2 ,中间token表示加法。编译器根据生成的AST转换到中间代码,最终转换成机器代码。
对编译器更多细节感兴趣的同窗能够移步龙书:编译原理
混淆器工做流程
编译器须要把源代码编译成中间代码或者机器码,而咱们的混淆器输出其实仍是js。因此咱们从语法分析以后往下的步骤并不须要。想一想咱们的目标是什么,是修改原有的js代码结构,在这里面这个结构对应的是什么呢?就是AST。任何一段正确的js代码必定能够组成一颗AST,一样,由于AST表示了各个token的逻辑关系,咱们也能够经过AST反过来生成一段js代码。因此,你只须要构造出一颗AST,就能生成任何js代码!混淆过程如上右图所示
经过修改AST生成一个新的AST,新的AST就能够对应新的JavaScript代码。
规则设计
知道了大体的混淆流程,最重要的环节就是设计规则。咱们上面说了,咱们须要生成新的AST结构意味着会生成和源代码不同的js代码,可是咱们的混淆是不能破坏原有代码的执行结果的,因此混淆规则必须保证是在不破坏代码执行结果的状况下,让代码变得更难以阅读。
具体的混淆规则各位能够自行根据需求设计,好比拆分字符串、拆分数组,增长废代码等等。
参考:提供商业混淆服务的jscramble的混淆规则
实现
不少人看到这里就望而却步,由于词法分析和文法分析对编译原理要求较高。其实这些如今都有工具能够帮助搞定了,借助工具,咱们能够直接进行最后一步,对AST的修改。
市面上JavaScript词法和文法分析器有不少,好比其实v8就是一个,还有mozilla的SpiderMonkey, 知名的esprima等等,我这里要推荐的是uglify,一个基于nodejs的解析器。它具备如下功能:
· parser,把 JavaScript 代码解析成抽象语法树
· code generator,经过抽象语法树生成代码
· scope analyzer,分析变量定义的工具
· tree walker,遍历树节点
· tree transformer,改变树节点
对比下我上面给出的混淆器设计的图,发现其实只须要修改语法树 这一步本身完成。
实例
说了这么多,可能不少人仍是一头雾水,为了帮助各位理解,我准备了一个简单的例子,假设咱们的混淆规则是想把 var a = 1; 中的数字1换成16进制,咱们该如何设计混淆器呢。首先对源代码作词法分析和语法分析,uglify一个方法就搞定了,生成一颗语法树,咱们须要作的就是找到语法树中的数字而后修改为16进制的结果,以下图所示:
实例代码:
var UglifyJS = require("uglify-js");
var code = "var a = 1;";
var toplevel = UglifyJS.parse(code); //toplevel就是语法树
var transformer = new UglifyJS.TreeTransformer(function (node) {
if (node instanceof UglifyJS.AST_Number) { //查找须要修改的叶子节点
node.value = '0x' + Number(node.value).toString(16);
return node; //返回一个新的叶子节点 替换原来的叶子节点
};
});
toplevel.transform(transformer); //遍历AST树
var ncode = toplevel.print_to_string(); //从AST还原成字符串
console.log(ncode); // var a = 0x1;
上面的代码很简单,首先经过parse方法构建语法树,而后经过TreeTransformer遍历语法树,当遇到节点属于UglifyJS.AST_Number类型(全部的AST类型见ast),这个token具备一个属性 value 保存着数字类型的具体值,咱们将其改为16进制表示,而后 return node 就会用新的节点代替原来的节点。
效果展现
贴一个我本身设计的混淆器混淆先后代码:
因为增长了废代码,改变了原有的AST,混淆对性能确定会形成必定的影响,可是咱们能够经过规则来控制影响的大小。
· 减小循环混淆,循环太多会直接影响代码执行效率
· 避免过多的字符串拼接,由于字符串拼接在低版本IE下面会有性能问题
· 控制代码体积,在插入废代码时应该控制插入比例,文件过大会给网络请求和代码执行都带来压力
咱们经过必定的规则彻底能够把性能影响控制在一个合理的范围内,实际上,有一些混淆规则反而会加快代码的执行,好比变量名和属性名的压缩混淆,会减少文件体积,好比对全局变量的复制,会减小做用域的查找等等。在现代浏览器中,混淆对代码的影响愈来愈小,咱们只须要注意合理的混淆规则,彻底能够放心的使用混淆。
混淆的目的是保护代码,可是若是由于混淆影响了正常功能就舍本逐末了。
因为混淆后的AST已经和原AST彻底不一样了,可是混淆后文件的和原文件执行结果必须同样,如何保证既兼顾了混淆强度,又不破坏代码执行呢?高覆盖的测试必不可少:
· 对本身的混淆器写详尽的单元测试
· 对混淆的目标代码作高覆盖的功能测试,保证混淆先后代码执行结果彻底同样
· 多样本测试,能够混淆单元测试已经完备了的类库,好比混淆 Jquery 、AngularJS 等,而后拿混淆后的代码去跑它们的单元测试,保证和混淆前执行结果彻底同样
· 可信web系统是咱们的愿景
· 可信web系统离不开可信的前端环境
· js混淆在对抗中必不可少
· 实现一款本身的混淆器并无那么难
· 混淆器对性能的影响是可控的