JS混淆学习及实战
简介
JavaScript 压缩即去除 JavaScript 代码中的不必要的空格、换行等内容或者把一些可能公用的代码进行处理实现共享,最后输出的结果都被压缩为几行内容,代码可读性变得很差,同时也能提高网站加载速度。
如果仅仅是去除空格换行这样的压缩方式,其实几乎是没有任何防护作用的,因为这种压缩方式仅仅是降低了代码的直接可读性。如果我们有一些格式化工具可以轻松将 JavaScript 代码变得易读,比如利用 IDE、在线工具或 Chrome 浏览器都能还原格式化的代码。
目前主流的前端开发技术大多都会利用 Webpack 进行打包,Webpack 会对源代码进行编译和压缩,输出几个打包好的 JavaScript 文件,其中我们可以看到输出的 JavaScript 文件名带有一些不规则字符串,同时文件内容可能只有几行内容,变量名都是一些简单字母表示。这其中就包含 JavaScript 压缩技术,比如一些公共的库输出成 bundle 文件,一些调用逻辑压缩和转义成几行代码,这些都属于 JavaScript 压缩。另外其中也包含了一些很基础的 JavaScript 混淆技术,比如把变量名、方法名替换成一些简单字符,降低代码可读性。
JavaScript 加密基本思路是将一些核心逻辑使用诸如 C/C++ 语言来编写,并通过 JavaScript 调用执行,从而起到防护作用。其加密的方式现在有 Emscripten 和 WebAssembly 等,其中后者越来越成为主流。
Emscripten: Emscripten编译器可以将 C/C++ 代码编译成 asm.js 的 JavaScript 变体,再由 JavaScript 调用执行。因此某些 JavaScript 的核心功能可以使用 C/C++ 语言实现,这可以算是一种前端加密技术。
WebAssembly: WebAssembly 的技术也能将 C/C++ 转成 JavaScript 引擎可以运行的代码,但转出来的代码是二进制字节码,而asm.js 是文本,
因此运行速度更快、体积更小。得到的字节码具有和 JavaScript 相同的功能,在语法上完全脱离 JavaScript,同时具有沙盒化的执行环境。
利用 WebAssembly 技术,我们可以将一些核心的功能利用 C/C++ 语言实现,形成浏览器字节码的形式。然后在 JavaScript 中通过类似如下的方式调用:
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)))
})这种加密方式更加安全,想要逆向或破解那得需要逆向 WebAssembly,难度极大。
但整体来说,JavaScript 压缩技术只能在很小的程度上起到防护作用,要想真正提高防护效果还得依靠 JavaScript 混淆和加密技术。原文链接「小小明-代码实体」
混淆主要涉及两种思路:
a.通过正则替换实现的混淆器;
b.通过语法树替换实现的混淆器。
第一种实现成本低,但是效果也一般,适合对混淆要求不高的场景。第二种实现成本较高,但是更灵活,而且更安全,更适合对抗场景。
JS混淆简单参考:几个实用的JS混淆工具。
主要混淆技术:
变量混淆
将带有含意的变量名、方法名、常量名随机变为无意义的类乱码字符串,降低代码可读性,如转成单个字符或十六进制字符串。字符串混淆
将字符串阵列化集中放置、并可进行MD5
或Base64
加密存储,使代码中不出现明文字符串,这样可以避免使用全局搜索字符串的方式定位到入口点。属性加密
针对 JavaScript 对象的属性进行加密转化,隐藏代码之间的调用关系。控制流平坦化
打乱函数原有代码执行流程及函数调用关系,使代码逻变得混乱无序。僵尸代码
随机在代码中插入无用的僵尸代码、僵尸函数,进一步使代码混乱。调试保护
基于调试器特性,对当前运行环境进行检验,加入一些强制调试debugger
语句,使其在调试模式下难以顺利执行 JavaScript 代码。多态变异
使 JavaScript 代码每次被调用时,将代码自身即立刻自动发生变异,变化为与之前完全不同的代码,即功能完全不变,只是代码形式变异,以此杜绝代码被动态分析调试。锁定域名
使 JavaScript 代码只能在指定域名下执行。反格式化
如果对 JavaScript 代码进行格式化,则无法执行,导致浏览器假死。特殊编码
将 JavaScript 完全编码为人不可读的代码,如表情符号、特殊表示内容等等。
总之,以上方案都是 JavaScript 混淆的实现方式,可以在不同程度上保护 JavaScript 代码。
混淆工具有UglifyJS,JScrambler,jsbeautifier,JSDetox,obfuscator等
此次介绍OB混淆,即obfuscator
OB混淆
介绍
OB混淆官网:JavaScript Obfuscator Tool
来自官方的混淆示例:
|
混淆后(已格式化):
|
OB 混淆特点
- 一般由一个大数组或者含有大数组的函数、自执行函数、解密函数和加密后函数四部分组成
- 函数名和变量名通常以
_0x
或者0x
开头,后接1~6位数字或字母组合 - 自执行函数进行移位操作有明显的
push
、shift
关键字
实战
逆向
目标:极简壁纸
该网站有无限debugger,在首页加载完毕后,可以用将以下代码保存到片段里并运行来绕过(或直接在控制台运行):
|
同时本网站有滑块(并不是每次请求都有),滑块不在讨论范围,因此以没有进行滑块验证后的请求作为研究目标
可以看到表单中无加密(请求头中也无)
而响应结果加密:
通过XHR断点或者发起程序开找,找到以下疑似处(可以多下断点广撒网)
可以发现基本就是这了
以下代码便是解密代码:
|
而_0x3bb7ad[_0x430ddf(0x1f7, 'JJcn')]['result']
便是响应结果中的result
,因此只需要扣_0xa60097['a']['decipher']
即可
跳转代码如下(已封装至本地):
|
开始慢扣三个函数,发现运行无结果,于是在已扣出_0x1372a9
、_0x230351
和_0x43f8fa
的情况下(因为这三个函数包含在一个对象中,有点像webpack),直接全盘复制,不过要注意去掉涉及到无限debugger
的代码(不要和老夫说什么半扣优雅,老夫上来就是一个全扣,省时省力),运行,结果正常,这样就拿到了图片的ID(但是想获取图片还需要逆向对应的API,这里点到为止),逆向结束