某视频解析分析-AST反混淆与nodejs调用

查看 204|回复 10
作者:漁滒   
@TOC
一、抓包并进行简单的静态分析
打开Fiddler,然后打开目标网址https://jsap.attakids.com/?url=https://v.qq.com/x/cover/mzc0020002ka95z/k0036081njj.html

这里可以看到请求了https://jsap.attakids.com/Api.php这个接口,其中还需要10个请求的参数。
接着继续看看主页的内容

这里可以看到,除了Sign和Token两个参数是实际有加密的,其他的参数都是直接在源代码中给出,使用正则匹配等方式提取即可。
下面还有一段ob混淆的js代码,将其先进行反混淆(关于ast的详细内容可以查看之前的帖子,这里就不做过长的解析。地址:《JavaScript AST其实很简单》)
反混淆的js过程,下面只展示部分内容
function ajax_api() {
  $["cookie"]("uuid", Vkey + '-' + Key + '-' + Sign + '-' + Token);
  AccessToken = Vkey + '-' + Key + '-' + Sign + '-' + Token;
  if (isios) {
    ios = '1';
  } else {
    ios = '0';
  }
  if (isiPad) {
    wap = '1';
  } else {
    wap = '0';
  }
  $["ajax"]({
    'type': "post",
    'url': Api + "/Api.php",
    'dataType': "json",
    'headers': {
      'Token': Vkey,
      'Access-Token': AccessToken,
      'Version': Version
    },
    'data': {
      'url': Vurl,
      'wap': wap,
      'ios': ios,
      'host': Host,
      'key': Key,
      'sign': Sign,
      'token': Token,
      'type': Type,
      'referer': Ref,
      'time': Time
    },
    'success': function (_0x3040ec) {
      if (_0x3040ec["code"] == "200") {
        var _0x4acba7 = decode_url(_0x3040ec["url"], $["md5"](Host + Token));
        _0x3040ec["url"] = decodeURIComponent(_0x4acba7);
      }
    }
  });
}
此时,请求的逻辑就非常清晰了,但是还需要加密参数Sign和Token的算法。
二、网页断点动态分析
如果有一些实际的参数,会更好的分析。打开f12,然后重新加载网页

直接出现反调试,无限debugger。从函数调用堆栈可以看出,是从jquery.md5.js中出来的。接着查看一下这个js

可以看到大数组,又是一段ob混淆,继续进行反混淆,然后使用浏览器的Overrides功能,加载本地的js文件。(详细的Overrides功能教程可以参考鹅大的文章,地址:基于Chrome Overrides和Initiator进行js分析)
设置好以后再次刷新,此时就不会出现无限debugger,可以自己设置断点

这里一共套了三层函数,分别为
[table]
[tr]
[td]函数名[/td]
[/tr]
[tr]
[td]$.md5

在这里, 插入图片

wannabe   


漁滒 发表于 2021-6-4 15:00
工具需要自己用babel模块来写

[JavaScript] 纯文本查看 复制代码
//babel库及文件模块导入
const fs = require('fs');
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const types = require("@babel/types");
const generator = require("@babel/generator").default;
/**********************************************************
命令行输入,获取混淆前的js源代码及解混淆后生成的新的js代码。
eg: node decode_obfuscator.js encode.js decode_result.js
encode.js 混淆前js源代码的路径
decode_result.js 生成新js代码的路径
默认 混淆前js源代码的路径为 ./encode.js
默认 生成新js代码的路径为   ./decode_result.js
***********************************************************/
let encode_file = "./source.js",
    decode_file = "./decode_result1.js";
