news 2026/9/17 13:46:26

JS逆向实战:破解私募排行加密接口的完整流程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JS逆向实战:破解私募排行加密接口的完整流程

我先把结论放在前面:别看“私募pp网-私募排行js逆向”这标题像个选股工具测评,其实它是个纯正的前端加密对抗实战项目。核心不是私募数据本身,而是“排行榜页面的数据是加密接口吐出来的,怎么把这个加密过程还原出来”。完完整整走完一遍,你至少能掌握XHR断点、调用栈回溯、混淆JS还原、Node补环境这几板斧,往后遇到更复杂的加密接口,思路会清晰很多。

这篇博文就按我当时做这个项目的顺序来写,从最开始的抓包定位,到断点找入口,再到还原加密函数,最后用Python把整条链路串起来。中间那些踩过的坑、误判过的方向,我也会一并写出来,免得你重复交学费。

1. 逆向目标与前期观察

1.1 先说清楚要抓什么

这个项目的目标很明确:某家做私募产品展示的垂直网站,里面有个“私募排行”栏目,按收益、回撤等指标对产品排序。页面本身能看到数据,但直接请求接口返回的是一团乱码,加密字段一个叠一个,没法直接用于分析。

我习惯把这类逆向项目分成三层:

  • 第一层是“看得见的”:浏览器里渲染出来的排行榜表格,字段名、排序规则、前端展示逻辑;
  • 第二层是“藏在请求里的”:Network面板里那个XHR请求,请求头、query参数、request payload;
  • 第三层是“真正干活的”:JS代码里生成签名、加密参数的逻辑,一般隐藏在压缩混淆的chunk文件里。

这次的核心矛盾出在接口的两个请求参数上:一个类似sign的签名值,以及整个响应体被做了编码处理。不解决这两个点,哪怕你把Cookie、User-Agent全部带齐,服务器照样返回非200或者一团乱码。

1.2 打开开发者工具快速定位请求

推荐直接用Chrome DevTools,不推荐一开始就上抓包工具。原因很简单:我们面对的是HTTPS站点,Fiddler或Charles即使配好证书,有时候也会被前端指纹检测干扰;而DevTools是浏览器自带的,天然在“可信环境”里,干扰最少。

实际操作步骤:

  1. 进入排行榜页面,按F12打开DevTools,切到Network面板;
  2. 勾选Preserve log,防止页面跳转刷掉请求记录;
  3. 手动刷新页面,等列表数据加载完毕;
  4. 在Network面板过滤XHR,找返回内容带产品净值或收益率的请求。

这里有个小技巧:不要急着看请求头,先看请求入口。在Network面板的请求列表里,把鼠标移到某个可疑请求上,会显示Initiator链。我们直接点进去,能看到是页面里的哪个JS文件发起的请求、对应的调用栈是什么。这一下就能省下很多全局搜索的时间。

我这次遇到的请求,URL大概是/api/rank/list?page=1&size=20&sort=profit&asc=0,但后面跟着一长串&sign=...&ts=...&nonce=...之类的参数。一眼就能判断,这是典型的“动态签名”模式。

2. 定位加密参数:从网络请求到JS调用栈

2.1 遇到底层加密参数怎么办

请求头里的参数不是我们要找的核心。真正要关心的是query参数里那几个看起来随机的东西。比如signtsnonce,它们往往是一个组合。

判断优先级的方式很简单:

  • 时间戳类参数(比如ts=1691234567890),一眼能认出来;
  • 随机串类参数(比如nonce=7f3a912b...),大概率是UUID或随机字节的十六进制表示;
  • 签名类参数(比如sign=da3c8f...),最头疼,通常是前面多个参数拼起来之后做摘要算法。

这次三个都占全了。

