JS逆向入门第一课:从看懂F12的“乱码”到实战定位加密逻辑(保姆级教程)
你有没有过这种经历:打开一个网站,按下 F12 点进 Sources 面板,找到那个看起来最关键的 JS 文件,然后你就傻眼了。满屏的 a、b、c、_0x1234,函数套函数,变量名像乱码。你心想:“写这个网站的程序员是不是不会起名?”
其实不是他不会起名,是你看到的东西,根本就不是他写的。
这篇文章先解决最劝退的几个问题:为什么我们逆向时看到的 JS 代码,和“正常代码”长得完全不一样? 以及:Chrome DevTools 里的 Call Stack、函数跳转、蓝色下划线这些到底怎么用? 中间会用北京大学登录页当案例走一遍完整流程,最后讲 XHR/Fetch 断点——也就是关键词搜索失效时的兜底方案,带你入门 JS 逆向。
假设你已经会按 F12 打开开发者工具。如果还不会,那你现在会了。
一、JS 要不要学?
你不需要会写 JS,但得能读 JS。逆向本质上是阅读理解,你看的是别人写好的代码,不需要自己从零写一个项目。就像你看得懂菜单但不会炒菜,不妨碍你点菜。
1. 变量
var a = 1、let b = "hello"、const c = 123。var / let / const 的区别在逆向里不重要,你只要知道它是在给一个名字赋值。
2. 函数
function f(x) { return x + 1 }。定义一个函数叫 f,接收参数 x,返回 x + 1。如果在别处看到 f(5),就是在调用它,结果是 6。
3. 对象
{ name: "张三", age: 18 }。花括号包起来的一堆 key: value。你看到的 d.e("phone").value 就是在访问对象 d 的属性 e,然后调用它,再取 .value。
4. 箭头函数
(x) => x + 1,等价于 function(x) { return x + 1 }。压缩后的代码里大量出现,见多了就习惯了。
5. 回调
$.ajax({ success: function(res) { console.log(res) } }),简单说就是把一个函数当参数传给另一个函数。登录请求、加密操作几乎都是回调的形式。
6. 内置函数
浏览器自带、不用引入就能用。setTimeout(fn, 1000) 延时执行,setInterval(fn, 1000) 重复执行,无限 debugger 就拿它搞你。JSON.stringify(obj) 对象转字符串,JSON.parse(str) 反向操作,加密前必调。document.getElementById("id").value 取输入框值,$("#id").val() 是 jQuery 的等价写法,北大案例里会出现。
遇到不认识的语法直接搜,或者丢给 AI 问“这段 JS 是什么意思”就行。别被吓跑,你只需要能读,不需要会写。
如果你连
var a = 1都看不懂,那你现在会了。
二、第一个真相:你看到的从来不是「源码」
一个正常的前端项目,开发时是这样写的:
// 开发者写的代码(源码)
function handleSubmitButtonClick(event) {
const userPhoneNumber = document.getElementById("phone").value;
const encryptedPassword = md5(document.getElementById("password").value);
sendLoginRequest(userPhoneNumber, encryptedPassword);
}
变量名叫 userPhoneNumber,函数名叫 handleSubmitButtonClick,见名知意,清清爽爽。
但你 F12 看到的是这样的:
function a(b){var c=d.e("phone").value,f=g(d.e("password").value);h(c,f)}
a、b、c、d、e、f、g、h,每个名字不超过两个字母。
这不是加密,不是混淆,甚至不算是刻意防你。 这是前端工程的标配操作,叫压缩(Minification)。
为什么网站要这样做?
你的浏览器在打开网站时,要先下载所有 JS 文件。一个中大型项目可能有几千行代码,变量名 handleSubmitButtonClick 有 26 个字符,改成 a 只有 1 个字符。几千个变量、函数名全部缩成单字母,文件体积能减少 40%–60%。
对于日活百万的网站,这就意味着页面秒开、带宽和 CDN 流量费省了一大截。所以这不是网站“在防你”,而是网站在给所有用户省流量,顺带让你逆向时看起来像天书 😂。
压缩还做了什么?
除了重命名,压缩工具(Webpack、Vite、esbuild、Terser 等)还会:
| 操作 | 效果 |
|---|---|
| 删除所有空格和换行 | 整个文件变成一行 |
| 删除注释 | 所有 // 和 /* */ 消失 |
| 缩短变量名 | userPhoneNumber → a |
| 缩短函数名 | handleSubmitButtonClick → b |
| 合并语句 | 能并成一行就并成一行 |
| Tree Shaking | 删掉所有没被用到的代码 |
所以你看到的 abcd 天书,是经过打包 + 压缩两道工序之后的最终产物。
至于真正的「代码混淆」(Obfuscation),比如把字符串编码、把控制流打乱、加入死代码,那是更进一步的操作,留到进阶篇讲。
三、Chrome DevTools 的介绍
在开始分析之前,先搞懂 DevTools 里哪几个东西是逆向的核心工具。

