<返回更多

JS代码安全之路:用JS对JS代码混淆加密

2021-09-03    修丹道的程序猿
加入收藏

作者:JShaman.com:w2sft

内容预告:

本文将实例讲解以下JS代码混淆加密技术:

方法名转义和转码、成员表达式转IIFE、函数标准化、数值混淆、布尔型常量值混淆、二进制表达式转为调用表达式、字符串转Unicode、局部变量变形、屏蔽输出语句,以及:无限断点、时间差检测等反调试方案。

大纲:

理论层面:为什么要对JS代码进行混淆加密?

技术层面:用JS编程实现对JS代码混淆加密。

防逆向措施:检测与对抗。

专业的混淆加密:JShaman。

彩蛋:字节码加密技术。

理论层面:为什么要对JS代码进行混淆加密?

1、问:JS代码需要考虑安全性吗?

答:当然。

2、问:为什么?

答:JS因为应用环境需要,功能设计目的等历史原因,成为了一种代码公开透明的语言。

前端JS代码,直接暴露在浏览器中,任何访问者,都可以随意查看代码。这就导致代码可以被分析、复制、盗用等,进而引发安全问题,如被利用代码bug攻击、揭露功能逻辑、复制出雷同应用等等。

互联网早些年,安全场景如上。而发展到当下,JS的应用范围更加广泛,如NodeJS的兴起,使很多后端服务、产品、项目也应用了JS。

在后端的角度,如果项目或产品,提交给第三方时,是否要交出源码?显然不妥。

假设服务器被入侵,如果部署的后端服务产品源码也是JS明文,那将导致更严重的安全问题。

更多的应用领域,如小程序开发、H5应用,含ThreeJS引擎类游戏,等,都广泛应用了JS。

在所有这些场景中,都不应该忽视JS代码的安全问题,都应该且需要对JS代码进行保护。

3、问:如何让JS代码变的安全?

答:对JS代码进行保护:混淆&加密,使代码不可读。即:它人依然可以看到代码,但看到的是加密的代码、无法理解代码,更无法修改。

深入并精准的说:通过混淆加密,使代码变的难以阅读和理解。可能有人说,混淆后机器能执行,人就能理解,只是需要的时间长短问题。这种极端的说法,从理论上来说没错,如果可以投入足够长的时间,程序员甚至可以直接用0101写代码。而从实际角度而言,一段代码如果保护后分析需要的时长,超过开发需要的时长,保护的目的就达到了,就会劝退99.9999%对它有想法的正常人类。

理论已探讨完毕,接下来步入正题,探索如何对JS代码进行混淆加密,可不仅仅是应用层面,而是全面掌握:会用、知其然,知其所在然,还要动手编码,实现:用JS对JS代码混淆加密。

接下来的内容,将在NodeJS环境中,使用JS编程,实现对JS代码的混淆加密。

技术层面:用JS编程实现对JS代码混淆加密。

技术理论:如何实现?

确定实现方案之前,首先需要排除几种不可用方案:

混淆原理:非replace或regexp方式字符串替换,而是对JS源码进行重编译。从源码,进行词法分析、语法分析、得到AST(抽象语法树),此处是重点,得到AST后,在AST中执行关键混淆加密操作,如:字符算阵列化、字符加密、平展控制流、僵尸代码值入、反调试埋雷、花指令插入等,最后,再将AST重建为JS代码。这样就得到了一份被更改的面目全非的安全JS代码:不可读、不可理解、不可修改、不可还原。

编程现实:用JS对JS代码混淆加密。

由以上的理论可知,重点是混淆加密,而入口点及整体流程框架是AST操作。

JS代码&AST。

在JS引擎之下,代码编译执行大体流程是:

JS代码→AST(抽象语法树)→ByteCode(字节码)→机器码→解释器→执行。

AST设计之初并不是用于对JS代码混淆加密,但AST却很适合这个事情。

基于AST的JS代码混淆加密大体流程:

JS代码→AST→(基于AST的混淆加密)→JS代码。