我当时的策略是:先不管签名算法是什么,先去全局搜索“sign”这个字符串。在Sources面板里按Ctrl+Shift+F全局搜索,搜出来的文件可能很多,怎么缩小范围?

  • 排除所有.min.js里的source map注释行;
  • 优先看主入口文件(webpack的runtime、app.bundle.js之类);
  • 搜的时候带上关键字上下文,比如sign:sign =setRequestHeader('sign'

全局搜索的好处是能直接找到赋值语句的源头。坏处是压缩混淆后的代码里,你会搜出来上百个匹配项,挨个看会疯。

2.2 关键词搜索定位加密函数

我当时没有死磕全局搜索,我用了更直接的XHR断点方式。

操作流程:

  1. 切到Sources面板,右侧Breakpoints区域打开XHR/fetch Breakpoints;
  2. 点击加号,输入URL中的关键字片段,比如rank/list
  3. 刷新页面,脚本会在发起XHR请求前自动断住;
  4. 这时候右侧Call Stack就是金子,一层层点进去找自己写的业务代码。

断住以后,我通常先看Scope区域,找包含所有参数的那个对象。比如configrequestDatabody,这个对象里通常能看见明文参数和那个sign值。然后沿着Call Stack往下走,找最近的那个非第三方库函数。

我记得当时断住的位置是在一个叫ajax的封装函数内部,参数已经拼好了,就差被加密。往上一层看到调用方是类似fetchRankList的方法,里面有一行代码非常关键,大致是这样:

var params = buildParams({ page: page, size: size, sort: sort, asc: asc, ts: Date.now(), nonce: generateNonce() }); params.sign = signParams(params);

看到这里,基本可以确定加密入口就是signParams。接下来只需要搞清楚两件事:

  • generateNonce生成的是什么;
  • signParams内部用了什么算法,摘要的输入格式是什么。

3. JavaScript逆向实战:还原加密逻辑

3.1 分析混淆代码时别慌,先做格式化

定位到signParams函数以后,我先把那段JS源码完整复制出来,放到本地一个临时文件里,用Prettier格式化了一遍。这一步强烈建议做,压缩后的代码经常是一整行,人眼看不出嵌套关系,格式化之后才看得清函数边界和变量作用域。

格式化完,这个函数的结构基本清楚了:

function signParams(params) { var keys = Object.keys(params).sort(); var str = ""; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var value = params[key]; if (value !== null && value !== undefined) { str += key + "=" + value + "&"; } } str = str.slice(0, -1); str += "&secret_key=X9sK2wR7"; return md5(str); }

这里有几个典型特征:

  • 先对参数名做字典序排序,这是签名算法的常见套路,目的是保证服务端拿到参数后按同样规则拼串能对齐;
  • 把参数拼成key=value&key=value的格式,结尾再拼一个固定密钥;
  • 最终做MD5摘要。

理论上到这一步,签名算法就明确了。但我不能光看这个函数就完事,因为页面上跑的JS是压缩混淆过的,真实场景可能还嵌套了rsaaesbase64等操作。这次比较走运,只用了MD5。

但我还是要提醒一句:看到MD5别激动,很多网站现在会用MD5做第一层包装,后面再套一层AES,或者用自定义的字符替换规则。你需要继续确认md5这个函数是不是真的就是标准MD5。最简单的验证方法是找个在线MD5工具,用同样输入算一遍,对比结果。如果能对上,说明没有摘歪。

3.2 补环境绕过检测

光知道算法还不够,因为你不能保证signParams内部没有依赖windowdocumentnavigator这些浏览器环境对象。比如有些加密函数内部会读取navigator.userAgentdocument.cookie作为盐值。这时候直接扔进Node.js里跑,肯定会报错。

我的处理办法是“补环境”。先跑一遍,看报错:

node sign_logic.js

如果报window is not defined,就在文件头部补一个最小化的window对象;如果报navigator is not defined,同样补一个。这次运气不错,signParams是纯函数,只依赖参数和固定的secret_key,所以不用补环境就能直接跑。

但为了演示通用流程,我建议你建立一套“最小补环境模板”,哪怕这次用不上,下次可能就用上了。模板大致长这样:

global.window = global.window || {}; global.navigator = global.navigator || { userAgent: "Mozilla/5.0 ... Chrome/120.0.0.0" }; global.document = global.document || { cookie: "", getElementById: function() { return null; }, createElement: function() { return { getContext: function(){ return null; } }; } };

补环境的核心原则是:能不补就不补,补了就不要慌。只补报错涉及到的属性,千万别想着把整个浏览器环境模拟出来,那是浪费时间。

3.3 用Python调用还原后的加密函数

签名算法确认没问题之后,我把签名逻辑用Python重写了一遍。有人说“直接用Node子进程调用不就行了”,但这会引入额外依赖,而且在高并发请求下进程开销不小。对于这种简单MD5签名,用Python重写是最高效的方案。