1. Sources 面板(核心战场)
按 F12 → 点 Sources 标签。左侧是文件树,中间是代码查看区,右侧是调试面板。你接下来 90% 的时间都在这个面板里。
2. 进来先用 Pretty Print 格式化
打开一个压缩成一行的 JS 文件后,点击代码区左下角的 {} 按钮(叫 "Pretty print")。

格式化前:
function a(b){var c=d.e("phone").value,f=g(d.e("password").value);h(c,f)}
格式化后:
function a(b) {
var c = d.e("phone").value,
f = g(d.e("password").value);
h(c, f)
}
这是你要做的第一个动作。不格式化,什么都别想看懂。
3. 右侧调试面板
重点关注这三个区域:
| 区域 | 英文名 | 干什么用的 |
|---|---|---|
| 调用堆栈 | Call Stack | 看「谁调用了谁」,逆向溯源的核心 |
| 作用域 | Scope | 看当前变量值,断点调试时用 |
| 断点 | Breakpoints | 管理你打的所有断点 |
四、Call Stack(调用堆栈),逆向的导航地图
什么是堆栈?
假设你的代码长这样:
function 做早饭() {
煎蛋();
热牛奶();
}
function 煎蛋() {
打鸡蛋();
倒油();
开火();
}
function 打鸡蛋() {
console.log("啪");
}
做早饭();
当程序执行到 console.log("啪") 这一行时,调用堆栈从下到上是:
打鸡蛋 ← 你现在在这里
煎蛋 ← 煎蛋调用了打鸡蛋
做早饭 ← 做早饭调用了煎蛋
(全局) ← 最开始运行的代码
这就是 Call Stack:记录「谁调用了谁」,从入口一路到当前位置的完整轨迹。
在 DevTools 里怎么用?

