关于 js:7. 模块化、构建与工具链
一、模块系统:CommonJS、ESM、UMD
模块系统的目标:
将代码拆分为独立的逻辑单元(模块),实现封装、复用、依赖管理。
在 Web 前端/Node 中,因为 JavaScript 起初没有模块机制,因此出现了多个模块系统:
-
CommonJS:用于 Node.js
-
ESM:浏览器标准模块系统
-
UMD:兼容 CommonJS + AMD + 浏览器全局,常用于库
1. CommonJS(Node.js 标准)
由 CommonJS 组织提出,是 Node.js 默认模块格式。每个 .js
文件都是一个模块。
核心语法
// math.js
const add = (a, b) => a + b;
module.exports = { add };// main.js
const math = require('./math');
console.log(math.add(2, 3));
模块机制
特性 | 描述 |
---|---|
加载方式 | 同步加载,适合服务器环境 |
导出 | module.exports 或 exports.xxx |
引入 | require() ,在运行时加载模块 |
缓存机制 | 加载一次后,缓存模块对象 |
文件单位 | 每个 .js 文件就是一个模块 |
缓存机制举例:
require('./a'); // 执行 a.js 里的代码
require('./a'); // 不再执行,只返回上次结果
有助于判断模块初始化逻辑的位置,比如入口代码是否只执行一次。
逆向识别特征
表现形式 | 说明 |
---|---|
require("xxxx") | 模块导入 |
module.exports = {} | 模块导出 |
exports.func = ... | 导出函数/变量 |
__dirname 、__filename | 模块当前路径 |
2. ESM(ECMAScript Module)
ESM 是 ES6 中引入的 JavaScript 原生模块标准,现代浏览器与 Node.js(v14+)都支持。
核心语法
// math.js
export const add = (a, b) => a + b;
export default function sub(a, b) { return a - b; }// main.js
import { add } from './math.js';
import sub from './math.js';
模块机制
特性 | 描述 |
---|---|
加载方式 | 静态分析 + 异步加载 |
导出 | export / export default |
引入 | import (必须写在顶层) |
支持 Tree-shaking | 未使用的代码会在打包时被移除 |
跨模块引用 | 静态结构清晰,易于优化 |
静态 vs 动态加载
import x from './x.js'; // 静态导入,编译时就知道依赖const m = await import('./m.js'); // 动态导入,返回 Promise
在混淆还原中,可以通过静态导入结构推断模块依赖。
逆向识别特征
表现形式 | 含义 |
---|---|
import ... from ... | 标准导入语法 |
export const ... | 命名导出 |
export default | 默认导出 |
.mjs 文件 | 明确表示该文件是 ESM 模块 |
3. UMD(Universal Module Definition)
UMD 是一种兼容所有主流模块系统的格式,适用于发布 JS 库(如 jQuery、lodash、crypto-js)。
核心结构
(function (root, factory) {if (typeof define === 'function' && define.amd) {define([], factory); // AMD} else if (typeof module === 'object' && module.exports) {module.exports = factory(); // CommonJS} else {root.myLib = factory(); // 浏览器全局变量}
}(this, function () {return {sayHi: () => console.log('Hi!')};
}));
模块机制
特性 | 描述 |
---|---|
自动适配 | 判断当前运行环境自动使用适合的加载方式 |
导出对象 | 返回一个全局对象,挂载在 window 或 global |
多平台支持 | 同时支持浏览器、Node.js、AMD、RequireJS 等 |
多见于库文件 | 如 crypto-js、axios 发布的 umd 文件 |
逆向识别特征
表现形式 | 含义 |
---|---|
typeof module === 'object' && module.exports | CommonJS 检测 |
typeof define === 'function' && define.amd | AMD 检测 |
root.XXX = factory(); | 浏览器全局对象挂载 |
如果在逆向某个库函数(比如混淆的加密函数),看到这种结构,那几乎可以断定是一个通用的 UMD 打包库。
总结
对比项 | CommonJS | ESM | UMD |
---|---|---|---|
加载方式 | 同步 | 异步(静态) | 自适应 |
使用平台 | Node.js | 浏览器 + Node.js | 浏览器、Node、RequireJS |
导出方式 | module.exports | export / export default | root.xx = factory() 等 |
Tree-shaking | 不支持 | 支持 | 不支持 |
缓存机制 | 有 | 有 | 有 |
是否支持动态导入 | 是(require) | 是(import() ) | 否 |
二、import/export
传统 JS(使用 <script>
标签)的问题:
-
全局变量污染
-
模块依赖混乱
-
无法静态分析依赖关系
-
不支持按需加载优化(tree-shaking)
ES6 模块系统引入 export/import
,解决了以上问题:
-
每个 JS 文件就是一个模块(默认独立作用域)
-
export
明确模块对外暴露的内容 -
import
明确模块依赖,且是静态结构,可优化、分析
1. export
导出语法详解
1)命名导出(Named Export)
每个模块可以导出多个内容:
// utils.js
export const add = (a, b) => a + b;
export const sub = (a, b) => a - b;
export const PI = 3.14;
导入时用花括号:
import { add, sub } from './utils.js';
可以重命名:
import { add as addFunc } from './utils.js';
2)默认导出(Default Export)
每个模块只能有一个默认导出,常用于导出单个功能或类:
// logger.js
export default function log(msg) {console.log('Log:', msg);
}
导入时无需花括号,名字可随意:
import log from './logger.js';
默认导出可以是函数、类、对象、值:
export default {name: 'module',version: 1
};
3)混合使用(命名 + 默认)
export const name = 'abc';
export default function main() {}
导入方式:
import main, { name } from './mod.js';
2. import
导入语法详解
导入命名导出
import { a, b } from './mod.js';
导入默认导出
import anyName from './mod.js';
导入所有(命名空间方式)
import * as utils from './utils.js';
utils.add(1, 2);
动态导入(异步 import)
const mod = await import('./mod.js');
mod.fn();
动态导入常用于懒加载、条件加载、前端按需打包(如 Webpack code splitting)。
3. 模块的执行与缓存机制
-
每个模块 只执行一次
-
多次
import
实际复用缓存(单例引用) -
模块中定义的变量、状态会被共享
例子:
// counter.js
let count = 0;
export function increment() {count++;console.log(count);
}
多次导入使用 increment()
,会打印递增数字,说明是同一个模块实例。
总结
// mod.js
export const a = 1;
export default function b() {}
// main.js
import b, { a } from './mod.js';
一个模块 = 一个作用域单元
export
定义接口 → import
引入依赖
多次 import / 执行一次 / 缓存共享
三、require
在 Node.js 中,每个 .js
文件都被视为一个模块,模块内部通过 module.exports
导出内容,其他模块通过 require()
导入使用。
1. 基本语法与例子
// math.js
const add = (a, b) => a + b;
module.exports = { add };
// app.js
const math = require('./math.js');
console.log(math.add(1, 2)); // 输出:3
注意:require
是同步、运行时加载的。
2. 模块导出:module.exports
vs exports
// 正确用法
module.exports = {name: 'hello'
};// 另一种写法(推荐只用一种)
exports.name = 'hello'; // 等价于 module.exports.name = ...
// 错误用法
exports = { name: 'lost' }; // 此时 exports 与 module.exports 脱钩
exports
只是 module.exports
的引用。如果重新赋值,会失效。
3. 模块加载机制
require('模块名')
加载路径规则:
-
核心模块(如
fs
、path
) -
自定义模块(相对/绝对路径)
-
第三方模块(从
node_modules
向上查找)
4. 模块缓存机制
每个模块在第一次被 require()
时会被执行并缓存,后续 require
返回的是缓存对象,不会重复执行。
示例:
// a.js
console.log('a 加载了');
module.exports = { count: 0 };// b.js
const a = require('./a');
a.count++;
console.log('b:', a.count);// c.js
const a = require('./a');
console.log('c:', a.count);
输出顺序为:
a 加载了
b: 1
c: 1
说明 a.js
只执行一次,模块状态共享。
5. 清除缓存(热更新、绕过防护)
const path = require.resolve('./a.js');
delete require.cache[path];
或者:
delete require.cache[require.resolve('./a.js')];
再次 require
将重新加载并执行模块。
require.resolve('./a.js')
和 require('./a.js')
的区别:
方法 | 作用 |
---|---|
require('./a.js') | 加载并执行模块,返回导出的内容 |
require.resolve('./a.js') | 只返回路径字符串,不加载模块本身 |
6. require 的高级用法
1)动态路径(运行时字符串)
const lang = 'zh';
const messages = require(`./lang/${lang}.js`);
这种写法常见于国际化、本地化、配置分离。
2)条件加载
if (process.env.NODE_ENV === 'dev') {require('./mock-server.js');
}
这种方式是 CommonJS 的优势,ESM 模块不允许这样写。
3)只执行模块副作用(无导出)
// monitor.js
console.log('开启监控');// main.js
require('./monitor.js'); // 只为执行副作用
总结
特性 | 描述 |
---|---|
模块系统 | CommonJS |
加载方式 | 同步、运行时 |
是否支持动态路径 | 是 |
是否缓存 | 是,require.cache |
导出方式 | module.exports 或 exports |
是否能清除缓存 | 可以手动清除 |
是否支持顶层 await | 不支持 |
四、模块缓存机制
模块缓存(Module Cache) 是指:
一个模块在第一次通过 require()
或 import
被加载时,系统会将其“执行结果”缓存起来,后续再次加载该模块时,不会再次执行代码,而是直接返回缓存结果。
这保证了模块是“单例”的,并且执行效率更高。
1. Node.js 中 CommonJS 的缓存机制
模块加载流程:
-
首次
require()
:-
解析路径
-
读取文件内容
-
包裹成函数并执行
-
缓存导出的
module.exports
-
-
再次
require()
:-
直接返回缓存的
module.exports
对象
-
示例代码:
// counter.js
let count = 0;
module.exports = {add: () => ++count,
};
// a.js
const counter = require('./counter');
console.log('A:', counter.add()); // 输出 A: 1
// b.js
const counter = require('./counter');
console.log('B:', counter.add()); // 输出 B: 2
即使 a.js
和 b.js
分别加载,只会执行一次 counter.js
,并共享其导出对象。
2. 缓存存储位置:require.cache
Node.js 的模块缓存实际是一个对象:
console.log(require.cache);
结构类似:
{'/path/to/counter.js': {id: '/path/to/counter.js',filename: '/path/to/counter.js',loaded: true,exports: {...},children: [...],...}
}
可以:
-
查看缓存:
Object.keys(require.cache)
-
清除缓存:
delete require.cache[require.resolve('./counter')]
3. 清除缓存机制
方法 1:删除 require.cache
项
delete require.cache[require.resolve('./counter')];
下次再 require('./counter')
,会重新加载并执行模块。
方法 2:热更新模块(开发工具使用)
开发服务器(如 nodemon
)就是检测文件变化后清除缓存并重新加载模块。
4. 模块多次加载行为图解
首次 require('./a') ➜ 加载并缓存
第二次 require('./a') ➜ 直接返回缓存
删除缓存后 require('./a') ➜ 重新加载
这就是 模块单例性(singleton) 和缓存机制的来源。
5. 缓存导致的副作用
1)模块状态共享(计数器、连接池、缓存等)
// database.js
let conn = null;
module.exports = {connect: () => {if (!conn) conn = createConnection();return conn;}
};
多个模块调用 require('./database')
,会复用同一个连接。
2)某些模块只执行一次副作用
// logger.js
console.log('Logger initialized');
不管被多少文件 require()
,只打印一次。
6. 与 ESModule(ESM)的缓存机制对比
ESM 也缓存模块,但:
特性 | CommonJS | ESM |
---|---|---|
缓存机制 | 是 | 是 |
可清除缓存 | 手动清除 | 不可(静态结构) |
影响变量共享 | 是 | 是 |
是否单例 | 是 | 是 |
ESModule 的缓存机制更严格、不可清除、更适合编译优化(如 Tree-shaking)
7. 逆向与安全分析场景中常见用法
1)Webpack 模块缓存结构
打包后的 Webpack 也有缓存机制(变种):
// 内部使用 __webpack_module_cache__ 缓存模块
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {if (__webpack_module_cache__[moduleId]) {return __webpack_module_cache__[moduleId].exports;}...
}
在分析 Webpack 产物时,要特别关注这个缓存对象,便于定位真实模块和还原逻辑。
2)绕过缓存注入 Payload
可以通过以下手段实现注入:
// 修改 module.exports 中的方法
require('./target').login = function() {console.log('Hooked!');
};
或直接替换整个模块:
require.cache[require.resolve('./target')].exports = fakeModule;
总结
点 | 内容 |
---|---|
缓存位置 | require.cache |
缓存对象 | module.exports 返回值 |
缓存作用 | 避免重复加载、提高性能 |
清除方式 | delete require.cache[...] |
注意事项 | 会导致模块共享状态(副作用) |
五、使用 Babel 转译源码
Babel 是一个 JavaScript 编译器,核心作用是:
功能 | 描述 |
---|---|
转译 | 把 ES6+ / JSX / TypeScript 转为 ES5 |
分析 | 生成、遍历、修改 AST(抽象语法树) |
插件系统 | 支持自定义插件操作 AST |
逆向用途 | 可用于提取、重构混淆代码 |
Babel 转译的三个阶段
Babel 的整体流程是:
源码(string)→ Parse(转成 AST)→ Transform(操作 AST)→ Generate(再生成 JS)
环境搭建:核心依赖
需要安装以下 npm 包:
npm install @babel/core @babel/parser @babel/traverse @babel/generator @babel/types
这些库的作用如下:
包名 | 用途 |
---|---|
@babel/core | Babel 核心引擎 |
@babel/parser | 将源码转成 AST |
@babel/traverse | 遍历/修改 AST |
@babel/types | 判断/构建 AST 节点 |
@babel/generator | AST 转回代码字符串 |
1. 实战流程:转译并操作 JS 源码
1)读取源码
const fs = require("fs");
const code = fs.readFileSync("input.js", "utf-8");
2)解析为 AST
const parser = require("@babel/parser");
const ast = parser.parse(code, {sourceType: "module", // 可选:script | module
});
3)遍历并修改 AST
const traverse = require("@babel/traverse").default;traverse(ast, {Identifier(path) {if (path.node.name === "_0xabc123") {path.node.name = "decryptedData";}},
});
4)AST 生成新代码
const generate = require("@babel/generator").default;
const output = generate(ast, {}, code);
fs.writeFileSync("output.js", output.code);
2. 逆向实战:混淆变量改名
输入代码:
const _0x12a3f = 1 + 2;
console.log(_0x12a3f);
操作 AST:
traverse(ast, {Identifier(path) {if (path.node.name === "_0x12a3f") {path.node.name = "sum";}},
});
输出结果:
const sum = 1 + 2;
console.log(sum);
3. 结合 Babel Types 进行节点构造
也可以手动构造一个 AST 节点:
const t = require("@babel/types");const newVar = t.variableDeclaration("const", [t.variableDeclarator(t.identifier("injectedVar"),t.stringLiteral("hacked!")),
]);path.insertBefore(newVar);
这段代码会向目标节点前插入:
const injectedVar = "hacked!";
总结
阶段 | 工具 | 作用 |
---|---|---|
解析(parse) | @babel/parser | 源码 ➝ AST |
遍历(traverse) | @babel/traverse | 读/改 AST 节点 |
判断构造 | @babel/types | 判断类型 / 构造新节点 |
生成(generate) | @babel/generator | AST ➝ JS 源码 |
输出文件 | fs.writeFileSync | 写出修改结果 |
六、生成 AST 分析工具
AST(抽象语法树)分析工具 = 使用 Babel 将 JS 源码转换成结构化的树状结构,供我们进行以下操作:
功能 | 示例 |
---|---|
结构还原 | 混淆变量名一键改回 a, b, c |
函数提取 | 找出所有包含 CryptoJS 的函数 |
参数还原 | 替换复杂表达式为简单值 |
注入调试 | 自动加 console.log() |
依赖安装
npm install @babel/core @babel/parser @babel/traverse @babel/types @babel/generator
1. AST 分析工具目录结构
ast-tool/
├── input.js # 待分析的混淆源码
├── output.js # 处理后的输出代码
├── index.js # 分析主程序
└── rename-map.json # 存储改名映射(可选)
2. 核心流程 = 四步法
1 读取源码 → 2 解析 AST → 3 遍历修改 → 4 输出新代码
// index.jsconst fs = require("fs");
const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const generator = require("@babel/generator").default;
const t = require("@babel/types");// 1. 读取原始源码
const code = fs.readFileSync("input.js", "utf-8");// 2. 生成 AST
const ast = parser.parse(code, {sourceType: "unambiguous", // 自动识别是 script 还是 module
});// 3. 遍历并操作 AST
traverse(ast, {Identifier(path) {if (path.node.name === "_0x12ab") {path.node.name = "decodedStr";}},CallExpression(path) {if (t.isIdentifier(path.node.callee) &&path.node.callee.name === "eval") {path.replaceWith(t.stringLiteral("eval removed"));}},
});// 4. 生成新的源码并写入
const output = generator(ast, {}, code);
fs.writeFileSync("output.js", output.code);
3. 常见操作合集
1)遍历函数定义(提取加密函数)
traverse(ast, {FunctionDeclaration(path) {const name = path.node.id.name;if (path.toString().includes("btoa") || path.toString().includes("CryptoJS")) {console.log("找到加密函数:", name);}}
});
2)遍历字符串字面量(提取混淆密文)
traverse(ast, {StringLiteral(path) {console.log("字符串:", path.node.value);}
});
3)自动注入 console.log
traverse(ast, {FunctionDeclaration(path) {const logStmt = t.expressionStatement(t.callExpression(t.identifier("console.log"), [t.stringLiteral("进入函数:" + path.node.id.name),]));path.get("body").unshiftContainer("body", logStmt);}
});
4)记录并保存重命名映射
const renameMap = {};
let counter = 0;traverse(ast, {Identifier(path) {if (/^_0x/.test(path.node.name)) {const newName = "var" + counter++;renameMap[path.node.name] = newName;path.node.name = newName;}}
});fs.writeFileSync("rename-map.json", JSON.stringify(renameMap, null, 2));
总结
用途 | 技术实现 |
---|---|
解混淆 | 遍历 Identifier 重命名 |
提取加密函数 | 查找 FunctionDeclaration 含 btoa 、CryptoJS |
提取字符串 | 遍历 StringLiteral |
替换表达式 | 替换某个节点为固定返回值 |
注入日志 | path.insertBefore() 或 path.get("body").unshiftContainer(...) |
七、Webpack 打包结构识别
Webpack 是当前网页常见的构建工具之一,使用它打包后的 JavaScript 文件具有以下特点:
特性 | 描述 |
---|---|
模块化封装 | 所有模块合并为一个大函数 |
模块索引化 | 模块用数字或混淆变量标识,如 0xabc123 |
闭包封装 | 整个包被包装为自执行函数 |
隐藏真实函数名 | 所有函数变量名被重命名为无意义的标识符 |
通过 __webpack_require__ 加载模块 | 模拟 CommonJS 的 require() |
1. 常见 Webpack 打包结构(3 种核心模式)
1)IIFE 自执行结构(所有模块被包装)
(function(modules) {function __webpack_require__(moduleId) {// 模块缓存与执行}return __webpack_require__(0);
})({0: function(module, exports, __webpack_require__) {// 主模块代码},1: function(module, exports) {// 其它模块}
});
识别点:
-
自执行匿名函数
-
内部存在
__webpack_require__
-
参数是一个对象或数组,模块编号为 0、1、2...
-
模块实现是
function(module, exports, ...) {}
2)数组结构(简化混淆模式)
(function(modules) {// modules 是数组
})([function(module, exports) { console.log("main"); },function(module, exports) { console.log("sub"); }
]);
识别点:
-
参数是一个数组,每个模块是数组中的一个函数
-
常用于小项目或极简打包
3)eval 模式(加速构建,调试模式常见)
eval("module.exports = 'hello';\n//# sourceURL=webpack://app/./src/index.js?");
识别点:
-
eval(...)
中是模块代码字符串 -
sourceURL=webpack://...
是调试信息 -
通常用于
devtool: 'eval'
模式,方便调试
2. Webpack 核心结构拆解图(常见标准打包)
(function(modules) { // modules 是一个数组,保存所有模块函数// 模块缓存var installedModules = {}; // 用来缓存已经加载的模块,防止重复执行// 模拟 require 函数function __webpack_require__(moduleId) { // 自定义的模块加载函数,参数是模块编号if (installedModules[moduleId]) { // 如果该模块已经缓存过了return installedModules[moduleId].exports; // 直接返回该模块的导出结果}var module = installedModules[moduleId] = { // 创建一个新的模块对象exports: {} // 初始导出对象为空};// 执行模块函数// 把 module, exports, 和 __webpack_require__ 传入模块函数// 相当于 CommonJS 中的 (module, exports, require)modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);return module.exports; // 返回该模块的导出对象}// 加载入口模块return __webpack_require__(0); // 启动程序,从模块 ID 为 0 的模块开始执行})([ // 模块数组,每个模块是一个函数,对应一个模块文件// 模块 0(主入口模块)function(module, exports, __webpack_require__) {const helper = __webpack_require__(1); // 加载模块 1 的导出内容console.log(helper()); // 执行模块 1 返回的函数并打印返回值},// 模块 1(被导入模块)function(module, exports) {module.exports = function() { // 导出一个函数return "helper result"; // 这个函数返回一个字符串};}]);
3. 如何逆向分析 Webpack 打包代码?
步骤一:找到 Webpack 包装函数入口
-
搜索关键词:
__webpack_require__
-
或搜索自执行函数
(function(modules)
或(function(){...})({...})
步骤二:提取模块数组/对象
-
将 modules 拆成一个个子模块
-
如果是数组,可用索引访问
-
如果是对象,可用数字/字符串 key
const modules = {0: function (module, exports, __webpack_require__) { ... },1: function (module, exports) { ... }
};
步骤三:寻找主模块
通常是 __webpack_require__(0)
或类似写法
return __webpack_require__(0);
找到模块 0
的实现,即为主入口。
步骤四:定位关键加密逻辑模块
可按如下思路定位关键逻辑模块:
技术 | 操作 |
---|---|
关键词匹配 | 在所有模块中搜索 "CryptoJS"、"md5"、"AES"、"btoa" 等关键字 |
hook __webpack_require__ | 打印加载顺序,定位哪些模块被频繁加载 |
使用 Babel AST | 分析各模块函数结构,找加密点或 WebSocket 通信逻辑 |
4. 结合 Babel 工具自动拆包 + 重构
示例:识别所有模块并重命名变量
traverse(ast, { // 遍历整个 AST 抽象语法树CallExpression(path) { // 当遍历到调用表达式 (函数调用) 时触发const callee = path.node.callee; // 获取调用的函数名部分(callee = 被调用的函数)if (t.isIdentifier(callee) && callee.name === "__webpack_require__") {// 如果 callee 是一个标识符,并且名字是 "__webpack_require__"// 说明这是 webpack 打包后调用模块的语句console.log("发现模块调用:", path.toString());// 打印出当前调用语句的源码形式,比如:__webpack_require__(134)}}
});
总结
特征 | 说明 |
---|---|
自执行函数结构 | (function(modules){...})([...]) |
__webpack_require__ 函数存在 | 标志着模块加载机制 |
模块数组 / 对象 | 所有业务逻辑封装为函数 |
模块编号 | 模块索引常是数字或 _0xabc123 形式混淆字符串 |
主模块加载入口 | 一般是 __webpack_require__(0) ,可定位逻辑起点 |
八、sourceMap 的作用与反调试方法识别
概念:
sourceMap
是一种映射文件,用来将压缩/混淆后的代码映射回原始源码。
作用:
-
帮助开发者调试混淆压缩后的代码
-
允许浏览器控制台显示源代码位置
-
支持断点调试和还原变量名/函数名
1. sourceMap 结构详解
通常是一个 .map
文件,如 bundle.js.map
,是一个 JSON 文件,包含以下字段:
{"version": 3,"file": "bundle.js","sources": ["webpack:///src/index.js"],"names": ["add", "a", "b"],"mappings": "AAAA,IAAIA..."
}
字段 | 含义 |
---|---|
version | sourceMap 版本 |
file | 对应的打包输出文件 |
sources | 原始源文件路径 |
names | 源码中的变量名列表 |
mappings | 压缩代码到源码的具体位置映射 |
2. sourceMap 的作用总结
作用 | 举例 |
---|---|
恢复可读源码 | a=function(x){return x*x} → function square(x) { return x * x } |
浏览器调试支持 | Chrome 可直接点进原始文件如 src/index.js |
JS 逆向分析辅助 | 可定位真实加密逻辑、函数名、调用链 |
调试时插桩 | 可在原始位置插入 console.log 、hook |
3. sourceMap 常见获取方式
方式 1:页面中引用了 .map
<script src="app.js"></script>
<!-- HTML 源码中包含 -->
<!-- 浏览器会自动下载 app.js.map -->
方式 2:JS 文件末尾提示
//# sourceMappingURL=app.js.map
可以在 Chrome 的 Source 面板中看到带 /src/xxx.js
的文件。
方式 3:手动尝试拼接 URL
https://example.com/static/js/app.js → app.js.map
尝试访问 同目录 + .map
通常能获取
4. 常见反调试技术(及识别方式)
技术手段 | 描述 | 识别方法 |
---|---|---|
debugger | 中断调试器执行 | 搜索关键词:debugger |
toString 欺骗 | 隐藏函数体内容 | console.log(fn.toString()) 看是否与预期不符 |
控制台检测 | 检测 devtools 是否打开 | 查看是否有 console.log.toString() 、window.outerWidth 相关判断 |
死循环卡调试 | 利用大量计算阻塞调试 | 搜索关键字如 while(true) 、for(;;) |
动态构造函数名 | 函数名用 eval 、Function 动态生成 | 搜索 Function("return this")() 结构 |
堆栈检查 | 检查 call stack 中是否有 debugger 或调试器函数 | 搜索 Error().stack |
Object.defineProperty 劫持 | 禁止控制台输出 | 检查是否重写了 console 相关属性 |
5. 识别反调试代码的技巧
1)关键字扫描(用于 AST、静态分析)
grep -i 'debugger' app.js
grep -i 'devtools' app.js
或用 Babel AST:
traverse(ast, {DebuggerStatement(path) {path.remove(); // 删除所有 debugger}
});
2)动态调试行为检测(浏览器)
-
打开 Chrome DevTools
-
看是否自动跳转或自动刷新
-
是否不断触发
debugger
、页面假死、console 乱码
3)插桩日志 Hook 法
替换关键函数为 console.log
包装函数
window.alert = function(msg) {console.log("[alert 调用] 参数:", msg);
};
6. 如何绕过反调试逻辑
技术 | 描述 |
---|---|
删除 debugger | 静态或 AST 方式清除 |
hook Function | 替换为你自己的构造器 |
替换死循环 | 修改为空代码块 |
patch console 检测 | Object.defineProperty(console, 'log', { get() {...} }) hook 掉 |
使用 Puppeteer Stealth | Puppeteer 自动规避部分 DevTools 检测逻辑 |
7. 总结:如何结合 sourceMap 与反调试分析代码
-
判断是否存在 .map 文件
-
查看是否加载、尝试拼接获取
-
-
使用 Chrome Source 查看源文件结构
-
找到有意义的源文件,跳转到
关键函数位置
-
-
清除反调试代码
-
使用 Babel AST 或手动清理
debugger
、死循环
-
-
结合 AST 做深入分析
-
提取加密函数、通信逻辑、参数生成等模块
-
相关文章:
关于 js:7. 模块化、构建与工具链
一、模块系统:CommonJS、ESM、UMD 模块系统的目标: 将代码拆分为独立的逻辑单元(模块),实现封装、复用、依赖管理。 在 Web 前端/Node 中,因为 JavaScript 起初没有模块机制,因此出现了多个模…...
一次IPA被破解后的教训(附Ipa Guard等混淆工具实测)
一行代码的疏忽,一个默认的类名,一个未混淆的资源路径,都可能成为攻击者入侵的入口。 背景:一次“不值一提”的上线,成了代价惨重的经验 故事的起点很简单:我们给销售部门做了一款小型内部演示 App&#x…...
麒麟系统安装.net core环境变量
本文主要记录在麒麟系统上安装.net core的运行环境,这里使用的是麒麟V10桌面版,后续测试服务器到了之后再使用服务器版进行安装测试。 环境安装 下载 这里由于是桌面版,我直接使用浏览器下的包,下完之后在终端中安装。 安装 1…...
如何使用 React Hooks 替代类组件的生命周期方法?
文章目录 1. 引言2. useEffect 概述3. 模拟类组件的生命周期方法3.1 模拟 componentDidMount3.2 模拟 componentDidUpdate3.3 模拟 componentWillUnmount 4. 多个 useEffect 的使用5. 注意事项6. 总结 1. 引言 在 React 16.8 版本之前,开发者主要通过类组件&#x…...
windows 在安装 Ubuntu-20.04 显示操作超时解决办法
1. 问题概述与原因分析 在我们用下面命令安装 Ubuntu-20.04 时系统显示操作超时: wsl --install -d Ubuntu-20.04大概率是没打开 Windows 虚拟机监控程序平台,可以在控制面板–>程序和功能里面打开 2. 解决办法与步骤 解决方式如下: 我…...
Spring Boot中Redis序列化配置详解
精心整理了最新的面试资料和简历模板,有需要的可以自行获取 点击前往百度网盘获取 点击前往夸克网盘获取 引言 在使用Spring Boot集成Redis时,序列化方式的选择直接影响数据存储的效率和系统兼容性。默认的JDK序列化存在可读性差、存储空间大等问题&am…...
OpenCV进阶操作:光流估计
文章目录 前言一、光流估计1、光流估计是什么?2、光流估计的前提?1)亮度恒定2)小运动3)空间一致 3、OpenCV中的经典光流算法1)Lucas-Kanade方法(稀疏光流)2) Farneback方…...
2025年渗透测试面试题总结-渗透测试红队面试八(题目+回答)
网络安全领域各种资源,学习文档,以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具,欢迎关注。 目录 渗透测试红队面试八 二百一十一、常见中间件解析漏洞利用方式 二百一十二、MySQL用户密码存储与加密 …...
前端面试高频50个问题,解答
以下是前端面试中常见的50个高频问题及其简要解答: HTML HTML5 有哪些新特性? 语义化标签(如 <header>、<footer>)、多媒体支持(如 <audio>、<video>)、本地存储(如 l…...
Elasticsearch架构原理
1、Elasticsearch的节点类型 1.1 Master节点 在Elasticsearch启动时,会选举出来一个Master节点。当某个节点启动后,然后 使用Zen Discovery机制找到集群中的其他节点,并建立连接。 discovery.seed_hosts: ["192.168.21.130", &qu…...
前端面试宝典---webpack面试题
webpack 的 tree shaking 的原理 Webpack 的 Tree Shaking 过程主要包含以下步骤: 模块依赖分析:Webpack 首先构建一个完整的模块依赖图,确定每个模块之间的依赖关系。导出值分析:通过分析模块之间的 import 和 exportÿ…...
Vue 2 项目中配置 Tailwind CSS 和 Font Awesome 的最佳实践
Vue 2 项目中配置 Tailwind CSS 和 Font Awesome 的最佳实践 一、Tailwind CSS 配置 1. 安装依赖 npm install tailwindcssnpm:tailwindcss/postcss7-compat tailwindcss/postcss7-compat postcss^7 autoprefixer^92. 创建配置文件 npx tailwindcss init3. 创建样式文件 在…...
hiveserver2与beeline进行远程连接hive配置及遇到的问题
1、hiveserver2 参与用户模拟功能,因为开启后才能保证各用户之间的权限隔离。 1.1、配置 $HADOOP_HOME/etc/hadoop/core-site.xml <!--配置所有节点的root用户都可作为代理用户--> <property><name>hadoop.proxyuser.root.hosts</name>&…...
单词短语0512
当然可以,下面是“opportunity”在考研英语中的常用意思和高频短语,采用大字体展示,便于记忆: ✅ opportunity 的考研常用意思: 👉 机会,良机 表示有利的时机或条件,尤指成功的可能…...
c++刷题便捷函数(类似于stoi的小函数)
标题 stoi(字符串转整形)map和set都有count成员函数,返回值是该key的个数,可以用来查是否存在该元素。bool is_sorted(nums.begin(), nums.end() 检验是否有序INT_MAX,INT_MIN分别是整形最大和最小初始化二维矩阵 vector<vector\<int>> mart…...
想实现一个基于MCP的pptx生成系统架构图【初版实现】
技术栈:Python + MCP协议 + python-pptx + FastMCP 核心创新点:通过MCP协议实现PPTX元素的动态化生成与标准化模板管理 当前还是个半成品,后续持续更新。 主要先介绍一下思路。 一、MCP协议与系统设计原理 1.1 为什么选择MCP? 标准化工具调用:通过MCP将PPTX元素生成逻辑封…...
jwt学习
基于token的鉴权机制也是无状态的(类似于http协议),不需要保在服务端保留用户的认证或会话信息。 构成 jwt由三部分构成:头部、payload、签名,中间用.隔开 头部(header) 包含两部分信息:声明类型、声明加密的算法 例如:…...
pth的模型格式怎么变成SafeTensors了?
文章目录 背景传统模型格式的安全隐患效率与资源瓶颈跨框架兼容性限制Hugging Face 的解决方案:SafeTensors行业与社区的推动SafeTensors 的意义总结 背景 最近要找一些适合embedding的模型,在huggingface模型库上看到一些排名比较靠前的,准…...
如何判断IP是否被平台标记
一、基础检测:连通性与黑名单筛查 网络连通性测试 Ping与Traceroute:通过命令测试延迟和路由路径,若延迟>50ms或存在异常节点(如某跳延迟>200ms),可能影响可用性。示例命令: bash ping 8.…...
【c++】异常详解
目录 C语言处理错误的局限性异常的定义异常的具体使用细则异常的抛出与捕获在函数调用链中异常栈展开匹配原则异常的重新抛出异常规范throw(类型)noexcept 成熟的异常体系c自己的异常体系异常的优缺点优点缺点 异常安全 C语言处理错误的局限性 C语言处理错误常常会用到assert和…...
从模型加密到授权交付,CodeMeter赋能3D打印商业化全流程
引言 在数字化制造快速演进的当下,3D 打印(增材制造)作为具备高度灵活性与创新潜力的制造方式,正重塑备件供应链与产品生命周期管理。然而,随着应用场景不断扩展,企业面临的知识产权保护、数字资产商业化与…...
ESP32开发之freeRTOS的事件组
什么是事件组事件组的应用场景事件组的API函数事件组应用举例总结什么是事件组 概念:事件组就是一个整数,高8位给内核使用,其他位用来表示事件。在ESP32的IDF freeRTOS中,这个整数是32位的,低24位用来供事件组使用。 举一个生活中的例子: 你在等快递,有三个包裹来自不…...
K8S中构建双架构镜像-从零到成功
背景介绍 公司一个客户的项目使用的全信创的环境,服务器采用arm64的机器,而我们的应用全部是amd64的,于是需要对现在公司流水线进行arm64版本的同步镜像生成。本文介绍从最开始到最终生成双架构的全部过程,以及其中使用的相关配置…...
腾讯怎样基于DeepSeek搭建企业应用?怎样私有化部署满血版DS?直播:腾讯云X DeepSeek!
2025新春,DeepSeek横空出世,震撼全球! 通过算法优化,DeepSeek将训练与推理成本降低至国际同类模型的1/10,极大的降低了AI应用开发的门槛。 可以预见,2025年,是AI应用落地爆发之年! ✔…...
【论信息系统项目的质量管理】
论信息系统项目的质量管理 前言一、抓好质量管理规划工作,为质量管理和确认提供指南和方向。二、做好管理质量相关工作,促进质量过程改进。三、抓好控制质量,确保实现质量目标四、综合协调质量与成本、进度、范围的关系总结 前言 为解决日常出…...
SpringAI框架中的RAG模块详解及应用示例
SpringAI框架中的RAG模块详解及应用示例 RAG(Retrieval-Augmented Generation)可以通过检索知识库,克服大模型训练完成后参数冻结的局限性,携带知识让大模型根据知识进行回答。SpringAI框架提供了模块化的API来支持RAG࿰…...
图像增强技术
一、目的 通过本实验加深对数字图像增强操作的理解,熟悉MATLAB中的有关函数;了解直方图均衡化和卷积滤波的原理;熟悉低通和高通滤波模板的构造方法。 二、实验内容与设计思想 1、观察实验结果可看出, 原图像 I的对比度较低&…...
【Java学习笔记】多态参数
多态参数 应用:方法定义的形参类型为父类类型,实参允许为子类类型 // 父类 package polyparemeter;public class employee {private String name;private double salary;//构造器public employee(){}public employee(String name, double salary) {thi…...
计算机网络核心技术解析:从基础架构到应用实践
计算机网络作为现代信息社会的基石,承载着全球数据交换与资源共享的核心功能。本文将从网络基础架构、核心协议、分层模型到实际应用场景,全面解析计算机网络的核心技术,并结合行业最新趋势,为读者构建系统的知识体系。 一、计算机…...
LiveData:Android响应式编程的核心利器
LiveData是一种可观察的数据持有类,用于在Android应用中实现数据的响应式编程。它具有以下特点和作用: 特点 生命周期感知:LiveData能够感知与其关联的组件(如Activity、Fragment)的生命周期状态。只有当组件处于活跃状态(如Activity处于RESUMED状态)时,LiveData才会将…...
【LeeCode】1.两数之和
文章目录 1. 暴力求解2. 哈希表具体过程1. nums [2, 7, 11, 15],target 9:2. nums [11, 15, 2, 7], target 9 给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数ÿ…...
继承关系下创建对象的具体流程
public class Person {int x initX(); // 显式初始化:调用方法 initX()public Person() {System.out.println("Parent 构造器执行, x " x);}int initX() {System.out.println("initX() 被调用了");return 100;} }public class Child extends…...
基于世界土壤数据库(HWSD)的中国土壤数据集(v1.1)(2009)
时间分辨率:年共享方式:开放获取数据大小:156.47 MB数据时间范围:2009元数据更新时间:2020-03-26 数据集摘要 数据来源于联合国粮农组织(FAO)和维也纳国际应用系统研究所(IIASA)所构建的世界土…...
mac M2能安装的虚拟机和linux系统系统
目前网上的资料大多错误,能支持M2的很少。 推荐安装的改造过的centos7也无法进行yum操作,建议安装centos8 VMware Fusion下载地址: https://pan.baidu.com/s/14v3Dy83nuLr2xOy_qf0Jvw 提取码: jri4 centos8下载地址: https://…...
212. 单词搜索 II【 力扣(LeetCode) 】
文章目录 零、原题链接一、题目描述二、测试用例三、解题思路四、参考代码 零、原题链接 212. 单词搜索 II 一、题目描述 给定一个 m x n 二维字符网格 board 和一个单词(字符串)列表 words, 返回所有二维网格上的单词 。 单词必须按照字母…...
【软考-高级】【信息系统项目管理师】论文写作注意事项及2014年至2024年历年论文题目汇总
论文写作注意事项 要求 字数要求:2500字以内(2024年超过2500字,在线答题系统无法输入)时长要求:2小时(大多数人不够用)内容要求: 必须响应子标题,如子标题要求写如何优…...
MySQL数据库表的约束
目录 1.null属性 2.默认值约束(default) 3.comment 4.zerofill 5.主键(primary key) 6.自增长(auto_increment) 7.唯一键(unique) 编辑 8.外键 约束是为了安全插入数据&a…...
硅基计划2.0 学习总结 壹 Java初阶
一、初见Java (1)Java简介 首先不得不承认Java是一门优秀的程序设计语言 其系列的计算机软件和跨平台体系包括国内的生态链完善是C/C语言难以弥补的 (2)Java SE 全称Java Standard Edition,是Java体系的基础 &am…...
逆向破解:x64dbg
文章目录 一、CPU窗口1、反汇编窗口2、寄存器窗口3、栈地址窗口4、十六进制数据窗口5、堆栈参数解析窗口 二、常用快捷键三、字符串检索功能四、调试功能1、上一步 一、CPU窗口 1、反汇编窗口 2、寄存器窗口 寄存器窗口用于显示和解释当前线程环境下CPU寄存器的各种状态值和内…...
从MCU到SoC的开发思维转变
目录 1、硬件设计 2、软件开发 3、调试与测试 4、电源管理 微控制器单元(MCU)和系统级芯片(SoC)是嵌入式开发中最常见的两种处理器类型。MCU以其简单、低功耗的特点,广泛应用于特定控制任务;而SoC凭借强…...
3DGS-to-PC:3DGS模型一键丝滑转 点云 or Mesh 【Ubuntu 20.04】【2025最新版!!】
一、引言 3D高斯泼溅(3DGS)是一种新兴的三维场景表示方法,可以生成高质量的场景重建结果。然而,要查看这些重建场景,需要特殊的高斯渲染器。大多数3D处理软件并不兼容3D高斯分布模型,但它们通常都兼容点云文件。 3DGS-to-PC项目提…...
互联网大厂Java求职面试:优惠券服务架构设计与AI增强实践-3
互联网大厂Java求职面试:优惠券服务架构设计与AI增强实践-3 场景背景 面试场景设定在一家大型互联网公司,面试官为拥有10年以上经验的技术总监,专注于高并发、高可用系统的架构设计。候选人郑薪苦是一名技术潜力十足的程序员,擅…...
ABP-Book Store Application中文讲解 - 前期准备 - Part 3:Acme.BookStore项目模块详解
ABP-Book Store Application中文讲解-汇总-CSDN博客 本文通过对Acme.BookStore项目各模块的详解,让大家知道每个project用来干什么的,他们之间的引用关系是什么,同时知道怎样添加新的功能模块。 Acme.Bookstore 是主要 ABP Studio 模块的主…...
智慧城市综合运营管理系统Axure原型
这款Axure原型的设计理念紧紧围绕城市管理者的需求展开。它旨在打破传统城市管理中信息孤岛的局面,通过统一标准接入各类业务系统,实现城市运营管理信息资源的全面整合与共享。以城市管理者为中心,为其提供一个直观、便捷、高效的协同服务平台…...
Java中进阶并发编程
第一章、并发编程的挑战 并发和并行:指多线程或多进程 线程的本质:操作系统能够进行运算调度的最小单位,是进程(Process)中的实际工作单元 进程的本质:操作系统进行资源分配和调度的基本单位,…...
cursor 出现问题 为客户解决问题
文档出自:https://www.kdocs.cn/l/cp5GpLHAWc0p...
【氮化镓】GaN在不同电子能量损失的SHI辐射下的损伤
该文的主要发现和结论如下: GaN的再结晶特性 :GaN在离子撞击区域具有较高的再结晶倾向,这导致其形成永久损伤的阈值较高。在所有研究的电子能量损失 regime 下,GaN都表现出这种倾向,但在电子能量损失增加时,其效率会降低,尤其是在材料发生解离并形成N₂气泡时。 能量损失…...
用drawdb.app可视化创建mysql关系表
平时自己建表,没有可视化图形参考 为了便于理解,用drwadb画mysql关系表 drawDB | Online database diagram editor and SQL generator...
模型上下文协议(MCP):AI的“万能插座”
~犬📰余~ “我欲贱而贵,愚而智,贫而富,可乎? 曰:其唯学乎” 一、MCP解决什么问题? \quad 在过去的几年中,AI大模型快速发展,从横空出世的GPT到“AI界拼多多”DeepSeek&am…...
初识 Pandas:Python 数据分析的利器
在数据分析、数据清洗和可视化等领域,Python 无疑是最受欢迎的语言之一,而在 Python 的数据处理生态中,Pandas 是最核心、最基础的库之一。如果你接触数据分析、机器学习、金融建模,或者只是想处理一些 Excel 表格,那么…...