news 2026/10/30 6:51:58

招聘站JS逆向卡壳?Playwright+AST组合拳,控制流平坦化也能轻松拆

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
招聘站JS逆向卡壳?Playwright+AST组合拳,控制流平坦化也能轻松拆

做行业人才供需分析项目的时候,碰过某头部招聘站的岗位详情接口,所有请求都带一个动态sign参数,有效期几分钟,改一下岗位ID直接返回403。最开始走了老路:抓包、搜关键词、扒混淆JS硬啃,结果对方做了控制流平坦化+字符串加密,满屏的单字母变量和switch-case状态机,看了一下午头都大,啃了三天才还原了一半逻辑,结果人家周末悄悄更了一版,之前的工作全白费。

后来调整了思路,不再死磕静态代码,改成Playwright运行时定位入口 + AST自动化还原混淆的组合方案。不用逐行捋状态机,也不用挨个猜加密字符串,先靠运行时抓准输入输出,再用AST批量拆解混淆结构,前后两个多小时就把完整签名算法扒了下来。后续站点小版本更新,跑一遍还原脚本就能自动适配,维护成本直接降了一个量级。

今天就把这套完整流程分享出来,从定位入口、还原混淆到交叉验证,全是实战踩出来的经验,适合绝大多数中等混淆程度的业务站点。

一、先讲痛点:为什么硬啃混淆代码效率极低?

现在中大型站点的JS混淆,基本都是标配三件套:变量名压缩、控制流平坦化、字符串加密。纯靠肉眼逐行还原,效率极低,还很容易出错。

1.1 三座大山:常规逆向的死穴

第一座山是控制流平坦化。原本顺序执行的十几行代码,被拆成几十个代码块,塞进一个大switch-case里,靠一个状态变量跳来跳去。逻辑本身不难,但绕来绕去非常费脑子,捋着捋着就断了,稍微复杂点的函数,捋一遍就要一两个小时。

第二座山是字符串加密。所有的接口路径、固定盐值、密钥字符串,全被加密成了乱码,运行时调用解密函数才还原。静态搜sign、encrypt、key这类关键词,根本搜不到,连加密函数在哪都找不到。

第三座山是高频更新。招聘、电商这类反爬强度高的站点,每隔一两周就更一次混淆版本,变量名、控制流结构全换。你花一周还原的代码,更新之后又要重来一遍,维护成本高到离谱。

1.2 为什么是Playwright + AST?

两者刚好互补,一个负责精准定位,一个负责批量还原:

  • Playwright:站在运行时视角,不用管代码多混淆,只要请求发出去,就能顺着调用栈摸到加密函数,还能拿到真实的输入输出做验证。静态分析找不到的入口,运行时一抓一个准。
  • AST(抽象语法树):站在代码结构视角,不用逐行读逻辑,按规则批量拆解控制流、替换加密字符串,自动化完成80%的还原工作。人只需要看最终的可读代码,抓核心逻辑就行。

简单说就是:Playwright帮你找到「在哪」,AST帮你快速看懂「是什么」,两者结合,比纯静态硬啃效率至少高一个数量级。

二、第一步:Playwright精准定位,5分钟找到加密入口

很多人逆向第一步就走错了:打开JS文件全局搜关键词,搜sign、encrypt,搜出几十个结果挨个看,纯碰运气。混淆程度高的站点,变量名全是单字母,字符串全加密,搜关键词根本没用。

最高效的方法永远是从请求入手,运行时追调用栈,混淆再狠也藏不住调用关系。

2.1 监听请求,精准断在签名生成处

不用手动找JS文件,直接用Playwright拦截目标接口,在请求发出的瞬间断住,顺着调用栈往上追,几步就能定位到签名函数。

核心代码非常简单:

// Playwright 监听请求,触发时断住awaitpage.route('**/api/job/detail*',async(route,request)=>{// 打印请求参数,确认sign字段console.log('请求参数:',request.postData());// 在这里打断点,或者注入调试脚本debugger;awaitroute.continue();});

