破解JS混淆动态Cookie:猿人学逆向实战
网址:第二题js 混淆 动态cookie 1 - 猿人学爬虫逆向练习平台
虽然这道题通过mcp的方式得到了最终答案,但是这里面还有很多知识没有搞懂,继续接着上次的绕过反调试继续逆向cookie的值,其中反调试的可以参考js 混淆 - 动态cookie 1(猿人学 match/2 反调试绕过)-CSDN博客
首先抓包复制到curl中得到基础代码

import requests
cookies = {
'Hm_lvt_f80b2b389f44bbfb3bfe1704817d44e0': '1785761802',
'HMACCOUNT': '1199FDD53B1CA0B3',
'sessionid': '75hgdzj80qyff0kugfugonjluaelwxi0',
'Hm_ck_1786344794346': '42',
'RM4hZBv0dDon443M': 'SisbB0JEswOIglSOS68YhAk495aFH+ZvZEsebU4qzFUsU5T8BbF3yiwR0soXlYBQX3YxlkHZtXyLadVDn6gn4tqJzDsWo6ZihtdnDiAqShu/UNAzJ2ZO/ZcrapuvnbOZF69yKgOeHNUYUe2Udz5DU/V53KoC5jucfOsTAdv5s6U5gk0HECtsscytKP9SEqrTP/6E9W4tylR3GRvFIldFm4uXdXP7ChKwnoFhgPk7agQ=',
# 'm': '500a625ac065dad8b1a016afddd02c1e|1786453180783',
'Hm_lpvt_f80b2b389f44bbfb3bfe1704817d44e0': '1786453185',
}
headers = {
'accept': 'application/json, text/javascript, */*; q=0.01',
'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6',
'cache-control': 'no-cache',
'dnt': '1',
'pragma': 'no-cache',
'priority': 'u=1, i',
'referer': 'https://match.yuanrenxue.cn/match/2',
'sec-ch-ua': '"Not=A?Brand";v="99", "Microsoft Edge";v="151", "Chromium";v="151"',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Windows"',
'sec-fetch-dest': 'empty',
'sec-fetch-mode': 'cors',
'sec-fetch-site': 'same-origin',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/151.0.0.0 Safari/537.36 Edg/151.0.0.0',
'x-requested-with': 'XMLHttpRequest',
# 'cookie': 'Hm_lvt_f80b2b389f44bbfb3bfe1704817d44e0=1785761802; HMACCOUNT=1199FDD53B1CA0B3; sessionid=75hgdzj80qyff0kugfugonjluaelwxi0; Hm_ck_1786344794346=42; RM4hZBv0dDon443M=SisbB0JEswOIglSOS68YhAk495aFH+ZvZEsebU4qzFUsU5T8BbF3yiwR0soXlYBQX3YxlkHZtXyLadVDn6gn4tqJzDsWo6ZihtdnDiAqShu/UNAzJ2ZO/ZcrapuvnbOZF69yKgOeHNUYUe2Udz5DU/V53KoC5jucfOsTAdv5s6U5gk0HECtsscytKP9SEqrTP/6E9W4tylR3GRvFIldFm4uXdXP7ChKwnoFhgPk7agQ=; m=500a625ac065dad8b1a016afddd02c1e|1786453180783; Hm_lpvt_f80b2b389f44bbfb3bfe1704817d44e0=1786453185',
}
params = {
'page': '1',
'pageSize': '10',
'kw': '',
}
response = requests.get('https://match.yuanrenxue.cn/api/question/2', params=params, cookies={}, headers=headers)
print(response.status_code)
print(response.json())
通过观察可以发现第一个请求的结果是一段字符串,js乱码,通过注释掉可以发现这里的cookie是没有用的,可以直接拿到数据,数据在第二个请求中,继续分析第二个请求。继续抓包之后发现两个数据包的携带参数都是一样的,这里让人很费解。豆包之后,才明白第一次请求得到后的是js加密逻辑,这时候是没有cookie的,后面继续发送请求,才得到最后的数据,下面是页面的伪代码(不是页面的逻辑,豆包生成帮助理解的)
// 伪代码,还原网页真实逻辑
async function loadData() {
const url = "/2?page=1&pageSize=10&kw=";
// ==========第一次请求==========
// 此时cookie里没有有效的m,或者是旧的m
const res1 = await fetch(url);
// 注意!这里拿到的不是json,是一段混淆JS字符串
const jsCode = await res1.text();
// 核心:把后端返回的字符串,当成JS代码运行
// 运行的时候,这段混淆代码内部会执行 document.cookie="m=xxxx"
eval(jsCode);
// ==========第二次请求==========
// eval执行完!浏览器cookie已经被注入全新有效的 m=xxx
// 再次请求完全相同url,浏览器自动带上刚刚生成的cookie
const res2 = await fetch(url);
const realData = await res2.json();
console.log("真正业务数据:", realData);
}
loadData();
通过定位找到两次请求的位置
// 匿名自执行函数,把jQuery的$传入,隔离内部变量,防止污染全局作用域
(function ($) {
// ====================== 全局常量定义 ======================
var API="/api/question/2", // 目标接口地址
TP=5, // 总页数 totalPage,一共5页数据
PS=10, // 每页条数 pageSize,一页10条
p=1; // 当前页码,默认从第1页开始
// 获取页面上各个DOM元素,缓存jQuery对象,后面直接操作页面
var $r=$("#pgxPager"), // 分页外层容器
$l=$("#pgxList"), // 数据列表容器,用来展示数字
$pg=$("#pgxPages"), // 页码按钮容器(1 2 3 4 5)
$pre=$("#pgxPrev"), // 上一页按钮
$nxt=$("#pgxNext"), // 下一页按钮
$kw=$("#pgxKw"), // 搜索输入框
$s=$("#pgxStatus"), // 状态文字显示(就绪/请求中...)
$dbg=$("#pgxDebug"); // debug调试信息展示框
/**
* @function ui 更新分页按钮UI状态
* 控制上一页、下一页是否禁用,高亮当前页码
*/
function ui(){
// 如果当前页码<=1,禁用【上一页】按钮
$pre.prop("disabled", p<=1);
// 如果当前页码>=总页数5,禁用【下一页】按钮
$nxt.prop("disabled", p>=TP);
// 遍历所有页码按钮,给当前页加上高亮class is‑active
$pg.find(".pgx-page").each(function(){
// $(this).data("page") 获取按钮上存的页码数字
$(this).toggleClass("is-active", +$(this).data("page")===p);
});
}
/**
* @function loading 加载状态开关
* @param {Boolean} on true=正在请求;false=请求结束
*/
function loading(on){
// 修改状态文字:请求中... / 就绪
$s.text(on?"请求中...":"就绪");
// 分页容器透明度,加载时变半透明
$r.css("opacity", on?0.92:1);
// 批量禁用/启用:上一页、下一页、页码按钮,请求过程不能点击
$pre.add($nxt).add($pg.find(".pgx-page")).prop("disabled", on);
}
/**
* @function render 渲染页面列表
* @param {Array} a 后端返回的数据数组
*/
function render(a){
// 兜底,没有数据就空数组;最多只展示PS=10条
a=(a||[]).slice(0,PS);
// 把数组数据渲染成html字符串,插入列表容器
$l.html('<div class="pgx-line">'+(a.length
// 有数据:循环数组,每个数字套上span标签
? a.map(n=>'<span class="pgx-num">'+n+'</span>').join("")
// 数组为空,显示暂无数据
: '<span class="pgx-empty">暂无数据</span>')+'</div>');
}
/**
* @function req 核心入口:发起分页数据请求
* @param {Number} np 用户想要跳转的目标页码
*/
function req(np){
// 页码边界处理:不能小于1,不能大于总页数TP=5
p=Math.min(TP,Math.max(1,np));
ui(); // 更新分页按钮样式
// 如果全局有window.request函数,就执行(混淆脚本注入的钩子)
window.request&&window.request();
// 组装get请求参数
var d={
page:p, // 当前页码
pageSize:PS, // 每页数量10
kw:$.trim($kw.val()||""),// 搜索框输入内容,去除前后空格
m:window.match1 // ⭐关键参数 m,来自全局变量window.match1,由eval混淆js生成
};
loading(true); // 开启加载状态,按钮禁用
// 如果debug框存在,把本次请求url打印到页面上
$dbg&&$dbg.text("GET "+API+"?"+$.param(d));
// 页面先渲染10个“加载中...”占位
render(Array(10).fill('加载中...'));
/**
* @function load_data 递归AJAX请求函数
* 第一次请求拿到混淆JS字符串 → eval执行生成m → 递归调用自己再次请求拿真实数据
*/
function load_data(){
$.ajax({
url:API,
type:"GET",
dataType:"json", // 📌告诉jQuery,把响应自动解析成json对象
data:d, // get查询参数
timeout:1e4, // 超时时间10000毫秒=10秒
// 请求成功回调
success:function(res){
// ✅分支1:res.data是数组,代表拿到真实业务数据
if (res.data instanceof Array){
render(res&&res.data); // 把数据渲染到页面
$s.text("加载完成");
window.load_finish = false;
}else {
// 🚨分支2:res.data不是数组,是【混淆JS字符串】,后端返回{"data":"xxx混淆代码"}
// window.load_finish 防止无限递归死循环
if (window.load_finish){
failedAlert("发生了不期待的错误,即将刷新页面重置")
setTimeout(function (){location.reload()}, 2000)
}
eval(res.data); // ✨执行后端返回的混淆JS脚本!!运行后会给全局赋值 window.match1
load_data(); // 🔥递归!立刻再次调用本函数,发第二次AJAX请求
window.load_finish = true; // 标记已经跑过一次eval
}
},
// 请求出错:网络错误、超时、跨域等
error:function(){
$s.text("接口异常,请使用chrome浏览器开启无痕模式,若错误持续请联系管理员");
},
// success/error无论成功失败,最后都会执行complete
complete:function(){
loading(false); // 关闭加载状态,按钮恢复可用
ui();
}
});
}
load_data() // 第一次调用,发起AJAX
}
// ======================绑定页面按钮事件======================
// 上一页点击:页码大于1的时候执行req,页码-1
$pre.on("click", ()=>p>1&&req(p-1));
// 下一页点击:页码小于总页数5,执行req,页码+1
$nxt.on("click", ()=>p<TP&&req(p+1));
// 页码按钮点击事件,点击页码,跳转到对应页
$pg.on("click",".pgx-page", function(){ req(+$(this).data("page")); });
// 搜索输入框按下回车键,重新加载第1页
$kw.on("keydown", e=>e.key==="Enter"&&req(1));
// 页面初始化:更新UI,并且直接请求第1页数据
ui();
req(1);
})(window.jQuery);

