javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > js反调试与混淆识别

关于JavaScript反调试与混淆识别举例详解

作者:shenyan~

JS代码混淆是一种保护代码安全的重要手段,通过将代码转换为难以理解和阅读的形式,增加代码被盗用和逆向工程的难度,这篇文章主要介绍了关于JavaScript反调试与混淆识别的相关资料,需要的朋友可以参考下

一、常见反调试手段识别

1. debugger 死循环(阻塞调试器)

样例代码:

while (true) {
  debugger;
}

原理:

特征识别:

应对方法:

traverse(ast, {
  DebuggerStatement(path) {
    path.remove();
  }
});

2. 时间延迟检测(检测调试耗时)

样例代码:

const start = Date.now();
for (let i = 0; i < 1e8; i++) {}
const end = Date.now();
if (end - start > 500) {
  alert("你调试我了!");
}

原理:

特征识别:

应对方法:

Date.now = () => 123456789;
performance.now = () => 100;

3. 函数串扰检测(检测函数是否被 hook)

样例代码:

const realAlert = alert;
alert = function () {
  console.log("你调试了吗?");
  return realAlert.apply(this, arguments);
};

原理:

特征识别:

应对方法:

alert = window.__proto__.alert;
console.log = window.__proto__.console.log;

4. Function.prototype.toString 检测(伪装函数原型)

样例代码:

if (alert.toString().indexOf('[native code]') === -1) {
  throw new Error('你修改了 alert 函数!');
}

原理:

反制手段:

alert = new Proxy(alert, {
  apply: function(target, thisArg, argumentsList) {
    return target.apply(thisArg, argumentsList);
  }
});
alert.toString = function() {
  return "function alert() { [native code] }";
};

5. window.console 检测

样例代码:

if (!window.console || typeof console.log !== "function") {
  throw new Error("console 被篡改!");
}

原理:

解决方式:

6. 异常捕获干扰调试

样例代码:

try {
  throw new Error("调试干扰");
} catch (e) {
  debugger;
}

原理:

应对方案:

7. 堆栈跟踪干扰(检测调试器存在)

样例代码:

function checkStack() {
  try {
    throw new Error();
  } catch (e) {
    if (e.stack.indexOf("Debugger") !== -1) {
      alert("发现调试器");
    }
  }
}

原理:

应对:

8. requestAnimationFrame 反调试

样例代码:

let lastTime = performance.now();
function checkDebugger() {
  let now = performance.now();
  if (now - lastTime > 100) {
    alert("调试器卡住了浏览器");
  }
  lastTime = now;
  requestAnimationFrame(checkDebugger);
}
requestAnimationFrame(checkDebugger);

原理:

应对方法:

9. 全局函数自毁 / 还原干扰

样例代码:

(function(){
  Function = null;
})();

原理:

应对方法:

总结

技术名检测方式应对手段
debugger 死循环debugger 频繁触发AST 移除 / DevTools 设置
时间延迟检测Date.now() / performance.now()Hook 时间函数 / 删除判断
函数串扰检查 alert、console、eval 是否被 hook还原函数 / Proxy + toString 伪装
toString 检测判断函数是否为 [native code]改写 toString 方法
console 检测是否存在 console 方法伪造完整 console 对象结构
异常捕获干扰try-catch 嵌入 debugger替换整个代码块
堆栈跟踪调试检测分析 Error.stack 内容重写 Error / 清空 stack
requestAnimationFrame 检测检测浏览器执行频率重写 rAF / 时间伪造

二、JavaScript 混淆手法

混淆的目标是让代码变得:

1. 字符串拆分拼接

原始代码:

var key = "secretKey";

混淆后代码示例:

var _0x1a2b = "sec" + "ret" + "Key";

甚至更复杂:

var a = "s";
var b = "e";
var c = b + "c";
var d = a + c + "ret" + "Key";  // 最终是 "secretKey"

混淆目的:

识别与还原方法:

traverse(ast, {
  BinaryExpression(path) {
    if (path.node.operator === "+" &&
        t.isStringLiteral(path.node.left) &&
        t.isStringLiteral(path.node.right)) {
      path.replaceWith(
        t.stringLiteral(path.node.left.value + path.node.right.value)
      );
    }
  }
});

2. base64 编码隐藏字符串

混淆代码示例:

var data = atob("c2VjcmV0S2V5");  // "secretKey"

原理:

混淆目的:

识别特征:

还原方式:

console.log(atob("c2VjcmV0S2V5"));

3. eval / Function 动态执行

示例 1(eval):

eval("console." + "log('hello')");

示例 2(Function):

var code = "return 5 + 5;";
var fn = new Function(code);
console.log(fn()); // 输出 10

原理:

特征识别:

还原与处理:

1)打补丁替换 eval 为 console.log

