爬虫逆向-AST解混淆

目录

在线解混淆工具

混淆技术

AST的意义

常见的混淆还原


在线解混淆工具

混淆技术

对于网页来说,起逻辑依赖于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)=>{});



猜你喜欢

转载自blog.csdn.net/xmx_000/article/details/131152456
今日推荐