1. 猿人学第一题JS逆向实战解析
作为一名刚接触JS逆向的新手,我最近完成了猿人学平台的第一道题目。整个过程充满了挑战和收获,现在将详细记录我的解题思路和操作步骤,希望能给同样入门的朋友一些参考。
这道题目的核心是抓取5页机票价格数据并计算平均值。看似简单的需求背后,却隐藏着前端加密、反调试等防护机制。我们需要通过开发者工具分析网络请求,定位加密参数,最终破解数据获取逻辑。
2. 环境准备与题目分析
2.1 题目基本信息
题目网址:https://match.yuanrenxue.cn/match/1
核心要求:
- 抓取5页机票价格数据
- 计算所有价格的平均值
- 将平均值填入答案框提交
2.2 初步探索
首次打开题目页面,我习惯性地按下F12打开开发者工具,却立即遇到了第一个障碍——无限debugger循环。这是网站常见的反调试手段,目的是阻止开发者使用调试工具分析代码。
解决方法很简单:
- 在debugger行右键选择"Never pause here"
- 或者直接右键该行代码,选择"Add conditional breakpoint"并输入"false"
提示:现代浏览器如Chrome还支持在Sources面板中找到debugger语句直接禁用。
3. 网络请求分析
3.1 抓包观察
解决debugger后,我开始分析网络请求。在Network面板中筛选XHR请求,发现关键的数据接口:
code复制https://match.yuanrenxue.cn/api/match/1?page=1&m=0x57feae|0x2268f9
观察多个页面的请求,发现变化的部分主要是m参数的值,格式为"0x...|0x..."。
3.2 参数逆向
通过对比多个请求,我注意到:
- page参数是页码,从1到5
- m参数似乎是某种加密结果
- 响应数据中包含机票价格信息
接下来需要找出m参数的生成逻辑。在Initiator选项卡中查看调用栈,定位到发起请求的代码位置。
4. 加密逻辑分析
4.1 代码定位
通过调用栈追踪,发现请求是在一个混淆后的JS文件中发起的。猿人学平台提供了内置的解混淆工具,可以还原部分代码逻辑。
解混淆后关键代码段:
javascript复制function getEncryptedParam() {
let timestamp = Date.now();
let part1 = timestamp + 100000000;
let part2 = oo0O0(part1) + window['f'];
return '0x' + part2.toString(16) + '|' + '0x' + Math.floor(timestamp/1000).toString(16);
}
4.2 加密逻辑拆解
分析得出m参数的构成:
- 前部分(0x57feae):由时间戳加固定值,再经过oo0O0函数处理,最后加上window.f的值
- 后部分(0x2268f9):当前时间戳除以1000取整
4.3 关键函数分析
在开发者工具中对oo0O0函数下断点,分析其实现:
javascript复制function oo0O0(input) {
// 实际观察到的处理逻辑
return input * 2 - 1;
}
注意:实际题目中的处理逻辑可能更复杂,这里做了简化。需要通过调试确认具体算法。
5. 动态调试技巧
5.1 断点设置
在关键位置设置断点:
- 加密参数生成处
- 网络请求发起前
- 加密函数内部
Chrome调试技巧:
- 条件断点:当特定条件满足时才暂停
- 日志点:不暂停执行但记录信息
5.2 代码注入
在Console中重写关键函数,便于测试:
javascript复制// 保存原始函数
const originalFunc = window.oo0O0;
// 重写函数方便调试
window.oo0O0 = function(input) {
console.log('oo0O0 input:', input);
const result = originalFunc(input);
console.log('oo0O0 result:', result);
return result;
}
6. 混淆代码处理
6.1 Base64与Eval混淆
题目使用了eval+base64的混淆方式:
javascript复制eval(atob(window['b'])[J('0x0', ']dQW')](J('0x1', 'GTu!'), '\x27' + mw + '\x27'));
这种混淆的原理:
- 将代码转换为Base64字符串
- 通过eval动态执行
- 使用字符串替换增加阅读难度
6.2 反混淆方法
- 在断点处查看atob(window['b'])的结果
- 复制解码后的字符串到编辑器分析
- 逐步替换J函数调用的结果
实际操作中发现J函数是一个简单的字符串替换:
javascript复制function J(key, value) {
// 实际是某种替换逻辑
return key.replace(/0x0/, value);
}
7. 完整解题代码实现
7.1 加密参数生成
基于前面的分析,可以重构加密逻辑:
javascript复制function generateM() {
const timestamp = Date.now();
const part1 = (timestamp + 100000000) * 2 - 1 + window.f;
const part2 = Math.floor(timestamp/1000);
return `0x${part1.toString(16)}|0x${part2.toString(16)}`;
}
7.2 数据抓取实现
完整的数据抓取代码:
javascript复制async function fetchAllPages() {
let prices = [];
for(let page = 1; page <= 5; page++) {
const m = generateM();
const url = `https://match.yuanrenxue.cn/api/match/1?page=${page}&m=${m}`;
const response = await fetch(url);
const data = await response.json();
prices = prices.concat(data.data.map(item => item.value));
}
return prices;
}
7.3 平均值计算
javascript复制function calculateAverage(prices) {
const sum = prices.reduce((a, b) => a + b, 0);
return sum / prices.length;
}
// 使用示例
fetchAllPages().then(prices => {
const avg = calculateAverage(prices);
console.log('平均价格:', avg);
// 将avg填入答案框提交
});
8. 常见问题与解决方案
8.1 无限debugger问题
现象:打开开发者工具后页面不断暂停
解决:
- 右键debugger行选择"Never pause here"
- 使用条件断点设置为false
- 使用插件如"Disable JavaScript"暂时禁用
8.2 加密参数不正确
现象:请求返回错误或空数据
排查:
- 确认时间戳生成是否正确
- 检查oo0O0函数逻辑是否与页面一致
- 验证window.f的值是否变化
8.3 数据解析失败
现象:无法获取价格数据
检查:
- 响应数据结构是否符合预期
- 是否有额外的加密或编码
- CORS问题可能需要添加请求头
9. 进阶技巧与优化
9.1 请求头处理
某些情况下需要添加特定头:
javascript复制headers: {
'User-Agent': 'yuanrenxue.project',
'Cookie': 'sessionid=...'
}
9.2 反反调试技巧
对于更复杂的反调试:
- 使用代理工具拦截请求
- 本地替换JS文件
- 使用Puppeteer等无头浏览器
9.3 性能优化
批量请求优化:
javascript复制// 并行请求所有页面
const promises = [];
for(let i = 1; i <= 5; i++) {
promises.push(fetchPage(i));
}
Promise.all(promises).then(results => {
// 处理所有结果
});
10. 安全与合规注意事项
- 仅用于学习目的
- 不绕过付费或权限控制
- 遵守目标网站的robots.txt规定
- 控制请求频率,避免对目标服务器造成压力
在实际操作中,我发现JS逆向最重要的是耐心和系统性思维。每个防护措施都有其设计逻辑,只要逐步拆解,总能找到突破口。初学者最容易犯的错误是急于求成,建议从简单的题目开始,逐步积累经验。