在前端逆向工程中,加解密逻辑往往隐藏在海量代码、动态加载脚本或混淆代码中,直接查找加密函数如同“大海捞针”。想要高效定位核心逻辑,必须先掌握堆栈原理(理解代码执行脉络),再结合合适的定位方式(精准锁定关键代码)。本文将先拆解堆栈的底层逻辑,再详细讲解全局搜索、事件断点、XHR/Fetch断点、启动器调试四种核心定位方式,帮助逆向工程师建立“先懂流程,再找代码”的逆向思维。
一、堆栈原理:理解JS代码的“执行脉络”
在定位加解密逻辑前,必须先搞懂“堆栈”是什么——它是浏览器记录函数调用关系的核心机制,如同一张“代码执行地图”,能帮我们还原从“触发操作”到“加密执行”再到“请求发送”的完整链路。
1. 堆栈的核心概念
- 调用堆栈(Call Stack):当JS代码执行时,每调用一个函数,浏览器会将该函数的执行上下文(如参数、局部变量、执行位置)压入“栈”中;函数执行完毕后,会从栈中弹出。最终,堆栈会记录当前所有未执行完毕的函数调用关系。
- 栈的特性:后进先出(LIFO):最后被调用的函数会压在栈顶,最先执行完毕并弹出;最先被调用的函数压在栈底,最后执行完毕。
-
堆栈与执行顺序的关系:
- 堆栈展示顺序:从上到下 = 调用顺序(后调用的函数在栈顶);
- 代码执行顺序:从下到上 = 执行顺序(栈底的函数最先执行,栈顶的函数当前正在执行)。
2. 堆栈在逆向中的核心作用
逆向加解密的本质是“追溯数据流向”——从“加密后的密文”反向找到“明文输入”和“加密函数”。而堆栈恰好能提供这条“追溯路径”:
- 当代码暂停在某一位置(如请求发送、加密执行)时,通过堆栈可向上(栈底方向)追溯“谁调用了当前函数”,逐步找到明文参数的来源和加密函数的入口;
- 避免盲目搜索代码,精准锁定与加解密相关的函数调用链,忽略无关代码干扰。
3. 堆栈示例:还原加密执行流程
假设某网站登录的加密执行链路如下:
// 1. 页面加载时绑定点击事件
window.onload = function() {
document.querySelector('.login-btn').addEventListener('click', handleLogin); // 步骤1:绑定事件
};
// 2. 点击按钮触发事件回调
function handleLogin() {
const username = document.getElementById('username').value;
const password = document.getElementById('password').value;
const encryptedPwd = encryptPassword(password); // 步骤2:调用加密函数
submitRequest(username, encryptedPwd); // 步骤3:调用请求提交函数
}
// 3. 密码加密核心函数
function encryptPassword(pwd) {
return CryptoJS.MD5(pwd + 'salt123').toString(); // 步骤4:执行加密
}
// 4. 提交请求
function submitRequest(username, pwd) {
const xhr = new XMLHttpRequest();
xhr.open('POST', '/user/login');
xhr.send(JSON.stringify({ username, password: pwd })); // 步骤5:发送请求
}
当我们在xhr.send()处触发断点时,调用堆栈(Call Stack)会显示如下(从上到下):
send @ xhr.js:50 // 栈顶:当前正在执行的函数(请求发送)
submitRequest @ login.js:30 // 调用send的函数(组装请求)
handleLogin @ login.js:15 // 调用submitRequest的函数(事件回调)
(anonymous) @ login.js:5 // 栈底:最先执行的函数(绑定事件)
此时通过堆栈追溯:
- 栈顶
send:确认请求已组装完成,password是密文; - 上一层
submitRequest:找到密文pwd的来源是encryptedPwd参数; - 再上一层
handleLogin:找到encryptedPwd是encryptPassword(password)的返回值,且password是表单输入的明文; - 最终定位到加密核心函数
encryptPassword。
二、四大定位方式:精准锁定加解密逻辑
掌握堆栈原理后,结合以下四种定位方式,可覆盖绝大多数前端加解密场景。每种方式各有适用场景,需根据“是否已知函数名、是否已知接口、触发方式”灵活选择。
1. 全局搜索:最快定位已知特征的加密函数
全局搜索是最直接的定位方式,适用于已知加密相关特征的场景(如加密函数名、参数名、关键字),核心是通过“特征匹配”快速锁定可疑代码。
核心原理
前端加解密逻辑往往包含明显的特征关键词,如:
- 函数/参数名:
encrypt(加密)、decrypt(解密)、sign(签名)、password(密码)、salt(盐值)、key(密钥); - 算法关键词:
MD5、SHA256、AES、RSA、CryptoJS(常用加密库); - 操作关键词:
base64、encode(编码)、decode(解码)。
通过浏览器开发者工具的全局搜索功能,匹配这些关键词,可快速找到疑似加密函数。
操作步骤
- 打开浏览器开发者工具(F12),切换到「Sources」面板;
- 按下「Ctrl+Shift+F」(全局搜索快捷键),输入关键词(如
encrypt、CryptoJS、password); - 搜索结果会显示所有匹配的文件和代码行,点击结果可直接跳转到对应代码;
- 在疑似加密函数的入口处设置普通断点,触发加密操作(如点击登录),观察变量和执行流程。
进阶技巧
- 模糊匹配:若不确定完整关键词,可使用通配符(如
encry*匹配encrypt、encryptPassword); - 多关键词组合:同时搜索
encrypt+password,缩小匹配范围; - 排除框架代码:搜索结果中若包含
jquery.js、vue.js等框架文件,可勾选「Filter」筛选业务代码文件(如login.js、api.js)。
优劣分析
- 优势:操作最快、效率最高,适合有明确特征的场景(如已知用CryptoJS加密、参数名是
sign); - 劣势:对无明显特征的加密逻辑无效(如函数名被混淆为
a、b,无关键词)。
实战场景
某网站登录时密码加密,已知前端使用CryptoJS库,通过全局搜索:
- 输入关键词
CryptoJS,找到login.js中包含CryptoJS.AES.encrypt的代码; - 定位到函数
function d(e) { return CryptoJS.AES.encrypt(e, 'key123').toString() }; - 在该函数第一行设置断点,点击登录,代码暂停,观察到
e是输入的明文密码,确认这是核心加密函数。
2. 事件断点:追溯用户操作触发的加密逻辑
事件断点适用于加密逻辑由用户操作触发的场景(如点击登录按钮、提交表单、输入文本),无需提前知道任何代码特征,通过“捕获用户事件”反向追溯加密函数。
核心原理
用户操作(点击、输入、提交)会触发DOM事件(click、submit、input),而加解密逻辑通常在事件的回调函数中执行(如点击登录按钮后,回调函数中获取表单数据并加密)。通过给DOM元素或全局事件设置断点,可在事件触发时暂停代码,再通过堆栈追溯到加密函数。
两种事件断点用法
(1)元素级事件断点(精准)
适用于已知触发加密的具体DOM元素(如登录按钮、提交按钮):
- 切换到「Elements」面板,通过「Ctrl+Shift+C」选中目标元素(如登录按钮
<button class="login-btn">); - 右键点击该元素,选择「Break on」→ 「Attribute modifications」(属性修改,如按钮禁用状态变化)或「Event handlers」(直接捕获事件回调);
- 触发用户操作(如点击按钮),代码会暂停在事件回调函数中;
- 通过调用堆栈向上追溯,找到加密函数。
(2)全局事件断点(广谱)
适用于未知具体触发元素,但知道事件类型(如click、submit):
- 切换到「Sources」面板,展开「Event Listener Breakpoints」;
- 勾选对应事件类型(如「Mouse → click」、「Form → submit」);
- 触发用户操作,代码会暂停在所有
click或submit事件的回调函数中; - 结合堆栈和代码逻辑,筛选出与加解密相关的回调函数。
优劣分析
- 优势:无需提前了解代码结构,覆盖范围广,适合加密函数隐藏深、无明显特征的场景;
- 劣势:可能触发大量无关事件(如页面其他元素的点击),需要耐心筛选堆栈。
实战场景
某网站登录按钮点击后无明显加密关键词,通过元素级事件断点:
- 选中登录按钮,右键「Break on」→「Event handlers」→「click」;
- 点击按钮,代码暂停在
handleLogin回调函数中; - 查看堆栈,发现
handleLogin调用了f(pwd),进入f函数后,看到return btoa(pwd + 'salt')(Base64编码加密),成功定位加密逻辑。
3. XHR/Fetch断点:从请求出发逆向加密源头
XHR/Fetch断点适用于已知加密参数所在接口的场景(如登录接口/user/login、数据接口/api/data),核心是“拦截请求发送过程”,通过堆栈逆向追溯参数加密的源头。
核心原理
所有前端请求(XHR或Fetch)发送前,必须先完成参数组装和加密(如将明文密码转为密文、生成sign签名)。给目标接口设置断点后,当JS执行到XMLHttpRequest.send()或fetch()时会自动暂停,此时堆栈中会保留从“加密函数”到“请求发送”的完整调用链,向上追溯即可找到加密逻辑。
操作步骤
- 切换到「Sources」面板,点击「XHR/Fetch Breakpoints」标签;
- 点击「+」号,输入目标接口的路径(支持精确匹配和模糊匹配):
- 精确匹配:
/user/login(仅拦截该接口); - 模糊匹配:
/api/(拦截所有包含/api/的接口);
- 精确匹配:
- 触发请求(如点击登录、刷新页面),当请求即将发送时,代码会暂停在
send()或fetch()处; - 查看「Call Stack」调用堆栈,从栈顶(
send函数)向上追溯,找到参数加密的关键函数。
关键技巧
- 观察请求数据:暂停后,通过「Scope」面板查看
send()的参数(如请求体data),确认哪些参数是加密后的密文(如password、sign); - 跳过框架代码:堆栈中若包含
axios.js、fetch.js等请求库代码,可通过「黑盒脚本」(Blackbox Script)隐藏,专注于业务代码; - 结合条件断点:若同一接口有多次请求(如分页加载),可给XHR断点添加条件(如
url.includes('login')),仅拦截目标请求。
优劣分析
- 优势:精准锁定与请求相关的加密逻辑,无无关干扰,是登录、数据提交接口逆向的首选方式;
- 劣势:仅支持XHR/Fetch请求,不支持
form表单提交、script标签动态加载等非异步请求。
实战场景
某网站数据接口/api/getData的请求参数包含sign(签名),通过XHR断点:
- 添加XHR断点,输入
/api/getData; - 刷新页面触发请求,代码暂停在
xhr.send()处; - 查看堆栈,向上追溯到
buildParams函数,发现该函数接收原始参数(page=1&size=10); - 继续向上追溯到
generateSign函数,看到sign的生成逻辑:md5(params + timestamp + 'secretKey'),成功还原签名算法。
4. 启动器:网络面板的“快捷堆栈工具”
启动器(Initiator)是浏览器「***work」面板中XHR/Fetch请求的内置功能——无需手动设置断点,可直接查看该请求的“调用堆栈”,快速关联到请求对应的JS代码,是效率最高的定位方式之一。
核心原理
每个前端请求都有“启动者”(即触发该请求的JS函数),启动器功能会记录请求的完整调用堆栈,并直接提供代码跳转入口。逆向时,通过启动器可跳过“设置断点-触发暂停”的步骤,直接从请求关联到加密函数所在的代码位置。
操作步骤
- 打开开发者工具(F12),切换到「***work」面板;
- 筛选请求类型:点击「XHR」或「Fetch」,只显示目标请求类型;
- 触发目标请求(如点击登录、刷新页面),找到需要逆向的接口(如
/user/login); - 点击该请求,在右侧详情面板中切换到「Initiator」(启动器)标签;
- 查看调用堆栈:堆栈从上到下是“调用顺序”,点击任意堆栈条目,可直接跳转到对应的JS代码行;
- 追溯加密逻辑:从堆栈的“请求发送函数”(如
send、fetch)向上追溯,找到参数加密的核心函数。
优劣分析
- 优势:无需手动断点,直接通过请求关联堆栈,省去设置断点、触发暂停的步骤,效率极高;
- 劣势:仅能查看“已发送请求”的堆栈,若加密逻辑在请求发送前被拦截(如条件不满足),则无法捕获;部分混淆代码可能导致堆栈显示不完整。
实战场景
某网站登录接口/user/login的password参数是加密值,通过启动器快速定位:
- 打开「***work」→「XHR」,点击登录按钮,找到
/user/login请求; - 点击该请求,切换到「Initiator」标签,看到堆栈如下(从上到下):
fetch @ fetch.js:30 sendLoginReq @ login.js:40 encryptAndSubmit @ login.js:25 handleLoginClick @ login.js:15 - 点击堆栈条目
encryptAndSubmit @ login.js:25,直接跳转到该函数代码:function encryptAndSubmit() { const pwd = document.getElementById('pwd').value; const encryptedPwd = cryptoFunc(pwd); // 加密函数 sendLoginReq(encryptedPwd); } - 再点击
cryptoFunc函数,即可查看完整加密逻辑,全程无需设置任何断点。
三、四大定位方式选择指南
| 已知条件 | 推荐定位方式 | 核心优势 |
|---|---|---|
| 已知加密关键词(如encrypt、CryptoJS) | 全局搜索 | 速度最快,直接匹配特征 |
| 已知触发加密的用户操作(如点击登录) | 事件断点 | 无需代码特征,追溯事件回调 |
| 已知加密接口(如/user/login) | 启动器(优先)+ XHR断点 | 启动器快捷高效,XHR断点精准拦截 |
| 加密逻辑在页面加载时执行 | XHR断点 + 启动器 | 捕获初始化请求的堆栈 |
| 加密函数无特征、被混淆 | 启动器(优先)+ 事件断点 | 双重验证,快速缩小范围 |
四、总结:逆向加解密的核心逻辑闭环
JS逆向加解密的本质是“追溯数据流向”,而堆栈原理是“追溯的工具”,四大定位方式是“追溯的入口”。完整的逆向流程可总结为:
- 场景分析:明确加密触发时机(用户操作/页面加载)、相关接口/元素、是否有已知特征;
- 选择入口:根据场景匹配定位方式(全局搜索/事件断点/XHR断点/启动器调试);
- 触发断点:执行对应操作(点击/刷新/输入),让代码暂停在关键位置;
- 堆栈追溯:从暂停位置向上追溯,还原“明文输入→加密执行→密文输出→请求发送”的完整链路;
- 还原逻辑:通过单步执行、变量观察,明确加密算法(如AES/MD5)、密钥/盐值、参数组合规则,最终复现加密过程。