if (process.argv.length > 2) {
    encode_file = process.argv[2];
}
if (process.argv.length > 3) {
    decode_file = process.argv[3];
}
let jscode = fs.readFileSync(encode_file, { encoding: "utf-8" });
let ast = parser.parse(jscode);
/***********************************************************
NumericLiteral ---> Literal
StringLiteral  ---> Literal
用于处理已十六进制显示的字符串或者数值
***********************************************************/
const delete_extra = {
    "NumericLiteral|StringLiteral": function(path) {
        delete path.node.extra;
    },
}
traverse(ast, delete_extra);
/********************************************************
BinaryExpression --> Literal,object对象还原预处理
UnaryExpression  --> Literal,object对象还原预处理
********************************************************/
const combin_BinaryExpression = {
    "BinaryExpression|UnaryExpression|ConditionalExpression" (path) {
        if (path.type == "UnaryExpression" && path.node.operator == "-") {
            return;
        }
        const { confident, value } = path.evaluate();
        value != "Infinity" && confident && path.replaceInline(types.valueToNode(value));
    },
}
traverse(ast, combin_BinaryExpression);
/********************************************************************
替换函数调用处的字符串 即 CallExpression ----> StringLiteral
obfuscator 混淆过的js代码特征很明显 大数组 + 移位函数 + 解密函数,
然后在其他地方多次调用该解密函数
下面的插件将调用处的CallExpression直接计算出来,然后再替换值。
*********************************************************************/
const decode_str = {
    VariableDeclarator(path) {
        let { id, init } = path.node;
        if (!types.isArrayExpression(init) || init.elements.length == 0) return;
        let code = path.toString();
        let second_sibling = path.parentPath.getNextSibling(); //获取移位函数节点
        let third_sibling = second_sibling.getNextSibling(); //获取解密函数节点
        //******************************************************特征判断开始
        if (!second_sibling.isExpressionStatement() ||
            !third_sibling.isVariableDeclaration()) {
            return;
        }
        let expression = second_sibling.get('expression');
        if (!expression.isCallExpression()) return;
        let { callee, arguments } = expression.node;
        if (!types.isFunctionExpression(callee) || arguments.length  { return p.isIfStatement(); });
                    if_parent_path && if_parent_path.replaceWith(_path.node);
                },
            })
        }
        code += ';!' + second_sibling.toString() + third_sibling.toString();
        //eval到本地环境
        eval(code);
        const binding = third_sibling.scope.getBinding(func_name);
        if (!binding || binding.constantViolations.length > 0) {
            return;
        }
        let can_removed = true;
        for (const refer_path of binding.referencePaths) {
            if (refer_path.node.start  { return p.isCallExpression(); });
            try {
                let value = eval(call_path.toString());
                console.log(call_path.toString(), "-->", value);
                call_path.replaceWith(types.valueToNode(value))
            } catch (e) { can_removed = false; }
        }
        if (can_removed) {
            path.remove();
            second_sibling.remove();
            third_sibling.remove();
        }
    },
}
traverse(ast, decode_str);
traverse(ast, combin_BinaryExpression);
//SequenceExpression ---> ExpressionStatement,object对象还原预处理
const decode_comma = {
    ExpressionStatement(path) {
        //****************************************特征判断开始
        let prev_sibling = path.getPrevSibling();
        if (!prev_sibling.isVariableDeclaration()) return;
        let { declarations } = prev_sibling.node;
        if (declarations.length  {
            body.push(types.ExpressionStatement(express));
        })
        path.replaceInline(body);
    },
}
traverse(ast, decode_comma);
/*******************************************************
还原object,key多为字符串,value为字符串和函数
*******************************************************/
const decode_object = {
    VariableDeclarator(path) {
        const { id, init } = path.node;
        if (!types.isObjectExpression(init)) return;
        let name = id.name;
        let properties = init.properties;
        let all_next_siblings = path.parentPath.getAllNextSiblings();
        for (let next_sibling of all_next_siblings) {
            if (!next_sibling.isExpressionStatement()) break;
            let expression = next_sibling.get('expression');
            if (!expression.isAssignmentExpression()) break;
            let { operator, left, right } = expression.node;
            if (operator != '=' || !types.isMemberExpression(left) ||
                !types.isIdentifier(left.object, { name: name }) || !types.isStringLiteral(left.property)) {
                break;
            }
            properties.push(types.ObjectProperty(left.property, right));
            next_sibling.remove();
        }
        if (properties.length == 0) {
            return;
        }
        let scope = path.scope;
        let next_sibling = path.parentPath.getNextSibling();
        if (next_sibling.isVariableDeclaration()) {
            let declarations = next_sibling.node.declarations;
            if (declarations.length > 0 && types.isIdentifier(declarations[0].init, { name: name })) {
                scope.rename(declarations[0].id.name, name);
                next_sibling.remove();
            }
        }
        for (const property of properties) { //预判是否为 obfuscator 混淆的object
            let key = property.key.value;
            let value = property.value;
            if (!types.isStringLiteral(value) && !types.isFunctionExpression(value)) {
                return;
            }
        }
        for (const property of properties) {
            let key = property.key.value;
            let value = property.value;
            if (types.isLiteral(value)) {
                scope.traverse(scope.block, {
                    MemberExpression(_path) {
                        let _node = _path.node;
                        if (!types.isIdentifier(_node.object, { name: name })) return;
                        if (!types.isLiteral(_node.property, { value: key })) return;
                        _path.replaceWith(value);
                    },
                })
            } else if (types.isFunctionExpression(value)) {
                let ret_state = value.body.body[0];
                if (!types.isReturnStatement(ret_state)) continue;
                scope.traverse(scope.block, {
                    CallExpression: function(_path) {
                        let { callee, arguments } = _path.node;
                        if (!types.isMemberExpression(callee)) return;
                        if (!types.isIdentifier(callee.object, { name: name })) return;
                        if (!types.isLiteral(callee.property, { value: key })) return;
                        let replace_node = null;
                        if (types.isCallExpression(ret_state.argument) && arguments.length > 0) {
                            replace_node = types.CallExpression(arguments[0], arguments.slice(1));
                        } else if (types.isBinaryExpression(ret_state.argument) && arguments.length === 2) {
                            replace_node = types.BinaryExpression(ret_state.argument.operator, arguments[0], arguments[1]);
                        } else if (types.isLogicalExpression(ret_state.argument) && arguments.length === 2) {
                            replace_node = types.LogicalExpression(ret_state.argument.operator, arguments[0], arguments[1]);
                        }
                        replace_node && _path.replaceWith(replace_node);
                    }
                })
            }
        }
        path.remove();
    },
}
traverse(ast, decode_object);
/*******************************************************
去控制流
*******************************************************/
const decode_while = {
    WhileStatement(path) {
        const { test, body } = path.node;
        if (!types.isLiteral(test, { value: true }) || body.body.length === 0 || !types.isSwitchStatement(body.body[0])) return;
        let switch_state = body.body[0];
        let { discriminant, cases } = switch_state;
        if (!types.isMemberExpression(discriminant) || !types.isUpdateExpression(discriminant.property)) return;
        let arr_name = discriminant.object.name;
        let arr = [];
        let all_pre_siblings = path.getAllPrevSiblings();
        all_pre_siblings.forEach(pre_path => {
            const { declarations } = pre_path.node;
            let { id, init } = declarations[0];
            if (arr_name == id.name) {
                //arr = init.callee.object.value.split("|");
            }
            pre_path.remove();
        })
        let ret_body = [];
        arr.forEach(index => {
            let case_body = cases[index].consequent;
            if (types.isContinueStatement(case_body[case_body.length - 1])) {
                case_body.pop();
            }
            ret_body = ret_body.concat(case_body);
        })
        path.replaceInline(ret_body);
    },
}
traverse(ast, decode_while);
/***************************************************
处理IfStatement,规范If表达式
删除条件已知的语句
***************************************************/
traverse(ast, combin_BinaryExpression);
const decode_if = {
    IfStatement(path) {
        let { test, consequent, alternate } = path.node;
        if (!types.isBlockStatement(consequent)) {
            path.node.consequent = types.BlockStatement([consequent]);
        }
        if (alternate !== null && !types.isBlockStatement(alternate)) {
            path.node.alternate = types.BlockStatement([alternate]);
        }
        if (!types.isLiteral(test)) return;
        let value = test.value;
        consequent = path.node.consequent;
        alternate = path.node.alternate;
        if (value) {
            path.replaceInline(consequent.body);
        } else {
            alternate === null ? path.remove() : path.replaceInline(alternate.body);
        }
    },
    EmptyStatement(path) {
        path.remove();
    },
}
traverse(ast, decode_if);
/************************************
处理完毕,生成新代码
*************************************/
let { code } = generator(ast);
fs.writeFile(decode_file, code, (err) => {});
wannabe   

