JS 代码安全保护

最初原因是有防爬的需求,因此需要对于 web 应用进行安全防护,从最初的数据对称加密(aes) ,到字体混淆(fontmin) ,再到后面的非对称加密(rsa + sm4) ,以及 js 代码混淆(javascript-obfuscator) 。做的越多越是觉得前端所能做其实很有限,以上种种都避不开一个问题,js 最终是要在浏览器运行的并且界面也要响应用户的交互行为的,甚至爬虫不用关心你的底层逻辑,直接执行你的代码即可拿到最终数据, 这个结论无疑是令人沮丧的,但是转念一想不能因为锁能被撬开,就连门都不关了。防爬,准确来说攻防相关是一个循环往复的过程,只能不断提升攻击者的攻破的成本,拦截掉一大部分,非专业人士的爬取。那我们先抛开整个风控系统不谈,思考一下前端还能做些什么。 ‍

代码安全的常见方案

其实最开始的时候,下意识想的防爬其实是怎么不让用户调试代码,方案用的也都是大家日常见到的方案。

  1. 禁用 devtools

    1. 最初级的是屏蔽快捷键,屏蔽右键,让用户无法打开 devtool。 先打开 devtool 再打开网页可以破解
    2. 埋入死循环 debugger,debugger 只有在 devtool 时才会执行,进阶一些检测 debugger 间隔时间。不管怎么变种只要禁用掉 debugger 即可。
    3. 调用 performance,devtool 打开的时候性能变差,可以大量 console 差异会更加明显。
    4. 检测窗口变化,使用 mobile 模式可以破解。
    5. 其他类型,基本都可以原理都要基于 window 上的某些函数或者 API 来实现的,那么加载之前修改 window 对象上方法都可以破解。更多可以参考 disable-devtool 这个库。
  2. 代码混淆

    1. 最基础的压缩。最常用的 UglifyJSTerseresbuild 等。只是压缩了代码体积,简单的字符串混淆。经过 devtool format​ 之后具有较强的可读性,调试起来十分方便。

    2. 基础的混淆,eval 混淆,aa & jj 混淆。此类都是比较有规律的,比如 eval 内部方法的字符串一般使用 base64。aa & jj 就是相似字符作变量名,降低可阅读性。

    3. 集大成者 javascript-obfuscator 或者类似的 OOLVM 混淆,主要包含了 控制流平坦化​、虚假控制流​、指令替换​,核心是控制流平坦化,简单介绍一下:

      控制流平坦化控制流平坦化

      基本块的执行流程,通过控制流平坦化,基本块间的前后关系将被混淆,从而加大了程序逆向分析的难度。逆向的思路基本是使用符号执行​或者AST还原混淆代码

    4. WebAssembly, 使用C、C++、C# 、Rust 开发编译为wasm格式的低级类汇编语言,WebAssembly本质上是为提升性能使用等, 一些核心的逻辑可以使用这些语言比如说加解密逻辑。用户只能在浏览器看到二进制数据而无法查看加解密的逻辑,当然只是能提升逆向的难度而已,能编译就能反编译。

      # Todo 后续计划数据加解密相关的代码使用rust或者其他C++之类的重构。
    5. jsvmp 配合WebAssembly使用

      jsvmp 全称 js虚拟化保护(Virtual Machine based code Protection for JavaScript)​,

      简单来说就是将代码转换成自定义的字节码,浏览器加载的就是这些字节码,这些字节码只有虚拟解释器才能识别出来。这个方案特殊在虚拟解释器​使用WebAssembly​ 来实现,使用C语言做解释器,WebAssembly​ 方案可以隐藏关键的解释器执行逻辑。

      从本质上来说,上跟上一篇文章的吾爱破解​插入到NeatReader​ 源码内部的那段代码一致。那段免责声明就是一段无法识别的Unicode(指令编码),只有经过他写的某个函数(解释器),才能计算出这段文本是什么。jsmvp这种方案是将代码也全部指令化了。解释器放入到了wasm文件中。 js代码安全-反编译初试

      JSVMP的保护流程图JSVMP的保护流程图

参考文献

爬虫逆向进阶实战-李玺

基于 WebAssembly 的 JavaScript 代码虚拟化保护方法- 匡开园