文章目录
- 页面流程
- 调试干扰
-
- [替换 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 对应的页码,没有加密参数
cookie
请求数据接口时是需要携带 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 生成的位置
Hook 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)