学习了,最近也在忙于这个破解,楼主有兴趣可以破解这个,http://kj.gouhys.cn/appzy/09031916/?url=https://v.qq.com/x/cover/mzc00200wi4iqo4/v0036evukju.html;
我找到了核心断点: hpostpm,(https://api.jiubojx.com/vip/?url=https://v.qq.com/x/cover/mzc00200wi4iqo4/v0036evukju.html)扣代码出来鬼鬼调试一运行就卡死,求指导
漁滒
OP
  


wannabe 发表于 2021-5-31 22:22
学习了,最近也在忙于这个破解,楼主有兴趣可以破解这个,http://kj.gouhys.cn/appzy/09031916/?url=https: ...

下次有空看一下
wannabe   


漁滒 发表于 2021-5-31 22:25
下次有空看一下

嗯嗯,断点好找,但是抠出来之后js,鬼鬼调试js 就卡死!
xixicoco   

大佬牛逼
JakerPower   

写的不错,大部分都看懂了,有点营养呀!~~~谢谢了
djxding   

学习一下。
大体思路,知道是怎么回事。
但是,细节上有些还不明白啊。
继续学习啊,自己给自己加油。
qfqyl   

小白一个,看来自己还需要努力啊
cwb6357123   

兄弟好样的,前一段时间想要拿下他,奈何败在了混淆JS上,你这简直是雪中送炭
您需要登录后才可以回帖 登录 | 立即注册

返回顶部