目录
在线解混淆工具
混淆技术
对于网页来说,起逻辑依赖于js来实现,js有如下特点:
- js运行于客户端,必须在客户浏览器加载并运行
- js代码是公开透明的,浏览器可以获取到正在运行的js源码
基于这两个原因,js代码是极不安全的,任何人都可以阅读,分析,复制,甚至篡改,于是各种混淆技术出现了
-
变量混淆 将带有含意的变量名、方法名、常量名随机变为无意义的类乱码字符串,降低代码可读性,如转成单个字符或十六进制字符串。
-
字符串混淆 将字符串阵列化集中放置、并可进行 MD5 或 Base64 加密存储,使代码中不出现明文字符串,这样可以避免使用全局搜索字符串的方式定位到入口点。
-
属性加密 针对 JavaScript 对象的属性进行加密转化,隐藏代码之间的调用关系。
-
控制流平坦化 打乱函数原有代码执行流程及函数调用关系,使代码逻变得混乱无序。
-
僵尸代码 随机在代码中插入无用的僵尸代码、僵尸函数,进一步使代码混乱。
-
调试保护 基于调试器特性,对当前运行环境进行检验,加入一些强制调试 debugger 语句,使其在调试模式下难以顺利执行 JavaScript 代码。
-
多态变异 使 JavaScript 代码每次被调用时,将代码自身即立刻自动发生变异,变化为与之前完全不同的代码,即功能完全不变,只是代码形式变异,以此杜绝代码被动态分析调试。
-
锁定域名 使 JavaScript 代码只能在指定域名下执行。
-
反格式化 如果对 JavaScript 代码进行格式化,则无法执行,导致浏览器假死。
-
特殊编码 将 JavaScript 完全编码为人不可读的代码,如表情符号、特殊表示内容等等。
那有没有办法可以把混淆的代码还原回可阅读的代码呢?答案是有的,即AST. 网址:AST explorer
AST
(Abstract Syntax Tree),译为抽象语法树,我们可以通过对 AST
树节点的一系列操作,借助机器高效且精准地修改代码.它不是某一种编程语言独有的,几乎所有编程语言都有语法树。
AST的意义
对于爬虫工程师来说,它并不能帮你找到加密参数的具体位置,但是可以使用它把混淆的代码解混淆后替换到浏览器里,方便找加密参数的生成逻辑.
解混淆--> babel库的使用
安装 npm install @babel/core --save-dev
Babel 是一个 JavaScript 编译器,也可以说是一个解析库,Babel 内置了很多分析 JavaScript 代码的方法,我们可以利用 Babel 将 js 代码转换成 AST 语法树,然后增删改查等操作之后,再转换成 JavaScript 代码。Babel 包含的各种功能包、API、各方法可选参数等,都非常多,在实际使用过程中,应当多查询官方文档 网址:Document
在做逆向解混淆中,主要用到了 Babel 的以下几个功能包
@babel/core
:Babel 编译器本身,提供了 babel 的编译 API;@babel/parser
:将 JavaScript 代码解析成 AST 语法树;@babel/traverse
:遍历、修改 AST 语法树的各个节点;@babel/generator
:将 AST 还原成 JavaScript 代码;@babel/types
:判断、验证节点的类型、构建新 AST 节点等。
常见的混淆还原
const generator = require('@babel/generator').default //将 AST 还原成 JavaScript 代码
const parser = require("@babel/parser"); // 编译成语法树
const traverse = require("@babel/traverse"); //对语法树进行操作
const types = require("@babel/types"); //判断、验证节点的类型、构建新 AST 节点等
var fs =require("fs"); //读取文件
js = fs.readFileSync('mfw.js',{encoding:'utf-8'})
let ast = parser.parse(js);
function writeFile(code) {
console.log("Write start\\n");
fs.writeFile(file_out, code, function (err) {
if (err) {
return console.error(err);
}
});
console.log("Write finish\\n");
}
visitor1={
"Program"(path){
var body =path.get('body.0');
var node =body.node;
var args=node.expression.argument;
if(args==undefined)return;
var params=args.callee.params;
var paramsvalue=args.arguments;
var name,valuelist;
for(var i=0;i<params.length;i++){
name=params[i].name;
valuelist=paramsvalue[i].elements;
body.traverse({
MemberExpression(_path){
var _node=_path.node;
var _name=_node.object.name;
if(!types.isNumericLiteral(_node.property))return;
var _value=_node.property.value;
if(name==_name){
if(valuelist[_value]==undefined)return;
if(valuelist[_value].value==undefined)return;
rvalue=valuelist[_value].value;
switch(typeof rvalue){
case "string":
_path.replaceWith(types.StringLiteral(rvalue));
break;
case "number":
_path.replaceWith(types.NumericLiteral(rvalue));
break;
}
}
}
});
}
}
}
const visitor2={
VariableDeclarator(path)
{
const {id,init}=path.node;
if(!types.isLiteral(init))return;
const binding=path.scope.getBinding(id.name);
if(binding.constantViolations.length===0)
{
for(const refer_path of binding.referencePaths)
{
refer_path.replaceInline(init);
}
//path.remove();
}
}
}
replaceliteral=function(path,value){
switch(typeof value){
case 'boolean':
path.replaceWith(types.booleanLiteral(value));
break;
case 'number':
path.replaceWith(types.NumericLiteral(value));
break;
case 'string':
path.replaceWith(types.stringLiteral(value));
break;
default:
break;
}
}
const visitor3={
"UnaryExpression|BinaryExpression|CallExpression|ConditionalExpression":{
enter:function(path){
const{value}=path.evaluate();
replaceliteral(path,value);
}
}
}
const visitor4={
"FunctionDeclaration"(path){
let {id}=path.node;
let code=path.toString();
if(code.indexOf("try")!=-1 ||code.indexOf("random")!=-1||code.indexOf("Date")!=-1){
return;
}
eval(code);
let scope =path.scope;
const binding = path.scope.parent.getBinding(id.name);
let isdel=false;
if(!binding || binding.constantViolations.length>0){
return;
}
for(const refer_path of binding.referencePaths)
{
let call_express=refer_path.findParent(p=>p.isCallExpression());
let arguments=call_express.get('arguments');
let args=[];
arguments.forEach(arg=>{args.push(arg.isLiteral())});
if(args.length ===0 || args.indexOf("false")!=-1){
continue;
}
try{
let value= eval(call_express.toString());
if(value==undefined)return;
switch(typeof value){
case "string":
call_express.replaceWith(types.StringLiteral(value));
isdel=true;
break;
case "number":
call_express.replaceWith(types.NumericLiteral(value));
isdel=true;
break;
}
}catch(e){
}
}
if(isdel){
//path.remove();
}
}
}
const visitor5={
"StringLiteral|NumericLiteral"(path){
delete path.node.extra;
}
}
const visitor6={
"CallExpression"(path){
var node =path.node;
var code=path.toString();
var value;
if(!node.arguments.length>0)return;
if(!types.isLiteral(node.arguments[0]))return;
if(code.indexOf("Time")!=-1)return;
try{
value=eval("value="+code);
}catch(e){
}
if(value==undefined)return;
switch(typeof value){
case "string":
path.replaceWith(types.StringLiteral(value));
break;
case "number":
path.replaceWith(types.NumericLiteral(value));
break;
case "boolean":
path.replaceWith(types.BooleanLiteral(value));
break;
}
}
}
traverse.default(ast,visitor1);
traverse.default(ast,visitor2);
traverse.default(ast,visitor3);
traverse.default(ast,visitor4);
traverse.default(ast,visitor5);
traverse.default(ast,visitor6);
const {code} = generator(ast,opts = {"comments":false},js);
fs.writeFile('mfw_decode.js', code, (err)=>{});