调试干扰
进入题目 打开开发者工具会进入一个无限 debugger;
向上查看堆栈,可以找到生成 debugger 的代码段
手动解混淆后可以知道 debugger 生成的方式
(function () {
// 函数内的代码是不需要的,因为里面的代码不会执行
}['constructor']('debugger')['call']('action'));
是利用 Function.prototype.constructor 函数生成的 debugger
因为 (function(){}[‘constructor’]) === Function.prototype.constructor;
Hook 代码
_Function = Function.prototype.constructor;
Function.prototype.constructor = function (val) {
if(val.indexOf('debugger') !== -1){
return _Function.call(this, '')
}
return _Function.call(this, val)
}
请求流程
数据接口 https://match.yuanrenxue.cn/api/match/14
请求参数为 对应的页码
cookie 参数需要携带 m,mz 字段
mz 字段是一样的
m 字段是动态的
每次请求数据前,都会先请求 m文件
m 文件是动态的,返回的是 js 代码>
Hook Cookie
请求数据时需要携带 cookie 中 m 和 mz 参数
// Hook Function
_Function = Function.prototype.constructor;
Function.prototype.constructor = function (val) {
if(val.indexOf('debugger') !== -1){
return _Function.call(this, '')
}
return _Function.call(this, val)
};
// Hook Cookie
(function(){
const cookieSet = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie')
Object.defineProperty(document, 'cookie', {
get(){
return cookieSet.get.call(this);
},
set(val){
debugger
return cookieSet.set.call(this, val);
}
})
})()
勾选脚本断点 --> 刷新页面 --> 注入 Hook 代码 --> 取消勾选脚本断点 --> 放开断点
生成 cookie 的 m.js 文件是经过混淆的,给 m.js 安排一下 AST
AST 还原 m.js文件
先保存一份 m.js 文件到本地
AST 代码
// 安装 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语法树
// 这里插入解析规则 ==============================================================================================================================================================================
// unicode, 十六进制数值
traverse(ast, {
StringLiteral(path) {
delete path.node.extra.raw;
},
NumericLiteral(path) {
delete path.node.extra.raw;
}
})
// 字符串相加
function addStr(path) {
if (path.get('left').isStringLiteral() && path.get('right').isStringLiteral()) {
let left = path.node.left.value;
let right = path.node.right.value;
let result = left + right
path.replaceWith(types.stringLiteral(result))
} else if (path.get('left').isBinaryExpression() && path.get('right').isStringLiteral()) {
path.traverse({
BinaryExpression(path_) {
addStr(path_)
}
})
}
}
for (let i = 0; i < 2; i++) {
traverse(ast, {
BinaryExpression(path) {
addStr(path)
}
})
}
// 逗号表达式
traverse(ast, {
VariableDeclaration(path) {
if (path.get('declarations').length > 1) {
let kind = path.node.kind
let declarations = path.node.declarations
let nodeList = []
for (const idx in declarations) {
let newNode = types.variableDeclaration(kind, [declarations[idx]])
nodeList.push(newNode)
}
path.replaceWithMultiple(nodeList)
}
},
SequenceExpression(path) {
if (path.get('expressions').length > 1) {
let expressions = path.node.expressions;
let nodeList = [];
for (const idx in expressions) {
nodeList.push(expressions[idx])
}
path.replaceWithMultiple(nodeList)
path.skip()
}
},
ReturnStatement(path) {
if (path.node.argument && path.get('argument.expressions').length > 1) {
let expressions = path.node.argument.expressions;
let nodeList = expressions.slice(0, -1);
let retNOde = types.returnStatement(expressions[expressions.length - 1])
nodeList.push(retNOde)
path.replaceWithMultiple(nodeList)
}
}
})
js_code = generator(ast, {
compact: false, // 是否压缩,默认 false
}).code
ast = parser.parse(js_code);
function addObj(path, newObj) {
let name = path.node.id.name;
let binding = path.scope.getBinding(name);
if (binding) {
let refPath = binding.referencePaths;
for (const idx in refPath) {
let grandPath = refPath[idx].parentPath.parentPath;
if(grandPath.isAssignmentExpression() && grandPath.get('left').isMemberExpression() && grandPath.get('left.property').isStringLiteral()){
let key = grandPath.node.left.property.value;
newObj[key] = grandPath.get('right');
}else if(grandPath.isVariableDeclaration()){
repObj(grandPath, newObj)
}
}
}
}
function repObj(path, newObj) {
let name = path.node.declarations[0].id.name;
let binding = path.scope.getBinding(name);
if(binding){
let refPath = binding.referencePaths.reverse();
for(const idx in refPath){
let parPath = refPath[idx].parentPath;
if(parPath.isMemberExpression() && parPath.get('property').isStringLiteral()){
let key = parPath.node.property.value;
if(newObj[key].isStringLiteral()){
parPath.replaceWith(newObj[key].node);
}else if (newObj[key].isFunctionExpression() && newObj[key].get('body.body').length === 1){
let returnStatement = newObj[key].get('body.body')[0].get('argument');
let grandPath = parPath.parentPath;
if(returnStatement.isBinaryExpression() || returnStatement.isLogicalExpression()){
let operator = returnStatement.node.operator;
let callArg = grandPath.node.arguments;
let newNode = returnStatement.isBinaryExpression()
? types.binaryExpression(operator, callArg[0], callArg[1])
: types.logicalExpression(operator, callArg[0], callArg[1]) ;
grandPath.replaceWith(newNode);
}else if(returnStatement.isCallExpression()){
let callArg = grandPath.node.arguments;
let newNode = types.CallExpression(callArg[0], callArg.slice(1))
grandPath.replaceWith(newNode);
}
}
}
}
}
}
// 花指令
traverse(ast, {
VariableDeclarator(path) {
if (path.get('init').isObjectExpression()) {
let newObj = {};
addObj(path, newObj)
}
}
})
// 字符串相加
for (let i = 0; i < 2; i++) {
traverse(ast, {
BinaryExpression(path) {
addStr(path)
}
})
}
js_code = generator(ast, {
compact: false, // 是否压缩,默认 false
}).code
ast = parser.parse(js_code);
// switch
traverse(ast, {
WhileStatement(path){
if(path.inList && path.get('test').isUnaryExpression()){
let idxArray = path.getSibling(path.key - 2).node.declarations[0].init.callee.object.value.split('|')
let cases = path.get('body.body')[0].get('cases')
let idxObj = {}
for (const idx in cases){
let key = cases[idx].get('test').node.value;
idxObj[key] = cases[idx].get('consequent')
}
let nodeArr = []
idxArray.forEach((objkey) => {
for (const arrIdx in idxObj[objkey]){
if(!idxObj[objkey][arrIdx].isContinueStatement()){
nodeArr.push(idxObj[objkey][arrIdx].node)
}
}
})
for (let i=0; i<=path.key; i++){
path.getSibling(0).remove()
}
path.replaceWithMultiple(nodeArr)
}
}
})
// ==============================================================================================================================================================================
js_code = generator(ast, {
compact: false, // 是否压缩,默认 false
}).code // 将ast节点转换成js代码
// 写入
fs.writeFileSync('New_demo.js', js_code, {
encoding: 'utf-8',
})
将 m.js 内容替换成还原后的继续 Hook cookie
Hook 到 cookie生成的位置后,向上查看堆栈
mz 字段生成的位置
m 字段生成的位置
加密流程
cookie mz 字段
Hook Cookie 后首先看到的是 mz 字段
向上查看堆栈,查看生成 cookie mz 字段的代码段
b64_zw 通过 btoa(z) 生成 z为数组,环境值
mz 字段可以固定,不需要在本地生成,每个人的电脑环境不一样,生成的值也不一样,
同一台电脑的 mz 字段是一样的
cookie m 字段
向上查看堆栈
正常情况下 Object[“defineProperty”] 不传参数执行是会报错的
但在 14 题的页面中执行返回的是空字符串
因为在文件的开头 Object[“defineProperty”] 就被重写了
(所以 hook cookie 要在脚本执行前 hook才能 hook 到)
所以生成 m 字段 代码会执行 false 条件的代码段
document["cookie"] = "m=" + m5(gee(aa, bb, c, d, e, b64_zw)) + "|" + b + "|" + a + "|" + window["n"] + ";path=/";
现在 window[‘n’], window[‘v142’], window[‘v14’] 还不知道是怎么生成的
hook window 的 ‘n’, ‘v142’, ‘v14’ 属性
勾选脚本断点,在页面脚本开始执行前 hook
function HookFunc(obj, key) {
let blockVar = obj[key];
Object.defineProperty(obj, key, {
get() {
return blockVar
},
set(val) {
console.log(key, val)
debugger;
blockVar = val;
}
})
}
['n', 'v142', 'v14'].forEach((value) => {HookFunc(window, value)})
window[‘n’]
window[‘v142’]
window[‘‘v14’’]
注意
m 文件是动态的,n值是固定的0,但 v14, v142 的值是动态变化的
生成 v14, v142 属性的代码段是在 eval 函数里生成的 Y 是请求
Y 是请求 “/api/match/14/m” 后返回的数据
将 “/api/match/14/m” 接口里的代码还原后进行替换
m 代码反混淆
先将将 m 文件替换
将代码保存到本地 demo.js
解密函数
前三列是解密函数和对应的依赖,还原时是需要用到的
解密函数内部是有格式化检测的,这里将 REgExp.prototype.test 方法重写即可
代码
// 安装 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语法树
// 这里插入解析规则 ==============================================================================================================================================================================
// unicode, 十六进制数值
traverse(ast, {
StringLiteral(path) {
delete path.node.extra.raw;
},
NumericLiteral(path) {
delete path.node.extra.raw;
}
})
RegExp.prototype.test = function(){return true}
var $_0x5109 = [解密函数数组];
(function (_0x3608d7, _0x510963) {
// 自执行数组乱序
}($_0x5109, 0x147));
var $_0x5d3c = function (_0x3608d7, _0x510963) {
// 解密函数
return _0x5d3c4d;
};
// 解密函数还原字符串
traverse(ast, {
CallExpression(path){
if(path.get('callee').isIdentifier({name: $_0x5d3c.name})){
let callArg = path.node.arguments;
let result = $_0x5d3c(callArg[0].value, callArg[1].value);
path.replaceWith(types.StringLiteral(result))
}
}
})
// 字符串相加
function addStr(path) {
if (path.get('left').isStringLiteral() && path.get('right').isStringLiteral()) {
let left = path.node.left.value;
let right = path.node.right.value;
let result = left + right
path.replaceWith(types.stringLiteral(result))
} else if (path.get('left').isBinaryExpression() && path.get('right').isStringLiteral()) {
path.traverse({
BinaryExpression(path_) {
addStr(path_)
}
})
}
}
for (let i = 0; i < 2; i++) {
traverse(ast, {
BinaryExpression(path) {
addStr(path)
}
})
}
// 解决正则字符串报错
traverse(ast, {
StringLiteral(path){
let result = path.node.value;
if(result.indexOf('"') !== -1 || result.indexOf("'") !== -1 || result.indexOf("\\") !== -1){
path.replaceWith(types.templateLiteral(
[types.templateElement({raw: result})],
[]
))
}
}
})
// 逗号表达式
traverse(ast, {
VariableDeclaration(path) {
if (path.get('declarations').length > 1) {
let kind = path.node.kind
let declarations = path.node.declarations
let nodeList = []
for (const idx in declarations) {
let newNode = types.variableDeclaration(kind, [declarations[idx]])
nodeList.push(newNode)
}
path.replaceWithMultiple(nodeList)
}
},
SequenceExpression(path) {
if (path.get('expressions').length > 1) {
let expressions = path.node.expressions;
let nodeList = [];
for (const idx in expressions) {
nodeList.push(expressions[idx])
}
path.replaceWithMultiple(nodeList)
path.skip()
}
},
ReturnStatement(path) {
if (path.node.argument && path.get('argument.expressions').length > 1) {
let expressions = path.node.argument.expressions;
let nodeList = expressions.slice(0, -1);
let retNOde = types.returnStatement(expressions[expressions.length - 1])
nodeList.push(retNOde)
path.replaceWithMultiple(nodeList)
}
}
})
js_code = generator(ast, {
compact: false, // 是否压缩,默认 false
}).code
ast = parser.parse(js_code);
// 花指令
function addObj(path, newObj) {
let name = path.node.id.name;
let binding = path.scope.getBinding(name);
if (binding) {
let refPath = binding.referencePaths;
for (const idx in refPath) {
let grandPath = refPath[idx].parentPath.parentPath;
if(grandPath.isAssignmentExpression() && grandPath.get('left').isMemberExpression() && grandPath.get('left.property').isStringLiteral()){
let key = grandPath.node.left.property.value;
newObj[key] = grandPath.get('right');
}else if(grandPath.isVariableDeclaration()){
repObj(grandPath, newObj)
}
}
}
}
function repObj(path, newObj) {
let name = path.node.declarations[0].id.name;
let binding = path.scope.getBinding(name);
if(binding){
let refPath = binding.referencePaths.reverse();
for(const idx in refPath){
let parPath = refPath[idx].parentPath;
if(parPath.isMemberExpression() && parPath.get('property').isStringLiteral()){
let key = parPath.node.property.value;
if(newObj[key].isStringLiteral() || newObj[key].isTemplateLiteral()){
parPath.replaceWith(newObj[key].node);
}else if (newObj[key].isFunctionExpression() && newObj[key].get('body.body').length === 1){
let returnStatement = newObj[key].get('body.body')[0].get('argument');
let grandPath = parPath.parentPath;
if(returnStatement.isBinaryExpression() || returnStatement.isLogicalExpression()){
let operator = returnStatement.node.operator;
let callArg = grandPath.node.arguments;
let newNode = returnStatement.isBinaryExpression()
? types.binaryExpression(operator, callArg[0], callArg[1])
: types.logicalExpression(operator, callArg[0], callArg[1]) ;
grandPath.replaceWith(newNode);
}else if(returnStatement.isCallExpression()){
let callArg = grandPath.node.arguments;
let newNode = types.CallExpression(callArg[0], callArg.slice(1))
grandPath.replaceWith(newNode);
}
}else{
console.log('未处理的类型', newObj[key].type)
console.log(newObj[key].toString())
}
}
}
}
}
traverse(ast, {
VariableDeclarator(path) {
if (path.get('init').isObjectExpression()) {
let newObj = {};
addObj(path, newObj)
}
}
})
// 字符串相加
for (let i = 0; i < 2; i++) {
traverse(ast, {
BinaryExpression(path) {
addStr(path)
}
})
}
// switch
traverse(ast, {
WhileStatement(path){
if(path.inList && path.get('test').isUnaryExpression()){
let idxArray = path.getSibling(path.key - 2).node.declarations[0].init.callee.object.value.split('|')
let cases = path.get('body.body')[0].get('cases')
let idxObj = {}
for (const idx in cases){
let key = cases[idx].get('test').node.value;
idxObj[key] = cases[idx].get('consequent')
}
let nodeArr = []
idxArray.forEach((objkey) => {
for (const arrIdx in idxObj[objkey]){
if(!idxObj[objkey][arrIdx].isContinueStatement()){
nodeArr.push(idxObj[objkey][arrIdx].node)
}
}
})
for (let i=0; i<=path.key; i++){
path.getSibling(0).remove()
}
path.replaceWithMultiple(nodeArr)
}
}
})
// 无用 if
traverse(ast, {
"IfStatement"(path){
if(path.get('test').isStringLiteral()){
let test = path.node.test.value;
let ifStatement = path.node.consequent;
let elseStatement = path.node.alternate;
let result = Boolean(test);
result ? path.replaceWith(ifStatement) : path.replaceWith(elseStatement)
}
}
})
// ==============================================================================================================================================================================
js_code = generator(ast, {
compact: false, // 是否压缩,默认 false
}).code // 将ast节点转换成js代码
// 写入(路径记得改)
fs.writeFileSync('New_demo.js', js_code, {
encoding: 'utf-8',
})
全局搜索 test, 将对应的 Regexp 注释,将 test 改为 true (格式化检测)
将 m 文件替换成还原后的代码
补环境
使用反混淆后的 m.js / m文件补环境会轻松的多(能看得到操作浏览器哪些对象的属性)
在本地执行文件,首先报错 window is not defined
在文件头部声明 window = global;
在文件头部声明 document = {};
在文件尾部打印 console.log(document.cookie)
再次运行就没有报错了,document.cookie 打印出来的值是 undefined
观察浏览器对应的页面,是执行了 sp() 方法后才进入流程的,在本地也执行 sp() 方法
执行后报错了
看看这个代码段 和 cookie mz字段的生成有关系
这里拿的都是自己的环境值
Object.prototype.toString = function(){
return `[object ${this.constructor.name}]`
}
function NetworkInformation(){}
function Geolocation(){}
function MediaCapabilities(){}
function MediaSession(){}
function MimeTypeArray(){}
function Permissions(){}
function PluginArray(){}
function UserActivation(){}
function DeprecatedStorageQuota(){}
navigator = {};
navigator["appCodeName"] = 拿你自己的;
navigator["appName"] = 拿你自己的;
navigator["appVersion"] = '拿你自己的
navigator["connection"] = new NetworkInformation();
navigator["cookieEnabled"] = true
navigator["doNotTrack"] = null
navigator["geolocation"] = new Geolocation();
navigator["hardwareConcurrency"] = 拿你自己的;
navigator["language"] = '拿你自己的';
navigator["languages"] = [拿你自己的];
navigator["maxTouchPoints"] = 0
navigator["mediaCapabilities"] = new MediaCapabilities();
navigator["mediaSession"] = new MediaSession();
navigator["mimeTypes"] = new MimeTypeArray();
navigator["onLine"] = true
navigator["permissions"] = new Permissions();
navigator["platform"] = 'Win32';
navigator["plugins"] = new PluginArray();
navigator["product"] = 'Gecko';
navigator["productSub"] = '20030107';
navigator["userActivation"] = new UserActivation();
navigator["userAgent"] = '拿你自己的';
navigator["vendor"] = '拿你自己的';
navigator["vendorSub"] = '';
navigator["webkitPersistentStorage"] = new DeprecatedStorageQuota();
navigator["webkitTemporaryStorage"] = new DeprecatedStorageQuota();
screen = {}
function ScreenOrientation(){}
screen["availHeight"] = 1040
screen["availLeft"] = 0
screen["availTop"] = 0
screen["availWidth"] = 1920
screen["colorDepth"] = 24
screen["height"] = 1080
screen["orientation"] = new ScreenOrientation();
screen["pixelDepth"] = 24
screen["width"] = 1920;
function DOMStringList(){}
document = {};
document["location"] = {};
document["location"]["ancestorOrigins"] = new DOMStringList();
document["location"]["hash"] = ''
document["location"]["host"] = 'match.yuanrenxue.cn'
document["location"]["hostname"] = 'match.yuanrenxue.cn'
document["location"]["href"] = 'https://match.yuanrenxue.cn/match/14'
document["location"]["origin"] = 'https://match.yuanrenxue.cn'
document["location"]["pathname"] = '/match/14'
document["location"]["port"] = ''
document["location"]["protocol"] = 'https:'
document["location"]["search"] = ''
document["location"]["assign"] = function assign(){}
document["location"]["assign"].toString = function (){return 'function assign() { [native code] }'}
document["location"]["reload"] = function reload(){}
document["location"]["reload"].toString = function (){return 'function reload() { [native code] }'}
document["location"]["replace"] = function replace(){}
document["location"]["replace"].toString = function (){return 'function replace() { [native code] }'}
document["location"]["toString"] = function toString(){}
document["location"]["toString"].toString = function (){return 'function toString() { [native code] }'}
document["location"]["valueOf"] = function valueOf(){}
document["location"]["valueOf"].toString = function (){return 'function valueOf() { [native code] }'}
再次执行后报错 $ is not defined
看看这段代码
再看看浏览器,浏览器里是执行了 success 方法,并且用 eval 执行了接口返回的代码
k 是接口返回的内容
将这个方法补上
$ = {
'ajax': function(){
// 替换成 eval 内的代码段
}
}
继续执行后报错 ASN1 is not defined
ASN1 是有给 window 赋值的
单独调用 ASN1 会报错 undefined
因为 window 并不是全局变量 global 才是
如果赋值的代码为 global[‘ASN1’] = … 再单独调用 ASN1 是找得到值的
设置 window 的 属性的描述符
// 全局对象有两个,globalTHis, global
// 可以通过 globalThis 查看 global(globalThis.global)
// 可以通过 global 查看 globalTHis(global.globalThis)
Object.defineProperty(globalThis, 'window', {
get(){
return globalThis;
}
})
调试 调用 ASN1 就可以找到了
继续执行后进入死循环(这个代码段如果按照前面的方法做了,其实已经被删除了)
条件判断的代码
if (m5"toString"“indexOf” != -(552 + 71 * -103 + -156 * -53 + -(4814 + -2 * -3371 + -11245) * -(-6316 + -8443 * 1 + -1055 * -14) + -(-1333 + 1468 + 2 * 2396))) while (!![]) {
判断通过后会进入死循环
这行代码可以直接删掉,里面没有业务逻辑(调试的时候 格式化的代码会被检测到)
继续执行会报错 alert is not defined
这是在一个 try catch 块中的代码
调试看看 try 块中的代码报了什么错(或者可以将 try catch 块删除只保留框选的那一段代码)
只用执行 框选的那一句,因为 definedProperty 被重写了 执行后只返回 空字符串
报错 undefined(reading ‘length’) undefined 无法读取 ‘length’ 属性
逐个看 gee() 方法传入进去的参数 aa, bb, c, d, e, b64_zw
c 和 e 为 defined, c 和 e 各自为 window.v14, window.v142 属性的值
window.v14, window.v142 是在 m接口返回的代码段设置的
m接口 的代码是通过 ajax success 方法执行的
将 ajax 方法重写
$ = {
'ajax': function(){
// 替换成还原好的 m 的代码
}
}
再次运行 代码会一直阻塞,不打印结果
将 setTimeout, setInterval 置空
setTimeout = function(){}
setInterval = function(){}
成功运行结束了,但是没有输出结果
console.log 被重写了
在文件开头声明一个变量 接收console.log 方法
_log = console.log;
顺利输出了
验证结果的时候,加密出来的值不一致
将浏览器和本地加密的值都写死,一起单步调试,排查问题
不报错的环境检测
搜索 eval 这 4 个操作对浏览器是无效的,这两个内置对象不能被删除,不能被设置值
在本地修改这两个对象的属性描述符,设置为不可删除,不可修改
// window
Object.defineProperty(globalThis, 'window', {
get(){
return globalThis;
},
configurable: false, // 对象不可删除
})
// navigator
Object.defineProperty(globalThis, 'navigator', {
configurable: false, // 不可删除
writable: false // 不可修改
})
// document
Object.defineProperty(globalThis, 'document', {
configurable: false, // 不可删除
writable: false // 不可修改
})
全局搜索 try
补 CanvasCaptureMediaStreamTrack 方法
CanvasCaptureMediaStreamTrack = function CanvasCaptureMediaStreamTrack(){};
浏览器中查看 gloabl 会报错,nodejs不会,设置 nodejs 中 global 的属性描述符
Object.defineProperty(globalThis, 'global', {
get(){
// 只要一访问 global 就会报错
throw Error('')
},
})
Python 代码
js 模板
还有一个坑
如果页码为 1 那么 m字段是加密一次生成的
如果页码为 2 那么 m字段是加密两次生成的
所以模板最后应该改为
for(let i=1; i<=// 页码; i++){
sp()
}
Object.defineProperty(globalThis, 'global', {
get(){
throw Error('')
},
})
_log = console.log;
window = {}
Object.defineProperty(globalThis, 'window', {
get(){
return globalThis;
},
configurable: false, // 对象不可删除
})
setTimeout = function setTimeout(){};
setInterval = function setInterval(){};
CanvasCaptureMediaStreamTrack = function CanvasCaptureMediaStreamTrack(){};
Object.prototype.toString = function(){
return `[object ${this.constructor.name}]`
}
function NetworkInformation(){}
function Geolocation(){}
function MediaCapabilities(){}
function MediaSession(){}
function MimeTypeArray(){}
function Permissions(){}
function PluginArray(){}
function UserActivation(){}
function DeprecatedStorageQuota(){}
navigator = {};
Object.defineProperty(globalThis, 'navigator', {
configurable: false, // 不可删除
writable: false // 不可修改
})
navigator["appCodeName"] = 'Mozilla';
navigator["appName"] = 'Netscape';
navigator["appVersion"] = '5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36'
navigator["connection"] = new NetworkInformation();
navigator["cookieEnabled"] = true
navigator["doNotTrack"] = null
navigator["geolocation"] = new Geolocation();
navigator["hardwareConcurrency"] = 12;
navigator["language"] = 'zh-CN';
navigator["languages"] = ['zh-CN', 'zh'];
navigator["maxTouchPoints"] = 0
navigator["mediaCapabilities"] = new MediaCapabilities();
navigator["mediaSession"] = new MediaSession();
navigator["mimeTypes"] = new MimeTypeArray();
navigator["onLine"] = true
navigator["permissions"] = new Permissions();
navigator["platform"] = 'Win32';
navigator["plugins"] = new PluginArray();
navigator["product"] = 'Gecko';
navigator["productSub"] = '20030107';
navigator["userActivation"] = new UserActivation();
navigator["userAgent"] = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36';
navigator["vendor"] = 'Google Inc.';
navigator["vendorSub"] = '';
navigator["webkitPersistentStorage"] = new DeprecatedStorageQuota();
navigator["webkitTemporaryStorage"] = new DeprecatedStorageQuota();
screen = {}
function ScreenOrientation(){}
screen["availHeight"] = 1040
screen["availLeft"] = 0
screen["availTop"] = 0
screen["availWidth"] = 1920
screen["colorDepth"] = 24
screen["height"] = 1080
screen["orientation"] = new ScreenOrientation();
screen["pixelDepth"] = 24
screen["width"] = 1920;
function DOMStringList(){}
document = {};
Object.defineProperty(globalThis, 'document', {
configurable: false, // 不可删除
writable: false // 不可修改
})
document["location"] = {};
document["location"]["ancestorOrigins"] = new DOMStringList();
document["location"]["hash"] = ''
document["location"]["host"] = 'match.yuanrenxue.cn'
document["location"]["hostname"] = 'match.yuanrenxue.cn'
document["location"]["href"] = 'https://match.yuanrenxue.cn/match/14'
document["location"]["origin"] = 'https://match.yuanrenxue.cn'
document["location"]["pathname"] = '/match/14'
document["location"]["port"] = ''
document["location"]["protocol"] = 'https:'
document["location"]["search"] = ''
document["location"]["assign"] = function assign(){}
document["location"]["assign"].toString = function (){return 'function assign() { [native code] }'}
document["location"]["reload"] = function reload(){}
document["location"]["reload"].toString = function (){return 'function reload() { [native code] }'}
document["location"]["replace"] = function replace(){}
document["location"]["replace"].toString = function (){return 'function replace() { [native code] }'}
document["location"]["toString"] = function toString(){}
document["location"]["toString"].toString = function (){return 'function toString() { [native code] }'}
document["location"]["valueOf"] = function valueOf(){}
document["location"]["valueOf"].toString = function (){return 'function valueOf() { [native code] }'}
$ = {
'ajax': function() {
// 替换成还原好的 m的代码
}
}
// m.js 代码
for(let i=1; i<=// 页码; i++){
sp()
}
_log(document.cookie)
页面执行流程
第一次请求 m.js --> m --> 数据接口
后续请求 m --> 数据接口
python 代码
替换 m.js
首先请求 m.js 代码,替换到 js模板代码内
def get_mJs():
url = 'https://match.yuanrenxue.cn/static/match/match14/m.js'
response = requests.get(url, headers=headers, cookies=cookies)
with open('14.js', mode='r', encoding='utf-8') as f:
js_demo = f.read()
js_demo = js_demo.replace('// m.js 代码', response.text)
return js_demo
替换 m 接口代码
请求接口前都会请求 m 接口的代码(设置 window 的 v12 v142 属性值 后再给 cookie m字段赋值
def get_m():
url = 'https://match.yuanrenxue.cn/api/match/14/m'
response = requests.get(url, headers=headers, cookies=cookies)
return response.text
请求接口前会先设置 cookie m的字段
mz 字段可以写死,同一台电脑的环境值就是固定的
def get_cookie(js_demo, page_):
js_demo = js_demo.replace('// 替换成还原好的 m的代码', get_m())
js_demo = js_demo.replace('// 页码', str(page_))
with open('new_14.js', mode='w', encoding='utf-8') as f:
f.write(js_demo)
output = subprocess.check_output("node new_14.js")
return output.strip().split('=')[1].split(';')[0]
完整的 python 代码
import subprocess
import requests
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 = {
'mz': '你浏览器生成的 mz 值,是可以固定的',
"sessionid": "你的SessionId",
}
def get_mJs():
url = 'https://match.yuanrenxue.cn/static/match/match14/m.js'
response = requests.get(url, headers=headers, cookies=cookies)
with open('14.js', mode='r', encoding='utf-8') as f:
js_demo = f.read()
js_demo = js_demo.replace('// m.js 代码', response.text)
return js_demo
def get_m():
url = 'https://match.yuanrenxue.cn/api/match/14/m'
response = requests.get(url, headers=headers, cookies=cookies)
return response.text
def get_cookie(js_demo, page_):
js_demo = js_demo.replace('// 替换成还原好的 m的代码', get_m())
js_demo = js_demo.replace('// 页码', str(page_))
with open('new_14.js', mode='w', encoding='utf-8') as f:
f.write(js_demo)
output = subprocess.check_output("node new_14.js")
return output.strip().split('=')[1].split(';')[0]
def get_match14(js_demo, page_):
url = "https://match.yuanrenxue.cn/api/match/14"
params = {
'page': f'{page_}'
}
cookies['m'] = get_cookie(js_demo, page_)[0:-1] + f'{page_}'
print(cookies['m'])
response = requests.get(url, headers=headers, cookies=cookies, params=params)
print(response.json())
return response.json()['data']
if __name__ == '__main__':
js_code = get_mJs() # m.js 文件
nums = 0
for page in range(1, 6):
nums_list = get_match14(js_code, page)
for num in nums_list:
nums += num['value']
print('page: ', page, 'nums: ', nums)