news 2026/9/18 4:49:26

JS逆向入门:用Chrome DevTools看懂混淆代码

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
JS逆向入门:用Chrome DevTools看懂混淆代码

1. 别被“逆向”吓住:它其实只是“看懂别人写的JS代码”

很多人一看到“JS逆向”四个字,脑子里立刻浮现出黑客电影里飞速滚动的绿色代码、密不透风的混淆字符串、层层嵌套的eval和Function构造器——然后默默关掉网页,觉得这玩意儿离自己十万八千里。我刚接触时也这样,花了整整三天盯着一段_0x4a7b[0x12](_0x4a7b[0x34], _0x4a7b[0x56])发呆,连括号都数不清。后来才明白:JS逆向不是造火箭,而是读说明书;不是破解密码,而是还原作者写代码时的真实意图。它的核心动作就三个字:看、猜、验。你看的是浏览器里实际运行的JS逻辑,猜的是这段逻辑背后想实现什么业务目标(比如“登录前要算个sign”,“翻页前要生成个token”),最后用控制台、断点、修改变量等方式去验证你的猜测对不对。

你完全不需要会写React或Vue,也不用背熟V8引擎源码。只要你能看懂if (a > b) { console.log('ok') },能理解for (let i = 0; i < arr.length; i++)在循环什么,你就已经具备了90%的入门门槛。剩下的10%,是学会在Chrome开发者工具里不迷路、不手抖、不误点刷新键。我带过的二十多个零基础学员里,最快的一个——做电商运营的姑娘,只用两天时间就成功抓到了某平台商品列表页的加密参数生成逻辑,把原来手动复制粘贴的流程变成了自动脚本。她没学过算法,也没碰过Node.js,靠的就是把“看懂JS”这件事拆解成可操作的步骤,而不是当成一门玄学。

关键词里反复出现的“js逆向教程”“js反爬实战”“js逆向入门”,背后藏着一个真实需求:大量非程序员岗位的人,需要绕过前端限制获取数据,但又不想、也没时间成为全栈工程师。他们要的不是从零造轮子,而是“知道哪一行代码在干啥,改哪几个变量就能拿到想要的结果”。所以这篇内容不讲AST解析、不聊Babel插件、不碰WebAssembly,只聚焦一件事:如何用最朴素的方式,在浏览器里把一段被压缩、混淆、动态执行的JS代码,还原成你能理解、能调试、能复用的逻辑。适合每天和Excel、后台系统、爬虫工具打交道的运营、产品、数据分析岗,也适合刚学完HTML+CSS+JS基础语法、想试试水的真实初学者。你不需要“深入浅出Vue”,你需要的是“看清那个按钮点击后到底发生了什么”。

提示:本文所有操作均在Chrome浏览器中完成,无需安装任何插件(广告拦截类插件建议临时关闭,避免干扰网络请求面板)。所有案例基于真实公开网站简化重构,不涉及任何未授权数据抓取行为,仅用于技术原理学习与调试能力训练。

2. 从“点一下就跳转”开始:定位关键JS代码的三步定位法

逆向的第一道坎,从来不是代码多难,而是你根本不知道该看哪一段。很多新手卡在第一步:打开开发者工具,Network面板里几十个JS文件刷屏,Sources面板里全是chunk-vendors.123abc.js,点开全是var _0x123=['\x68\x65\x6c\x6c\x6f']这种十六进制字符串,瞬间失去方向感。其实,真正的突破口,往往藏在你最熟悉的操作里——那个让你产生“咦?怎么没反应?”或“咦?怎么跳走了?”的交互动作。比如点击“加载更多”按钮后页面没动、输入手机号后提示“格式错误”但你明明输对了、点击下载按钮却弹出“请先登录”——这些“异常反馈”,就是JS在后台悄悄执行逻辑的铁证。

我用一个极简但高频的场景来演示:某音乐平台的“播放”按钮。你点下去,页面没跳转,但Network里突然冒出一个/api/v1/song/play?songId=123456&sign=xxx请求。这个sign参数就是我们要找的“钥匙”。定位它的生成逻辑,不用大海捞针,只需三步:

2.1 第一步:锁定触发动作的DOM元素

打开开发者工具(F12),切换到Elements面板。把鼠标移到“播放”按钮上,右键 → “检查”。你会看到对应的HTML代码高亮,比如:

<button class="play-btn">function _0x3e4d(_0x1a2b, _0x5c6d) { var _0x7e8f = _0x1a2b + _0x5c6d; var _0x9a0b = _0x7e8f['split'](''); for (var _0xc1d2 = 0; _0xc1d2 < _0x9a0b['length']; _0xc1d2++) { _0x9a0b[_0xc1d2] = String['fromCharCode'](_0x9a0b[_0xc1d2]['charCodeAt'](0) + 3); } return _0x9a0b['join'](''); }

这哪是JS?简直是摩斯电码!但别慌,混淆的本质是“增加阅读成本”,不是“消灭逻辑”。它只是把addThreeAndJoin变成了_0x3e4d,把split('')变成了['split']('')。解码的关键,不是硬记每个_0x变量对应什么,而是建立一套“人话翻译”流程。我把它总结为三重策略,像剥洋葱一样层层展开:

3.1 第一层:语义还原——给变量和函数起“真名”

打开Sources面板,右键函数 → “Pretty print”(格式化)。代码变成可读的缩进结构。接着,把光标放在_0x3e4d上,右键 → “Rename”。输入一个描述性名字,比如caesarCipherEncrypt。同理,把_0x1a2b改成inputStr_0x5c6d改成salt_0x7e8f改成concatenated。Chrome会自动替换所有同名变量。这一步的价值在于:你不再是在读符号,而是在读意图。原来的“var _0x7e8f = _0x1a2b + _0x5c6d;”变成了“var concatenated = inputStr + salt;”,逻辑瞬间清晰。

3.2 第二层:行为映射——用生活类比理解API调用

继续看_0x9a0b['split']('')。你肯定知道split('')是把字符串切成字符数组,但为什么写成['split']('')?因为混淆器把方法名当成了字符串,避免被静态分析。这时,不要纠结语法,直接在Console里敲:

"hello"['split']('') // ["h", "e", "l", "l", "o"] "hello".split('') // ["h", "e", "l", "l", "o"]

结果一样。所以['split']('').split('')。同理,String['fromCharCode']String.fromCharCode。把这些“伪装”API列个对照表,贴在便签纸上:

混淆写法真实含义生活类比
['split']('').split('')把一串糖葫芦(字符串)掰成单颗山楂(字符数组)
['charCodeAt'](0).charCodeAt(0)查第一个字的Unicode编码(就像查字典第一页的页码)
['join']('').join('')把散落的珠子(字符数组)用线串起来(变回字符串)

3.3 第三层:逻辑抽象——用数学公式代替代码细节

现在看核心循环:

for (var i = 0; i < arr.length; i++) { arr[i] = String.fromCharCode(arr[i].charCodeAt(0) + 3); }

这不就是凯撒密码移位+3吗?a→d,b→e,z→c。你完全不用记住charCodeAt,只要知道:它在对每个字符的ASCII码加3。所以整个函数逻辑可以一句话概括:“把输入字符串和盐值拼接后,每个字符ASCII码+3,再拼成新字符串”。这个抽象层级,才是逆向者真正需要的——你不需要会写加密算法,但必须一眼看出它在做什么变换。我处理过一个电商比价项目,目标网站的price参数是base64(md5(price + timestamp).substr(0,8)),我第一次看到时也懵,但拆解后发现:md5是哈希(防篡改)、substr(0,8)是截取(缩短长度)、base64是编码(适配URL)。三步组合,目的很单纯:生成一个短小、唯一、不可逆的校验码。一旦抽象到位,复现就只是查MD5和Base64的JS库文档而已。

实操心得:遇到evalFunction('return '+str)时,别急着点进去。先在Console里把str变量打印出来(比如console.log(str)),再复制粘贴到新标签页的Console里执行。很多混淆器生成的str其实是明文JS,只是被eval包了一层壳。直接执行,比调试eval内部快得多。

4. 动态调试实战:用Chrome DevTools亲手“掰开”加密流程

定位和解码之后,下一步是验证你的理解是否正确。这才是逆向最有成就感的环节——你不是在猜,而是在操控。很多人止步于“看懂”,但真正的能力体现在“改得动”。我用一个真实案例演示:某新闻聚合站的“获取文章详情”接口,需要token参数,且该token 5分钟失效。我们目标是:在控制台里,输入任意articleId,直接生成有效token