Python实现代码:

import time import uuid import hashlib def generate_nonce(): return uuid.uuid4().hex def generate_sign(params): sorted_keys = sorted(params.keys()) raw_string = "" for key in sorted_keys: if params[key] is not None and params[key] != "": raw_string += f"{key}={params[key]}&" raw_string = raw_string[:-1] raw_string += "&secret_key=X9sK2wR7" return hashlib.md5(raw_string.encode("utf-8")).hexdigest() def build_params(page=1, size=20, sort="profit", asc=0): params = { "page": page, "size": size, "sort": sort, "asc": asc, "ts": int(time.time() * 1000), "nonce": generate_nonce() } params["sign"] = generate_sign(params) return params if __name__ == "__main__": print(build_params())

这里我强调几个容易踩坑的细节:

  • ts参数我用了毫秒级时间戳,服务端判断过期时间通常也是按毫秒算的,如果用了秒级,很容易返回“签名过期”;
  • nonce直接用UUID的十六进制字符串,去掉连字符,格式正好是32位,和网站自带的策略一致;
  • 排序参数asc别写成布尔值。JS里true会被转成字符串"true",但Python里True会被转成"True",首字母大写,签名对不上。

当年我第一次跑的时候就栽在asc这个参数上。JS那边传的是数字01,我在Python里写成FalseTrue,结果签名一直验证失败,排查了好久才发现是大小写问题。

4. 常见问题与排查技巧

4.1 浏览器环境与Node环境差异

这个坑在多数字签名场景里都会出现。浏览器里的JS自带windowdocument等对象,而Node.js里没有。如果你把一段依赖navigator.userAgent的加密函数直接搬到Node里,它就会报错。

我遇到的情况是:加密函数本身是纯函数,不依赖任何浏览器对象,但外层有个模块初始化代码,一加载就试图访问window.localStorage。遇到这种,最简单的办法是把那行初始化代码注释掉,或者提前声明一个伪localStorage对象。

global.localStorage = { getItem: function() { return null; }, setItem: function() {} };

不要觉得这是作弊,逆向工程里“够用就行”是常态。只要能跑出和浏览器一致的结果,补丁越少越好。

4.2 动态加载与Hook技巧

有些网站会把加密逻辑拆成动态加载的组件,主入口里只有一个import(),然后异步拉取真正的加密模块。这种情况用XHR断点可能捕捉不到直接入口,因为请求发起者在异步回调里。

我的处理方式是Hook,这里推荐两个方向:

  • Hookwindow.fetchXMLHttpRequest.prototype.open,把所有请求公共信息打印出来,快速定位发起时间点;
  • HookString.prototype的关键方法,比如replaceslice,因为很多加密函数会调用这些方法处理参数字符串,通过堆栈就能抓到调用链。

但Hook也有风险,如果网站做了原型链检测,你贸然改原型可能会导致页面直接崩溃。我一般只在调试阶段Hook,确认逻辑后立刻移除。

还有一点要注意:现在的网站非常喜欢用webpackJsonp机制。你在全局搜索某个关键字找不到时,可以尝试搜索webpackChunk相关变量。加密逻辑往往被单独打进一个chunk文件里,只有触发某个行为时才加载。解决办法是手动触发对应行为,比如切页、排序、点击查询,然后再去Network里找chunk文件。

4.3 请求频率与合规提醒

这部分不属于纯技术,但我必须写。

我见过不少新手拿到签名算法之后,直接写个循环每秒请求几十次,结果IP被风控封禁。正确的姿势是:

  • 控制请求频率,加个time.sleep(random.uniform(1, 2))之类的限速;
  • 尽量复用Session和Cookie,不要频繁新建连接;
  • 对目标域名做robots检查,确认哪些路径允许爬取;
  • 下载下来的数据只用于个人学习研究,不要用于商业用途,更不要二次公开传播。

另外提一句,逆向JS本身是中性技术,但不同站点的服务条款差异很大。我在做这个项目前,会查看该站是否有反爬声明。如果明确禁止了数据爬取,我会缩小抓取范围,只做少量验证测试,不做全量数据采集。

4.4 调试过程中的三个救命技巧

最后分享几个小技巧,都是本项目中实际帮上大忙的:

技巧一:巧用Console来验证加密结果