触发一次请求,代码会自动断在请求发出的位置。然后看右侧Call Stack调用栈,跳过axios、request这类封装库的层,往上追2-3层,基本就能落到业务代码的签名生成函数里。

踩坑提醒:不要在请求库的拦截层打转。很多站点封装了统一的request方法,断住后第一层都是拦截器、公共参数拼接的代码,多往上追几层,到具体的业务逻辑层才是真正的签名生成。

2.2 Hook 函数,拿到真实输入输出

定位到函数还不够,要先搞清楚「传进去什么、返回什么」。混淆代码里参数名全是a、b、c,根本看不出哪个是参数、哪个是密钥。

不用硬读代码,直接在控制台重写这个函数,加一行打印,就能拿到真实的入参和返回值:

// 假设定位到的加密函数在 window.__sign 上constoriginalSign=window.__sign;window.__sign=function(...args){console.log('签名入参:',args);constresult=originalSign.apply(this,args);console.log('签名结果:',result);returnresult;}

再触发一次请求,控制台就会打印出完整的入参和签名结果。这时候你已经知道了「输入什么格式,输出什么结果」,相当于有了标准答案,后面还原算法的时候,可以随时对比验证,不会走偏。

2.3 提取混淆源码

定位到文件之后,把对应的JS源码保存下来。不用纠结文件里其他无关代码,只把加密函数所在的模块抠出来就行,通常也就几百到几千行,后续交给AST处理。

三、第二步:AST自动化还原,批量拆解混淆结构

拿到混淆代码不要硬读,用AST做结构化处理,把控制流铺平、把字符串解密,80%的机械工作可以自动完成。前端常用的工具是@babel/parser+@babel/traverse+@babel/generator,三件套就能搞定绝大多数混淆还原。

3.1 环境准备

装三个核心库:

npminstall@babel/parser @babel/traverse @babel/generator
  • parser:把混淆代码解析成AST语法树
  • traverse:遍历语法树,按规则修改节点
  • generator:把修改后的AST还原成可读代码

3.2 还原控制流平坦化:拆平switch状态机

控制流平坦化的核心就是一个大switch语句,靠一个状态变量控制执行顺序。我们要做的就是按状态顺序,把各个case里的代码块按执行顺序拼起来,变回正常的顺序代码。

简化版实现逻辑:

constparser=require('@babel/parser');consttraverse=require('@babel/traverse').default;constgenerate=require('@babel/generator').default;constt=require('@babel/types');functiondecontrolFlow(ast){traverse(ast,{SwitchStatement(path){// 只处理状态机模式的switch:判断变量是数字,case全是数字constdiscriminant=path.node.discriminant;if(!t.isIdentifier(discriminant))return;constcases=path.node.cases;// 提取所有case的代码块,按序号排序constcaseMap={};cases.forEach(c=>{if(t.isNumericLiteral(c.test)){caseMap[c.test.value]=c.consequent;}});// 按执行顺序拼接语句(简单场景直接按序号顺排,复杂场景可模拟执行)constsortedKeys=Object.keys(caseMap).map(Number).sort((a,b)=>a-b);constbody=[];sortedKeys.forEach(k=>{body.push(...caseMap[k]);});// 用顺序语句替换整个switchpath.replaceWithMultiple(body);}});}// 使用示例constast=parser.parse(混淆代码字符串);decontrolFlow(ast);const{code}=generate(ast,{compact:false});console.log(code);

说明:上面是最简版的实现,适合单顺序执行的平坦化。如果是带条件跳转的复杂状态机,可以加一步模拟执行,根据条件分支的走向拼接代码,思路是一样的,不用逐行读逻辑,程序自动跑一遍就知道顺序。

3.3 字符串解密:批量替换加密字符串

字符串加密的套路基本都是:定义一个解密函数,所有加密字符串调用这个函数还原。我们只要在AST里找到所有调用解密函数的地方,提前算出来明文,替换掉函数调用就行。

核心思路:

  1. 先从混淆代码里把解密函数抠出来,或者用Node.js直接运行拿到解密结果
  2. 遍历AST,找到所有调用解密函数的节点
  3. 计算出明文字符串,替换掉原来的函数调用节点

实现之后,原本乱码一样的字符串调用,会直接变成明文的"sign"、"appKey"这类可读字符串,瞬间就能看懂哪个参数是干嘛的。

3.4 还原效果

经过两步处理之后,原本几百行绕来绕去的混淆代码,会变成几十行顺序执行的可读代码:

  • 没有了switch-case状态机,逻辑从上到下一目了然
  • 没有了加密字符串,密钥、盐值、字段名全是明文
  • 变量名虽然还是单字母,但结合上下文已经很容易看懂

这时候再去梳理签名规则,比如参数怎么排序、用的什么哈希算法、盐值拼在前面还是后面,十几分钟就能捋清楚。

四、第三步:运行时交叉验证,确保100%准确

捋完逻辑不要直接写代码复现,先做交叉验证,避免漏掉细节。很多人还原完算法,算出来的签名永远不对,就是因为漏了某个隐藏的拼接步骤,或者排序规则错了。

4.1 固定参数对比法

用Playwright在页面里注入代码,传固定的参数,拿到真实签名结果;再用自己还原的算法,传同样的参数计算结果。两者完全一致,说明算法还原正确;不一致,就说明哪里漏了。

// 页面里调用原生签名函数,固定参数consttestParams={jobId:'123456',_t:'1715000000000'};constrealSign=window.__sign(testParams);console.log('真实签名:',realSign);

然后用自己的Python/JS实现算一遍,逐字符对比。如果不一样,就逐步骤打日志对比,看是参数排序错了,还是盐值没加对,或者加密算法选错了。

实战经验:90%的不一致,都出在三个细节上:参数排序是ASCII升序还是降序、盐值拼在开头还是结尾、加密算法是MD5还是HMAC。不要上来就怀疑大逻辑,先核对这三个细节,大概率就能找到问题。

4.2 多组参数验证

一组对了还不够,多测几组不同的参数,包括边界情况,确保所有场景都对齐。尤其是参数为空、含特殊字符、含中文的情况,很容易出现编码不一致的问题。

全部对齐之后,再把算法移植到你的采集代码里,基本就能一次跑通,不会出现「本地好好的,线上全403」的尴尬。

五、工程化:网站更新了怎么办?

这是纯手动逆向最大的痛点:站点一更,之前的工作全白费。用AST的好处就是,只要混淆的整体方案没变,就可以写通用的还原脚本,更新了重新跑一遍就行。

我的常规做法是:

  1. 把「定位加密函数 → 提取混淆代码 → AST还原 → 输出算法核心」的流程,尽量写成半自动化脚本
  2. 站点更新后,只需要重新定位一下函数位置,丢进还原脚本跑一遍,几分钟就能拿到新的可读代码
  3. 对比新旧版本的差异,调整自己的实现,通常改一两个细节就搞定

维护成本从原来的几天,降到了几十分钟,应对高频更新的站点非常实用。

六、逆向最容易踩的六个坑

坑1:死磕完整代码还原

很多人追求把所有混淆都还原成和源码一样,花大量时间处理边缘逻辑。其实完全没必要,我们的目标是复现签名算法,不是还原整个项目。抓住核心的加密逻辑就行,无关的代码块直接跳过,能省大量时间。

坑2:控制流只靠肉眼捋

对着switch-case硬捋分支,效率极低还容易错。要么写AST脚本自动拆,要么加日志运行时看执行顺序,都比纯肉眼靠谱。工具能搞定的事,就不要浪费人力。

坑3:字符串解密只找一层

有些站点做了多重加密,字符串解密之后还是密文,要再调一次解密函数。替换的时候要递归处理,直到最终是明文字符串,不然还原出来还是半乱码。

坑4:忽略编码细节

参数里有中文、特殊字符的时候,URL编码、UTF-8编码的处理方式不一样,很容易导致签名不一致。一定要和原生函数的编码方式严格对齐,不要想当然用默认编码。

坑5:AST还原后直接信以为真

AST还原不是100%准确的,尤其是带复杂条件跳转的控制流,可能会拼错顺序。还原完一定要用运行时结果验证,不能觉得脚本跑出来的就一定对。

坑6:硬刚最高强度的混淆

遇到虚拟机混淆、指令集虚拟化这类高强度混淆,AST也不好使,就不要死磕了。直接用Playwright调用页面原生函数生成签名,成本低得多,还不怕更新。技术是为业务服务的,能解决问题的方案就是好方案。

总结

JS逆向这件事,从来不是比谁更能啃硬骨头,是比谁的方法更高效。

纯静态硬啃是最笨的方法,混淆强度稍微高一点就会卡壳。把运行时定位和结构化还原结合起来,先用Playwright精准找到入口、拿到标准答案,再用AST批量处理机械的还原工作,人只做核心逻辑的判断,效率才能提上去。

当然,技术方法一定要用在合规的场景里。采集公开数据要遵守网站规则和法律法规,不要碰非公开信息和个人隐私,技术永远要走在合规的框架里。

合规提示:本文仅分享前端代码逆向分析的技术方法,用于安全研究与学习。数据采集请严格遵守《网络安全法》及目标网站的用户协议与robots规定,不得用于非法用途。

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

企业知识库的FAQ怎么写:从一个具体问题开始(原生端验收)

企业知识库里有很多资料,并不意味着员工能够快速找到可用答案。搜索结果可能很长,问题却很具体:服务适用于哪些情况,办理之前要准备什么,遇到例外应该联系谁。FAQ可以帮助团队把资料整理成便于查找和确认的问答。 一、…

作者头像 李华
网站建设 2026/10/30 6:50:40

2026家电经销商系统怎么选?真实数据、价盘返利、订单履约全解析

家电行业渠道以"层级多、客单高、服务重、价盘敏感"为特征,经销商既是销量主力,也是品牌商最想看清却又最难看清的环节。进入2026年,家电以旧换新、下沉市场与全渠道融合持续推进,品牌商对经销商的数字化管控从"可…

作者头像 李华
网站建设 2026/10/30 6:49:29

人工智能教程篇:WorkBuddy职场提效实操手册

下班前半小时,工作群弹出一条消息: “明早开会,今晚先把竞品分析和汇报PPT做出来。” 你把资料丢给AI,十几分钟就拿到了大纲。 可接下来,竞品数据要逐条核对,引用链接有的打不开,图表还得重做&…

作者头像 李华
网站建设 2026/10/30 6:49:13

小白程序员必看:LLM+Harness打造Agent推理行动闭环(ReAct深度解析)

本文深入解析ReAct范式,阐述Agent系统如何通过LLM进行理解决策与Harness连接环境工具,形成推理行动闭环。文章对比ReAct与CoT、Act-only的优势局限,并通过HotpotQA、FEVER、ALFWorld和WebShop任务验证ReAct价值。核心观点包括:推理…

作者头像 李华
网站建设 2026/10/30 6:49:12

如何用 FileZilla 在 Windows 11/10 上搭建 FTP 服务器

要在 Windows 11 或 10 上搭一个文件传输协议(FTP)服务器,不一定非得启用系统自带那个。用功能更扎实的 FileZilla Server 就行——它是在 Windows 11、10、8、7 乃至 Windows Server 上快速搭起 FTP 服务器最省事的方案之一。 搞 FTP 方案的核心目的,是提供一条便捷通道,…

作者头像 李华
网站建设 2026/10/30 6:49:09

【A11 PC信息】PC资产信息提取主程序解析

这是一个 Rust 编写的 Windows GUI 小程序,作用是:自动采集本机设备信息 → 弹窗让用户选择保存位置 → 导出为 Excel 盘点表。下面按结构逐部分拆解。一、整体功能流程 启动 → 隐藏控制台(GUI 子系统)│├─ 1. terminal::detec…

作者头像 李华