4.1 构建最小可执行单元:从函数调用链中“剪”下核心逻辑

假设我们已定位到生成token的函数genToken(articleId),它内部调用了getTimestamp()calcHash()encodeBase64()三个辅助函数。传统做法是把整个JS文件复制过来,但里面可能有依赖的全局变量、定时器、甚至window对象方法。更高效的做法是:只提取“纯函数”部分。在Sources面板,选中genToken函数体(不包括function genToken(){...}声明),右键 → “Copy selected text”。新建一个Console标签页,粘贴进去,但要做三处改造:

  • getTimestamp()替换成Date.now()(去掉依赖)
  • calcHash(str)的实现直接写在函数内(避免调用外部)
  • encodeBase64(str)替换成原生btoa(str)(浏览器内置)

改造后,函数变成:

function genToken(articleId) { const ts = Date.now(); const raw = articleId + '_' + ts; // 这里插入MD5计算逻辑(可用crypto-js.min.js,或用在线MD5工具生成固定值测试) const hash = 'a1b2c3d4e5f67890'; // 临时用固定值 const base64 = btoa(hash.substr(0,8)); return base64; }

4.2 注入式调试:在运行时修改变量,观察输出变化

现在,你在Console里输入genToken('12345'),得到一个base64字符串。但这是错的,因为hash是假的。怎么办?回到Sources面板,在genToken函数里,把const hash = ...这一行改成:

const hash = md5(raw); // 假设页面已加载md5.js

然后在Console里执行genToken('12345')。如果报错md5 is not defined,说明页面没加载MD5库。这时,不要放弃,打开Network面板,搜索md5,找到md5.min.js,右键 → “Copy link address”,在Console里执行:

const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/blueimp-md5@2.18.0/js/md5.min.js'; document.head.appendChild(script);

等待几秒,再执行genToken('12345'),成功!你得到了真实的token。这个过程的关键在于:你没有复制整个网站代码,而是在它的运行环境中,借力打力,只补最关键的缺口。就像修水管,不需要拆整栋楼,只换掉漏水的那一节。

4.3 自动化封装:把调试成果变成可复用的工具函数

最后一步,把调试成功的逻辑,封装成一个独立工具。在Console里输入:

// 全局挂载,方便随时调用 window.genArticleToken = function(articleId) { const ts = Date.now(); const raw = articleId + '_' + ts; return btoa(md5(raw).substr(0,8)); }; // 测试 console.log(genArticleToken('12345')); // 输出token

现在,无论你刷新多少次页面,只要网站还加载了md5.js,这个函数就一直有效。你可以把它保存为Snippet(Sources → Snippets → New snippet),下次打开开发者工具,右键运行即可。这就是逆向的终极价值:把别人的黑盒,变成你自己的白盒工具。我有个做自媒体的朋友,用这套方法为12个不同平台写了各自的genToken函数,存成一个JSON配置文件,配合Python脚本,实现了“一键抓取全网热点文章标题”的自动化流程。他不是程序员,但他掌握了让JS为自己服务的能力。

警告:所有调试操作必须在目标网站的页面上下文中进行。跨域脚本注入(如fetch其他域名)会触发CORS错误,这是浏览器安全机制,不是你的代码问题。解决方案只有两个:要么用浏览器插件(如Tampermonkey)注入,要么把逻辑迁移到后端(Node.js)执行。前者适合快速验证,后者适合长期稳定使用。

5. 避坑指南:那些让小白崩溃的“幽灵问题”与真实解法

逆向路上,80%的挫败感不是来自代码本身,而是来自环境、时机和认知偏差。我整理了五个最常被问、也最影响信心的“幽灵问题”,它们不写在任何教程里,但几乎每个新手都会撞上:

5.1 问题一:“断点打了,但点按钮没停!”——事件监听器被移除

现象:你给按钮打了断点,点击后毫无反应。检查Elements面板,发现按钮的onclick属性消失了。
根因:很多网站用removeEventListener在操作后主动卸载监听器,防止重复触发。你的断点还在,但事件早被解绑了。
解法:回到Elements面板,右键按钮 → “Event Listeners”,勾选“Ancestors”(祖先元素)。你会发现,真正的点击事件其实在父级<div>上,通过事件冒泡触发。把断点打在父级的clickhandler里,一抓一个准。这是DOM事件机制的基础知识,但恰恰是混淆环境下最容易忽略的。

5.2 问题二:“代码看懂了,但生成的sign和网站不一样!”——时间戳精度陷阱

现象:你用Date.now()生成的时间戳,和网站请求里的ts参数差了几毫秒,导致sign校验失败。
根因:Date.now()返回毫秒级时间,但有些网站的后端只取秒级(Math.floor(Date.now()/1000)),或者要求时间戳必须是偶数、必须在某个范围内。
解法:在Network面板里,右键目标请求 → “Copy as fetch”,粘贴到Console里执行,观察ts参数的真实值。然后在你的生成函数里,把Date.now()替换成这个固定值,测试是否通过。确认后再逐步放开,加入时间范围校验逻辑。永远相信网络请求里的真实数据,而不是你的假设。

5.3 问题三:“eval里的代码复制出来执行报错!”——作用域污染

现象:你从eval("...")里复制出一段JS,粘贴到Console执行,报错ReferenceError: xxx is not defined
根因:eval执行时,代码处于函数内部作用域,能访问闭包变量;而Console是全局作用域,找不到那些变量。
解法:不要复制eval内容,而是把eval调用本身复制出来,比如eval(codeStr)。在Console里先定义codeStr = "...";,再执行eval(codeStr)。或者更简单:在Sources面板里,把光标停在eval调用行,按F9打个断点,执行后,codeStr变量会出现在Scope面板里,直接右键它 → “Store as global variable”,Chrome会生成temp1这样的全局变量,你就可以eval(temp1)了。

5.4 问题四:“混淆代码里有__webpack_require__,这是啥?”——打包器的“方言”

现象:你看到__webpack_require__(123)__webpack_exports__,以为是加密函数。
根因:这是Webpack打包后的模块加载机制,123是模块ID,不是加密参数。它只是告诉JS“去模块123里找你要的东西”。
解法:在Sources面板左侧,展开toplocalhost:8080(或你的域名),找到webpack://开头的文件夹。里面是原始未混淆的源码(如果有Source Map的话)。如果没有,就忽略__webpack_require__,直接搜索你关心的函数名(如genToken),它一定在某个./src/utils/crypto.js里。打包器是组织代码的工具,不是混淆器。分清这两者,能省下70%的无效搜索时间。

5.5 问题五:“网站更新后,我的脚本不能用了!”——前端逻辑的脆弱性

现象:上周还能跑的脚本,今天突然失效,Network里看不到预期的请求。
根因:前端代码每天都在迭代。一个class="play-btn"可能今天改成class="btn-play",一个>

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

oh-my-hermes:开源AI Agent框架的部署与实战指南

1. oh-my-hermes 是什么&#xff1a;给AI装上一双"信使之足"先解释一下这个项目的来头。oh-my-hermes 的命名很明显是在致敬 oh-my-zsh 这套装机率极高的终端框架&#xff0c;而 Hermes 则是希腊神话里的信使之神&#xff0c;负责在众神之间传递消息。把这两个词拼在…

作者头像 李华
网站建设 2026/9/18 4:47:14

智能分类垃圾桶传动系统设计:从机构选型到运动仿真全解析

简介&#xff1a;面向机械设计、机械电子及智能装备相关专业的智能分类垃圾桶传动设计与仿真文档&#xff0c;围绕城市生活垃圾智能分拣场景&#xff0c;完整覆盖了从前期网络调研、方案对比到机构设计的全部流程。资源为1个doc文档&#xff0c;压缩包大小3.36MB&#xff0c;内…

作者头像 李华
网站建设 2026/9/18 4:46:42

Oracle 19c升级实战:从版本盘点到高频问题全解析

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

作者头像 李华
网站建设 2026/9/18 4:45:36

JavaScript高频面试题解析:从类型转换到事件循环与手写实现

最近两周陆续帮几个朋友做了模拟面试&#xff0c;有个现象挺扎心的&#xff1a;简历上写着“熟练掌握 JavaScript”的候选人&#xff0c;基础题答起来反而最容易翻车。问事件循环&#xff0c;能背出宏任务微任务的定义&#xff0c;换一道带 async/await 的输出排序题就乱&#…

作者头像 李华
网站建设 2026/9/18 4:44:14

胶粘剂行业研究报告:口径、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/18 4:42:57

x86电脑为什么能编译ARM程序?交叉编译原理与实践全解析

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

作者头像 李华