本文所有内容均以学习为目的,如有不良影响请联系我立即删除!

距离上一篇瑞数反调试的文章已经过了2个月,今天来聊一聊瑞数第一层js的反混淆,下面就来看看反混淆思路。

首先,拿到HTML中的js代码,简单阅读后发现,js是个自执行函数,首先声明了一个很大的二维数组:

之后定义了一大堆方法后,出现了控制流平坦化,大量的if-else if-else语句,并且使用的控制流数组就是从开头定义的二维数组中取的

var _$Al, _$_e, _$9n, _$fv, _$Di, _$GJ, _$YD, _$Dm, _$vl, _$rX, _$8a, _$Wj, _$qK, _$C8, _$Gm, _$ZP, _$7G, _$mk,
            _$0D;
var _$82, _$45, _$r_ = _$fJ, _$jy = _$LW[1];

我们可以先把变量还原成控制流数组,先把数组拿出来,然后通过AST修改代码:

_$LW = [...]
function step3(ast) {
    traverse(ast, {
        MemberExpression: handleList,
    })

    function handleList(path){
        let node = path.node;
        let parentNode = path.parent;
        if (node.object.name === '_$LW' && t.isVariableDeclarator(parentNode)){
            var p = node.property.value
            var value = _$LW[p]
            path.replaceInline(t.valueToNode(value))
        }
    }
}

这里需要注意的是,控制流数组在代码运行过程中,会被修改,所以大家要拿到修改之后的新数组进行替换,并修改起始下标,才没有问题。

第二步我们需要把所有的if-else if-else,全都替换成if-else的形式,方便我们后续做处理:

function step1(ast) {
    traverse(ast, {
        IfStatement: func
    })
    function func(path) {
        let node = path.node;
        let parentNode = path.parent;
        if (t.isIfStatement(parentNode) &&
            parentNode.alternate !== undefined &&
            parentNode.alternate === node
        ){
            path.replaceWith(t.blockStatement([node]))
        }
    }
}

这样所有的控制流都变成了if-else了,接下来就是重头戏,我们要把if-else控制流去除了。

大量的嵌套if-else其实都是没有实际作用的代码,每一个从控制流数组中遍历出来的值,最终经过大量的if-else都只会运行一句有用的代码。所以我们需要做的就是,将这个值带进if-else中计算,最终拿到这一句代码,同时去除掉所有的if-eise。

可以发现控制流在代码中不止出现了一次,其中有些存在于函数中,控制流数组下标是通过参数传进来的,但他们都是通过一个while(1)无限循环中执行的,同时while语句的前几句也都是固定的格式,我们可以通过这一特征,来定位控制流平坦化代码,定位到控制流平坦化代码之后,我们在前面提到,有些控制流平坦化代码在函数内部,这样的话我们需要知道这个函数被调用了多少次,传了哪些下标进来,并保存起来,组成一个Switch语句:

function handleWhile(path) {
        var prevSiblingNodePath = path.getPrevSibling();
        var prevSiblingNode = prevSiblingNodePath.node;
        if (prevSiblingNode.declarations.length === 4){
            var subscriptNameNode = prevSiblingNode.declarations[2];
            var subscriptName = subscriptNameNode.id.name;
            var parent_function = path.getFunctionParent().node
            var node = path.node
            var initNode = node.body
            var arrNode = prevSiblingNode.declarations[3];
            var arr = arrNode.init.elements;
            if (parent_function.params.length !== 0 && parent_function.params[0].name !== undefined){
                var param = parent_function.params[0].name
                var function_name = parent_function.id.name

                var case_list = []
                var p_parent_function = path.getFunctionParent().getFunctionParent()
                p_parent_function.traverse({
                    CallExpression(c_path){
                        var c_node = c_path.node
                        if (c_node.callee !== undefined &&
                            c_node.callee.name === function_name &&
                            c_node.arguments.length > 0 &&
                            t.isNumericLiteral(c_node.arguments[0]) &&
                            case_list.indexOf(c_node.arguments[0].value) === -1
                        ){
                            case_list.push(c_node.arguments[0].value)
                        }
                    }
                })

                let ast_case_list = []
                for (let cs of case_list){
                    let new_node = test(node.body, cs, 0)[0]
                    new_node.push(t.breakStatement())
                    ast_case_list.push(t.switchCase(t.numericLiteral(cs), new_node))
                }

                let a = t.switchStatement(t.identifier(param), ast_case_list)
                path.replaceWith(a)

            } 
        }
}

最后我们要实现的就是如上代码中的test方法,手动判断if-else中的逻辑表达式、二元表达式和一元表达式,需要注意的是,真正执行的代码中,可能存在修改控制流数组下标的语句,我们也需要相应的对下标进行修改

由于此处实现后的代码过长,因为篇幅原因此处只展示大概,可以参考这篇文章来实现 https://blog.csdn.net/qq_35491275/article/details/117969108


oh yeah