这里到eval(res.data),可以猜到这里面肯定是生成cookie的地方,但是由于绕过debuggering所以没法调试,到这里我也不知道怎么办了.继续豆包,了解cookie的生成原理,这里hook一下cookie。下面是hook脚本:
// hook脚本,控制台运行,所有写cookie都会断下
(function(){
let originSetter = Object.getOwnPropertyDescriptor(Document.prototype,'cookie').set;
Object.defineProperty(document,'cookie',{
set:function(val){
debugger; // 直接断在写cookie的地方,看调用栈
console.log("写入cookie:",val)
return originSetter.call(this,val)
}
})
})()

在eval(res.data)和load_data()之间打上断点,并且在控制台注入hook cookie脚本,点击F8就会在定住,然后在调用栈里面继续寻找cookie的加密逻辑。但是这个eval(res.data),其中res.data是第一次请求的结果,每次结果都不一样,所以还不可以直接扣代码。参考猿人学 js 混淆动态cookie逆向_js 混淆 - 动态cookie 1猿人学解题-CSDN博客,知道可以直接扣代码,且这代码被ob混淆了,可以通过使用猿人学自带的ob反混淆工具解混淆测试版 (yuanrenxue.cn)解决
通过豆包了解
每次请求返回的 JS 文本都不一样,还有没有必要定位 m 生成逻辑?
区分两种完全不同的现象:
- 外层混淆外壳每次变,底层加密算法不变(猿人学 2 真实情况)
- 真正动态下发,每一次请求,加密算法本身都被服务器重新生成(瑞数这类高强度反爬)
情况 1:外壳变,算法内核不变(猿人学 match2)
接口返回的字符串每次都不同:
- RC4 密钥每次换、字符串池打乱、变量名全部重命名、控制流扁平化重新打乱;
- 但是真正计算 m 的数学逻辑、输入输出规则完全不变:输入
page + 时间戳 + ua,输出 m。
👉依然有必要定位、提取核心加密逻辑
- 虽然混淆外壳千变万化,抠出来的加密函数的数学运算逻辑不变;
- 你只需要还原一次算法,之后不需要每次请求都解析一大段混淆 JS。
举例子: 每次返回的 JS 变量名:
$dbsm_0xaaaa/$dbsm_0xbbbb一直在变; 但是内部:魔改md5(page + "|" + timestamp)这个运算逻辑是固定的。
误区:
“每次返回 js 不一样,那我扣出来的代码也没用” ❌错。变的是混淆壳(变量名、字符串池、反调试垃圾代码),不变的是加密运算。
情况 2:真正动态 JS,加密算法本身每次请求服务器重新生成(瑞数 5、部分企业站)
每一次 http 请求,服务器下发一套全新算法,运算逻辑、密钥全部改写。 这种场景:你静态抠一次算法完全没有意义!
- 你今天抠出来的函数,下一次请求服务器返回另一套算法,直接失效。
✅此时只有两条可行路线:
- 运行时执行(模拟浏览器环境) 每次请求拿到服务器返回的动态 JS,交给浏览器 /playwright/vm 沙箱执行,让它现场算出 m,直接拿输出结果,不去人工分析算法。
不做静态逆向,只做环境模拟。
- 高级方案:AST 动态提取,每次拿到脚本自动解混淆,自动 hook 抓输入输出(开发成本很高)
回到猿人学 2 的真实现象
抓包看:接口 response 的 base64 串每刷新一次就变。 但本质:
- 反调试外壳、字符串池、解密器变量名一直在变;
- m 的生成算法逻辑是固定写死在服务端模板,不会改动。
所以:逆向定位 m 的生成逻辑依然有价值,只需要剥离混淆壳,拿到底层运算。
| 模式 | 适用场景 | 做法 | 优缺点 |
|---|---|---|---|
| ✅静态抠算法 | 外壳变、内核算法不变(猿人学 2) | hook 抓 eval 解密后的源码,剥离反调试,提取加密函数,node 本地复现 | 速度快,不需要浏览器,一次分析永久复用 |
| ✅动态执行脚本 | 算法本身每次都变(瑞数类) | 每次请求拿到 response,丢进沙箱 / 无头浏览器 eval 执行,直接拿 cookie 结果 | 不用分析算法;开销大,需要模拟完整浏览器环境 |
m的生成算法
通过Hook定位加密位置

