以下根据您提供的框架,逐节补充完整内容。标题层级严格遵循:一/二/三为二级标题(##),1/2/3为三级标题(###),a/b/c为四级标题(####)。关键信息已加粗,冒号前的文字(如“关键说明:”)已加粗。
一、基础知识与环境搭建
关键说明: 本节是所有逆向工作的起点,环境配置错误会导致后续无法调试。
1、HTTP/HTTPS 协议基础
请求/响应结构:
请求行:方法(GET/POST)、URL、协议版本
请求头:Host、User-Agent、Cookie、Referer、Content-Type
请求体:FormData、JSON、明文或加密数据
响应状态码:200(成功)、301/302(重定向)、401(未授权)、403(禁止)、500(服务器错误)
常见请求方式: GET(参数在URL)、POST(参数在Body)、PUT、DELETE、OPTIONS
Cookie与Session机制: HTTP无状态,Cookie维持会话。Cookie可能由服务器下发(Set-Cookie)或前端JS生成(document.cookie赋值),逆向时需区分来源。
2、浏览器开发者工具使用
元素面板: 查看DOM结构,定位加密入口(如按钮的
onclick绑定、表单提交事件)控制台: 执行JS代码、Hook关键函数(见第六节)、输出调试日志
网络面板: 捕获XHR/Fetch请求,查看请求参数、响应数据。右键请求 → Copy → Copy as cURL 可导出为命令行,方便重放。
源代码面板: 设置断点、格式化混淆代码(点击
{}按钮)、查看调用栈、Scope变量、Watch表达式。
3、抓包工具配置与使用
Fiddler: 配置HTTPS解密(Tools → Options → HTTPS → Decrypt HTTPS traffic),使用
bpu命令断点修改请求/响应。Charles: Map Local功能可将远程JS替换为本地修改版;Rewrite功能可批量修改请求头或响应体;Throttle Settings模拟弱网环境。
Wireshark: 极少情况用于分析非HTTP协议(如WebSocket帧、TCP流重组),需要过滤表达式
tcp.port == 443。
4、Node.js 与 Python 环境
Node.js: 安装LTS版本,使用
npm init创建项目。本地执行加密片段时,需补齐浏览器环境(见第八节)。vm模块可创建沙盒运行不信任代码。Python: 安装
execjs(依赖Node)或subprocess调用Node脚本。推荐将加密逻辑封装成Node模块,由Python通过subprocess调用,稳定且易调试。虚拟环境: Python用
venv或conda;Node用nvm管理版本。
5、常用逆向辅助工具
油猴脚本(Tampermonkey): 编写注入脚本,在页面加载前执行Hook代码,如:
javascript
// ==UserScript== // @name Hook JSON.stringify // @namespace http://tampermonkey.net/ // @version 0.1 // @description try to take over the world! // @author You // @match *://target.com/* // @grant none // ==/UserScript== (function() { const original = JSON.stringify; JSON.stringify = function(obj) { console.log("JSON.stringify called:", obj); return original(obj); }; })();Requestly: 修改请求/响应、重定向JS文件至本地服务器(如
http://127.0.0.1:8080/hooked.js)。ReRes(Chrome插件): 将远程JS资源映射到本地文件,无需自建服务器。
二、JavaScript 核心语法速查
关键说明: 不理解JS闭包、原型链、异步,无法读懂混淆后的代码。
1、作用域与闭包
词法作用域:
var函数作用域,let/const块级作用域。混淆代码常大量使用var和立即执行函数(IIFE)制造独立作用域。闭包形成与作用: 函数内部返回函数,内部函数持有外部函数变量。闭包常用于封装私有变量和模块模式,逆向时可查找
return { ... }暴露的方法。经典反调试利用: 通过闭包隐藏加密函数,只暴露加密结果。定位加密函数需在调用栈中找到闭包内的具体实现。
2、原型链与 this 指向
原型继承:
__proto__(对象内部指针)与prototype(构造函数属性)。Hook原型方法可以监控所有实例的调用,如:javascript
const originalPush = Array.prototype.push; Array.prototype.push = function(...args) { console.log("Array push:", args); return originalPush.apply(this, args); };this绑定规则: 默认(全局/undefined)、隐式(对象调用)、显式(call/apply/bind)、new绑定、箭头函数(继承外层this)。混淆代码中常见的
var that = this是为了保存外层this。Hook原型方法: 常用于拦截数组操作、字符串处理等基础行为。
3、异步编程
回调地狱: 多层嵌套的
function,老旧混淆可能将加密逻辑拆散在多个回调中,需按顺序跟踪。Promise:
then/catch链,async/await是语法糖。破解时若遇到Promise,需关注resolve传入的值。生成器与async/await:
function*和yield,反混淆时可将await还原为Promise.then形式以便理解。
4、常见内置对象与函数
Object:
Object.defineProperty用于定义属性访问器,常用于实现数据双向绑定或隐藏敏感属性。逆向时可改写getter/setter输出日志。Function:
new Function('a','b','return a+b')动态创建函数,混淆代码常用来延迟执行或规避静态分析。HookFunction构造函数可捕获动态生成的内容。正则表达式: 提取加密参数时经常用
match、replace。例如提取URL中的sign=([a-f0-9]{32})。
三、调试与定位技巧
关键说明: 找到加密入口是逆向的核心,本节技巧能节省80%的时间。
1、断点调试
XHR断点: Sources面板 → XHR/fetch Breakpoints → 添加包含URL关键字的断点。当请求URL匹配时自动断下,可快速定位发请求的代码。
DOM断点: 元素面板右键 → Break on → attribute modification / subtree modifications。当加密参数被写入DOM时触发。
事件监听器断点: Sources右侧 → Event Listener Breakpoints → 选择
click、submit等。点击登录按钮时断下,查看调用栈。
2、调用栈分析与日志输出
调用栈(Call Stack): 断点后右侧面板显示调用链,从下往上追溯:最下面是触发事件,最上面是当前执行函数。加密函数通常在中间某层。
console.log: 在Sources中修改代码,插入
console.log('value:', variable),然后右键保存修改。注意修改的是临时内存代码,刷新后失效,可用Overrides持久化。console.trace: 在控制台或注入代码中执行,打印当前调用堆栈,适用于追踪函数调用来源。
3、全局搜索与代码注入
搜索关键词: 使用Sources面板下的Search all files(
Ctrl+Shift+F),搜索encrypt、sign、token、password、key、iv、mode、padding等。如果被混淆,可搜索=或return等通用符号。代码注入: 在控制台直接重写函数,例如:
javascript
window.originalEncrypt = window.encrypt; window.encrypt = function(data) { console.log("encrypt input:", data); let result = window.originalEncrypt(data); console.log("encrypt output:", result); return result; };保存JS文件本地替换: Sources → 打开JS文件 → 右键 → Save for overrides,然后修改代码保存,刷新后生效。
4、条件断点与日志断点
条件断点: 右键行号 → Add conditional breakpoint,输入表达式如
data.length > 16,只有满足条件时才暂停。日志断点: 右键行号 → Add logpoint,输入
"参数值为:" + param,不会暂停执行,只打印日志,适合高频调用的函数。
5、内存漫游
查找全局变量: 控制台输入
Object.keys(window),观察是否有可疑的全局对象(如_0x1234、encryptModule)。定位隐藏函数: 如果加密函数挂载在某个对象下,可递归打印属性:
javascript
function printProps(obj, prefix) { for(let k in obj) { console.log(prefix + k, typeof obj[k]); if(typeof obj[k] === 'object') printProps(obj[k], prefix + ' '); } } printProps(window, '');调用匿名函数: 如果遇到
(function(){...})(),可通过arguments.callee在函数内部获取自身引用,但更简单的方法是在代码中搜索(function并打断点。
四、网络请求逆向
关键说明: 最终目标是模拟加密请求,因此需要完整还原参数构造过程。
1、请求参数加密
URL参数加密: 如
?data=xxx&sign=yyy,sign通常由其他参数拼接后计算得出。定位方法:在Network面板找请求,复制URL,再在Sources中搜索sign。POST FormData / JSON: body内的嵌套加密字段。注意:有时整个body是一个加密字符串,需要先解密才能看到内部结构。
加密位置: 可能在
XMLHttpRequest.send或fetch调用之前。断点打在send方法或fetch函数上,查看调用栈。
2、响应数据解密
识别加密响应: 响应内容为无意义乱码、Base64长串、或十六进制字符串。常见于图片验证码、敏感数据接口。
解密位置: 通常在
response拦截器中,或then回调里,或者重写了XMLHttpRequest.prototype.onreadystatechange。HookJSON.parse可以捕获解密后的对象。
3、Cookie/Session 生成逻辑
Cookie来源:
服务器通过
Set-Cookie下发(无需逆向,直接携带即可)。前端JS生成:例如通过
document.cookie = 'token=' + encrypt(data)。逆向方法:Hookdocument.cookie的setter。
Hook示例:
javascript
Object.defineProperty(document, 'cookie', { set: function(val) { console.log('Setting cookie:', val); debugger; // 断点 return val; }, get: function() { return originalCookie; } });
4、Token(JWT、自定义)逆向
JWT三段式结构: 头部、载荷、签名。可直接复制JWT字符串使用,无需破解签名,除非需要伪造内容。注意JWT可能带有过期时间。
自定义Token: 通常由时间戳、用户ID、随机数等拼接,再经哈希或对称加密生成。逆向目标:找到生成函数。
5、请求签名(Sign)与时间戳
常见签名方式:
将所有请求参数(包括timestamp)按key排序
拼接成
key1=value1&key2=value2...格式末尾加上固定密钥(secret)
进行MD5或SHA256得到签名
时间戳校验: 服务器会检查
timestamp与当前时间差,超过范围则拒绝。需要同步服务器时间(可从响应头Date字段获取)。加粗关键点: Sign的构造顺序(参数排序、拼接方式、密钥位置)决定了是否能重放成功。稍有偏差就会验签失败。
五、常见加密算法识别与还原
关键说明: 能一眼看出MD5、AES、RSA是逆向的基本功。
1、哈希(MD5、SHA 系列)
特征: 固定长度32/40/64位十六进制,不可解密。常见调用方式:
CryptoJS.MD5(str).toString()、crypto.createHash('md5').update(str).digest('hex')。识别技巧: 搜索
md5、sha1、sha256、createHash、digest。如混淆,搜索输出长度32或64的十六进制字符串。
2、对称加密(AES、DES、3DES)
识别特征: 出现
iv、mode、padding、CryptoJS.AES.encrypt、crypto.createCipheriv。AES的密钥长度常见128/256位。关键参数:
密钥(Key):可能硬编码、从服务器获取、或通过固定算法生成。
初始向量(IV):CBC模式需要IV,ECB模式不需要。
模式与填充:CBC、ECB、CFB等,填充方式PKCS7、Zero等。
加粗提醒: IV通常不能固定,需要从请求或响应中动态提取。如果IV固定,可硬编码。
3、非对称加密(RSA、ECC)
特征: 公钥加密私钥解密,JS中常见公钥硬编码。搜索
-----BEGIN PUBLIC KEY-----、-----BEGIN RSA PUBLIC KEY-----或hex格式的公钥。提取公钥: 可能在JS变量中,或从接口动态获取。RSA加密后的数据长度等于密钥长度(如1024位密钥加密后128字节)。
还原方法: Node.js使用
crypto.publicEncrypt(publicKey, buffer)模拟。如果使用JSEncrypt库,可提取其getKey方法。
4、Base64 与变种
标准Base64: 字符集
A-Za-z0-9+/,末尾可能有=填充。识别特征:长度是4的倍数,仅包含上述字符。变种:
自定义字母表:例如
-_代替+/(URL-safe)。去除填充:末尾无
=。多次嵌套:先Base64再URL编码等。
还原方法: Node.js
Buffer.from(str, 'base64'),自定义表需手动替换。
5、自定义编码与混淆算法
举例: 循环移位、查表替换、位运算组合(异或、与、或)。
还原思路:
动态调试,输入
'abc'得到输出'def',推测映射规律。如果算法简单,可手工重写为JS函数。
如果涉及复杂循环,可直接将加密函数复制出来,补全依赖。
六、Hook 与拦截技术
关键说明: Hook是篡改程序行为的利器,也是动态分析的基础。
1、关键函数 Hook
eval: 拦截动态执行代码,常见于反混淆(将字符串当成代码执行)。
javascript
const origEval = eval; window.eval = function(code) { console.log('eval called:', code); // debugger; return origEval(code); };Function: 拦截
new Function()创建的函数,方法同上。setTimeout/setInterval: 阻止定时器反调试,可替换为空函数或立即执行。
2、原型链 Hook
toString: 某些检测会调用
Function.prototype.toString判断函数是否为原生。可Hook使其返回function () { [native code] }。javascript
const origToString = Function.prototype.toString; Function.prototype.toString = function() { if(this === fakeFunction) return 'function () { [native code] }'; return origToString.apply(this); };JSON.stringify/parse: 监控数据序列化过程,可打印参数。
3、属性描述符 Hook
使用
Object.defineProperty重写getter/setter,例如监控localStorage:javascript
const ls = localStorage; Object.defineProperty(window, 'localStorage', { get: function() { console.trace('localStorage accessed'); return ls; } });案例: Hook
document.cookie(见四-3),Hooknavigator.webdriver(改为false)。
4、浏览器环境 Hook
navigator: 修改
userAgent、platform、languages,绕过检测。window: 伪造
window.chrome(非headless浏览器的特征)。document: 改写
document.getElementById,监控特定元素的获取。
5、使用外部工具 Hook
Frida: 注入JS到Chromium内核,可Hook底层C++函数,适合对抗重度反调试。
Oil(浏览器插件): 持久化Hook脚本,类似油猴但更底层。
七、AST(抽象语法树)与代码混淆
关键说明: 对付高强度混淆(如obfuscator.io)必须掌握AST还原。
1、AST 基础
Babel全家桶:
@babel/parser:将JS代码解析成AST。@babel/traverse:遍历AST节点,进行增删改。@babel/generator:将AST生成回代码。
节点类型:
Identifier(变量名)、Literal(字面量)、FunctionDeclaration、CallExpression等。
2、常见混淆手法
字符串加密: 原始字符串变为
"\x68\x65\x6c\x6c\x6f"或 调用解密函数_0x1234('0x1'),解密函数从数组取值。控制流平坦化: 将顺序代码转为
while-switch结构,有一个状态变量控制跳转。还原时需记录状态变化,合并基本块。死代码注入: 插入永远不会执行的垃圾语句,如
if(false){...},可直接删除。逗号表达式混淆:
a=1,b=2,c=3代替多行语句,可展开为多个表达式语句。
3、反混淆实战
还原字符串:
若字符串通过函数调用返回,可在运行时执行该函数获得真实字符串,然后AST替换。
示例脚本:使用
vm模块在Node中执行解密函数,得到映射表,然后替换所有CallExpression。
简化控制流:
识别
while循环和switch结构。记录状态变量(如
_state)的取值变化。按顺序提取有效语句,重组为基本块。
删除死代码: 常量传播,如
if(false)分支删除。
4、自动化反混淆脚本编写
模板:
javascript
const parser = require('@babel/parser'); const traverse = require('@babel/traverse').default; const generate = require('@babel/generator').default; const code = `...混淆代码...`; const ast = parser.parse(code); traverse(ast, { CallExpression(path) { // 处理字符串解密调用 } }); const output = generate(ast).code; console.log(output);常用插件:
@babel/plugin-transform-xxx可辅助简化。
八、补环境与模拟执行
关键说明: 当无法直接本地运行加密JS时,需要补全浏览器环境。
1、浏览器环境模拟
缺失变量:
window、document、navigator、location、localStorage、screen、history。补齐策略:
从真实浏览器导出API快照:在控制台执行
JSON.stringify(window, null, 2)(注意循环引用问题)。使用
globalThis赋值:globalThis.window = globalThis;,但很多API无法直接模拟。推荐使用Puppeteer或JSDOM。
2、使用 PyExecJS、Node.js vm 模块
PyExecJS: 简单调用,但性能差,不支持大型库(如CryptoJS可能报错)。适合轻量加密。
Node.js vm:
javascript
const vm = require('vm'); const context = { window: {}, document: {}, console }; vm.createContext(context); const script = new vm.Script(encryptedJS); script.runInContext(context); console.log(context.window.encrypt('test'));
3、使用 JsDom、Puppeteer 实现半模拟
JsDom: 模拟DOM API,但性能较低,部分现代特性不支持(如WebGL)。
javascript
const jsdom = require('jsdom'); const { window } = new jsdom.JSDOM('<!DOCTYPE html>'); global.window = window; global.document = window.document; // 再执行加密JSPuppeteer: 启动真实浏览器执行,速度慢但最准确。适合调试时使用,生产环境不推荐。
4、完美补环境框架
Proxy陷阱: 使用
Proxy拦截任意属性读写,返回伪造值或记录日志。javascript
const fakeWindow = new Proxy({}, { get: (obj, prop) => { console.log('Getting window.' + prop); return undefined; // 或返回特定伪造值 } });getter/setter模拟: 精确还原浏览器API行为,如
localStorage需实现存储。指纹绕过: 补充canvas、webgl、时区、音频上下文等。可复制真实浏览器的指纹值。
5、指纹对抗
Canvas指纹: 固定
toDataURL返回值。可通过HookHTMLCanvasElement.prototype.toDataURL返回预设base64。WebGL指纹: 固定
getParameter返回值。HookWebGLRenderingContext.prototype.getParameter。时区与语言: 设置
Intl.DateTimeFormat().resolvedOptions().timeZone为目标时区,navigator.language为目标语言。
九、RPC 与进程通信
关键说明: 如果加密过于复杂,可以通过RPC直接调用浏览器内已解密的函数。
1、WebSocket 即时通信
原理: 在页面注入WebSocket客户端,本地起一个WebSocket服务端。客户端接收参数,调用页面内加密函数,将结果返回服务端。
实现步骤:
油猴脚本注入WebSocket客户端。
本地Node.js起WebSocket Server。
发送
{id:1, data:"hello"},客户端调用window.encrypt(data)后返回{id:1, result:"..."}。
2、基于页面注入的 RPC
sekiro: 开源RPC框架,基于Netty,支持多客户端管理。客户端注入
sekiro.js,服务端接收HTTP请求并分发。BrowserRPC: Chrome扩展,暴露页面函数给本地服务,通过
chrome.runtime.connectNative与本地程序通信。
3、配合安卓逆向
应用场景: App内嵌WebView加载H5,加密逻辑在JS中。可通过
addJavascriptInterface暴露Java对象给JS,或在WebView中注入JS代码。方法:
使用Frida hook WebView的
loadUrl,注入RPC客户端。通过
console.log输出加密结果,用logcat捕获。
十、实战案例分类
关键说明: 每一种反爬手段都有对应的突破套路,建议按类别建立笔记。
1、登录参数逆向
密码加密: RSA公钥加密、AES对称密钥(密钥可能由RSA加密传输)、SM系列国密。
验证码处理: 图片识别(OCR/打码平台)、滑块轨迹加密(需逆向轨迹生成算法)。
加粗提醒: 注意登录时的公钥可能每次请求都变化,需从HTML或接口动态获取。
2、数据采集接口逆向
翻页参数:
pageNo、limit,以及对应的sign变化规律。注意sign通常包含页码和时间戳。时间戳防重:
ts、_t、timestamp,服务器会校验时间窗口。
3、反爬虫机制突破
极验(Geetest): 轨迹、指纹、w参数还原。w参数逆向极为复杂,可考虑使用RPC调用。
瑞数(RiverSafe): VM保护、Cookie后缀(
__jsl_clearance)、动态JS。需处理首次请求的302跳转和JS计算。阿里系: 无痕环境、设备指纹(
umid)、滑块验证。
4、小程序/公众号逆向思路
获取源码: 解密
wxapkg包(工具:wxappUnpacker),反编译得到JS文件。定位加密: 搜索
encrypt、sign等关键词,注意小程序使用wx.request发送请求。
5、WebAssembly 初步逆向
识别: JS中调用
WebAssembly.instantiate,加载.wasm文件。分析方法:
提取wasm文件,使用
wasm2wat转换为文本格式(S表达式)。理解导出的函数签名,使用
wat2js或直接动态调用传入参数观察输出。也可将wasm反编译为伪C代码(如
wasm-decompile),但可读性较差。
十一、工具与自动化
关键说明: 熟练掌握以下工具能极大提升逆向效率。
1、抓包修改重放工具
Burp Suite: Repeater重放请求、Intruder批量测试参数、Decoder编解码。
Postman: 环境变量管理,可将加密结果写入变量供后续请求使用。
2、JS 代码格式化与美化
在线工具: beautifier.io、jsnice.org(提供变量名猜测)。
本地IDE: VSCode + Prettier,WebStorm自带格式化。
3、调试器与反调试绕过
禁用断点: Deactivate breakpoints按钮(Chrome Sources面板右侧),所有断点失效。
清除定时器: 控制台执行
for (let i=1; i<9999; i++) clearInterval(i);clearTimeout(i);清除所有定时器。绕过无限debugger:
右键断点行 → Never pause here。
或者
Function.prototype.constructor = function(){};注意这会破坏所有函数构造,谨慎使用。
4、自动化脚本
Python + execjs: 适合简单加密(如MD5、简单AES)。
Node.js 调度: 直接
require加密JS模块,但需补环境。可将加密函数挂载到global对象上。Puppeteer/Playwright: 完全模拟浏览器行为,适合需要频繁页面交互的爬虫,但速度慢。
十二、反逆向对抗与绕过
关键说明: 网站也会升级防御,你需要了解反爬是如何检测你的。
1、反调试手段
无限debugger: 通过
setInterval反复执行debugger;或eval("debugger;")。绕过方法:禁止断点或替换eval。控制台检测: 检测
console.log是否被篡改、toString是否返回原函数字符串。Hook检测函数。断点检测: 计算函数执行时间差,若超过阈值则认为有断点。绕过:不在该函数内断点,或修改时间检测逻辑。
2、代码自校验与时间戳检测
完整性校验: 对自身代码求哈希,存于闭包中,执行前对比,不一致则拒绝。绕过:Hook哈希函数或直接禁止校验。
时间戳陷阱: 记录关键函数的执行时间,超时(调试时)则终止。绕过:在函数开头重置计时变量。
3、环境检测与特征识别
检测浏览器插件:
navigator.plugins长度,headless浏览器通常长度很小。检测headless:
navigator.webdriver(正常应为false)、window.chrome(正常应存在)。检测非人类交互: 鼠标移动轨迹、按键频率。模拟时需添加随机延迟和轨迹。
4、常见对抗绕过思路
替换检测函数: 重写
console.log、RegExp.prototype.test(拦截检测正则)。使用
Proxy伪造所有环境属性,使任何属性访问都返回正常值。直接patch掉反调试代码: 通过本地替换(Overrides)删除相关逻辑,例如删除
setInterval的调用。
十三、资源与持续学习
关键说明: 技术更新快,保持学习习惯至关重要。
1、优质博客与论坛
国内: 看雪论坛(逆向版块)、先知社区、CSDN逆向专栏、掘金(搜索JS逆向)。
国外: crackmes.one(破解练习)、Reddit的r/netsec、r/ReverseEngineering。
2、GitHub 优质项目
AST反混淆: de4js、js-deobfuscator。
补环境框架: puppeteer-extra-plugin-stealth、vm2。
RPC工具: sekiro、BrowserRPC。
3、逆向练习靶场
JS逆向训练: https://captcha.com/ 各类验证码、https://www.v2ex.com/ 某些接口。
真实网站: 电商、社交平台登录接口(仅供学习,严禁高频请求)。
4、法律法规与道德边界
加粗警告: 不得利用逆向技术窃取数据、破解付费、入侵系统。
合法范围: 个人学习、安全测试需获得授权、漏洞挖掘遵守SRC规则。逆向代码不得用于商业爬虫或数据倒卖。
JS逆向
本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
评论交流
欢迎留下你的想法