JS逆向---JavaScript混淆技术
JavaScript混淆技术
学习目标:
- 了解 混淆的作用
- 了解 ob混淆的方式
- 了解 如何将代码进行混淆
- 熟悉 解析混淆网站的过程
一. 混淆简介
1. 为什么要进行混淆
对于网页来说,其逻辑是依赖于
JavaScript
来实现的,JavaScript
有如下特点:
JavaScript
代码运行于客户端,也就是它必须要在用户浏览器端加载并运行。JavaScript
代码是公开透明的,也就是说浏览器可以直接获取到正在运行的JavaScript
的源码。
2. 压缩、混淆、加密技术
代码压缩:即去除
JavaScript
代码中的不必要的空格、换行等内容,使源码都压缩为几行内容,降低代码可读性,当然同时也能提高网站的加载速度。代码混淆:使用变量替换、字符串阵列化、控制流平坦化、多态变异、僵尸函数、调试保护等手段,使代码变得难以阅读和分析,达到最终保护的目的。但这不影响代码原有功能。是理想、实用的
JavaScript
保护方案代码加密:可以通过某种手段将 JavaScript 代码进行加密,转成人无法阅读或者解析的代码,如将代码完全抽象化加密,如 eval 加密。另外还有更强大的加密技术,可以直接将
JavaScript
代码用 C/C++ 实现,JavaScript
调用其编译后形成的文件来执行相应的功能,如Emscripten
还有WebAssembly
。
二.ob混淆
OB
混淆全称 Obfuscator,Obfuscator 其实就是混淆的意思,官网:https://obfuscator.io/ ,其作者是一位叫 Timofey Kachalov 的俄罗斯JavaScript
开发工程师,早在 2016 年就发布了第一个版本。
1. ob混淆的特点
- 一般由一个大数组或者含有大数组的函数、一个自执行函数、解密函数和加密后的函数四部分组成;
- 函数名和变量名通常以
_0x
或者0x
开头,后接 1~6 位数字或字母组合; - 自执行函数,进行移位操作,有明显的 push、shift 关键字;
混淆之前的代码
1 | source code |
混淆之后的代码
1 | var _0x3ed0 = ['1241023ikpdYM', 'Hello\x20World!', '291190xIUkft', '1251274vQVPdI', '124952hgHyOi', '1983KQSSIW', '247DipWFn', '7354VgseoG', '49680CQWPxl', '1ZTWTUo', '648lISKkF']; |
2. ob混淆结构
一般obfuscator混淆的代码结构分为以下几部分:
第一部分: 定义一个数组
- 数组里面保存了混淆或加密的变量,例如上面的_0x3ed0这个大数组,只是一个简单的形式数组,还可以再次混淆到看不出是一个数组。数组的位置也不一定在第一行,复杂一点的obfuscator混淆,会把这个数组放在比较隐藏的位置。
第二部分:重构数组
- 把数组_0x3ed0再次重构,例如,把数组的顺序重新排列等,这一部分一般用两个函数来处理,比如,上面的函数_0x4ed9和自执行函数,有内存泄漏的风险,不建议格式化这部分代码。
第三部分:自解密函数
- 解密函数是用来解密的,有可能有定时器,由于本例子简单没有解密函数,其实一般网站不会使用这么简单的混淆,本文只是为了讲解方便才使用此示例代码,有内存泄漏的风险,不建议格式化这部分代码。
第四部分:真实代码
- 整个ob混淆的难度取决于此部分,这里面是加密前的逻辑,主要由真实代码(我们想要的代码)+ 控制流平坦化构成。
- 这一部分就是示例中的hi()函数及其调用,这是我们真正想要的代码,分析代码就是为了找出这一部分。
第五部分:可以删除的垃圾代码
- 这部分主要由“控制流平坦化+无限debugger自执行函数+死代码注入”构成,一般不涉及业务逻辑,既然一般也有特殊情况,不要看到有debugger的,有控制流平坦化的就删除,具体问题具体分析。
注意:obfuscator混淆后的这五部分顺序不一定按上面列出的顺序,例如,有可能第三部分在在第一部分前面,第一部分会有可能在第三部分位置;另外,也不是每个obfuscator混淆的代码都包括这五部分,本列子中就只有三部分。
3.ob混淆介绍
JavaScript 混淆完全是在 JavaScript 上面进行的处理,它的目的就是使得 JavaScript 变得难以阅读和分析,大大降低代码可读性,是一种很实用的 JavaScript 保护方案。
JavaScript 混淆技术主要有以下几种:
变量混淆
将带有含意的变量名、方法名、常量名随机变为无意义的类乱码字符串,降低代码可读性,如转成单个字符或十六进制字符串。字符串混淆
将字符串阵列化集中放置、并可进行 MD5 或 Base64 加密存储,使代码中不出现明文字符串,这样可以避免使用全局搜索字符串的方式定位到入口点。属性加密
针对 JavaScript 对象的属性进行加密转化,隐藏代码之间的调用关系。控制流平坦化
打乱函数原有代码执行流程及函数调用关系,使代码逻变得混乱无序。僵尸代码
随机在代码中插入无用的僵尸代码、僵尸函数,进一步使代码混乱。调试保护
基于调试器特性,对当前运行环境进行检验,加入一些强制调试 debugger 语句,使其在调试模式下难以顺利执行 JavaScript 代码。多态变异
使 JavaScript 代码每次被调用时,将代码自身即立刻自动发生变异,变化为与之前完全不同的代码,即功能完全不变,只是代码形式变异,以此杜绝代码被动态分析调试。锁定域名
使 JavaScript 代码只能在指定域名下执行。反格式化
如果对 JavaScript 代码进行格式化,则无法执行,导致浏览器假死。特殊编码
将 JavaScript 完全编码为人不可读的代码,如表情符号、特殊表示内容等等。
总之,以上方案都是 JavaScript 混淆的实现方式,可以在不同程度上保护 JavaScript 代码。
4. 实现ob混淆
安装ob混淆库
1 | npm install javascript-obfuscator -g |
安装完成后,javascript-obfuscator
就是一个独立的可执行命令了。
1. 代码压缩
这里javascript-obfuscator
也提供了代码压缩的功能,使用其参数 compact
即可完成 JavaScript
代码的压缩,输出为一行内容。默认是 true,如果定义为 false,则混淆后的代码会分行显示。
1 | const JavaScriptObfuscator = require('javascript-obfuscator'); |
感兴趣的同学可以自行研究
三.项目实战
1. 案例1
1. 逆向目标
首页:https://bz.zzzmh.cn/index
- API:https://api.zzzmh.cn/bz/v3/getData
- 目标:result:
ak+9VCsq4dEdB+UdVfGo8kh5JDEbMHGTCmF/
2. 逆向分析
- 解除无线debugger
1 | Function.prototype.__constructor_back = Function.prototype.constructor; |
- 当前加载的数据为动态数据加密,我们需要定位到解密的位置
- 找到发送ajax的位置,找到请求的回调位置,混淆的位置比较难找,需要一个个定位
- 或者通过hook
JSON.parse
的方式来进行定位,js是一定会去转换这个json数据的
- 可以看出数据是通过
_0xf79b3e['a']['decipher']()
来进行解密的,我们需要跟进到方法里去找 - 进函数之后可以看到就是三个函数来进行解密的操作
- 扣函数代码就行
3. 逆向结果
1 | function _0x3ed467(_0x58f7d4) { |
- python代码
1 | import requests |
2. 案例2
简介:octet-stream指任意类型的二进制流数据。
1.逆向目标
2.逆向分析
- 过无限debugger
1 | var _constructor = constructor; |
- 可以看到他的数据是请求载荷是加密数据数据信息是octet-stream 的二进制数据
- 这种数据就没有办法进行关键字定位
- 先通过xhr断点他的发包,可以看一下再构造请求的时候是不是一个加密的数据
- 要是构造的请求对象是为未加密的,但是抓包数据的数据是加密的数据,那我们就需要思考一下他是不是会有请求拦截器,拦截进行加密的
- 找请求拦截器会比较麻烦,我们可以直接跟响应的数据解析,一般有请求拦截就会同时有响应拦截
- 一般拦截器会回在同一个文件,我们可以直接在当前文件搜索
axiosInstance
, 看他后方有没有跟上request
可以看到请求拦截器应该是在这个位置,我们可以下断点,看看有没有进行触发
接着往下跟就能看到数据生成的位置
- 接着就需要扣
c[o(428, 362, 660, 1118, "E&B*") + "e"]
这些代码的实现过程 - 实现函数是一个异步代码,我们需要扣出来
Writer.create
方法我们需要找到生成他的位置,在js代码中可以看到,当前数据是一个自执行方法,感觉还是一个webpack的形式我们可以直接扣下来,执行看里面是否有我们需要的方法
- 现在就能执行得到我们想要的数据信息
- 执行完之后把
.finish().slice()
给补上 finish
是create的方法slice
可以用来取数组的数据
3.逆向结果
- JavaScript代码
1 | function PolicyInfoByTypeIdParam$encode(m) { |
- python代码
1 | import requests |