在 Sources 面板右侧的 Call Stack 区域,当代码在断点处停下时,它会显示当前调用链。
操作方式:
- 在 Sources 面板中,找到你怀疑的关键函数,点击行号打一个断点(行号上会出现蓝色箭头)
- 触发那个函数(刷新页面、点击按钮等)
- 代码停在断点处,右侧 Call Stack 显示完整调用链
- 点击堆栈中的任意一帧,代码区会自动跳转到那一帧对应的位置
这就是“顺着堆栈往上追”。你可以从一个底层函数出发,顺着堆栈一层层找到是谁调用了它,最终定位到业务逻辑。
业务逻辑一般在 Call Stack 的中间几帧,最上面是请求/监控层,最下面是事件触发层。 找到带业务文件名的帧,重点看那几层就够了。
先记住这个概念就行。Call Stack 什么时候真正派上用场,放到第七节讲,在那之前,有更简单的方法。
五、为什么 a() 点进去跳到了 b(){}?
这是初学者最困惑的现象之一。
在 DevTools 中,你把鼠标悬停在一个函数调用上(比如 a()),会弹出一个浮层,里面有一个蓝色带下划线的函数名(比如 b)。点击它,代码区会跳转到函数定义,然后你看到的是:
function b(c, d) {
// ...
}
你调用的是 a(),怎么跳到 b 了?
其实 a 只是个变量名,不是函数名
在压缩后的代码里,一个函数通常不是直接以「函数声明」的形式存在的。它往往是这样的:
// 原来的源码(你猜的)
function sendLoginRequest(phone, encryptedPwd) {
axios.post("/api/login", { phone: phone, password: encryptedPwd });
}
// 压缩后(实际看到的样子)
var a = function (b, c) {
d.post("/api/login", { phone: b, password: c });
};
这里 a 是一个变量,它的值是一个函数。DevTools 在显示函数引用时,显示的是这个函数本身的内部名称。你悬停 a,它告诉你 a 指向的那个函数实体叫 b,跳过去自然看到 function b(){}。
更常见的是类似这样的多级赋值:
var e = function () { /* 真正干活的代码 */ };
var d = e;
// ...
d(); // ← 你在这里悬停,显示的是 e,跳过去也看到 e
d 指向 e,所以悬停 d() 时显示的是 e。
那为什么有的函数悬停后什么都没有?
两种情况:
- 它是浏览器内置函数。 比如
setTimeout、JSON.stringify、document.getElementById。这些都是浏览器原生实现的,没有 JS 源码可以跳转。 - 它是动态生成的匿名函数。 比如
new Function("return 1")创建的,本身就没有名字。
六、以北京大学登录页为例走一遍完整实战
前面都是铺垫,现在用北京大学统一身份认证登录页(iaaa.pku.edu.cn)走一遍完整流程。
第一步:搜关键词
F12 → Sources → Ctrl+Shift+F 全局搜索 encrypt。
搜索结果第二条就看到了一行非常可疑的代码:
encryptedPWD = crypt.encrypt($("#password").val());
第二步:打断点
点进那个文件,在这一行上点击行号打一个蓝色断点。
第三步:触发
在登录页输入任意密码,点击登录。代码停在了断点处。
第四步:看结果
断点停住后,看右侧 Scope 面板,你会看到 $("#password").val() 的值就是你输入的密码明文。crypt.encrypt() 就是加密函数,密码明文传进去,加密结果赋值给 encryptedPWD。
这时候再看右侧 Call Stack:
crypt.encrypt ← 加密调用
oauthLogon ← 登录函数
onClick ← 按钮点击
只有两层有用帧。这是好事,说明代码结构简单,你不需要顺着堆栈一层层追,断点处的代码已经一目了然。
总结一下最常用的找加密套路
这就是 JS 逆向里找加密逻辑最常用的套路:
Ctrl+Shift+F搜索password、encrypt等关键词- 找到可疑函数,打上断点
- 在网页上触发操作(输入密码点登录)
- 断点触发,看 Scope 里的变量值
- 如果当前帧不够清楚,看 Call Stack 往上追
北大登录、B 站登录,以及你能遇到的大多数网站,用这个方法都够了。
七、搜不到关键词怎么办?试试 XHR/Fetch 断点
上一节的套路有一个前提:关键词还在源码里。 但如果网站压缩得太狠,或者额外加了混淆,encrypt、password 这些词在源码里根本不存在,你搜什么都是空的。
那这时候就得换一种思路:不看代码,看行为。 不管代码被压缩成什么样,登录请求最终总是要发出去的。在请求发出的那一刻把它断住,然后从 Call Stack 反向追溯。
这就是 Call Stack 真正派上用场的地方。
XHR 和 Fetch 是什么?
网页发网络请求,必须用这两个 API 之一:
| API | 特点 |
|---|---|
XMLHttpRequest(XHR) | 老一辈的请求方式,jQuery 时代的主流 |
fetch | 新一代的请求方式,Vue/React 等现代框架默认用这个 |
不知道网站用哪个?两个断点都打上就行。
怎么打 XHR/Fetch 断点?
我们首先通过 Network 面板,抓包找到登录接口的 URL,然后填入 XHR/Fetch 断点。这里以某东登录页为例。