解混淆之后的代码
(function $dbsm_0x53e9b3(_0x5f2a15) {
var _0x365c2b = function () {
var _0x453ec6 = true;
return function (_0x4ebd11, _0x764204) {
var _0x378e93 = _0x453ec6 ? function () {
if (_0x764204) {
var _0x5e5566 = _0x764204["apply"](_0x4ebd11, arguments);
_0x764204 = null;
return _0x5e5566;
}
} : function () {};
_0x453ec6 = false;
return _0x378e93;
};
}();
var _0x1517bc = function () {
var _0x40db21 = true;
return function (_0x541f69, _0x31a961) {
var _0x25f7b6 = _0x40db21 ? function () {
if (_0x31a961) {
var _0x38961d = _0x31a961["apply"](_0x541f69, arguments);
_0x31a961 = null;
return _0x38961d;
}
} : function () {};
_0x40db21 = false;
return _0x25f7b6;
};
}();
function _0x2a3c1f(_0x8cea22, _0xb9406e) {
var _0x1fc9f8 = (65535 & _0x8cea22) + (65535 & _0xb9406e);
return (_0x8cea22 >> 16) + (_0xb9406e >> 16) + (_0x1fc9f8 >> 16) << 16 | 65535 & _0x1fc9f8;
}
function _0x4eb501(_0x1c3057, _0x395ca7) {
return _0x1c3057 << _0x395ca7 | _0x1c3057 >>> 32 - _0x395ca7;
}
function _0x4dec7f(_0x58e1f8, _0x5678ce, _0x2a1f02, _0x1fefc4, _0x11c74c, _0x5d475c) {
return _0x2a3c1f(_0x4eb501(_0x2a3c1f(_0x2a3c1f(_0x5678ce, _0x58e1f8), _0x2a3c1f(_0x1fefc4, _0x5d475c)), _0x11c74c), _0x2a1f02);
}
function _0x571b88(_0xea1b37, _0x4e9216, _0x58f233, _0x3b7bfe, _0x1e8a9e, _0x3d837c, _0x5bfab1) {
return _0x4dec7f(_0x4e9216 & _0x58f233 | ~_0x4e9216 & _0x3b7bfe, _0xea1b37, _0x4e9216, _0x1e8a9e, _0x3d837c, _0x5bfab1);
}
function _0x88e491(_0x51e76a, _0x260f89, _0x3fa16d, _0x4baeda, _0x41c592, _0x363d5c, _0x39ba9a) {
return _0x4dec7f(_0x260f89 & _0x4baeda | _0x3fa16d & ~_0x4baeda, _0x51e76a, _0x260f89, _0x41c592, _0x363d5c, _0x39ba9a);
}
function _0x3b02a1(_0x479f3d, _0x4cd151) {
let _0x5287eb = [99, 111, 110, 115, 111, 108, 101];
let _0x29f643 = "";
for (let _0xc6f280 = 0; _0xc6f280 < _0x5287eb["length"]; _0xc6f280++) {
_0x29f643 += String["fromCharCode"](_0x5287eb[_0xc6f280]);
}
return _0x29f643;
}
function _0x1c45c4(_0x505ea1, _0x3a6c30, _0x41ae36, _0x2d6a86, _0x598229, _0x2bb4cb, _0x2769ae) {
return _0x4dec7f(_0x3a6c30 ^ _0x41ae36 ^ _0x2d6a86, _0x505ea1, _0x3a6c30, _0x598229, _0x2bb4cb, _0x2769ae);
}
function _0x3919b5(_0x1412b8, _0x39dd43, _0x138d9d, _0x155415, _0x56f326, _0x20f24e, _0x554f9c) {
return _0x4dec7f(_0x138d9d ^ (_0x39dd43 | ~_0x155415), _0x1412b8, _0x39dd43, _0x56f326, _0x20f24e, _0x554f9c);
}
function _0x5d7c44(_0x3dbfb8, _0x17d53e) {
if (_0x17d53e) {
return _0x3919b5(_0x3dbfb8);
}
return _0x3b02a1(_0x3dbfb8);
}
function _0x12fc84(_0x39ad5b, _0x42afbe) {
let _0xdd6431 = "";
for (let _0x57ea98 = 0; _0x57ea98 < _0x39ad5b["length"]; _0x57ea98++) {
_0xdd6431 += String["fromCharCode"](_0x39ad5b[_0x57ea98]);
}
return _0xdd6431;
}
function _0x1132b0(_0x1879ff, _0x2a0389) {
var _0x3022f9 = _0x365c2b(this, function () {
var _0x41c42e = function () {
var _0x5a97e7 = _0x41c42e["constructor"]("return /\" + this + \"/")()["compile"]("^([^ ]+( +[^ ]+)+)+[^ ]}");
return !_0x5a97e7["test"](_0x3022f9);
};
return _0x41c42e();
});
_0x3022f9();
(function () {
_0x1517bc(this, function () {
var _0x14ac66 = new RegExp("function *\\( *\\)");
var _0x26489a = new RegExp("\\+\\+ *(?:[a-zA-Z_$][0-9a-zA-Z_$]*)", "i");
var _0x53d298 = $dbsm_0x5cc7b0("init");
if (!_0x14ac66["test"](_0x53d298 + "chain") || !_0x26489a["test"](_0x53d298 + "input")) {
_0x53d298("0");
} else {
$dbsm_0x5cc7b0();
}
})();
})();
_0x5d7c44();
qz = [10, 99, 111, 110, 115, 111, 108, 101, 32, 61, 32, 110, 101, 119, 32, 79, 98, 106, 101, 99, 116, 40, 41, 10, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 32, 61, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 40, 115, 41, 32, 123, 10, 32, 32, 32, 32, 119, 104, 105, 108, 101, 32, 40, 49, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 102, 111, 114, 40, 105, 61, 48, 59, 105, 60, 49, 49, 48, 48, 48, 48, 48, 59, 105, 43, 43, 41, 123, 10, 32, 32, 32, 32, 32, 32, 32, 32, 104, 105, 115, 116, 111, 114, 121, 46, 112, 117, 115, 104, 83, 116, 97, 116, 101, 40, 48, 44, 48, 44, 105, 41, 10, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 125, 10, 32, 32, 32, 32, 125, 10, 10, 125, 10, 99, 111, 110, 115, 111, 108, 101, 46, 116, 111, 83, 116, 114, 105, 110, 103, 32, 61, 32, 39, 91, 111, 98, 106, 101, 99, 116, 32, 79, 98, 106, 101, 99, 116, 93, 39, 10, 99, 111, 110, 115, 111, 108, 101, 46, 108, 111, 103, 46, 116, 111, 83, 116, 114, 105, 110, 103, 32, 61, 32, 39, 402, 32, 116, 111, 83, 116, 114, 105, 110, 103, 40, 41, 32, 123, 32, 91, 110, 97, 116, 105, 118, 101, 32, 99, 111, 100, 101, 93, 32, 125, 39, 10];
eval(_0x12fc84(qz));
try {
if (global) {
console["log"]("人生苦短,何必python?");
} else {
while (1) {
console["log"]("人生苦短,何必python?");
/*db*/
}
}
} catch (_0x45d8c6) {
return navigator["vendorSub"];
}
}
setInterval(_0x1132b0(), 500);
function _0x489ff0(_0x29d3f6, _0xcc9bed) {
_0x29d3f6[_0xcc9bed >> 5] |= 128 << _0xcc9bed % 32, _0x29d3f6[14 + (_0xcc9bed + 64 >>> 9 << 4)] = _0xcc9bed;
if (qz) {
var _0x2159d8,
_0x578dd8,
_0x5cc688,
_0x44b230,
_0x54a8a5,
_0x506ae5 = 1732584193,
_0xce3f15 = -271733879,
_0x12ae4a = -1732584194,
_0x480272 = 271733878;
} else {
var _0x2159d8,
_0x578dd8,
_0x5cc688,
_0x44b230,
_0x54a8a5,
_0x506ae5 = 0,
_0xce3f15 = -0,
_0x12ae4a = -0,
_0x480272 = 0;
}
for (_0x2159d8 = 0; _0x2159d8 < _0x29d3f6["length"]; _0x2159d8 += 16) _0x578dd8 = _0x506ae5, _0x5cc688 = _0xce3f15, _0x44b230 = _0x12ae4a, _0x54a8a5 = _0x480272, _0x506ae5 = _0x571b88(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8], 7, -680876936), _0x480272 = _0x571b88(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 1], 12, -389564586), _0x12ae4a = _0x571b88(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 2], 17, 606105819), _0xce3f15 = _0x571b88(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 3], 22, -1044525330), _0x506ae5 = _0x571b88(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 4], 7, -176418897), _0x480272 = _0x571b88(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 5], 12, 1200080426), _0x12ae4a = _0x571b88(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 6], 17, -1473231341), _0xce3f15 = _0x571b88(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 7], 22, -45705983), _0x506ae5 = _0x571b88(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 8], 7, 1770010416), _0x480272 = _0x571b88(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 9], 12, -1958414417), _0x12ae4a = _0x571b88(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 10], 17, -42063), _0xce3f15 = _0x571b88(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 11], 22, -1990404162), _0x506ae5 = _0x571b88(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 12], 7, 1804603682), _0x480272 = _0x571b88(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 13], 12, -40341101), _0x12ae4a = _0x571b88(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 14], 17, -1502882290), _0xce3f15 = _0x571b88(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 15], 22, 1236535329), _0x506ae5 = _0x88e491(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 1], 5, -165796510), _0x480272 = _0x88e491(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 6], 9, -1069501632), _0x12ae4a = _0x88e491(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 11], 14, 643717713), _0xce3f15 = _0x88e491(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8], 20, -373897302), _0x506ae5 = _0x88e491(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 5], 5, -701558691), _0x480272 = _0x88e491(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 10], 9, 38016083), _0x12ae4a = _0x88e491(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 15], 14, -660478335), _0xce3f15 = _0x88e491(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 4], 20, -405537848), _0x506ae5 = _0x88e491(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 9], 5, 568446438), _0x480272 = _0x88e491(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 14], 9, -1019803690), _0x12ae4a = _0x88e491(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 3], 14, -187363961), _0xce3f15 = _0x88e491(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 8], 20, 1163531501), _0x506ae5 = _0x88e491(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 13], 5, -1444681467), _0x480272 = _0x88e491(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 2], 9, -51403784), _0x12ae4a = _0x88e491(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 7], 14, 1735328473), _0xce3f15 = _0x88e491(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 12], 20, -1926607734), _0x506ae5 = _0x1c45c4(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 5], 4, -378558), _0x480272 = _0x1c45c4(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 8], 11, -2022574463), _0x12ae4a = _0x1c45c4(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 11], 16, 1839030562), _0xce3f15 = _0x1c45c4(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 14], 23, -35309556), _0x506ae5 = _0x1c45c4(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 1], 4, -1530992060), _0x480272 = _0x1c45c4(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 4], 11, 1272893353), _0x12ae4a = _0x1c45c4(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 7], 16, -155497632), _0xce3f15 = _0x1c45c4(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 10], 23, -1094730640), _0x506ae5 = _0x1c45c4(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 13], 4, 681279174), _0x480272 = _0x1c45c4(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8], 11, -358537222), _0x12ae4a = _0x1c45c4(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 3], 16, -722521979), _0xce3f15 = _0x1c45c4(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 6], 23, 76029189), _0x506ae5 = _0x1c45c4(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 9], 4, -640364487), _0x480272 = _0x1c45c4(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 12], 11, -421815835), _0x12ae4a = _0x1c45c4(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 15], 16, 530742520), _0xce3f15 = _0x1c45c4(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 2], 23, -995338651), _0x506ae5 = _0x3919b5(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8], 6, -198630844), _0x480272 = _0x3919b5(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 7], 10, 1126891415), _0x12ae4a = _0x3919b5(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 14], 15, -1416354905), _0xce3f15 = _0x3919b5(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 5], 21, -57434055), _0x506ae5 = _0x3919b5(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 12], 6, 1700485571), _0x480272 = _0x3919b5(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 3], 10, -1894986606), _0x12ae4a = _0x3919b5(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 10], 15, -1051523), _0xce3f15 = _0x3919b5(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 1], 21, -2054922799), _0x506ae5 = _0x3919b5(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 8], 6, 1873313359), _0x480272 = _0x3919b5(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 15], 10, -30611744), _0x12ae4a = _0x3919b5(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 6], 15, -1560198380), _0xce3f15 = _0x3919b5(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 13], 21, 1309151649), _0x506ae5 = _0x3919b5(_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272, _0x29d3f6[_0x2159d8 + 4], 6, -145523070), _0x480272 = _0x3919b5(_0x480272, _0x506ae5, _0xce3f15, _0x12ae4a, _0x29d3f6[_0x2159d8 + 11], 10, -1120210379), _0x12ae4a = _0x3919b5(_0x12ae4a, _0x480272, _0x506ae5, _0xce3f15, _0x29d3f6[_0x2159d8 + 2], 15, 718787259), _0xce3f15 = _0x3919b5(_0xce3f15, _0x12ae4a, _0x480272, _0x506ae5, _0x29d3f6[_0x2159d8 + 9], 21, -343485441), _0x506ae5 = _0x2a3c1f(_0x506ae5, _0x578dd8), _0xce3f15 = _0x2a3c1f(_0xce3f15, _0x5cc688), _0x12ae4a = _0x2a3c1f(_0x12ae4a, _0x44b230), _0x480272 = _0x2a3c1f(_0x480272, _0x54a8a5);
return [_0x506ae5, _0xce3f15, _0x12ae4a, _0x480272];
}
function _0x488757(_0x516e25) {
var _0x17cd7b,
_0x23d64b = "",
_0x1a5ab3 = 32 * _0x516e25["length"];
for (_0x17cd7b = 0; _0x17cd7b < _0x1a5ab3; _0x17cd7b += 8) _0x23d64b += String["fromCharCode"](_0x516e25[_0x17cd7b >> 5] >>> _0x17cd7b % 32 & 255);
return _0x23d64b;
}
function _0x287d32(_0x5bc323) {
var _0x1be1be,
_0x341846 = [];
for (_0x341846[(_0x5bc323["length"] >> 2) - 1] = undefined, _0x1be1be = 0; _0x1be1be < _0x341846["length"]; _0x1be1be += 1) _0x341846[_0x1be1be] = 0;
var _0x5b24f9 = 8 * _0x5bc323["length"];
for (_0x1be1be = 0; _0x1be1be < _0x5b24f9; _0x1be1be += 8) _0x341846[_0x1be1be >> 5] |= (255 & _0x5bc323["charCodeAt"](_0x1be1be / 8)) << _0x1be1be % 32;
return _0x341846;
}
function _0x491da9(_0x2cdad7) {
return _0x488757(_0x489ff0(_0x287d32(_0x2cdad7), 8 * _0x2cdad7["length"]));
}
function _0x285d1e(_0x302c50) {
var _0xe28b56,
_0x33c099,
_0x2fa89a = "0123456789abcdef",
_0x1f4afd = "";
for (_0x33c099 = 0; _0x33c099 < _0x302c50["length"]; _0x33c099 += 1) _0xe28b56 = _0x302c50["charCodeAt"](_0x33c099), _0x1f4afd += _0x2fa89a["charAt"](_0xe28b56 >>> 4 & 15) + _0x2fa89a["charAt"](15 & _0xe28b56);
return _0x1f4afd;
}
function _0x2c4793(_0x5ec0e1) {
return unescape(encodeURIComponent(_0x5ec0e1));
}
function _0x4a02f7(_0x3b989a) {
return _0x491da9(_0x2c4793(_0x3b989a));
}
function _0xd798b1(_0x24a847) {
return _0x285d1e(_0x4a02f7(_0x24a847));
}
function _0x55a587(_0x2953ae, _0x1ed756, _0x6d066e) {
_0x1132b0();
return _0x1ed756 ? _0x6d066e ? _0x3b02a1(_0x1ed756, _0x2953ae) : y(_0x1ed756, _0x2953ae) : _0x6d066e ? _0x4a02f7(_0x2953ae) : _0xd798b1(_0x2953ae);
}
function _0x1f6abb(_0x47b71c, _0x374999) {
document["cookie"] = "m" + _0x1132b0() + "=" + _0x55a587(_0x47b71c) + "|" + _0x47b71c + "; path=/";
}
function _0x45bd42(_0x1033ec, _0x521e9b) {
return 1786539948245;
}
_0x1f6abb(_0x45bd42());
})();
function $dbsm_0x5cc7b0(_0x3c5563) {
function _0xb5c2e1(_0x159960) {
if (typeof _0x159960 === "string") {
return function (_0x377769) {}["constructor"]("while (true) {}")["apply"]("counter");
} else {
if (("" + _0x159960 / _0x159960)["length"] !== 1 || _0x159960 % 20 === 0) {
(function () {
return true;
})["constructor"]("debugger")["call"]("action");
} else {
(function () {
return false;
})["constructor"]("debugger")["apply"]("stateObject");
}
}
_0xb5c2e1(++_0x159960);
}
try {
if (_0x3c5563) {
return _0xb5c2e1;
} else {
_0xb5c2e1(0);
}
} catch (_0x2abea6) {}
}
setInterval(function () {
$dbsm_0x5cc7b0();
}, 4000);
到这一步,我完全找不到后续的突破口,脑子一片空白,整个人手足无措。自学 JS 逆向已经耗费了大量精力,这道别人口中的简单题目,我前后耗了三四天,每天五六个小时,可过程里不断冒出大量陌生知识点,到处都是卡点,越做越迷茫,甚至萌生了放弃的念头
function md5(string) {
function safeAdd(x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF);
var msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
}
function bitRotateLeft(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
}
function md5cmn(q, a, b, x, s, t) {
return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b);
}
function md5ff(a, b, c, d, x, s, t) {
return md5cmn((b & c) | ((~b) & d), a, b, x, s, t);
}
function md5gg(a, b, c, d, x, s, t) {
return md5cmn((b & d) | (c & (~d)), a, b, x, s, t);
}
function md5hh(a, b, c, d, x, s, t) {
return md5cmn(b ^ c ^ d, a, b, x, s, t);
}
function md5ii(a, b, c, d, x, s, t) {
return md5cmn(c ^ (b | (~d)), a, b, x, s, t);
}
function binlMD5(x, len) {
x[len >> 5] |= 0x80 << (len % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var i, olda, oldb, oldc, oldd;
var a = 1732584193;
var b = -271733879;
var c = -1732584194;
var d = 271733878;
for (i = 0; i < x.length; i += 16) {
olda = a; oldb = b; oldc = c; oldd = d;
a = md5ff(a, b, c, d, x[i], 7, -680876936);
d = md5ff(d, a, b, c, x[i + 1], 12, -389564586);
c = md5ff(c, d, a, b, x[i + 2], 17, 606105819);
b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330);
a = md5ff(a, b, c, d, x[i + 4], 7, -176418897);
d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426);
c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341);
b = md5ff(b, c, d, a, x[i + 7], 22, -45705983);
a = md5ff(a, b, c, d, x[i + 8], 7, 1770010416);
d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417);
c = md5ff(c, d, a, b, x[i + 10], 17, -42063);
b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162);
a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682);
d = md5ff(d, a, b, c, x[i + 13], 12, -40341101);
c = md5ff(c, d, a, b, x[i + 14], 17, -1502882290);
b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329);
a = md5gg(a, b, c, d, x[i + 1], 5, -165796510);
d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632);
c = md5gg(c, d, a, b, x[i + 11], 14, 643717713);
b = md5gg(b, c, d, a, x[i], 20, -373897302);
a = md5gg(a, b, c, d, x[i + 5], 5, -701558691);
d = md5gg(d, a, b, c, x[i + 10], 9, 38016083);
c = md5gg(c, d, a, b, x[i + 15], 14, -660478335);
b = md5gg(b, c, d, a, x[i + 4], 20, -405537848);
a = md5gg(a, b, c, d, x[i + 9], 5, 568446438);
d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690);
c = md5gg(c, d, a, b, x[i + 3], 14, -187363961);
b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501);
a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467);
d = md5gg(d, a, b, c, x[i + 2], 9, -51403784);
c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473);
b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734);
a = md5hh(a, b, c, d, x[i + 5], 4, -378558);
d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463);
c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562);
b = md5hh(b, c, d, a, x[i + 14], 23, -35309556);
a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060);
d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353);
c = md5hh(c, d, a, b, x[i + 7], 16, -155497632);
b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640);
a = md5hh(a, b, c, d, x[i + 13], 4, 681279174);
d = md5hh(d, a, b, c, x[i], 11, -358537222);
c = md5hh(c, d, a, b, x[i + 3], 16, -722521979);
b = md5hh(b, c, d, a, x[i + 6], 23, 76029189);
a = md5hh(a, b, c, d, x[i + 9], 4, -640364487);
d = md5hh(d, a, b, c, x[i + 12], 11, -421815835);
c = md5hh(c, d, a, b, x[i + 15], 16, 530742520);
b = md5hh(b, c, d, a, x[i + 2], 23, -995338651);
a = md5ii(a, b, c, d, x[i], 6, -198630844);
d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415);
c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905);
b = md5ii(b, c, d, a, x[i + 5], 21, -57434055);
a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571);
d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606);
c = md5ii(c, d, a, b, x[i + 10], 15, -1051523);
b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799);
a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359);
d = md5ii(d, a, b, c, x[i + 15], 10, -30611744);
c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380);
b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649);
a = md5ii(a, b, c, d, x[i + 4], 6, -145523070);
d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379);
c = md5ii(c, d, a, b, x[i + 2], 15, 718787259);
b = md5ii(b, c, d, a, x[i + 9], 21, -343485441);
a = safeAdd(a, olda);
b = safeAdd(b, oldb);
c = safeAdd(c, oldc);
d = safeAdd(d, oldd);
}
return [a, b, c, d];
}
function binl2rstr(input) {
var i, output = '';
for (i = 0; i < input.length * 32; i += 8) {
output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF);
}
return output;
}
function rstr2binl(input) {
var i, output = [];
output[(input.length >> 2) - 1] = undefined;
for (i = 0; i < output.length; i++) output[i] = 0;
for (i = 0; i < input.length * 8; i += 8) {
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32);
}
return output;
}
function rstrMD5(s) {
return binl2rstr(binlMD5(rstr2binl(s), s.length * 8));
}
function rstr2hex(input) {
var hexTab = '0123456789abcdef';
var output = '';
for (var i = 0; i < input.length; i++) {
var x = input.charCodeAt(i);
output += hexTab.charAt((x >>> 4) & 0x0F) + hexTab.charAt(x & 0x0F);
}
return output;
}
function str2rstrUTF8(input) {
return unescape(encodeURIComponent(input));
}
return rstr2hex(rstrMD5(str2rstrUTF8(string)));
}
// 生成 m
function generateM(ts) {
ts = ts || Date.now();
return md5(String(ts)) + '|' + ts;
}
// 测试你给的例子
// console.log(generateM(1786545623641));
// 输出: 53ca3d982204175482cfcd44d6854fdf|1786543535880
最后虽然计算得到m的值,但是仍然得不到正确结果,经过分析,时间戳是第一次请求的时候携带的,本地实现的话会跟服务器有延时或者根本通不过。
CDP
CDP 是chrome‑remote‑interface 是 CDP 协议客户端,相当于代码版 F12,远程操控真实 Edge 浏览器,所有 JS 逻辑运行在真实浏览器 V8 引擎
// 引入 chrome‑remote‑interface 包:CDP协议客户端,用来远程控制Edge/Chrome浏览器
// CDP:Chrome DevTools Protocol,就是你F12开发者工具背后的通信协议
const CDP = require('chrome-remote-interface');
async function main() {
// 连接本地Edge浏览器,端口9223,这个端口是你的bat脚本启动Edge时--remote-debugging-port=9223开放出来的
const client = await CDP({ port: 9223 });
// 解构CDP提供的模块:
// Runtime:在浏览器页面内部执行JS代码(等价于你在F12控制台敲代码)
// Network:网络相关能力:设置Cookie、修改UA、监听请求
// Page:页面导航、等待页面加载完成
const { Runtime, Network, Page } = client;
// 启用网络域,开启Network相关功能,不调用后面setCookie会无效
await Network.enable();
// 启用页面域,开启页面导航、页面加载事件监听
await Page.enable();
// =========替换成你自己的sessionid=========
// sessionid是登录凭证,猿人学靠这个识别你的账号,每个人拿到的数据不一样
const SESSION_ID = "75hgdzj80qyff0kugfugonjluaelwxi0";
// 通过CDP底层给Edge浏览器写入登录cookie
// 注意:这里只是写入cookie,还没有打开网页
await Network.setCookie({
name: 'sessionid', // cookie名字
value: SESSION_ID, // cookie的值(登录凭证)
domain: 'match.yuanrenxue.cn', // cookie生效域名
path: '/', // 网站根路径全部生效
secure: false // http非https环境也生效
});
// ✅关键一步:控制Edge浏览器导航跳转到题目页面 match.yuanrenxue.cn/match/2
// 如果不导航,后面Runtime.evaluate执行JS是跑在about:blank空白页面,fetch会跨域校验失败,服务器不认m cookie
await Page.navigate({ url: "https://match.yuanrenxue.cn/match/2" });
// 等待页面load事件触发,页面HTML、基础资源加载完毕,再执行后续逻辑
await Page.loadEventFired();
// --------------------------
// 第一步:模拟页面第一次请求,拿到混淆JS,浏览器原生V8引擎eval执行,生成cookie[m]
// 为什么不能node本地eval?混淆JS内置巨型正则反格式化检测,Node V8会直接OOM内存崩溃;并且服务器校验:m必须页面JS运行产生,不接受手动setCookie
// --------------------------
await Runtime.evaluate({
// expression:要在浏览器页面环境运行的JS字符串,等价于复制粘贴到F12控制台执行
expression: `
(async ()=>{
// 发送第一次GET接口请求,拿到返回的json,json.data是一大段混淆乱码JS
const res = await fetch("/api/question/2?page=1&pageSize=10&kw=",{
method:"GET",
headers:{
"X-Requested-With":"XMLHttpRequest", // 模拟ajax异步请求头,猿人学接口识别这个头
"Referer":"https://match.yuanrenxue.cn/match/2" // 请求来源页面
}
});
const json = await res.json();
// ⭐核心:浏览器原生eval执行混淆JS代码,混淆代码内部会执行 document.cookie="m=xxx|xxx"
// 执行完,浏览器cookie里面就自动带上合法m,后端校验认可这个m,不是我们手动注入的
eval(json.data);
// 返回当前页面全部cookie,给node层看(调试用,本代码没有拿这个返回值继续处理,浏览器内部已经存好cookie)
return document.cookie;
})()
`,
awaitPromise: true // 告诉CDP:这个JS是异步Promise,等待Promise执行完成之后再返回结果
});
// 存储5页所有数字累加总和
let totalSum = 0;
// 题目一共5页数据
const totalPage = 5;
// 循环遍历page=1 ~ page=5
for(let page = 1; page <= totalPage; page++){
console.log(`\n=====正在请求第 ${page} 页=====`);
// --------------------------
// ⚠️题目隐藏坑:第5页UA必须为yuanrenxue
// 注意:浏览器JS fetch不能直接修改User‑Agent请求头,属于浏览器安全限制,写headers里面会被直接丢弃
// 所以必须使用CDP Network.setUserAgentOverride,在浏览器网络底层直接覆盖UA,绕开JS层面限制
// --------------------------
if(page === 5){
// 第五页,全局覆盖浏览器UA为 yuanrenxue
await Network.setUserAgentOverride({ userAgent: "yuanrenxue" });
console.log("👉已切换浏览器UA为 yuanrenxue");
}else{
// 1‑4页,UA置空,恢复浏览器原始Edge UA
await Network.setUserAgentOverride({ userAgent: "" });
}
// 拼接要在浏览器运行的JS代码,模板字符串会把当前循环的page页码注入进去
const script = `
(async ()=>{
// 在浏览器页面环境发起fetch请求当前页码接口
const resp = await fetch("/api/question/2?page=${page}&pageSize=10&kw=",{
headers:{
"X-Requested-With":"XMLHttpRequest",
"Referer":"https://match.yuanrenxue.cn/match/2"
}
});
const resJson = await resp.json();
// ⭐CDP的Runtime.evaluate限制:复杂对象不能直接传回node,对象只返回远端句柄objectId
// 解决方案:浏览器内部 JSON.stringify 序列化为字符串,字符串属于简单类型,可以完整传回node.js
return JSON.stringify(resJson);
})()
`;
// 交给浏览器执行上面script代码,等待异步完成
const ret = await Runtime.evaluate({
expression: script,
awaitPromise:true
});
// node端拿到浏览器返回的JSON字符串,再解析回JS对象
const respData = JSON.parse(ret.result.value);
console.log(`第${page}页返回data数组:`, respData.data);
// --------------------------
// 数据累加逻辑
// 接口返回 data:[数字1,数字2,...],数组每一项直接是数字,不是{value:xxx}对象
// --------------------------
if(respData && respData.data && Array.isArray(respData.data)){
for(const item of respData.data){
// 判断类型,只累加数字,防止异常数据导致NaN
if(typeof item === "number"){
totalSum += item;
}
}
}
}
// 循环全部结束,输出总和,复制提交到猿人学网页输入框
console.log("\n🎉全部5页数据总和 = ", totalSum);
console.log("复制上面数字提交猿人学输入框!");
// 关闭CDP连接,程序结束(不会关闭Edge窗口,需要手动关)
await client.close();
}
// 启动主函数,捕获全局异常,打印错误信息
main().catch(err=>{
console.error("程序异常:",err);
});
sessionid
sessionid 不是 JS 前端生成的!完全由服务器下发,属于会话凭证,和我们逆向出来的 m 是两个完全不同的东西
| 参数 | 谁生成 | 来源 | 作用 |
|---|---|---|---|
| m | 浏览器 JS(eval 执行混淆代码) | 前端魔改 md5,每一页都要重新算 | 接口校验签名,防爬虫,必须 JS 逆向拿到算法 |
| sessionid | 服务器 (Django 后端) | 访问页面时,响应头Set‑Cookie返回,HttpOnly,JS 读不到 | 标记你的浏览器会话身份;同一个会话可以复用,不需要每次重新生成 |
sessionid 从哪里来
- 浏览器第一次访问页面
https://match.yuanrenxue.cn/match/2 - 服务器 HTTP 响应头返回:
Set‑Cookie: sessionid=abcdef; Path=/ - 浏览器自动保存这个 cookie;JS 混淆代码读取不到、也不能修改 sessionid,它是 HttpOnly 属性。
- 之后所有接口请求,浏览器自动带上
sessionid。
⚠️关键点:sessionid 不能自己写算法生成,不能瞎编字符串,必须从服务器响应拿。
和 m 的协作关系(做题必懂)
请求接口 /api/match/2?page=1 要同时满足两个条件,才返回数据:
- ✅Cookie 带上合法的
sessionid(会话身份) - ✅Cookie 带上当前时间计算出来的有效 m 值(签名校验)
- 只有 sessionid,没有 m → 返回报错;
- 只有 m,没有 sessionid → 直接报错;
- sessionid 可以复用一段时间;m 跟时间戳强绑定,几秒就失效,每页都要重新计算。
JS 中 5 种发送 HTTP 请求方式
在上面分析代码的时候,对于JS的几种请求方式很疑惑,分不清两次请求是怎么发送的。
1. XMLHttpRequest(老式 AJAX,老项目、混淆代码高频出现)
// GET示例
const xhr = new XMLHttpRequest();
xhr.open("GET","/api/xxx?page=1");
xhr.onload = function(){
console.log("响应结果",xhr.responseText);
}
xhr.onerror = function(){console.log("请求失败")}
xhr.send(); // 发送请求
// POST
const xhr2 = new XMLHttpRequest();
xhr2.open("POST","/api/login");
xhr2.setRequestHeader("Content-Type","application/json");
xhr2.send(JSON.stringify({name:"test"}));
2. fetch () 现代浏览器原生(Promise,现在最常用)
// GET
fetch("/2?page=1&pageSize=10")
.then(res=>{
// res不是直接数据,需要.text()/.json()解析
return res.text()
})
.then(text=>{
console.log("返回内容",text)
})
// POST
fetch("/api/post",{
method:"POST",
headers:{"Content-Type":"application/json"},
body:JSON.stringify({id:1})
})
// async await写法(更简洁)
async function demo(){
const resp = await fetch("/2?page=1");
const body = await resp.text();
console.log(body)
}
3. axios(第三方库,基于 XMLHttpRequest 封装)
// get
axios.get("/2?page=1&pageSize=10")
.then(res=>{
console.log(res.data)
})
//post
axios.post("/api/submit",{value:123})
4. jQuery $.ajax/ $.get/ $.post(老网站)
//get
$.get("/2?page=1",function(res){
console.log(res)
})
//ajax完整写法
$.ajax({
url:"/api/test",
method:"GET",
success:function(res){
console.log(res)
}
})
5. navigator.sendBeacon 信标请求(上报埋点,后端统计)
navigator.sendBeacon("/api/report", JSON.stringify({action:"leave"}));
(fetch 完整复现两次请求)
async function twoReqDemo(){
const url = "/2?page=1&pageSize=10&kw=";
//第一次:拿到混淆JS字符串
const resp1 = await fetch(url);
const jsCode = await resp1.text();
eval(jsCode); //执行混淆代码,写入cookie m=xxx
//第二次:同一个url,浏览器自动带上新生成cookie
const resp2 = await fetch(url);
const data = await resp2.json();
console.log("最终数据",data);
}
twoReqDemo()
逆向里 Cookie 的 3 种生成来源
Cookie 生成分 3 大类:
- 前端 JS 计算生成 Cookie(最常见,如猿人学 m 值)
- 请求响应头
Set‑Cookie,后端直接下发 - 第三方库(js‑cookie)封装设置 Cookie
document.cookie 是浏览器原生 API;Node 环境没有document.cookie,需要手动保存结果。
场景 1:原生 JS 直接设置 Cookie(简单)
// 直接写入cookie
document.cookie = "m=abc123456; path=/; domain=match.yuanrenxue.cn; max‑age=3600";
// 读取全部cookie
console.log(document.cookie);
// 读取单个cookie简易函数
function getCookie(name) {
let arr = document.cookie.split('; ');
for(let item of arr){
let [k,v] = item.split('=');
if(k === name) return v;
}
return null;
}
console.log(getCookie("m"));
场景 2:加密算法算出值,再写入 Cookie(逆向高频!)
业务:JS 执行加密函数拿到加密字符串,赋值给 Cookie。
真实网站:AES / MD5 / RC4 / 自定义异或算法算出 token,
document.cookie写进去。
// 模拟加密函数(真实网站这里是混淆加密逻辑)
function encryptToken(input) {
// 模拟加密,实际是一大段混淆、RC4、位运算
let s = input + "_" + Date.now();
return btoa(s);
}
// ①运行加密算法
const m_value = encryptToken("page=1");
// ②把加密结果写入Cookie
document.cookie = `m=${m_value};path=/;domain=match.yuanrenxue.cn`;
// 后续axios/fetch请求自动带上这个cookie
Hook document.cookie,捕获所有写入的 Cookie(逆向神器)
// Hook Cookie赋值,每次设置cookie直接打印【值+调用栈】
(function(){
let originalSetter = Object.getOwnPropertyDescriptor(Document.prototype, 'cookie').set;
Object.defineProperty(document, 'cookie',{
set:function(val){
console.log("🍪设置Cookie:", val);
console.trace("调用栈"); // 直接看到哪一行代码生成cookie
return originalSetter.call(this, val);
},
get:function(){
return originalSetter.call(this);
}
})
})();
场景 3:js‑cookie 库封装设置 Cookie(大量网站在用)
库会封装 domain、path、expires,底层还是操作document.cookie
// js‑cookie 库用法
// Cookies.set('m','xxxx',{domain:"xxx.com",path:"/"})
Cookies.set("m", "encrypted_string", {
domain: "match.yuanrenxue.cn",
path: "/",
expires: 1
});
Cookies.get("m"); //读取
场景 4:eval 动态执行代码生成 Cookie(猿人学第 2 题真实场景)
接口返回一段加密字符串,eval()解密执行,执行内部会设置 cookie。
// 模拟后端返回加密js字符串
let res_data = `
// 这一段是接口返回,eval动态执行
function genM(){
return "m_"+Math.random().toString(36)
}
document.cookie = "m="+genM()+";path=/";
`;
eval(res_data); //执行后,cookie就被设置完成
常见坑
- Cookie 不是 JS 生成,是后端
Set‑Cookie响应头返回,前端只是保存,不需要逆向 JS; - Cookie 是动态时效性,依赖时间戳
Date.now(),复制出来不能直接复用; - eval 动态代码生成 cookie,搜索页面源码搜不到
document.cookie字符串。
JS 逆向 3 大类主流反调试
第一种:无限 debugger 反调试
原理
通过setInterval定时器,循环执行 new Function("debugger")()。
- 打开开发者工具,只要启用断点,就会不断触发 debugger,页面疯狂断住,无法调试;
- 写在
new Function里面,静态搜索源码搜不到 debugger 字符串。
// 无限debugger反调试
setInterval(function(){
(function(){
new Function("debugger")();
})();
}, 500);
第二种:检测开发者工具是否打开(DevTools 探测)
原理
- 窗口尺寸探测:打开 F12,浏览器内外窗口高度 / 宽度差值会变大;
- 时间差探测:
console.log、toString 执行时间突变,判断调试器附加。
// 窗口尺寸探测反调试
function detectByWindowSize() {
// 计算高度差
const heightDiff = window.outerHeight - window.innerHeight;
const widthDiff = window.outerWidth - window.innerWidth;
// 阈值:差值大于150,判定F12已打开
if (heightDiff > 150 || widthDiff > 150) {
console.log("✅【尺寸探测】检测到开发者工具打开");
// 真实网站行为:死循环、篡改业务返回、抛出异常
// while(1){}
}
}
// 定时持续检测
setInterval(detectByWindowSize, 600);
function detectByTimeCost() {
const startTime = performance.now();
// 反复执行toString,打开调试器时耗时会飙升
for (let i = 0; i < 1000; i++) {
(function () { }).toString();
}
const endTime = performance.now();
const cost = endTime - startTime;
// 阈值,打开F12后耗时会明显变大
if (cost > 5) {
console.log("✅【时间差探测】检测到调试器,耗时:", cost);
// 触发反调试破坏逻辑
}
}
setInterval(detectByTimeCost, 800);
第三种:代码格式化检测(obfuscator‑io 特有,你刚才那段代码就是)
原理
把函数拿出来调用 .toString(),拿到源码字符串; 用正则匹配源码格式。
- 如果你在 devtools 点了 {} 格式化按钮,代码会出现换行、空格;
- 正则匹配成功,执行恶意逻辑:无限往数组 push 随机数,内存暴涨 OOM,页面卡死崩溃。
const DetectFormat = function(callback){
this.callback = callback;
// 正则匹配格式化后的换行函数源码
this.reg = new RegExp("\\w+ *\\(\\) *{\\w+ *['|\"]+.+['|\"];? *}");
}
DetectFormat.prototype.check = function(){
// 获取自身toString源码
const fnStr = this.check.toString();
if(this.reg.test(fnStr)){
// 检测被格式化,疯狂堆内存OOM
let arr = [];
while(true){
arr.push(Math.random());
}
}
}
// 实例化直接执行检测
new DetectFormat(function(){}).check();
JS 逆向:加密参数常见出现位置
加密参数:token、sign、m、_sign、timestamp、signature、x‑s‑token、cookie里加密值、header自定义签名
1. Cookie 里面(你现在猿人学 match2 m 就是这类)
- 特征:
document.cookie = "m=xxxx" - 触发时机:接口请求之前执行 JS 赋值 cookie
- 定位手段:
- XHR 断点,请求触发前打断点,看
document.cookie变化 - Hook
document.cookie,捕获赋值瞬间
- XHR 断点,请求触发前打断点,看
// hook cookie
Object.defineProperty(document,'cookie',{
set(val){console.log("设置cookie",val);debugger;return val},
get(){return ""}
})
很多网站签名不放在请求头、params,藏在 Cookie,这是高频坑。
2. Request Headers 请求头
常见名字: X‑Sign、X‑Token、Authorization、x‑w‑sign、sign、timestamp、nonce、signature
- 来源:JS 运行,
headers["X‑Sign"] = xxx - 定位:
- XHR 断点断住,向上看调用栈,看 headers 赋值的地方
- 搜索关键词
3. Query 参数(url 问号后面 params)
https://xxx/api?sign=aaa&t=1780000000
- 搜索关键词:
sign=、params、URLSearchParams - 经常会把时间戳 + 签名直接拼在 url。
4. Request Body 请求体(POST json /form‑data)
POST 提交,payload 里面携带加密字段
{"data":"xxx","sign":"abc123","timestamp":1780000000}
定位:搜索 JSON.stringify、data:、body。
5. webpack 打包模块内部(猿人学这类 webpack 站点)
加密逻辑藏在某个 webpack 模块 id 里面:
- 不会全局暴露函数,闭包内部运行
- 不会直接看到
function getM(),都是混淆变量名a.b() - 定位:靠
__webpack_require__调用栈,看哪个模块执行了 cookie 赋值、headers 赋值。
6. eval /new Function 动态执行(重点!)
网站把加密代码字符串,eval(res.data)动态执行。
猿人学 match2 就是这个模式!
- 源码搜索不到加密函数,代码是接口返回字符串,eval 之后才生成。
- 特征:
eval(xxx)/new Function()。
这种情况你直接搜页面源码找不到加密逻辑,必须拦截 eval 拿到真实代码
7. 定时器、回调里面生成(setInterval /setTimeout)
不是点击、不是接口回调,定时器异步生成 cookie/sign。
- 很难直接断点,经常出现无限 debugger 也在定时器
JS 逆向补环境 + 自动检测原理
补环境是什么:Node 没有浏览器的window、navigator、document、location这些 BOM/DOM 对象,网站混淆 JS 会读取这些浏览器指纹做校验、反调试;补环境就是在 Node 沙箱伪造这套浏览器宿主对象,让混淆 JS 以为自己跑在真实浏览器里,不会触发反调试
自动检测:用Proxy代理拦截所有对象属性读取,打印 JS 代码访问了哪些浏览器 API,缺什么补什么,不用瞎猜。
更多推荐

所有评论(0)