在DevTools里直接执行signParams({page:1}),会得到当前页面下的签名值。然后你在Python里跑同一条逻辑,对比两个结果。不一致的话,优先检查排序规则和类型转换。

技巧二:把response的编码逻辑也还原出来

这次响应体不是单纯的JSON,外面套了一层自定义编码。服务端把数据做了某种变换,前端拿到后再还原。针对这类情况,别急着从零看编码算法。先去全局搜JSON.parse,看它之前有没有经过其他方法加工。

我在这个项目里就是用“搜JSON.parse”的办法找到了解码函数。

技巧三:善用Source面板的Overrides

有些JS文件经过格式化和修改后,可以通过Overrides保存到本地。刷新页面时浏览器会用本地文件替换线上文件。这个功能很适合在验证签名逻辑时用:你可以在JS里手动console.log打印中间变量,然后观察真实请求。

具体操作不复杂:Sources面板左侧能找到Overrides标签,选一个本地文件夹,点击允许授权,然后在文件上右键选择Save for overrides,之后重新加载页面即可。调试完记得删除本地临时文件,否则会影响后续正常浏览。

写在最后的一点心得

这个项目从定位到跑通第一个接口,我大概花了两个晚上。第一晚全耗在断点和全局搜索上,始终找不到加密入口;后来切换思路用了XHR断点,才从调用栈里一下子揪出signParams。第二晚就是格式化代码、还原逻辑、用Python重写,顺手把响应解码也一并处理了。

如果让我重新做一遍,我会先花五分钟看请求调用栈,而不是一上来就全局搜索。调用栈给你的是“动态视角”,全局搜索是“静态视角”,对于动态生成的参数,前者高效得多。

另外,像“私募排行”这种偏垂直领域的数据站,前端加密通常不会上特别重的方案,但只要思路通了,任何加密都只是时间问题。过程中真正值钱的不是那个签名算法本身,而是你练出来的调试直觉。

最后提醒一句:做逆向要守住底线,别把爬下来的数据当成自己开发的宝贝去售卖,也别写什么“绕过反爬”的教程去教唆别人批量爬取人家核心数据。学技术,是为了遇到问题能解决,不是为了制造问题。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 13:45:42

CloudBase-AI-ToolKit环境配置问题排查指南

CloudBase-AI-ToolKit环境配置问题排查指南 【免费下载链接】CloudBase-AI-Toolkit Backend for AI coding agents on CloudBase — database, auth, functions via Plugin, Skills & MCP. 项目地址: https://gitcode.com/gh_mirrors/cl/CloudBase-AI-Toolkit 在使用…

作者头像 李华
网站建设 2026/9/17 13:45:17

BERT多标签专利分类实践:IPC标签筛选与微调

简介&#xff1a;一份基于预训练模型的多标签专利分类研究文档&#xff0c;面向自然语言处理与专利文本挖掘方向的研究者&#xff0c;系统阐述如何利用BERT、RoBERTa和RBT3预训练模型解决大规模专利自动分类问题。文档将分类粒度细化到IPC“小类”级别&#xff0c;并通过高频标…

作者头像 李华
网站建设 2026/9/17 13:44:12

Ollama本地部署大模型,用Python打造隐私安全的翻译工具

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 13:43:45

GBase-Up 统一数据平台:跨源查询、下推与调优实战

简介&#xff1a;GBase UP统一数据平台技术白皮书由南大通用数据技术股份有限公司编写&#xff0c;面向企业数据平台架构师、数据库运维与选型人员&#xff0c;以及关注国产化大数据方案的技术决策者。该白皮书围绕融合GBase 8a MPP、GBase 8s与开源Hadoop生态的统一数据平台展…

作者头像 李华
网站建设 2026/9/17 13:43:36

智慧军营安防集成方案要点:感知布点、平台联动与网络加固

简介&#xff1a;面向军队信息化建设与安防集成领域&#xff0c;这份docx解决方案完整阐述了智慧军营安防系统的建设路径&#xff0c;适合部队营区管理者、系统集成工程师及军工信息化项目人员。方案重点解析高清、智能、多维化的技术主线&#xff0c;涵盖Smart 265编码技术、边…

作者头像 李华
网站建设 2026/9/17 13:43:07

Windows 11 上安装 FSL 的完整避坑指南:WSL2 环境配置与常见错误排查

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华