- 打开 Sources 面板
- 在右侧调试面板中找到 XHR/fetch Breakpoints 区域(和 Call Stack、Scope、Breakpoints 并列)
- 点击
+号,添加断点规则 - 填入 URL 的关键片段。先用 Network 面板看一次登录请求的地址(比如
https://xxx.com/api/user/login),取其中独特的片段(比如login)填进去。这样只有包含login的请求会断住 - 勾选启用
怎么触发它?又该怎么用 Call Stack 顺藤摸瓜?
在登录页面输入账号密码、点击登录,代码停在 fetch 或 XMLHttpRequest.send 处,在这个时候,代码会停在浏览器底层发送网络请求的核心位置。
这时候右侧 Call Stack 会长得多,一般像这样:
监控/请求拦截层 ← XHR 断点通常停在这里(跳过)
jQuery send / ajax
加密/签名函数 ← 你要找的目标
login 登录函数 ← 重点关注这几帧
proceedWithLogin
验证码回调 / 事件分发 ← 触发源(跳过)
💡 业务逻辑一般在 Call Stack 的中间几帧。最上面几帧往往是请求发送层(jQuery、监控 SDK 之类的),最下面几帧是事件触发层(按钮点击、验证码回调之类的),两头都可以跳过。你要找的加密逻辑,通常就在中间那几个带业务文件名的帧里。
但怎么快速区分哪些帧该看、哪些该跳? 不用点进去看代码,光看右边的文件名就能判断:
| 文件名特征 | 大概率是什么 | 操作 |
|---|---|---|
带 sdk、min、版本号的(如 trace-chain-sdk.umd.min.js) | 第三方 SDK | 跳过 |
一看就是库名的(jquery-1.6.4.js、vue.js) | 框架 | 跳过 |
名字像乱码的(jcap_ujb96b.js) | 混淆后的 SDK | 跳过 |
名字是正常英文单词的(login2024.js、user.js、pay.js) | 业务代码 | ⭐ 重点看 |
找到带业务关键词的文件名,点进去看那几帧就够了。
逐帧点击,观察每一层的代码和 Scope 中的变量。你最终会找到这样一个函数:它的代码里出现了 MD5、SHA256、AES、sign 之类的关键词,或者生成了你看不懂的字符串。那就是加密逻辑。
下面我们以某东为例:

怎么知道找没找对?
把提取的加密函数在 Node.js 里跑一下,和抓包结果对比:
const crypto = require("crypto");
const result = crypto.createHash("md5").update("123456").digest("hex");
console.log(result); // e10adc3949ba59abbe56e057f20f883e
一致就说明你找对了 🎉。
实在不行?试试 Event Listener 断点
如果 XHR/Fetch 断点不适合你的场景,还有一个备选:

在 Sources 面板右侧找到 Event Listener Breakpoints → 展开 Mouse → 勾选 click。之后任何鼠标点击事件都会触发断点。从 Call Stack 往下追,一样能找到加密逻辑。
🎉 到这里,你已经算是正式入门 JS 逆向了。
能打开 DevTools、看懂压缩后的代码、找到关键的加密函数,这些就是逆向最核心的基本功。别小看这一步,很多人卡在“打开 F12 之后不知道该看哪”就直接放弃了,而你已经走到这里了。
📖 这一篇解决了「看懂代码」+「找到关键函数」。下一篇:
《JS逆向入门第二课:财联社逆向实战》
用第一课学到的东西,实战逆向财联社,从抓包到定位加密逻辑完整走一遍。
⚠️ 免责声明:本文介绍的技术(Chrome DevTools 调试、代码分析)是前端开发和安全研究的通用技能,广泛应用于性能分析、Bug 排查和合法安全测试。请勿将其用于非法入侵、未授权数据抓取或其他违反法律法规的行为。