【困难】 猿人学web第一届 第9题 js混淆-动态cookie 2

文章目录

  • 页面流程
  • 调试干扰
    • [替换 utc.js 文件](#替换 utc.js 文件)
    • [替换 9.html 文件](#替换 9.html 文件)
  • [Hook Cookie](#Hook Cookie)
  • [AST 反混淆](#AST 反混淆)
    • [9.html文件 反混淆](#9.html文件 反混淆)
      • [字符串解密函数 $b](#字符串解密函数 $b)
        • [a](#a)
        • [b 解密函数检测点](#b 解密函数检测点)
        • 验证结果
        • [AST 代码](#AST 代码)
      • [完整的 AST 还原代码](#完整的 AST 还原代码)
      • 分析反混淆后的代码代码
    • [udc.js文件 反混淆](#udc.js文件 反混淆)
      • [字符串解密函数 _0x56ae](#字符串解密函数 _0x56ae)
      • [完整的 AST 还原代码](#完整的 AST 还原代码)
      • 格式化检测
  • [还原加密函数 decrypt](#还原加密函数 decrypt)
  • [9.html 请求处理](#9.html 请求处理)
  • [python 代码](#python 代码)

页面流程

打开 调试工具,查看数据接口 https://match.yuanrenxue.cn/api/match/9

请求参数

请求参数只携带了 page 对应的页码,没有加密参数

请求数据接口时是需要携带 m 加密字段的

m字段失效后会弹窗提示 cookie 信息失效并刷新页面重新请求

调试干扰

删除 cookie m字段并刷新页面会出现 debugger

这段debugger是在 if 条件中生成的,可以在对应的代码段选择一律不在此处暂停,但会很卡

替换 utc.js 文件

比较建议替换源文件将对应的 bugger 字段删除

代码里有格式化检测,替换时应取消美观输出

删除 udc.js 文件里对应的 debugger

将 udc.js 文件取消美观输出后保存到本地

ctrl + f 搜索 debugger 关键字 将对应的 debugger 删除(一共有 3 处)

将 删除后的代码替换到浏览器中

替换 9.html 文件

9.html文件是动态的,每一次的返回的代码段都不一样(先固定,把debugger调试过掉)
第 2 次生成的 debugger 是在 9.html 文件中出现的

查看上一层堆栈, 这是一段利用 Function.prototype.constructor 生成的 debugger 代码

Hook Function 代码

javascript 复制代码
let _Function = Function.prototype.constructor;
Function.prototype.constructor = function(val){
    if(val.indexOf('debugger') !== -1){
        return _Function.call(this, '');
    }
    return _Function.call(this, val);
}

在本地创建一个 html 文件

继续将生成 debugger 的字段替换(也需要保存取消了美观输出的代码,代码中有格式化检测)

需要删除的代码段

9.html 文件有两个debugger需要删除的
文件是动态的,需要自己找出生成 debugger 的代码

这是我这份文件的debugger代码,可以做个参考
第一处
d[$b('\x30\x78\x31\x38\x38','\x47\x37\x39\x46')+'\x45\x47']($b('\x30\x78\x35\x66','\x69\x55\x6a\x45')+'\x75',$b('\x30\x78\x31\x31\x62','\x39\x6d\x23\x48')+'\x72')

第二处
$b('\x30\x78\x31\x30\x62','\x44\x65\x29\x74')+'\x75'+d['\x65\x4f\x46'+'\x64\x67']

替换完成之后右键选择替换内容即可
替换完成之后会页面补显示数据,反而一直刷新,查看请求列表

替换好页面后就可以正常调试了,接下来就是找到 cookie 生成的位置

javascript 复制代码
// Hook Cookie
(function(){
    let cookieFunc = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie')
    Object.defineProperty(document, 'cookie', {
        get(){
            return cookieFunc.get.call(this)
        },
        set(val){
            if(val.indexOf('m') !== -1){
                debugger;
            }
            return cookieFunc.set.call(this, val)
        }
    })
})()

勾选事件监听器断点中的脚本断点,刷新页面后注入 Hook cookie 的代码

取消勾选脚本断点,在控制台运行 hook Cookie 的代码后 放开断点,直到在 Hook 代码中断住

查看上一层堆栈

对应的 js 代码是混淆过的,AST 小小解个混淆先

AST 反混淆

9.html文件 反混淆

将 9.html 中第一个 script 标签中的代码解混淆

字符串解密函数 $b

9.html 文件是动态的,但是检测的点都差不多,跟着浏览器对着本地进行调试即可
代码中常用到解密函数 b('...', '...') ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/760bcc7cad57406084ae44beeeffe64f.png)b() 解密函数在文件的开头中有定义
$b() 解密函数 依赖 $a 数组

$a 数组在文件的开头定义了,而后又经过一个自执行函数打乱了排序

$a

拿到打乱排序后的 $a 数组(在浏览器中 copy 即可)

断点下在 经过自执行函数打乱排序的代码段后,

断住之后 copy($a) 到本地即可

$b 解密函数检测点

断点打在 $b 解密函数中引用到 $a 数组的地方, 刷新页面后 单步调试

f 经过 h()方法执行后赋值成了 window

后又通过 f 取了['atob'] 方法

atob 在浏览器环境是有的,在 node 环境中并没有
在 node 中的处理

global['atob'] = require('atob') // npm install atob

继续往下跟,有一处格式化检测

在浏览器中返回值是 true

在 node 中返回的值是 false

直接将这段格式化检测的代码写死成 true 即可

g['test'](this['BBYicw']['toString']()) 改成 true

以上都处理好后,解密函数就可以正常执行了

验证结果


AST 代码

将代码放入 AST explorer站点解析

遍历 CallExpression 节点

callee 为 Identifier 类型,且 name 属性为 $b

获取 arguments 列表 里的字符串内容(实参)

执行解密函数 $b() 并传入实参拿到解密后的字符

对该节点进行替换

javascript 复制代码
// 将解密函数与对应的依赖拿到 AST 文件中
global['atob'] = require('atob')
var $a = [
    "QsOkw7g=",
	...数组太长了这里忽略了
];
var $b = function (a, b) {
	...解密函数太长了这里忽略了
    return c;
};
traverse(ast, {
    CallExpression(path){
        // callee 为 Identifier 类型,且 name 属性为 $b
        if(path.get('callee').isIdentifier({'name': '$b'})){
            // 通过判断之后获取的节点都为
            // $b('\x30\x78\x31\x37\x36', '\x21\x5d\x44\x2a')
            
            // 获取 arguments列表 里的字符串内容(实参)
            // '\x30\x78\x31\x37\x36', '\x21\x5d\x44\x2a'
            let Arg = path.node.arguments;

            
            // 执行解密函数并传入对应的参数
            // $b('\x30\x78\x31\x37\x36', '\x21\x5d\x44\x2a')
            let result = $b(Arg[0].value, Arg[1].value)

            // 替换节点
            console.log('解密函数还原前的代码:  ', path + '')
            path.replaceWith(types.valueToNode(result))
            console.log('解密函数还原前的代码:  ', path + '')
            console.log('============================================================')
        }
    }
});

还原前

还原后

完整的 AST 还原代码

javascript 复制代码
// 安装 babel 库:  npm install @babel/core
const fs = require('fs');

const traverse = require('@babel/traverse').default; // 用于遍历 AST 节点
const types = require('@babel/types');  // 用于判断, 生成 AST 节点

const parser = require('@babel/parser');  // 将js代码解析成ast节点
const generator = require('@babel/generator').default;  // 将ast节点转换成js代码

// 读取(路径记得改)
const ast_code = fs.readFileSync('demo.js', {
    encoding: 'utf-8'
});

let ast = parser.parse(ast_code);  // 将js代码解析成ast语法树


// 这里插入解析规则
///

// 将解密函数与对应的依赖拿到 AST 文件中
global['atob'] = require('atob')
var $a = [
    "VsKwwrc=",
	...数组太长,剩下的省略了
];
var $b = function (a, b) {
    ...解密函数太长,剩下的省略了
    return c;
};
traverse(ast, {
    CallExpression(path) {
        // callee 为 Identifier 类型,且 name 属性为 $b
        if (path.get('callee').isIdentifier({'name': '$b'})) {
            // 通过判断之后获取的节点都为
            // $b('\x30\x78\x31\x37\x36', '\x21\x5d\x44\x2a')

            // 获取 arguments列表 里的字符串内容(实参)
            // '\x30\x78\x31\x37\x36', '\x21\x5d\x44\x2a'
            let Arg = path.node.arguments;


            // 执行解密函数并传入对应的参数
            // $b('\x30\x78\x31\x37\x36', '\x21\x5d\x44\x2a')
            let result = $b(Arg[0].value, Arg[1].value)

            // 替换节点
            console.log('解密函数还原前的代码:  ', path + '')
            path.replaceWith(types.valueToNode(result))
            console.log('解密函数还原前的代码:  ', path + '')
            console.log('============================================================')
        }
    }
});

// ASCII码/16进制 字符/数值还原
traverse(ast, {
    "StringLiteral|NumericLiteral"(path) {
        if (path.node.extra) {
            if (path.isStringLiteral()) {
                console.log('ASCII码替换前: ', path.node.extra);
                delete path.node.extra.raw
                // path.node.extra.raw = `'${path.node.extra.rawValue}'`
                console.log('ASCII码替换后: ', path.node.extra);
                console.log('============================================================');
            } else if (path.isNumericLiteral()) {
                console.log('十六进制数值还原前: ', path.node.extra);
                path.node.extra.raw = `${path.node.extra.rawValue}`
                console.log('十六进制数值还原后: ', path.node.extra);
                console.log('============================================================');
            }

        }
    }
})

// 字符串相加
function strConcat(path) {
    for (let i = 0; i <= 3; i++) {
        let node = path.node
        // left 节点为 StringLiteral 类型
        // right 节点为 StringLiteral 类型
        // operator 操作符属性为字符串 +
        if (types.isStringLiteral(node.left) && types.isStringLiteral(node.right) && node.operator === '+') {
            // 例 'e' + 'f'
            console.log('字符串相加前: ', path + '');

            // 例 'e' + 'f'
            let result = path.node.left.value + path.node.right.value;

            // 例: 'e' + 'f'
            // 替换成: 'ef'
            path.replaceWith(types.valueToNode(result));
            console.log('字符串相加后:  ', path + '');
            console.log('============================================================');
        } else {
            // 递归是针对多个字符串相加的
            // 例当前遍历到的节点: 'a' + 'b' + 'c' + 'd'
            // 这个节点在上面是不会处理的
            // path 对象的 traverse 方法是从当前节点继续遍历
            // 传入 strConcat 方法的节点就为:  'a' + 'b' + 'c' + 'd'
            // 调用过后还是会再次进入到 path.traverse 因为还是会有多个字符串相加
            // 上一次传入的节点被处理过了
            // 所以这一次传入的节点代码就为 'ab' + 'c' + 'd'
            // 一直递归到节点为 'abc' + 'd' 就会停止递归
            // 'abc' + 'd' 在 for 循环中会二次处理 (可以试着for循环只遍历一次看看效果
            path.traverse({
                BinaryExpression(path_) {
                    strConcat(path_)
                }
            })
        }
    }
}

traverse(ast, {
    BinaryExpression(path) {  // 遍历 BinaryExpression 节点
        strConcat(path)
    }
})

// 花指令
traverse(ast, {
    VariableDeclarator(path) {
        if (path.get('init').isObjectExpression()) {
            let name = path.node.id.name;
            let binding = path.scope.getOwnBinding(name);
            let newObj = {}
            if (binding) {
                let refPath = binding.referencePaths;
                for (const index in refPath) {
                    let parPath = refPath[index].parentPath.parentPath;
                    // console.log(parPath + '')
                    if (parPath.isAssignmentExpression() && parPath.get('left').isMemberExpression()) {
                        let key = parPath.node.left.property.value;
                        newObj[key] = parPath.node.right;
                        // console.log('isAssignmentExpression:  ', (generator(newObj[key]).code).replaceAll('\n', ''));
                    }
                }

                for (const index in refPath) {
                    let parPath = refPath[index].parentPath.parentPath;
                    if (parPath.isVariableDeclaration()) {
                        if (parPath.get('declarations').length === 1) {
                            let refName = parPath.node.declarations[0].id.name;
                            let refBinding = parPath.scope.getBinding(refName);
                            if (refBinding) {
                                let refRefPath = refBinding.referencePaths.reverse();
                                for (const ref in refRefPath) {
                                    let refParPath = refRefPath[ref].parentPath;
                                    if (refParPath.isMemberExpression() && refParPath.get('property').isStringLiteral()) {
                                        let refKey = refParPath.node.property.value;
                                        // console.log('isVariableDeclaration:  ', refParPath.toString().replaceAll('\n', ''));
                                        // console.log(refKey)
                                        if (types.isStringLiteral(newObj[refKey])) {
                                            // console.log(generator(newObj[refKey]).code)
                                            // console.log('字符串花指令还原前: ', refParPath.parentPath.toString())
                                            refParPath.replaceWith(newObj[refKey])
                                            // console.log('字符串花指令还原后: ', refParPath.parentPath.toString())
                                            // console.log('============================================================')
                                        }
                                        if (types.isFunctionExpression(newObj[refKey])) {
                                            let objRet = newObj[refKey].body.body[0].argument;
                                            // BinaryExpression 二项式
                                            if (types.isBinaryExpression(objRet)) {
                                                let grandPath = refParPath.parentPath
                                                let operator = objRet.operator;  // 操作符
                                                let left = grandPath.node.arguments[0];
                                                let right = grandPath.node.arguments[1];

                                                // console.log('二项式花指令还原前', grandPath.toString())
                                                grandPath.replaceWith(
                                                    types.binaryExpression(operator, left, right)
                                                )
                                                // console.log('二项式花指令还原后', grandPath.toString())
                                                // console.log('============================================================')
                                                // CallExpression 调用
                                            } else if (types.isCallExpression(objRet)) {
                                                let grandPath = refParPath.parentPath
                                                let callee = grandPath.node.arguments[0];
                                                let argument = grandPath.node.arguments.slice(1);

                                                console.log('函数调用花指令还原前', grandPath.toString().replaceAll('\n', '').slice(0, 100))
                                                grandPath.replaceWith(
                                                    types.callExpression(callee, argument)
                                                )
                                                console.log('函数调用花指令还原后', grandPath.toString().replaceAll('\n', '').slice(0, 100))
                                                console.log('============================================================')
                                                // console.log('isCallExpression:  ', grandPath.toString().replaceAll('\n', ''))
                                            }
                                        }
                                    }
                                }

                            }

                        }
                    }
                }
                // path.stop();
                // console.log('============================================================')
            }
        }
    }
})

// Switch
traverse(ast, {
    WhileStatement(path) {
        if (path.get('test').isUnaryExpression() && path.inList) {
            let splitPath = path.getSibling(0).node.declarations[0].init.callee.object.value.split('|');
            let casesArg = path.node.body.body[0].cases;
            let indexObj = {}
            for (const index in casesArg) {
                let key = casesArg[index].test.value;
                indexObj[key] = casesArg[index].consequent[0]
            }

            let pathArray = []
            for (const index in casesArg) {
                pathArray.push(indexObj[casesArg[index].test.value]);
            }
            console.log('还原的Switch语句: ', path.toString().replaceAll('\n', '').slice(0, 50));
            path.replaceWithMultiple(pathArray)
            console.log('============================================================')

            for (let i = path.key; i >= 0; i--) {
                path.getSibling(i).remove();
            }

        }
    }
})

js_code = generator(ast, {
    // compact: true,  // 是否压缩,默认 false
}).code
ast = parser.parse(js_code)
// 删除无引用代码段
// 无引用标识符
traverse(ast, {
    Identifier(path) {
        let name = path.node.name;
        let binding = path.scope.getBinding(name);
        let refPath = binding && binding.referencePaths;
        if (refPath && refPath.length === 0) {
            let grandPath = path.parentPath.parentPath
            if (grandPath.isVariableDeclaration()) {
                console.log(path.parentPath.parentPath.toString());
                grandPath.remove();
                console.log('============================================================')
            }
        }
    }
})

// 无引用对象
traverse(ast, {
    VariableDeclarator(path) {
        if (path.get('init').isObjectExpression()) {
            let name = path.node.id.name;
            let binding = path.scope.getOwnBinding(name);
            if (path.get('init.properties').length === 0) {
                console.log('删除的无引用对象代码段: ', path + '')
                path.remove()
            }
            if (binding) {
                let refPath = binding.referencePaths;
                for (const index in refPath) {
                    let parPath = refPath[index].parentPath.parentPath;
                    if (parPath.isAssignmentExpression()  && !parPath.get('right').isIdentifier()) {
                        console.log('删除的无引用对象parPath代码段: ', parPath.toString().replaceAll('\n', '').slice(0, 50));
                        parPath.remove()
                    }
                }
            }
            console.log('============================================================')
        }
    }
})


// 虚假 if
traverse(ast, {
    // IfStatement 为if判断语句
    // ConditionalExpression 为三元表达式
    "IfStatement|ConditionalExpression"(path) {
        // 该节点中的判断条件应该为 二元表达式
        // left 节点应该为 StringLiteral 类型
        // right 节点应该为 StringLiteral 类型
        if (path.get('test').isBinaryExpression() && path.get('test.left').isStringLiteral() && path.get('test.right').isStringLiteral()) {
            // 取出判断条件中对应的值
            // 例:  if ('a' === 'a') {  console.log("'a' === 'a' true");} else {  console.log("'a' === 'a' false");}

            let operator = path.node.test.operator;  // 取出操作符 例: ===
            let leftString = path.node.test.left.value;  // 左边的字符串 例: 'a'
            let rightString = path.node.test.right.value;  // 右边的字符串 例: === 'a'

            // 生成 eval 可判断的字符串
            let vmRun = `"${leftString}"  ${operator} "${rightString}"`;  // 例: 'a' === 'a'
            let result = eval(vmRun);
            console.log('虚假 if:  ' + (path + '').replaceAll('\n', '').slice(0, 50))

            // 取出 if 与 else 中的代码块
            // path.node.consequent.body 为 if(){}else{}; 形式取值
            // path.node.consequent.arguments 为 statement ? true : false; 形式取值
            let ifTrue = path.node.consequent.body || path.node.consequent.arguments;
            let elFalse = path.node.alternate.body || path.node.alternate.arguments;

            // 判断 result 的执行结果,替换对应的代码块 if 或 else
            result ? path.replaceWithMultiple(ifTrue) : path.replaceWithMultiple(elFalse)
        }
    }
})

///
js_code = generator(ast, {
    compact: true,  // 是否压缩,默认 false
}).code  // 将ast节点转换成js代码

// 写入(路径记得改)
fs.writeFileSync('New_demo.js', js_code, {
    encoding: 'utf-8',
})

分析反混淆后的代码代码

还原后继续 hook

可以看到 cookie m 的生成 (m-1)['toString']() + res

m 为 3 (经过循环后m为4,减去1后就为3)

res是经过 decrpyt 方法生成的

decrypt() 函数在 udc.js 文件中, udc.js 文件也是混淆过的,再将 udc.js 还原

图片的函数是decrypt() 函数,已经用 AST 还原好了

udc.js文件 反混淆

字符串解密函数 _0x56ae

函数依赖一个大数组,在浏览器 copy 即可

继续向下调试

window['atob']

在node中导包即可

const atob = require('atob'); // npm install atob

格式化检测

在 node 中将这行代码改为 true 即可
以上都处理好后,验证下结果

接下来写好对应的解析规则,就可以开始还原字符串了,思路与 9.html 文件的解密函数还原一样

完整的 AST 还原代码

javascript 复制代码
// 安装 babel 库:  npm install @babel/core
const fs = require('fs');

const traverse = require('@babel/traverse').default; // 用于遍历 AST 节点
const types = require('@babel/types');  // 用于判断, 生成 AST 节点

const parser = require('@babel/parser');  // 将js代码解析成ast节点
const generator = require('@babel/generator').default;  // 将ast节点转换成js代码

// 读取(路径记得改)
const ast_code = fs.readFileSync('demo.js', {
    encoding: 'utf-8'
});

let ast = parser.parse(ast_code);  // 将js代码解析成ast语法树

// 这里插入解析规则
///

let atob = require('atob')
let __0x9a4eb = [
    "RcOJesKLGcO7",
    // 数组太长了,按照上面去浏览器 copy 即可
]
var _0x56ae = function (_0x4f4e67, _0x43c602) {
    // 剩余的内容 按照上面去浏览器 copy 即可
    return _0x223635;
};
traverse(ast, {
    CallExpression(path) {
        // callee 为 Identifier 类型,且 name 属性为 $b
        if (path.get('callee').isIdentifier({'name': '_0x56ae'})) {
            // 通过判断之后获取的节点都为
            // $b('\x30\x78\x31\x37\x36', '\x21\x5d\x44\x2a')

            // 获取 arguments列表 里的字符串内容(实参)
            // '\x30\x78\x31\x37\x36', '\x21\x5d\x44\x2a'
            let Arg = path.node.arguments;


            // 执行解密函数并传入对应的参数
            // $b('\x30\x78\x31\x37\x36', '\x21\x5d\x44\x2a')
            let result = _0x56ae(Arg[0].value, Arg[1].value)

            // 替换节点
            console.log('解密函数还原前的代码:  ', path + '')
            path.replaceWith(types.valueToNode(result))
            console.log('解密函数还原前的代码:  ', path + '')
            console.log('============================================================')
        }
    }
});

// ASCII码/16进制 字符/数值还原
traverse(ast, {
    "StringLiteral|NumericLiteral"(path) {
        if (path.node.extra) {
            if (path.isStringLiteral()) {
                console.log('ASCII码替换前: ', path.node.extra);
                delete path.node.extra.raw;
                console.log('ASCII码替换后: ', path.node.extra);
                console.log('============================================================');
            } else if (path.isNumericLiteral()) {
                console.log('十六进制数值还原前: ', path.node.extra);
                delete path.node.extra.raw;
                console.log('十六进制数值还原后: ', path.node.extra);
                console.log('============================================================');
            }

        }
    }
})

// 花指令
traverse(ast, {
    VariableDeclarator(path) {
        if (path.get('init').isObjectExpression()) {
            let property = path.node.init.properties;
            let newObj = {}
            for (const index in property) {
                let key = property[index].key.value;
                newObj[key] = property[index].value;
            }

            let name = path.node.id.name;
            let binding = path.scope.getBinding(name);
            if (binding) {
                let refPath = binding.referencePaths.reverse();
                for (const index in refPath) {
                    let parPath = refPath[index].parentPath
                    if (parPath.isMemberExpression() && parPath.get('property').isStringLiteral()) {
                        let key = parPath.node.property.value;
                        // 字符串
                        if (types.isStringLiteral(newObj[key])) {
                            // console.log('字符串花指令前: ', parPath + '')
                            parPath.replaceWith(newObj[key])
                            // console.log('字符串花指令后: ', parPath + '')
                            // console.log('============================================================');
                        } else if (types.isFunctionExpression(newObj[key])) {
                            let retState = newObj[key].body.body[0].argument;
                            let grandPath = parPath.parentPath;
                            // 二项式表达式
                            if (types.isBinaryExpression(retState)) {
                                let operator = retState.operator;
                                let binArg = grandPath.node.arguments;
                                // console.log('二项式花指令前: ', grandPath + '');
                                grandPath.replaceWith(types.binaryExpression(operator, binArg[0], binArg[1]));
                                // console.log('二项式花指令后: ', grandPath + '');
                                // console.log('============================================================');


                            } else if (types.isLogicalExpression(retState)) {
                                let operator = retState.operator;
                                let logArg = grandPath.node.arguments;
                                let newLogical = types.logicalExpression(operator, logArg[0], logArg[1])
                                // console.log('二项式花指令前: ', grandPath + '');
                                grandPath.replaceWith(newLogical)
                                // console.log('二项式花指令后: ', grandPath + '');
                                // console.log('============================================================');

                                // 调用花指令
                            } else if (types.isCallExpression(retState)) {
                                let binArg = grandPath.node.arguments;
                                // console.log('调用花指令前: ', grandPath + '');
                                let newCallExp = types.callExpression(binArg[0], binArg.slice(1))
                                grandPath.replaceWith(newCallExp)
                                // console.log('调用花指令后: ', grandPath + '');
                                // console.log('============================================================');
                            }
                        }
                    }
                }
            }
        }
    }
})

js_code = generator(ast, {
    // compact: true,  // 是否压缩,默认 false
}).code
ast = parser.parse(js_code);
// 无引用对象
traverse(ast, {
    Identifier(path) {
        let name = path.node.name;
        let binding = path.scope.getBinding(name);
        if (binding) {
            let refPath = binding.referencePaths;
            let parPath = path.parentPath;
            if (refPath.length === 0 && parPath.isVariableDeclarator() && parPath.get('init').isObjectExpression()) {
                console.log('已删除: ', parPath.parentPath.toString().replaceAll('\n', '').slice(0, 50));
                parPath.parentPath.remove();
                console.log('============================================================');
            }
        }
    }
})
// 虚假 if
traverse(ast, {
    // IfStatement 为if判断语句
    // ConditionalExpression 为三元表达式
    "IfStatement|ConditionalExpression"(path) {
        // 该节点中的判断条件应该为 二元表达式
        // left 节点应该为 StringLiteral 类型
        // right 节点应该为 StringLiteral 类型
        if (path.get('test').isBinaryExpression() && path.get('test.left').isStringLiteral() && path.get('test.right').isStringLiteral()) {
            // 取出判断条件中对应的值
            // 例:  if ('a' === 'a') {  console.log("'a' === 'a' true");} else {  console.log("'a' === 'a' false");}

            let operator = path.node.test.operator;  // 取出操作符 例: ===
            let leftString = path.node.test.left.value;  // 左边的字符串 例: 'a'
            let rightString = path.node.test.right.value;  // 右边的字符串 例: === 'a'

            // 生成 eval 可判断的字符串
            let vmRun = `"${leftString}"  ${operator} "${rightString}"`;  // 例: 'a' === 'a'
            let result = eval(vmRun);
            console.log('虚假 if:  ' + (path + '').replaceAll('\n', '').slice(0, 50))

            // 取出 if 与 else 中的代码块
            // path.node.consequent.body 为 if(){}else{}; 形式取值
            // path.node.consequent.arguments 为 statement ? true : false; 形式取值
            let ifTrue = path.node.consequent.body || path.node.consequent.arguments;
            let elFalse = path.node.alternate.body || path.node.alternate.arguments;

            // 判断 result 的执行结果,替换对应的代码块 if 或 else
            result ? path.replaceWithMultiple(ifTrue) : path.replaceWithMultiple(elFalse)
        }
    }
})

// switch
traverse(ast, {
    WhileStatement(path) {
        if (path.inList && path.key === 1) {
            let sibLin = path.getSibling(0);
            let splitStr = sibLin.node.declarations[0].init.callee.object.value.split('|');

            let idxObj = {}
            let cases = path.node.body.body[0].cases;
            for (const index in cases){
                let key = cases[index].test.value;
                idxObj[key] = cases[index].consequent[0]
            }

            let newArray = []
            for (const index in splitStr){
                newArray.push(idxObj[splitStr[index]]);
            }
            console.log('switch:', path.toString().replaceAll('\n', '').slice(0, 50));
            path.replaceWithMultiple(newArray)
            sibLin.remove();
            console.log('============================================================');
        }
    }
})
/
js_code = generator(ast, {
    compact: false,  // 是否压缩,默认 false
}).code  // 将ast节点转换成js代码

// 写入(路径记得改)
fs.writeFileSync('New_demo.js', js_code, {
    encoding: 'utf-8',
})

格式化检测

在将还原好的 utc.js 文件替换之前,还需要将里面的格式化检测过掉
第一处

javascript 复制代码
var _0x5ea72e = new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"].+['|\"];? *}");
return !_0x5ea72e["test"](_0x3b5e10["toString"]());

// 改成
var _0x5ea72e = new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"].+['|\"];? *}");
return !true;

第二处

javascript 复制代码
var _0x2dc31f = new RegExp("(\\\\[x|u](\\w){2,4})+");
return _0x2dc31f["test"](_0x3b2471["toString"]());

// 改成
var _0x2dc31f = new RegExp("(\\\\[x|u](\\w){2,4})+");
return true;

第三处

javascript 复制代码
if (!_0x4d1b87["test"](_0x4818e0 + "chain") || !_0x1dda0b["test"](_0x4818e0 + "input"))

// 改成
if (!true || !true)

顺便全局搜搜是否存在 debugger 关键字,如果有的话将 debugger 替换成空即可
这两段代码也是不需要的,直接删除即可

第一段为解密函数部分

第二段为延时执行

还原加密函数 decrypt

将 utc.js 代码全部 copy 到本地
有两个地方的环境是需要补的

javascript 复制代码
"Microsoft Internet Explorer" == navigator["appName"]

// 在文件头部声明
navigator = {
    appName: 'Netscape'
};
javascript 复制代码
if (window["crypto"] && window["crypto"]["getRandomValues"])

// 在文件头部声明
window = global;

测试

javascript 复制代码
function sdk(nums, timeStrap){
    for (var m = 1; m <= nums; m++) {
        res = decrypt(timeStrap) + "r";
    }
    return (m - 1)["toString"]() + res;
}

console.log(sdk(3, "1723805794"));

会发现 console.log 打印不出来,是因为 console 里面的所有方法都被重置为空函数了
文件中重置 console.log 的代码

javascript 复制代码
// 将这里的代码注释或删除即可
_0xe77b28["console"]["log"] = _0xaf0f8f;
_0xe77b28["console"]["warn"] = _0xaf0f8f;
_0xe77b28["console"]["debug"] = _0xaf0f8f;
_0xe77b28["console"]["info"] = _0xaf0f8f;
_0xe77b28["console"]["error"] = _0xaf0f8f;
_0xe77b28["console"]["exception"] = _0xaf0f8f;
_0xe77b28["console"]["trace"] = _0xaf0f8f;

处理好以上的各个点之后,就可以顺利输出了

9.html 请求处理

9.html 文件是动态的, 关键的点是 m 循环的次数,还有 加密对应的字符串

例如 循环的次数: m <= 3, 加密的点:decrypt("1723805794")
用这则将这两个点匹配好就行
时间戳的正则

re.findall("(decrypt,'(\d+)')", response.text)[0]
循环次数的正则

try:

nums = re.findall(r'window=new Array();for(var m=0x1;m<=(\d);', response.text)[0]

except:

nums = re.findall(r"window=new Array();for(var m=0x1;.*?(m,(\d));", response.text)[0]

python 代码

python 复制代码
import re
import requests
import execjs

headers = {
   "user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/127.0.0.0 Safari/537.36",
}
cookies = {
   "sessionid": "你的SessionID",
}


def call_js(file_name, func_name, *args):
   with open(file_name, mode='r', encoding='utf-8') as f:
       js_code = execjs.compile(f.read())
   return js_code.call(func_name, *args)


def send_index():
   url = "https://match.yuanrenxue.cn/match/9"
   response = requests.get(url, headers=headers, cookies=cookies)
   time_strap_ = re.findall("\(decrypt,'(\d+)'\)", response.text)[0]
   try:
       nums = re.findall(r'window=new Array\(\);for\(var m=0x1;m<=(\d);', response.text)[0]
   except:
       nums = re.findall(r"window=new Array\(\);for\(var m=0x1;.*?\(m,(\d)\);", response.text)[0]
   return time_strap_, nums


def send_math9(page_):
   url = "https://match.yuanrenxue.cn/api/match/9"
   params = {
       "page": f"{page_}"
   }
   response = requests.get(url, headers=headers, params=params, cookies=cookies)
   print(response.json())
   return response.json()['data']


if __name__ == '__main__':
   time_strap, num = send_index()
   cookies['m'] = call_js('9.js', 'sdk', num, time_strap)
   nums = 0
   division_nums = 0
   for page in range(1, 6):
       math9_data = send_math9(page)
       for dataDict in math9_data:
           nums += int(dataDict['value'])
           division_nums += 1
       print('页数: ', page, '总和', nums, '被除数: ', division_nums)
   print(nums / division_nums)
相关推荐
真滴book理喻21 分钟前
Vue(四)
前端·javascript·vue.js
程序员_三木44 分钟前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
黄公子学安全1 小时前
Java的基础概念(一)
java·开发语言·python
程序员一诺2 小时前
【Python使用】嘿马python高级进阶全体系教程第10篇:静态Web服务器-返回固定页面数据,1. 开发自己的静态Web服务器【附代码文档】
后端·python
数据小小爬虫2 小时前
利用Java爬虫获取苏宁易购商品详情
java·开发语言·爬虫
小木_.2 小时前
【Python 图片下载器】一款专门为爬虫制作的图片下载器,多线程下载,速度快,支持续传/图片缩放/图片压缩/图片转换
爬虫·python·学习·分享·批量下载·图片下载器
lovelin+v175030409662 小时前
安全性升级:API接口在零信任架构下的安全防护策略
大数据·数据库·人工智能·爬虫·数据分析
开心工作室_kaic2 小时前
springboot476基于vue篮球联盟管理系统(论文+源码)_kaic
前端·javascript·vue.js
川石教育2 小时前
Vue前端开发-缓存优化
前端·javascript·vue.js·缓存·前端框架·vue·数据缓存
搏博2 小时前
使用Vue创建前后端分离项目的过程(前端部分)
前端·javascript·vue.js