eval = console.log;  // 打印出真实代码

2)拦截 Function:

window.Function = function(code) {
  console.log("[HOOKED FUNCTION]:", code);
  return () => {};
};

3)使用 AST 替换 eval:

traverse(ast, {
  CallExpression(path) {
    if (path.node.callee.name === 'eval') {
      path.node.callee.name = 'console.log';
    }
  }
});

4. 数组 + 索引跳转(Control Flow Flattening)

混淆代码示例:

var _0xabc = [
  "log",         // 索引 0
  "Hello",       // 索引 1
  "console",     // 索引 2
];

(function(arr) {
  var a = arr[2]; // "console"
  var b = arr[0]; // "log"
  var c = arr[1]; // "Hello"
  window[a][b](c);  // => console.log("Hello")
})(_0xabc);

或者极端一点:

var arr = ["\x63\x6f\x6e\x73\x6f\x6c\x65", "\x6c\x6f\x67"];
window[arr[0]][arr[1]]("hi");

原理:

特征识别:

还原方法:

总结

混淆类型特征应对手段
字符串拼接多个字符串拼成关键字AST 静态还原 / 打断点查看
Base64 编码出现 atob(), 字符串有 =解码查看 / Python 辅助
动态执行evalFunctionsetTimeoutHook 动态函数 / AST 替换打印
数组+索引跳转大数组 + 随机索引访问还原数组映射 / 替换所有访问语句

三、AST 抽象语法树分析

AST(抽象语法树) 是程序源代码的结构化、树状表示。

在 JavaScript 中,一段代码:

var a = "hello";

会被转换为一个 AST 树结构,描述这段代码的结构,比如:VariableDeclaration -> VariableDeclarator -> Identifier + Literal

它并不是运行代码,而是「代码结构本身」的抽象。

在 JS 混淆还原、定位加密函数、批量清理垃圾逻辑时,AST 是最强的静态分析工具:

任务AST 作用
还原混淆(字符串拼接、数组索引)静态提取还原拼接结果
删除垃圾代码(无用判断等)删除某些结构的语句(如 if (false)
替换函数调用将 eval() 改为 console.log() 等
查找加密入口、核心参数生成定位函数名和依赖链,追踪代码调用路径

Babel 是 JS 编译领域的核心工具,它能:

1. Babel AST 操作的基本流程

安装依赖(Node 环境)

npm install @babel/parser @babel/traverse @babel/generator

1)将代码解析成 AST

const parser = require('@babel/parser');
const code = 'var a = "he" + "llo";';

const ast = parser.parse(code, {
  sourceType: 'module'
});

2)遍历 AST 并修改

const traverse = require('@babel/traverse').default;
const t = require('@babel/types');

traverse(ast, {
  BinaryExpression(path) {
    if (
      path.node.operator === '+' &&
      t.isStringLiteral(path.node.left) &&
      t.isStringLiteral(path.node.right)
    ) {
      // 替换拼接表达式为结果字符串
      path.replaceWith(
        t.stringLiteral(path.node.left.value + path.node.right.value)
      );
    }
  }
});

3)生成新代码

const generate = require('@babel/generator').default;
const output = generate(ast);
console.log(output.code); // var a = "hello";

2. 实战:还原混淆数组+索引跳转代码

示例混淆代码:

var _0xabc = ["se", "cret", "Key"];
var key = _0xabc[0] + _0xabc[1] + _0xabc[2];

还原目标:把 _0xabc[0] 直接替换成 "se" 等,变成:

var key = "secretKey";

解法思路:

Babel 脚本:

const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const generate = require('@babel/generator').default;
const t = require('@babel/types');

const code = `
var _0xabc = ["se", "cret", "Key"];
var key = _0xabc[0] + _0xabc[1] + _0xabc[2];
`;

const ast = parser.parse(code);

let mapping = {};

traverse(ast, {
  VariableDeclarator(path) {
    if (
      t.isIdentifier(path.node.id) &&
      t.isArrayExpression(path.node.init)
    ) {
      const arrName = path.node.id.name;
      const elements = path.node.init.elements;
      mapping[arrName] = elements.map(e => e.value);
    }
  },

  MemberExpression(path) {
    const obj = path.node.object;
    const prop = path.node.property;

    if (
      t.isIdentifier(obj) &&
      mapping[obj.name] &&
      t.isNumericLiteral(prop)
    ) {
      const value = mapping[obj.name][prop.value];
      path.replaceWith(t.stringLiteral(value));
    }
  }
});

const output = generate(ast);
console.log(output.code);

3. 辅助工具推荐

工具说明
AST Explorer可视化查看 AST 结构,非常适合新手理解
Babel + Node 脚本实际静态还原代码
Chrome DevTools配合调试、打断点验证逻辑是否还原成功

