JS混淆

前言

总结一下关于JS混淆内容。

为什么是混淆而不是加密?
没有办法真正给js加密,毕竟最终js要被浏览器的JS引擎解析,这个解析的过程没办法隐蔽起来,所以JS加密的方向只有混淆而已
混淆的作用?

  1. 商业利益保护,防抄袭
  2. 让代码尽量的不被分析:如锤子手机翻车事件
  3. 防止代码被篡改
  4. 代码不可读是唯一目的

混淆方法

混淆方法多种多样可以使用base62,源码AST分析修改,借助webAssembly

base62编码

Base62编码:这类混淆的思路在于将需要执行的代码进行一次编码,在执行的时候还原出浏览器可执行的合法的脚本,然后执行。看上去和可执行文件的加壳有那么点类似。

1
2
3
4
//源代码
var a = 1;
//混淆后
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;}('0 2=1;',3,3,'var||a'.split('|'),0,{}))

其最明显的特征是生成的代码以 eval(function(p,a,c,k,e,r)) 开头。

无论代码如何进行变形,其最终都要调用一次 eval 等函数。解密的方法不需要对其算法做任何分析,只需要简单地找到这个最终的调用,改为 console.log 或者其他方式,将程序解码后的结果按照字符串输出即可。

字符串拼接等

  1. 访问一个对象的成员有两种方法——点运算符和下标运算符。调用 window 的 eval 方法,既可以写成 window.eval(),也可以 window[‘eval’];
  2. 为了让代码更变态一些,混淆器选用第二种写法,然后再在字符串字面量上做文章。先把字符串拆成几个部分:’e’ + ‘v’ + ‘al’;
  3. 再利用一个数字进制转换的技巧:14..toString(15) + 31..toString(32) + 0xf1.toString(22);
  4. 将数字也展开:(0b1110).toString(4<<2) + (‘ ‘.charCodeAt() - 1).toString(Math.log(0x100000000) / Math.log(2)) + 0xf1.toString(11 << 1);
  5. 效果:window(2*7).toString(4<<2) + (‘ ‘.charCodeAt() - 1).toString(Math.log(0x100000000) / Math.log(2)) + 0xf1.toString(11 << 1)

AST方式

其实也可以通过正则替换实现,单比较容易反混淆。

通过语法树替换实现的混淆器:
JS混淆
在源代码的解释和编译过程中,语法分析器创建出抽象语法树,它是源代码的抽象语法结构的树状表现形式,树上的每个节点都表示源代码中的一种结构。一颗抽象语法树展示一个程序的完整语法结构,并不会包含真实语法中出现的每个细节。
JS混淆

AST的其他工具
escope Javascript 作用域分析工具
esutil 辅助函数库,检查语法树节点是否满足某些条件
estraverse 语法树遍历辅助库,接口有一点类似 SAX 方式解析 XML
esrecurse 另一个语法树遍历工具,使用递归
esquery 使用 css 选择器的语法从语法树中提取符合条件的节点
escodegen 与 esprima 功能互逆,将语法树还原为代码

混淆规则设计

混淆变量和函数名:将变量、函数名称通过hash算法混淆,不可逆
字符串阵列化:将字符串进行阵列化放置,使赋值、读写等操作不易被查觉。并将字符串进行混淆,使之不出现明文字符串、常量
混淆控制流:通过组合拆分的方式,更改代码原有的分枝逻辑,打乱代码原有的相关系,使代码变的不可理解
运算符混淆:将点运算符替换为字符串下标形式,然后对字符串进行混淆。
字符串二次加密:对混淆后的字符串进行二次加密,使破解更加困难。
压缩代码:去除回车换行、空格。减小代码体积的同时使代码难以正常阅读。
反调试:使浏览器调试工具不可用,剔除命令行调试语句,使不打印出调试信息。
域名锁定:使代码只能在指定的域名(含子域名)下运行,复制到其它任何地方、任何网站都无法运行。 可以防止产品被复制盗用。

已有混淆工具

UglifyJS、javascript-obfuscator
国外商业软件:Jscrambler
国内商业软件:Jshaman

WebAssembly

是什么

C / C++ 编译成 JS 有两个最大的困难。

  1. C / C++ 是静态类型语言,而 JS 是动态类型语言。
  2. C / C++ 是手动内存管理,而 JS 依靠垃圾回收机制。
    asm.js 就是为了解决这两个问题而设计的:它的变量一律都是静态类型,并且取消垃圾回收机制。除了这两点,它与 JavaScript 并无差异,也就是说,asm.js 是 JavaScript 的一个严格的子集,只能使用后者的一部分语法。

Js引擎运行asm.js与运行普通Js代码不一样,跳过语法分析,直接转为汇编语言,浏览器还会调用webGL通过GPU执行asm.js。这使得asm.js在浏览器运行速度更快

webAssembly 的前身是asm.js 。c/c++代码通过Emscripten编译器编译成asm.js文件。
而webAssembly与asm原理一致,但是转出来的代码不一样是二进制.wasm文件,asm.js是文本格式。 webAssembly使得运行速度更快。

谷歌、苹果、微软和Mozilla的工程师正合力创建WebAssembly(又名wasm),这是能够运用在未来浏览器中承诺可带来20倍更快性能的字节码(bytecode)。WebAssembly项目创造全新的字节码(一种机器可读的指令集,能够更快为浏览器加载高级语言),让桌面和移动端浏览器相比较网页或者应用的整体源代码变得更加高效。

1
2
3
4
5
6
7
8
9
10
11
12
13
WebAssembly.compile(new Uint8Array(`
00 61 73 6d 01 00 00 00 01 0c 02 60 02 7f 7f 01
7f 60 01 7f 01 7f 03 03 02 00 01 07 10 02 03 61
64 64 00 00 06 73 71 75 61 72 65 00 01 0a 13 02
08 00 20 00 20 01 6a 0f 0b 08 00 20 00 20 00 6c
0f 0b`.trim().split(/[\s\r\n]+/g).map(str => parseInt(str, 16))
)).then(module => {
const instance = new WebAssembly.Instance(module)
const { add, square } = instance.exports
console.log('2 + 4 =', add(2, 4))
console.log('3^2 =', square(3))
console.log('(2 + 5)^2 =', square(add(2 + 5)))
})

一个工具

文档

混淆原理:
JS混淆

参考资料

  1. https://blog.knownsec.com/2015/08/use-estools-aid-deobfuscate-javascript/
  2. https://github.com/qiaozi-tech/SecurityWorker#4-securityworker-vm-api