题外话:能在ByteCode阶段进行加密吗?某些情况下可以,比如NodeJS环境中的JS代码,可以编译为ByteCode。但在前端运行的JS代码,且于DOM有交互的则不理想,小总结而言,有将JS代码进行VM式的加密方法,但通用性较差,使用起来复杂。因为这些弊端,因此,不是普遍性的JS代码保护方案。

注:在本结尾,会有一个彩蛋内容,实例介绍NodeJS字节码生成及运行。

图1,NodeJS字节码效果:

JS代码安全之路:用JS对JS代码混淆加密

 

回到正题,JS代码如何转化为AST?

其实,没有想象中那么复杂。得益于NodeJS成熟的生态,已经有多个已实现模块可以完成这一操作。比较流行的如:esprima、babel,都可以实现对JS代码进行词法分析、语法分析、生成AST、AST操作、从AST再生成JS代码。

用esprima进行JS代码混淆加密。程序框架。

图2、esprima框架demo:

JS代码安全之路:用JS对JS代码混淆加密

 

如图2所示,使用esprima进行JS代码保护的原始功能框架。

代码介绍:

Esprima实现将JS代码转化为AST;

estraverse对AST节点进行遍历,混淆加密的逻辑操作都将在此环节实现;

escodegen则是将操作后的AST转为JS代码输出。

此demo代码未对AST进行任何处理,所以图中右侧的执行结果中可以看到,输出的JS代码与最初代码完全一致。

AST是这样子的。

前面已经对AST进行了说明,AST具体是什么样?

一个方便的办法,是使用astexplorer.NET,可以对输入的代码的AST即时同步显示:

图3、const a=1的AST:

JS代码安全之路:用JS对JS代码混淆加密

 

Demo中使用的一行JS语句:“const a=1”,其AST即如图中所显示。

AST是一个JSON结构。

Program表示程序,子节点body中,是变量定义kind是“const”,字面量是“a”,值是“1”。

看似杂乱,但很规整,细看便不难理解。

demo程序里,在节点操作处可以用console输出AST,与astexplorer输出一至,不过前者更方便些。

图4、在程序中输出AST:

JS代码安全之路:用JS对JS代码混淆加密

 

借助Esprima修改AST实例:改“==”为“===”。

图5:

JS代码安全之路:用JS对JS代码混淆加密

 

代码如上图,这是一个很简单的示例。

程序中,estraverse对示例代码结点进行处理,当匹配到“==”时,改为“===”。

为了明确修改节点细节,再对前后代码进行分析。由图6、图7看到,差异仅在节点中的operator。

图6、代码中使用“==”:

JS代码安全之路:用JS对JS代码混淆加密

 

图7、代码中使用“===”:

JS代码安全之路:用JS对JS代码混淆加密

 

借助Esprima修改AST实例:把parseInt改为标准语法。

parseInt方法,有两个参数,参数一是要转化的值,参数二是可选择项,是要转化的进制类型。

图8:

JS代码安全之路:用JS对JS代码混淆加密

 

通过astexplorer,先了解parseInt的AST,未使用参数二时,AST如下:

图9:

JS代码安全之路:用JS对JS代码混淆加密

 

如果有第二参数,则AST如下:

图10:

JS代码安全之路:用JS对JS代码混淆加密

 

那么,要将parseInt转为标准形式即是要给只有一个参数的调用增加第二参数。

代码及执行结果如下:

图11:

JS代码安全之路:用JS对JS代码混淆加密

 

因为初入手的原因,以上描述较为细致,后续将简化。

方法名转义和转码。

如:console.log转为console[log]形式。

通过在aspexplorer中比较可知,造成语句形式差异的原因是CallExpression成员中computed属性值的不同。

图12:

JS代码安全之路:用JS对JS代码混淆加密

 

那么,只需修改节对应节点的computed属性值即可:

图13:

JS代码安全之路:用JS对JS代码混淆加密

 

而修改的条件,则是判断AST节点是CallExpression。上面的例子中,也是使用相似的条件判断方法,找出要修改内容相对应的AST节点。

再进一步,将方法名转为十六进制字符,console[log]会成为:console['x6cx6fx67'],以此进一步降低代码可读性。

图14、增加字符串转16进制操作:

JS代码安全之路:用JS对JS代码混淆加密

 

例程代码输出为:

图15:

JS代码安全之路:用JS对JS代码混淆加密

 

运行混淆后的代码:

图16:

JS代码安全之路:用JS对JS代码混淆加密

 

从简单的例程,可以初步学习到对AST的操作方法。

接下来,实现一个有点难度的功能。

成员表达式转为IIFE

成员表达式通常指调用对象的成员,例如 console 对象的 log 成员。

IIFE,全称为:Immediately Invoked Function Expression,在JAVAScript编程中,是指:立即调用函数表达式。

为了方便理解,先展示此功能实现后的效果:

图17:

JS代码安全之路:用JS对JS代码混淆加密

 

如上图中,console的log、warn、error方法,以及字符串的toUperCase()方法,在保护后都会成为匿名自执行的函数,且方法名都以数组化的形式被另外存放,代码相比之前混乱了许多。

图18、IIFE代码执行效果:

JS代码安全之路:用JS对JS代码混淆加密

 

实现方法如下:

架构与之前略有差异,traverse方法改为replace,enter事件改为leave事件,如下图:

图19:

JS代码安全之路:用JS对JS代码混淆加密

 

对变量定义结点,如console.log输出的信息,以及成员函数,如console的log方法进行操作。

图20、改写字符串定义、成员函数调用:

JS代码安全之路:用JS对JS代码混淆加密

 

Add_string函数把字符串信息、方法名,都写入到一个新的字符串数组,并且把方法改为IIFE。

字符串数组建立、方法改为IIFE的具体实现如下图:

图21:

JS代码安全之路:用JS对JS代码混淆加密

 

然后,把新增的数组加入到AST中,最重再重建代码:

图22:

JS代码安全之路:用JS对JS代码混淆加密

 

这样就完成了本例功能。

注:本例仅供功能演示,尚有不严谨的逻辑,比如成员方法IIFE化之前,除应该判断node.type为MemberExpression,还应排除节点computed为true的情况,否则代码执行会发生错误。

正如前文中所述,能对AST进行操作的模块不止esprima,babel也是个很好的选择。

接下来的例子,将使用babel来完成。

Babel的使用方式与esprima极为相似,其代码框架如下:

图23:

JS代码安全之路:用JS对JS代码混淆加密

 

同样是:JS代码→AST→节点处理→JS代码。

用Babel修改AST实例:去除代码中的console.log输出语句。

代码如下图所示:

图24、用Babel在AST中去除console.log节点:

JS代码安全之路:用JS对JS代码混淆加密

 

匹配AST中的成员操作节点,且满足条件callee的对像名为console,属性方法名为log,如检测掉,则remove该节点。

运行效果如下图所示,测试代码中含有console.log,修改后输出中已经被去除。

图25:

JS代码安全之路:用JS对JS代码混淆加密

 

严谨的考虑的话,需要注意对象挂载的识别,如global.console.log,此时remove则会剩下global,将导致语法错误,因此还应该判断父节点类型来排除这种情况。

指定局部变量变形

图26、对min、number两个局部变量变形:

JS代码安全之路:用JS对JS代码混淆加密

 

相当于是可设定、可配置的对某些变量进行变形。

反向思考,也可以排除对某些变量的处理,等同于白名单,类似于JShaman平台中的“保留字”功能。

删除代码中的空行。

图27:

JS代码安全之路:用JS对JS代码混淆加密

 

EmptyStatement表示空语句AST节点。

字符串转Unicode。

图28:

JS代码安全之路:用JS对JS代码混淆加密

 

代码及执行结果如上图,原理为:判断节字符串字面量节点是否为Unicode格式,如不是则转为Unicode。

在这几个例子中,可看到与esprima的差异,esprima使用的是enter、leave方法,Babel中是直接对要处理的节点类型操作,如上图中的StringLiteral。

更条理化的写法,上面的代码可以修改如下,这个方法被称为Babel-plugin(插件):

图29:

JS代码安全之路:用JS对JS代码混淆加密

 

二进制表达式转为调用表达式

即BinaryExpression节点转为CallExpression。

先看效果:

图30,左侧为二进制表达式,右侧为调用表达式:

JS代码安全之路:用JS对JS代码混淆加密

 

二进制表达式AST形式:

JS代码安全之路:用JS对JS代码混淆加密

 

实现代码:

图31:

JS代码安全之路:用JS对JS代码混淆加密

 

调用表达式AST形式:

图32:

JS代码安全之路:用JS对JS代码混淆加密

 

代码中的这部分,即是将二进制表达式转化为调用表达式:

图33:

JS代码安全之路:用JS对JS代码混淆加密

 

布尔型常量值混淆

代码及效果如下图:

图34:

JS代码安全之路:用JS对JS代码混淆加密

 

数值混淆

代码及效果如下图:

图35:

JS代码安全之路:用JS对JS代码混淆加密

 

JS代码混淆加密,虽不至博大精深,但也属于高段位技术。

在此分享部分浅显方案,以展现其实用效果,用于说明混淆加密手段对于JS代码加固的有效性。此外,还有更多高端的防护手段,如JShaman应用的:平展控制流、时间限制、域名锁定、僵尸代码植入等。

图36、JShaman的JS代码保护配置功能:

JS代码安全之路:用JS对JS代码混淆加密

 

防逆向措施:检测与对抗。

对JS代码进行混淆加密之后,代码安全度得到相当的加强,但还能更进一步,为了防止不法者进行逆向分析、破解,可在代码中加入防破解对抗功能。这也是被JShaman应用的方案。

JS当中有一个debugger指令,当处于调试工具中时,如在浏览器中,会形成断点,使调试中断,利用此特性,在程序中加入无限的debugger,可使代码无法被调试。

图37、每100毫秒一个断点:

JS代码安全之路:用JS对JS代码混淆加密

 

浏览器中执行效果如下,当打开“调试器”时,程序会不停的中断,导致无法跟踪代码:

图38:

JS代码安全之路:用JS对JS代码混淆加密

 

时间差检测。

代码及执行效果如下图所示:

图39:

JS代码安全之路:用JS对JS代码混淆加密

 

检测原理是:

在代码中加入console.log输出和console.clear语句,未在调试工具中时,这两句代码执行是不需渲染显示的,执行耗时短,但假如在浏览器中打开了开发者工具,则会因为显示输出并清除的操作而消耗较多时间,这会被程序察觉出耗时异常,从而检测出是在被调试。

专业的混淆加密:JShaman

本文讲述了部分JS代码混淆加密技术及实现,更多更专业的防护方案未有尽述,这里再展示一段经JShaman保护的代码,领略专业级的JS代码安全。

图40、测试代码准备进行混淆加密:

JS代码安全之路:用JS对JS代码混淆加密

 

图41、保护选项设置:

JS代码安全之路:用JS对JS代码混淆加密

 

图42、混淆加密后的JS代码:

JS代码安全之路:用JS对JS代码混淆加密

 

彩蛋:字节码加密技术。

提示:JS字节码(ByteCode)加密技术,理论可行,但通用性较差,在此仅做技术介绍,不推荐做为项目或产品正式使用方案。

在NodeJS中将JS代码生成字节码,方法很简单,需借助google的V8引擎,V8引擎内置有JS虚拟机。通过v8虚拟机,将JS代码编译为字节码。全程仅需十几行代码,如下图:

图43:

JS代码安全之路:用JS对JS代码混淆加密

 

关键处是cachedData,即字节码。

V8虚拟机是能够识别和直接运行该字节码的。

代码如下,如同创建字节码一样简单。

图44:

JS代码安全之路:用JS对JS代码混淆加密

 

生成的字节码是非文本形式的,如强行打开,内容如下图:

图45、字节码文件内容:

JS代码安全之路:用JS对JS代码混淆加密

 

JS字节码生成并运行效果如下:

图46:

JS代码安全之路:用JS对JS代码混淆加密

 

代码改变世界,献给广大JS开发者。全文结束,感谢阅读。

声明:本站部分内容来自互联网,如有版权侵犯或其他问题请与我们联系,我们将立即删除或处理。
▍相关推荐
更多资讯 >>>