总结

AST 是在面对 JS 混淆与参数还原时,最强的“静态分析武器”,一旦掌握,就能自动还原大量加密、反调试逻辑,让 JS 逆向效率质变!

四、定位核心参数生成函数

在爬虫、逆向场景中,服务端通常要求提交一些“加密参数”:

我们的任务是定位并还原这些函数!

1. 典型例子

例如某请求:

POST /api/check
headers:
  w: "e72fa5b18d320...(加密值)"

需要搞清楚:

2. 定位思路总览

方法原理
1. 关键词搜索搜索 w=sign=headersFormData.w.sign
2. hook XMLHttpRequest / fetch拦截请求参数,看 w 的生成前有哪些代码执行
3. 打断点(XHR/fetch/send)手动调试,寻找传输逻辑、函数调用栈
4. 控制台 hook 全局函数重定义 CryptoJS.MD5btoa(),打印入参与结果
5. 格式化 + 搜索函数调用格式化 JS 源码,搜索可疑函数调用
6. DOM 元素关联有些加密数据来源于点击、坐标、行为序列
7. AST 分析静态查找函数依赖链、追踪返回值
8. Blob / Worker 调试查看是否把加密逻辑放在独立线程或动态 blob JS 里

3. 最常用方式详解

【方法 1】关键字搜索法(适用于未严重混淆)

搜索:

"w="
"sign="
"form.append"
"headers"
"return {"

例子:

var t = get_w(UA, timestamp);
formData.append("w", t);

通过定位 get_w(),再深入分析。

【方法 2】hook fetch / XMLHttpRequest 拦截入参

// hook fetch
window.fetch = new Proxy(window.fetch, {
  apply(target, thisArg, args) {
    console.log("[fetch]", args);
    return Reflect.apply(target, thisArg, args);
  }
});

// hook XHR
const open = XMLHttpRequest.prototype.open;
const send = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function () {
  this._url = arguments[1];
  return open.apply(this, arguments);
};
XMLHttpRequest.prototype.send = function (body) {
  console.log("[XHR]", this._url, body);
  return send.apply(this, arguments);
};

作用:

【方法 3】打断点(最直接有效)

位置推荐:

技巧:

【方法 4】hook 加密函数打印入参

常见加密函数有:

CryptoJS.MD5(xxx)
CryptoJS.AES.encrypt
btoa()
encodeURIComponent()

可以这样 hook:

CryptoJS.MD5 = function (arg) {
  console.log("[MD5]", arg);
  return originalMD5(arg); // 原函数
}

或者 hook 所有函数:

Function.prototype.call = new Proxy(Function.prototype.call, {
  apply(target, thisArg, args) {
    console.log("[CALL]", thisArg, args);
    return Reflect.apply(target, thisArg, args);
  }
});

【方法 5】格式化搜索函数调用

使用 Pretty Print 格式化混淆代码,再查找形如:

var w = a.b(c, d);  // 参数生成函数

【方法 6】AST 静态追踪核心函数

const parser = require("@babel/parser");
const traverse = require("@babel/traverse").default;
const fs = require("fs");

const code = fs.readFileSync("./encrypt.js").toString();
const ast = parser.parse(code);

traverse(ast, {
  CallExpression(path) {
    const { callee } = path.node;
    if (
      callee.type === "Identifier" &&
      callee.name === "get_w"  // 可替换成你猜测的函数名
    ) {
      console.log("Found w generator:", path.toString());
    }
  }
});

也可静态追踪返回的字符串是否带有 w=...

4. 实战案例简化版

例子:

function gen_w(ts, cookie, ua) {
  var str = ts + "|" + cookie + "|" + ua;
  return btoa(str);
}

let w = gen_w(Date.now(), document.cookie, navigator.userAgent);

分析流程:

就可以写脚本还原它。

5. W 参数常见特征

特征解读
固定长度多为 32、64、128 位(MD5、SHA1、AES 编码)
每次不同含时间戳、行为 ID、cookie 等
与滑动验证/行为交互相关w 中可能包含点击坐标、移动轨迹、session_id、lot_number 等
通常通过层层封装多层函数嵌套,常混淆关键函数名

6. 辅助工具推荐

工具名用途
Charles/Fiddler抓包查看真实参数
DevTools Source Map调试压缩源码前的真实结构
Babel Parser + Traverse静态定位函数/AST 跟踪
mitmproxy + JS hook手机端逆向生成参数
Obfuscator-IO-Deobfuscator一键还原混淆代码

总结

定位加密函数 = 抓到“w 参数生成”的函数,并拆解其中逻辑(参数输入、算法过程、输出)

通常会结合这些手段:

到此这篇关于关于JavaScript反调试与混淆识别的文章就介绍到这了,更多相关js反调试与混淆识别内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文