各位前端的大佬。可以看一下有什么思路嘛?

作者:http://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代码混淆加密。技术理论:如何实现?确定实现方案之前,首先需要排除几种不可用方案:Eval思路不可用:可以被下断点调试或API HOOK,而轻松还原出原始代码。可逆加密方式不可用:加密方式可逆,则必然有解密函数,只需定位于解密出口,即可得到原始代码。异步代码获取并执行不可用:同样可被调试或hook,得到代码。可取的方式:代码混淆+数据加密。混淆原理:非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代码如何转化为AST?其实,没有想象中那么复杂。得益于NodeJS成熟的生态,已经有多个已实现模块可以完成这一操作。比较流行的如:esprima、babel,都可以实现对JS代码进行词法分析、语法分析、生成AST、AST操作、从AST再生成JS代码。用esprima进行JS代码混淆加密。程序框架。图2、esprima框架demo:如图2所示,使用esprima进行JS代码保护的原始功能框架。代码介绍:Esprima实现将JS代码转化为AST;estraverse对AST节点进行遍历,混淆加密的逻辑操作都将在此环节实现;escodegen则是将操作后的AST转为JS代码输出。此demo代码未对AST进行任何处理,所以图中右侧的执行结果中可以看到,输出的JS代码与最初代码完全一致。AST是这样子的。前面已经对AST进行了说明,AST具体是什么样?一个方便的办法,是使用http://astexplorer.net,可以对输入的代码的AST即时同步显示:图3、const a=1的AST:Demo中使用的一行JS语句:“const a=1”,其AST即如图中所显示。AST是一个JSON结构。Program表示程序,子节点body中,是变量定义kind是“const”,字面量是“a”,值是“1”。看似杂乱,但很规整,细看便不难理解。demo程序里,在节点操作处可以用console输出AST,与astexplorer输出一至,不过前者更方便些。图4、在程序中输出AST:借助Esprima修改AST实例:改“==”为“===”。图5:代码如上图,这是一个很简单的示例。程序中,estraverse对示例代码结点进行处理,当匹配到“==”时,改为“===”。为了明确修改节点细节,再对前后代码进行分析。由图6、图7看到,差异仅在节点中的operator。图6、代码中使用“==”:图7、代码中使用“===”:借助Esprima修改AST实例:把parseInt改为标准语法。parseInt方法,有两个参数,参数一是要转化的值,参数二是可选择项,是要转化的进制类型。图8:通过astexplorer,先了解parseInt的AST,未使用参数二时,AST如下:图9:如果有第二参数,则AST如下:图10:那么,要将parseInt转为标准形式即是要给只有一个参数的调用增加第二参数。代码及执行结果如下:图11:因为初入手的原因,以上描述较为细致,后续将简化。方法名转义和转码。如:console.log转为console[log]形式。通过在aspexplorer中比较可知,造成语句形式差异的原因是CallExpression成员中computed属性值的不同。图12:那么,只需修改节对应节点的computed属性值即可:图13:而修改的条件,则是判断AST节点是CallExpression。上面的例子中,也是使用相似的条件判断方法,找出要修改内容相对应的AST节点。再进一步,将方法名转为十六进制字符,console[log]会成为:console['\x6c\x6f\x67'],以此进一步降低代码可读性。图14、增加字符串转16进制操作:例程代码输出为:图15:运行混淆后的代码:图16:从简单的例程,可以初步学习到对AST的操作方法。接下来,实现一个有点难度的功能。成员表达式转为IIFE成员表达式通常指调用对象的成员,例如 console 对象的 log 成员。IIFE,全称为:Immediately Invoked Function Expression,在JavaScript编程中,是指:立即调用函数表达式。为了方便理解,先展示此功能实现后的效果:图17:如上图中,console的log、warn、error方法,以及字符串的toUperCase()方法,在保护后都会成为匿名自执行的函数,且方法名都以数组化的形式被另外存放,代码相比之前混乱了许多。图18、IIFE代码执行效果:实现方法如下:主架构与之前略有差异,traverse方法改为replace,enter事件改为leave事件,如下图:图19:对变量定义结点,如console.log输出的信息,以及成员函数,如console的log方法进行操作。图20、改写字符串定义、成员函数调用:Add_string函数把字符串信息、方法名,都写入到一个新的字符串数组,并且把方法改为IIFE。字符串数组建立、方法改为IIFE的具体实现如下图:图21:然后,把新增的数组加入到AST中,最重再重建代码:图22:这样就完成了本例功能。注:本例仅供功能演示,尚有不严谨的逻辑,比如成员方法IIFE化之前,除应该判断node.type为MemberExpression,还应排除节点computed为true的情况,否则代码执行会发生错误。正如前文中所述,能对AST进行操作的模块不止esprima,babel也是个很好的选择。接下来的例子,将使用babel来完成。Babel的使用方式与esprima极为相似,其代码框架如下:图23:同样是:JS代码→AST→节点处理→JS代码。用Babel修改AST实例:去除代码中的console.log输出语句。代码如下图所示:图24、用Babel在AST中去除console.log节点:匹配AST中的成员操作节点,且满足条件callee的对像名为console,属性方法名为log,如检测掉,则remove该节点。运行效果如下图所示,测试代码中含有console.log,修改后输出中已经被去除。图25:严谨的考虑的话,需要注意对象挂载的识别,如global.console.log,此时remove则会剩下global,将导致语法错误,因此还应该判断父节点类型来排除这种情况。指定局部变量变形图26、对min、number两个局部变量变形:相当于是可设定、可配置的对某些变量进行变形。反向思考,也可以排除对某些变量的处理,等同于白名单,类似于JShaman平台中的“保留字”功能。删除代码中的空行。图27:EmptyStatement表示空语句AST节点。字符串转Unicode。图28:代码及执行结果如上图,原理为:判断节字符串字面量节点是否为Unicode格式,如不是则转为Unicode。在这几个例子中,可看到与esprima的差异,esprima使用的是enter、leave方法,Babel中是直接对要处理的节点类型操作,如上图中的StringLiteral。更条理化的写法,上面的代码可以修改如下,这个方法被称为Babel-plugin(插件):图29:二进制表达式转为调用表达式即BinaryExpression节点转为CallExpression。先看效果:图30,左侧为二进制表达式,右侧为调用表达式:二进制表达式AST形式:实现代码:图31:调用表达式AST形式:图32:代码中的这部分,即是将二进制表达式转化为调用表达式:图33:布尔型常量值混淆代码及效果如下图:图34:数值混淆代码及效果如下图:图35:JS代码混淆加密,虽不至博大精深,但也属于高段位技术。在此分享部分浅显方案,以展现其实用效果,用于说明混淆加密手段对于JS代码加固的有效性。此外,还有更多高端的防护手段,如JShaman应用的:平展控制流、时间限制、域名锁定、僵尸代码植入等。图36、JShaman的JS代码保护配置功能:防逆向措施:检测与对抗。对JS代码进行混淆加密之后,代码安全度得到相当的加强,但还能更进一步,为了防止不法者进行逆向分析、破解,可在代码中加入防破解对抗功能。这也是被JShaman应用的方案。无限断点。JS当中有一个debugger指令,当处于调试工具中时,如在浏览器中,会形成断点,使调试中断,利用此特性,在程序中加入无限的debugger,可使代码无法被调试。图37、每100毫秒一个断点:浏览器中执行效果如下,当打开“调试器”时,程序会不停的中断,导致无法跟踪代码:图38:时间差检测。代码及执行效果如下图所示:图39:检测原理是:在代码中加入console.log输出和console.clear语句,未在调试工具中时,这两句代码执行是不需渲染显示的,执行耗时短,但假如在浏览器中打开了开发者工具,则会因为显示输出并清除的操作而消耗较多时间,这会被程序察觉出耗时异常,从而检测出是在被调试。专业的混淆加密:JShaman本文讲述了部分JS代码混淆加密技术及实现,更多更专业的防护方案未有尽述,这里再展示一段经JShaman保护的代码,领略专业级的JS代码安全。图40、测试代码准备进行混淆加密:图41、保护选项设置:图42、混淆加密后的JS代码:彩蛋:字节码加密技术。提示:JS字节码(ByteCode)加密技术,理论可行,但通用性较差,在此仅做技术介绍,不推荐做为项目或产品正式使用方案。字节码生成在NodeJS中将JS代码生成字节码,方法很简单,需借助Google的V8引擎,V8引擎内置有JS虚拟机。通过v8虚拟机,将JS代码编译为字节码。全程仅需十几行代码,如下图:图43:关键处是cachedData,即字节码。运行字节码V8虚拟机是能够识别和直接运行该字节码的。代码如下,如同创建字节码一样简单。图44:生成的字节码是非文本形式的,如强行打开,内容如下图:图45、字节码文件内容:JS字节码生成并运行效果如下:图46:代码改变世界,献给广大JS开发者。全文结束,感谢阅读。

您愿意向朋友推荐“博客详情页”吗?
强烈不推荐
不推荐
一般般
推荐
强烈推荐
提交
成就一亿技术人!
hope_wisdom 发出的红包
实付元使用余额支付
点击重新获取
钱包余额
0
抵扣说明: 1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。余额充值

我要回帖

更多关于 前端大佬 的文章

 

随机推荐