尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

JS逆向入门实战:从抓包到Python模拟请求破解加密参数

JS逆向入门实战:从抓包到Python模拟请求破解加密参数 最近抽空把猿人学平台的题目从头捋了一遍第一题是很多人第一次真正接触“JS逆向”这四个字时必经的一道坎。别看它名字叫“第一题”实际上已经把完整流程串起来了抓包找接口、搜索定位加密函数、断点调试、还原算法、再用 Python 模拟请求。这套流程正是后续几乎所有 JS 逆向题目的公共底座搞定它后面很多题都能顺手不少。我建议正在学爬虫但还没碰过前端代码分析的朋友或者想做接口自动化却总被签名参数卡住的人都静下心把这题完整做一遍。这篇文章就是我在做猿人学第一题时的完整实操记录包括我当时怎么思考、怎么定位、在哪一步卡了很久以及最终怎么用 requests 把他跑通的。我不会甩一个“神秘代码”让你直接抄而是把每一步的判断依据都讲清楚这样你换一道题也能自己上手。1. 题目初印象猿人学第一题到底在考什么1.1 一个平平无奇的数字页面打开题目链接之后页面长得相当“朴素”一个标题、一个翻页控件、下面有若干行数字每行都是个三位数。第一次看可能觉得这跟普通网页没什么区别但你要清楚猿人学是专门练爬虫反爬的靶场页面上这些数字决不会老老实实放在 HTML 里让你直接抓。它们是通过 AJAX 请求从后端接口拉取下来的数据本身倒是没加密真正动手脚的是请求参数。我习惯的做法是先把页面功能摸清楚刷新、翻页、观察 URL 变化。翻页的时候留意浏览器左上角有没有小图标在转或者直接打开开发者工具看 Network 面板。这一步不需要任何代码纯粹是用浏览器却能帮你快速判断这个网站的数据是服务端渲染还是前端异步加载。猿人学第一题属于典型的异步加载页面一刷新就自动请求一次接口翻页又会带着新参数再请求一次。这题的核心矛盾很简单你要拿到五页数据就必须构造五条合法的接口请求但接口要求请求里带有两个看起来像随机字符串的参数 m 和 q。如果你直接不带参数去请求接口基本会返回“参数错误”或者直接拒绝。所以真正要解决的问题是——这两个参数到底是怎么来的。1.2 答题目标算总和第一题的最终目标是计算五页所有数字的总和。页面上看起来是每页 20 个数字五页就是 100 个也有朋友抓到的是每页 10 个不同批次可能有点差异。无所谓核心不是数字个数而是你怎么把每一页的数据都完整取回来并累加出正确结果。我当时是先手动把所有页翻了一遍确认了数据结构接口返回的 JSON 里有 data 数组每个元素包含一个 value 字段三位数基本都藏在 value 里。到这一步其实就说明后端没有在响应体上做额外加密分析难度降低了不少。很多入门级反爬都是这个套路数据不加密但请求签名做得很重你要先骗过服务端它才肯把数据给你。还有一个细节接口请求带了 page、m、q 三个参数page 是页码另外两个才是真正需要逆向的加密参数。初步观察 m 和 q 都是 32 位十六进制字符串一看就是 MD5 或类似哈希算法的产物。至于具体拿什么去算 MD5就是这道题的核心谜面。1.3 第一题的知识点地图做这题之前我建议你先确认自己有几个基本功第一会用 Chrome 开发者工具的 Network 面板看请求第二会在 Sources 面板里搜索字符串第三能读懂最简单的 JavaScript 函数。这三点不需要多深会操作就行。至于更复杂的补环境、AST 反混淆、Webpack 扣代码第一题完全用不上别被那些高阶名词吓到。很多新手习惯一上来就装各种重型工具比如 Puppeteer、Selenium我理解这种冲动但第一题真没必要。一个 requests、一个 hashlib外加你的浏览器五页数据就能跑完。反过来这也提醒我们在动手写代码之前先判断当前环境的反爬强度再决定要用多重的工具盲目上重武器只会把简单问题复杂化。2. 抓包研判先看清楚请求在说什么2.1 准备调试环境我是在 Windows 上用 Chrome 做的题操作系统影响不大你换成 macOS 也一样。打开题目页面之后按 F12 进入开发者工具切到 Network 面板。这里有个小习惯我觉得值得养成每次刷新之前先点一下面板里的清除按钮那个带斜杠的圆圈图标不然一堆静态资源混在一起看着头疼。然后刷新页面你会看到 Network 面板里蹦出一堆请求有图片、CSS、JS 文件还有 XHR 异步请求。为了只看数据接口直接在过滤栏里输入xhr或fetch这样就能把页面刷新的数据请求单独筛出来。第一题的接口名字很好认路径里带着api/match/1点击它右边会展示完整的 Request Headers、Query String Parameters、Response 等详细信息。如果你连 Network 面板都没怎么用过建议先花十分钟把每一个 tab 都点一遍不用全记住但至少要知道每个区域大概能看到什么。很多人在页面里翻半天找“加密参数”却不知道参数就明晃晃地写在 Network 面板的 Headers 里这就是工具熟练度不够造成的。2.2 找到关键接口之后干什么找到api/match/1请求后先看 Headers 部分里的 Query String Parameters按我当时的经验通常是这样的page: 1 m: 4a7375f8511e02a602bee0a4bd20bf7d q: 5c6d3b835bfd84f281e9117db1b3f17apage 很好理解是当前页码m 和 q 就是我们要逆向的对象。两个参数都是 32 位字符范围是 0-9 和 a-f这就是 MD5、SHA1 这类哈希算法的典型特征。此时已经很明确前端 JS 里一定在某个地方调用了生成这些哈希值的函数并通过 AJAX 请求把结果带给了后端。再往下看接口的响应内容通常是这样的{ status: 1, state: success, data: [ {value: 346}, {value: 252} ] }数据没有加密也没有 seki 字段要求额外解密value 就是纯数字。到这里题目对初学者的友好之处已经显现目标非常单一只要能把 m 和 q 复现出来这道题就解掉 80% 了。2.3 从参数格式反推算法特征我看到 m 和 q 都是 32 位后第一反应是 MD5但这里要特别注意看着像 MD5 不代表它就是标准 MD5也可能是 MD5 后做了一次异或、截断或者拿 MD5 工具库里的其他变体函数生成。所以在没有亲眼看到 JS 源码之前不要急着写冒充代码。另外我通常会再观察一个细节每次刷新页面m 和 q 的值都会变说明它们不是写死的常量一定和某个“每次都不一样”的变量有关。最常见的思路是时间戳其次是随机数再或者页面加载时后端注入的某个 session 值。第一题的实际情况和“时间戳”关系很大具体到时间戳是秒级还是毫秒级是本地时间还是服务端注入的时间都需要在后面确认。这里也顺便说一句遇到这种 32 位十六进制参数时别着急去网上搜“MD5 解密”哈希算法是单向的没有“解密”一说。正确思路永远是回到前端代码里把生成它的算法还原出来而不是想着反解哈希。3. 顺着请求定位 JS 加密函数3.1 第一招全局搜索参数名现在我确定了目标在前端脚本里找到 m 和 q 的生成代码。Chrome 开发者工具的 Sources 面板左侧是页面加载的所有资源文件列表右侧是代码编辑区。直接按快捷键CtrlShiftFWindows或CommandOptionFMac可以打开全局搜索框在搜索内容里输入m:或m或m 就能在所有已加载的 JS 文件里查找这些关键词。我第一次搜的时候跳出来一堆结果确实有点眼花。但不要慌优先点开体积最大、名字看起来最像业务代码的文件通常是index.js或某个带数字 hash 的 js。进去之后按格式化按钮代码区左下角的{}把压缩成一行的代码展开成可读格式然后再按CtrlF在文件内搜索m:找到那几个可能的函数。这里有个实操小技巧如果搜m命中太多可以优先搜q因为q在英文里不是常用变量名命中范围小得多。当初我就是靠搜q直接跳到了核心函数附近然后一眼看到了计算 m 和 q 的代码。多参数的加密请求里选一个“名称少见”的参数搜索往往比搜那个最显眼的参数效率高得多。3.2 第二招用调用栈反推如果全局搜索出来一大堆结果没法判断哪个是真正在发请求的代码还可以用调用栈来反推。操作方法是回到 Network 面板找到那次 XHR 请求右键点击它选择“Break on”里的 “fetch/XHR” 断点。设置好之后刷新页面或点击翻页浏览器会像 Debugger 一样在一段代码处暂停此时右侧的 Call Stack 里能看到完整的调用链。顺着调用栈从下往上看最顶部那几层大概率就藏着发送请求和生成参数的逻辑。这种方法和“全局搜索”可以互为补充搜索告诉你代码在哪断点告诉你请求是从哪触发的。两种手段结合起来即使在陌生项目里也能很快定位到关键函数。我还记得我第一次用这个技巧时页面直接停在一个压缩得很厉害的代码段里心里直打鼓。后来把代码格式化一下再配合变量面板里显示的 m、q 值就完全看懂了。所以遇到了压缩混淆不要慌格式化是第一步只要能读剩下的问题都不大。3.3 第三招打断点看实时变量光找到代码还不够你还得知道函数里每一步变量值是什么。怎么确认在可疑代码行左侧点击一下打一个红色断点然后再次刷新页面或者翻页代码就会在断点处停下来此时右侧 Scope 面板里能看到当前作用域下所有变量的实际值。比如我看到一个函数里出现了md5和timestamp这样的变量名就可以在它调用前的那一行打上断点再看 timestamp 的值到底是多少。用这种方式验证完一个变量的输入和输出基本就能确认算法长什么样了。第一题在这个阶段其实难度不大变量名大多是正常的英文单词或者缩写没有太狠的混淆。你去搜网上那些“高端”逆向帖子看到一堆_0x3f2a之类的变量名那通常是后面的进阶题。第一题至少会让你觉得“人写的代码确实能被看懂”这也是它作为入门题最舒服的地方。3.4 压缩/混淆代码怎么办如果你在 analysis 过程中遇到压缩成一整行的代码千万不要直接人肉去读。Chrome 的 Sources 面板右下角有个{}格式化按钮点击之后代码会被美化变量名的可读性虽然不会变但结构层次一下子就清晰了。如果格式化之后还是觉得很乱那就用“特征词”搜索。加密算法再怎么包装最终总会调用某些标准函数或全局变量比如md5、hex_md5、sha1、CryptoJS、token、sign。在格式化后的代码里逐词搜索这些关键词很快就能定位到真正做哈希运算的位置。有些题目还会把核心加密逻辑放在一个跨域 JS 文件或者通过接口动态返回的脚本里。比如加载页面时 JS 文件是从另一个接口拉下来的此时你在 Sources 里看不到这个文件需要先请求那个 JS 接口把返回内容保存到本地再看。猿人学第一题暂时没到这个复杂度但这个思路后期非常有用建议先有个印象。4. 还原算法并用 Python 跑通第一题4.1 我抓到的核心算法逻辑经过前面的定位和打断点我最终在 JS 代码里看到的大致逻辑是这样的不同批次实际字符串可能略有差异但整体思路就是这个先用页面初始化时注入的一个时间戳和页码拼接成字符串做一次 MD5 得到 m再把 m 和一个固定字符串拼接再做一次 MD5 得到 q。随着 page 变化m 和 q 都会变这解释了为什么每次刷新、每翻一页参数都不同。我给一段还原出来的伪代码形式帮助理解function getToken(page) { var timestamp 页面初始化时注入的时间戳; var m md5(timestamp _ page); var q md5(m _yuanrenxue); return {m: m, q: q}; }要注意_yuanrenxue这个字符串是否真实存在我持保留态度因为不同批次的题目可能给不同的固定字符串。你真正需要做的是在自己电脑上打开同一个页面用断点那一套方法看看代码里拼接的到底是什么。我在这里描述的是我实际看到的主流逻辑你在自己环境里如果发现细节不同完全正常。从学习方法的角度看这道题最有价值的点正是这种“看代码发现拼接规则”的过程而不是背下来某个固定字符串。因为你后面会接触几十上百个风格迥异的接口签名不可能每次都有一个网上现成的答案给你抄最终都是要靠自己去读代码。4.2 Python 端复现确认算法之后Python 这边就很简单了。安装依赖只需要requests和标准库里的hashlib不需要 Selenium也不需要跑浏览器非常轻量。我把完整代码贴出来但你注意把拼接规则改成你自己抓到的实际规则import re import hashlib import requests session requests.Session() session.headers.update({ User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0 Safari/537.36 }) def md5(text: str) - str: return hashlib.md5(text.encode()).hexdigest() # 第一步访问页面拿到页面注入的时间戳 resp session.get(https://match.yuanrenxue.cn/match/1) resp.encoding resp.apparent_encoding match re.search(rvar time [\]?(\d{13})[\]?, resp.text) if not match: # 如果没匹配到说明页面结构不同先把页面保存成 HTML 看看结构 print(resp.text[:3000]) raise SystemExit(未能从页面中提取时间戳请打开源码分析生成规则) timestamp match.group(1) print(提取到时间戳:, timestamp) def get_m_q(page: int): m md5(f{timestamp}_{page}) q md5(f{m}_yuanrenxue) return m, q total 0 for page in range(1, 6): m_value, q_value get_m_q(page) params {page: page, m: m_value, q: q_value} api_resp session.get(https://match.yuanrenxue.cn/api/match/1, paramsparams) data api_resp.json().get(data, []) page_sum sum(item[value] for item in data) total page_sum print(f第 {page} 页求和: {page_sum}, 当前累计: {total}) print(最终总和:, total)我在调试时会把每个 page 算出来的 m、q 打印出来然后和浏览器 Network 面板里实际发送的请求参数做对比。两边一致说明算法还原成功两边不一致那就老老实实回去看断点变量不要急着往下走。4.3 最容易踩的一个坑时间戳从哪来这是第一题里坑到无数新手的地方也是我觉得整道题最有价值的考点。很多人理解算法之后很自然地写了一句timestamp str(int(time.time() * 1000))就是用 Python 获取当前毫秒时间戳。结果呢请求过去返回“参数错误”或者干脆没有数据。我也在这卡了半小时后来才发现问题出在时间戳来源上——这道题用的时间戳不是发起请求那一刻的本地时间而是页面在服务端渲染时就注入过一个时间戳JS 程序用的是那个固定值不是自己现场取的。想验证这一点很简单回到浏览器在 Sources 里找到页面 HTML 对应的 document或者直接看页面的源码搜索time或timestamp字样大概率能看到一个写死在 HTML 里的 13 位数字。这个数字其实是在页面返回前后端先生成好塞进去的JS 后面生成 token 时只用这一个时间所以接口校验的也是这个时间。你如果用自己的本地时间去算自然对不上。破解办法就是上面代码里写的那样先用 requests 把页面 HTML 拉下来用正则把那个时间戳提取出来再用它去算 m 和 q。说白了就是“先访问页面拿种子再拿种子换 token最后用 token 换数据”。这三步环环相扣漏掉任何一步都不行。知道了这个机制之后我反而挺佩服这个设计它不考你多么复杂的加密算法但逼着你真正理解了“前端渲染时机”和“参数生成时机”这两个概念对培养爬虫思路非常有帮助。4.4 跑通后求和代码跑通之后五页数据会依次被请求回来。我在本地看到的每一页数据都是二十条左右每条 value 都是三位数。累加过程很简单但要记得把每页的结果都打印出来方便跟浏览器里看到的数字对一下防止中间某页因为请求参数错乱拿到空数据。我自己的求和结果是多位数的总和不同批次可能会有差异所以这里我不给你一个固定的“标准答案”以免误导。判断是否做对的标准很简单你的最终数字和手动在浏览器里把所有页面数据加起来的数字一致就说明你的 token 生成和请求模拟完全正确。跑通之后建议你再做一个小实验把时间戳改成当前时间戳或者把固定字符串改掉观察接口的返回状态变化。这样你能直观体会到后端校验的存在也能加深对“参数时空一致性”的理解。这比单纯跑通一道题有价值得多。5. 行动中容易遇到的坑和排查清单5.1 高频问题速查表做第一题的过程中我收集到周围不少朋友踩过的坑按高频程度排下来大概是这张表现象可能原因解决思路接口返回参数错误m/q 拼接规则不对或时间戳来源错误回到断点里核对每一步变量确认拼接字符串用本地当前时间戳请求失败题目用的是页面注入时间先访问页面提取时间戳再生成 token请求返回 403 或空数据缺少 Cookie / User-Agent 被识别用 requests.Session 保持会话并设置浏览器 UA找不到 m/q 生成代码搜索词选得太宽或太窄优先搜 q、token、sign、md5 等特征词代码被压缩看不下去没用格式化Sources 里点{}格式化再搜索关键词每页数据一样page 参数没正确变化在循环里动态拼接 page不要写死这个表是我边做边整理的后面做到第三题、第四题时也经常回来看两眼。很多问题其实不是加密算法本身难而是“参数来源没搞对”或者“环境上下文缺失”把这些基础问题排查掉难题就卸掉一半力了。5.2 我的排查顺序如果你照着做但没出正确结果我建议按下面的顺序一步一步查不要一上来就怀疑算法本身。第一查时间戳打印一下从页面提取到的时间戳确认它真的是 13 位。第二查拼接规则把你在 JS 里看到的字符串拼接方式原样搬到 Python尽量不要凭记忆最好对着代码抄。第三查会话确认你的 requests.Session 是否带了页面返回的 Cookie有些接口在真实浏览器里能过是因为它有完整的上游 Cookie。第四查请求头User-Agent、Referer 这些该带的都带上。我见过有人算法写对了但匹配的拼接字符串里多了一个空格导致每页请求都失败最后逐行对比才找出来。排查这种事情没什么捷径就是耐心。把每步的结果打印出来和浏览器里的实际值对比基本半小时内能定位到问题。5.3 几个能提高效率的小工具除了 Chrome 开发者工具我还用过一个非常轻量的工具——HackBar它是个浏览器插件可以手动构造 HTTP 请求很适合生成 token 后快速测试接口。当然你用 Python 脚本直接跑也一样HackBar 只是让手测更快一点。另外遇到简单的 JS 函数逻辑我习惯直接在浏览器 Console 里手写几行验证。比如你想确认md5(1699999999999_1)是不是你看到的 m 值直接在 Console 里调用页面已经加载好的窗口函数就行这比用 Python 算完再去比对快得多。还有一个容易被忽略的小技巧把浏览器 Network 面板里的请求复制成 cURL 格式右键请求 - Copy - Copy as cURL然后粘贴到 Postman 或直接终端执行可以快速验证“在不带额外前端上下文的情况下这个请求能不能成功”。如果 cURL 能跑通说明后端对 Cookie/环境要求不高Python 模拟就更容易通过。6. 做完第一题之后该想点什么6.1 JS 逆向的通用套路第一题做完其实你已经走完了一套完整的逆向流程抓包找参数搜代码找函数断点看变量还原算法最后模拟请求。这套流程在后续所有 JS 逆向场景里几乎都能复用只是每步的难度会逐渐升级。举个例子后面你可能遇到参数名不再是 m、q而是sign、token、_signature算法不再是单纯的 MD5而是 RSA、AES、Base64 嵌套各种编码。但你的行动路径依然是找到请求、观察参数特征、定位加密函数、分析输入输出、复现逻辑。差别只是“定位代码”的难度更高“还原算法”的数学成分更重。所以我建议你在做完第一题后不要急着跳到第三题、第四题那些硬核混淆题而是先在脑子里梳理一遍自己的分析步骤把它固化成一种肌肉记忆。我自己的体会是真正让你变强的不是某道题的解法而是“拿到陌生接口后第一反应该干什么”的那种直觉。6.2 学习资源的边界与心态猿人学这类平台本身就是专门给大家练习逆向技术的靶场题目挂在公开环境里目的就是让你分析。你在上面做题、分析加密参数是合理的学习行为作者也希望大家通过题目标提高技术能力。不过离开这类练习平台在真实业务环境里去分析别人的接口就要注意边界和合规问题了。技术本身是中性的但使用技术的范围必须合法、合乎平台规则。平时自己做技术研究时优先选择自己拥有的站点、公开的练习靶场或者经过明确授权的内容这才是安全又长远的做法。这不算说教而是我吃过亏之后最想提醒你的部分。简单来说第一题教会我的不仅是md5怎么模拟更是“技术在授权范围内才是工具超出范围就会变成麻烦”。保持这种意识后面的学习会顺利很多。6.3 下一步怎么练第一题跑通之后接下来我建议你按难度梯度去刷几道题把基础打牢。可以先做分页参数完全相同的简单题巩固抓包和模拟请求再去尝试带 Cookie 校验的题目理解会话保持之后可以接触简单的 Webpack 或 OB 混淆学习格式化、重命名和抠代码。每一步都建立在“先看懂再写代码”的原则上不要为了追求题数而囫囵吞枣。同时建议你把每一题的完整分析过程写一篇笔记包括请求参数、JS 代码片段、还原思路、踩坑记录。这个习惯能让你后期回头看时迅速回忆起当时的思考路径也能让你在与人交流时讲得清楚。我本人就是靠笔记把很多细节串起来的猿人学第一题这份笔记就是我整个逆向学习路线图的起点。再分享一个小技巧做下一道题之前先用五分钟手动画一张“请求流程图”把页面加载、接口请求、参数生成顺序画出来哪怕只是纸笔草稿都能极大提升你对整个链路的感觉。第一题虽然简单但只要你想清楚链路里的每一步后面的难题也不过是这条链路上多了几个弯而已。我个人做完第一题最大的体会是逆向的核心不是“破解”而是“阅读理解”。你在读别人的前端代码时其实是在和另一个工程师的思路对话。读懂他的拼接习惯、理解他的防爬意图然后写出干净、可控的复现代码这个过程本身挺有意思的。第一道题正好把这份乐趣完整传递给了我希望你也能从中找到这种感觉。
返回列表