JS 逆向调试技巧
浏览器页面的执行流程决定了逆向的切入点: 核心目标是在「加密函数调用」这一环节断下,分析参数的生成逻辑。 在 Sources 面板中直接点击行号,最基础的断点方式。适合已知目标代码位置时使用。 右键行号 → Add conditional breakpoint,输入 JS 表达式,仅在条件为真时暂停。 在 Sources → XHR/fetch Breakpoints 中添加 URL 关键字(如 api、login),发起请求时自动在 send() 处断下。断下后查看 Call Stack 向上追溯加密逻辑。 操作步骤: 1. 打开 DevTools →
官方文档:https://developer.chrome.com/docs/devtools/javascript/
适用场景:使用 Chrome DevTools 在浏览器中定位和分析加密参数生成逻辑
整体分析流程
浏览器页面的执行流程决定了逆向的切入点:
加载 HTML → 加载 JS → 运行初始化 → 用户触发事件
→ 调用加密函数 → XHR 发送请求 → 接收响应 → 解密渲染
核心目标是在「加密函数调用」这一环节断下,分析参数的生成逻辑。
断点类型
普通断点
在 Sources 面板中直接点击行号,最基础的断点方式。适合已知目标代码位置时使用。
条件断点
右键行号 → Add conditional breakpoint,输入 JS 表达式,仅在条件为真时暂停。
// 示例:只在参数包含 "sign" 时断下
param.includes('sign')
XHR/Fetch 断点
在 Sources → XHR/fetch Breakpoints 中添加 URL 关键字(如 api、login),发起请求时自动在 send() 处断下。断下后查看 Call Stack 向上追溯加密逻辑。
操作步骤:
- 打开 DevTools → Sources → 右侧
XHR/fetch Breakpoints - 点击
+输入 URL 关键字(留空匹配所有请求) - 触发目标请求,断点自动命中
- 在 Call Stack 中从下往上逐帧分析
事件监听断点
Sources → Event Listener Breakpoints,展开对应事件类型(如 Mouse → click),触发该事件时自动断下。常用于定位按钮点击后的加密逻辑。
| 常用事件类型 | 场景 |
|---|---|
click |
登录/提交按钮 |
submit |
表单提交 |
keydown |
键盘输入触发 |
load |
页面加载完成后的初始化 |
DOM 断点
右键目标 DOM 元素 → Break on → 选择类型:
subtree modifications:子节点变化attribute modifications:属性变化node removal:节点被移除
适合追踪动态更新的验证码、token 等元素的生成来源。
Logpoint(日志断点)
右键行号 → Add logpoint,在不暂停执行的情况下打印表达式值。适合生产环境或不方便暂停的场景。
// 示例 logpoint 表达式
"sign 值:", sign, "timestamp:", Date.now()
调用堆栈分析
断点命中后,Call Stack 面板显示完整调用链。从下往上阅读,下方是最早的调用者,上方是最近的调用者。
分析技巧:
- 跳过框架代码(
webpack、chunk等文件),关注业务逻辑文件。 - 逐帧点击,观察右侧 Scope 面板中各层级的变量值。
- 找到参数从「明文」变为「加密值」的那一帧,即加密函数所在位置。
快速定位加密函数
全局搜索关键字
Ctrl + Shift + F(全局搜索),搜索策略:
| 搜索词 | 说明 |
|---|---|
sign |
签名参数名 |
encrypt / cipher |
通用加密函数名 |
md5 / sha / aes |
算法名称 |
CryptoJS |
常见加密库标志 |
\.encode\( / btoa |
编码函数 |
Initiator 堆栈
Network 面板中点击目标请求 → Initiator 标签页,直接显示请求发起的调用链,点击对应行跳转到源码。
搜索技巧
若直接搜索参数名找不到,尝试:
- 搜索参数名的部分字符串
- 搜索接口路径末段(如
/api/login→ 搜login) - 搜索相邻参数名(和加密参数在同一个对象里的其他字段)
反调试对抗
无限 debugger
原因:网站在代码中插入循环触发的 debugger,打开 DevTools 就无法正常操作。
对抗方法:
| 方法 | 操作 |
|---|---|
| 禁用断点 | DevTools 右上角 Deactivate breakpoints(蓝色圆圈图标) |
| 替换函数 | Console 执行 Function.prototype.constructor = function(){} |
| 本地覆盖 | 下载 JS 文件,删除 debugger 相关代码,启用 Local Override |
| Hook 绕过 | 使用反无限 debugger Hook 脚本(见 hook.md) |
开发者工具检测
常见检测手段:
- 监听
F12/Ctrl+Shift+I键盘事件 - 检测
window.outerHeight - window.innerHeight差值 - 通过
console.log对象检测 DevTools 是否打开 - 利用计时差:
debugger在 DevTools 开启时执行时间远大于正常值
对抗方法:使用单独的 Chrome 配置文件,启动时带 --disable-devtools-autoupdate,或直接使用 Local Override 修改检测逻辑。
本地覆盖(Local Override)
适合:需要持久化修改 JS 文件(删除反调试、添加 Hook)的场景。
操作步骤:
- DevTools → Sources →
Overrides标签 - 点击
+ Select folder for overrides,选择本地目录并授权 - Network 面板中右键目标 JS 文件 →
Save for overrides - 在 Sources 中找到覆盖文件,直接编辑保存
- 刷新页面,浏览器将加载本地版本而非网络版本
代码格式化
混淆或压缩过的代码可通过以下方式还原可读性:
- DevTools 内置:Sources 面板左下角
{}按钮(Pretty Print) - 在线工具:
js-beautify(支持本地 npm 安装) - 命令行:
npx prettier --write target.js
Scope 面板使用
断点暂停后,右侧 Scope 面板显示当前及上层作用域的所有变量。
Local:当前函数局部变量Closure:闭包捕获的变量Module:模块级变量Global:全局变量
重点关注 Closure 层,加密 key、盐值等敏感数据常存储在闭包中。
Watch 表达式
在 Watch 面板添加任意 JS 表达式,断点暂停时自动求值显示:
// 示例
window._key
JSON.stringify(requestData)
btoa(signStr)
最佳实践
XHR/Fetch 断点是首选入口:在 DevTools Sources → Event Listener Breakpoints → XHR → fetch / XMLHttpRequest 勾选,下次请求时自动暂停,从调用栈往上追溯签名生成函数,比盲目搜索快 10 倍。
条件断点过滤无关请求:在断点上右键 → "Edit breakpoint" 添加条件(如 url.includes('/api/sign')),只在目标 URL 触发,避免其他请求干扰。
Scope 面板追踪局部变量:暂停后展开 Scope 面板(Local / Closure / Global),直接查看当前函数的所有变量值,比在 Console 输入变量名更全面,能看到对象的完整结构。
copy() 函数复制复杂对象:copy(JSON.stringify(window._signData)) 将对象序列化后复制到剪贴板,方便粘贴到 Python 代码中测试,避免手动抄写长字符串。
Overrides 持久化修改:在 DevTools Sources → Overrides 本地覆盖 JS 文件,添加 console.log 或 debugger,即使刷新页面修改也会保留,适合需要多次调试的目标。
常见陷阱
陷阱:断点触发后堆栈太深找不到加密函数
现象: XHR 断点触发,Call Stack 有几十层,看不出哪里是签名逻辑。
原因: 现代框架(Vue/React)的请求拦截器、Promise 链会在实际发送前插入多层调用。
解决: 从最顶层(最近调用)往下查找包含 sign、hash、token 字样的函数;或在 Network 请求的 Initiator 列直接点击跳转到触发该请求的代码行。
陷阱:debugger 语句导致 DevTools 卡死循环
现象: 打开 DevTools 后页面不断触发 debugger,无法正常调试。
原因: 网站注入了反调试代码,检测到 DevTools 后循环触发 debugger 语句。
解决: 在 Sources 页右键 → "Never pause here" 跳过该 debugger;或在 DevTools 设置中禁用 JavaScript 断点,先找到反调试函数并 patch 后再重新开启。
陷阱:Hook 注入时机太晚,目标函数已执行
现象: 在 Console 注入 Hook 后触发请求,Hook 没有被调用。
原因: 页面初始化时就执行了加密函数(如计算设备指纹),Console 注入时已错过。
解决: 使用 Tampermonkey(@run-at document-start)或 DevTools Snippets 在页面加载最早期注入;或用 Overrides 直接修改 JS 源文件在函数定义处插入 Hook。
参见
适合追踪某个变量在不同断点间的变化。