JavaScript 调试问题

JavaScript 调试问题

JavaScript 逆向和调试中的常见问题及解决方案。


断点不生效

问题表现

  • 设置断点后代码不暂停
  • 断点显示灰色
  • 代码跳过断点继续执行

原因分析

  1. 代码已被优化或内联
  2. Source Map 不匹配
  3. 异步代码执行时机问题
  4. 代码被动态生成

解决方案

1. 使用 debugger 语句

1
2
3
4
5
// 在代码中直接插入
function suspiciousFunction(data) {
debugger; // 强制暂停
// ... 后续代码
}

通过 Hook 注入:

1
2
3
4
5
6
// 在控制台执行
const original = window.someFunction;
window.someFunction = function (...args) {
debugger; // 在调用前暂停
return original.apply(this, args);
};

2. 使用条件断点

在 DevTools 中右键断点 → Edit breakpoint:

1
2
3
4
5
// 只有当条件满足时才暂停
userId === 12345;

// 或使用表达式
console.log("Value:", someVar) || false;

3. DOM 断点

1
2
3
4
// 监听 DOM 修改
const element = document.querySelector("#target");

// Break on: subtree modifications, attribute modifications, node removal

4. Event Listener 断点

1
2
3
4
5
// DevTools → Sources → Event Listener Breakpoints
// 勾选相关事件 (如 click, xhr, timeout)

// 或者代码中监听
monitorEvents(document.body, "click");

无法查看变量值

问题表现

  • 变量显示 undefined<unavailable>
  • Scope 中看不到变量
  • 闭包变量无法访问

解决方案

1. 检查作用域

1
2
3
4
5
6
7
8
// 在 Console 中,使用正确的作用域
// 如果在函数内部断点:

// ❌ 错误 - 全局作用域
console.log(localVar); // undefined

// ✅ 正确 - 当前作用域可见
// 直接在 Scope 面板查看,或在 Console 输入变量名

2. 使用 Watch 表达式

1
2
3
4
5
6
// Sources → Watch → Add expression

// 添加复杂表达式
this.userData.profile.name;
JSON.stringify(config, null, 2);
Object.keys(this);

3. 使用 console.dir 查看对象

1
2
3
4
5
6
7
8
// 查看对象完整结构
console.dir(complexObject);

// 查看原型链
console.log(Object.getPrototypeOf(obj));

// 查看所有属性
console.log(Object.getOwnPropertyNames(obj));

4. 临时修改代码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// 使用 Overrides 功能
// DevTools → Sources → Overrides → Enable Local Overrides

// 在代码中添加日志
function encrypt(data) {
console.log("encrypt input:", data); // 添加这行
const result = doEncrypt(data);
console.log("encrypt output:", result); // 添加这行
return result;
}

Source Map 问题

问题表现

  • 代码显示混淆后的版本
  • 无法看到原始代码
  • Source Map 加载失败

解决方案

1. 手动加载 Source Map

1
2
3
4
5
// 如果有 .map 文件
//# sourceMappingURL=app.js.map

// 在 DevTools → Sources 右键文件 → "Add source map"
// 输入 Source Map URL

2. 使用在线工具

1
2
3
4
5
# 使用 source-map-cli 还原
npm install -g source-map-cli

# 查看原始位置
source-map resolve app.js.map 1 100

3. 格式化代码

1
2
// DevTools → Sources → 点击 {} 按钮 (Pretty print)
// 或快捷键: Ctrl + Shift + P → "Pretty print"

混淆代码调试

问题表现

  • 变量名如 _0x1a2b, a, b
  • 代码逻辑难以理解
  • 大量无意义代码

解决方案

1. 使用 AST 工具还原

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// 使用 Babel 还原
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, {
Identifier(path) {
if (path.node.name.startsWith("_0x")) {
path.node.name = "var_" + Math.random().toString(36).substr(2, 9);
}
},
});

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

2. 在线反混淆工具

3. 动态调试追踪

1
2
3
4
5
6
7
8
// Hook 所有函数调用
const originalFunction = window._0x1a2b;
window._0x1a2b = function (...args) {
console.log("Called with:", args);
const result = originalFunction.apply(this, args);
console.log("Returned:", result);
return result;
};

4. 使用反混淆脚本

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// Tampermonkey 脚本
// 在页面加载前注入
(function () {
"use strict";

// Hook eval
const originalEval = window.eval;
window.eval = function (code) {
console.log("eval code:", code);
return originalEval.call(this, code);
};

// Hook Function 构造器
const OriginalFunction = window.Function;
window.Function = function (...args) {
console.log("Function args:", args);
return OriginalFunction.apply(this, args);
};
})();

异步代码跟踪

问题表现

  • Promise/async 代码难以调试
  • 回调地狱
  • 执行顺序混乱

解决方案

1. Async Stack Traces

1
2
3
4
5
6
7
8
9
// Chrome DevTools 默认启用
// Settings → Enable async stack traces

// 现在可以看到完整的异步调用栈
async function fetchData() {
debugger; // 可以看到异步调用链
const data = await fetch("/api/data");
return data.json();
}

2. 使用 console.trace

1
2
3
4
5
6
7
8
9
async function complexAsync() {
console.trace("Start"); // 显示调用栈

await step1();
console.trace("After step1");

await step2();
console.trace("After step2");
}

3. 添加异步日志

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// 包装 fetch
const originalFetch = window.fetch;
window.fetch = async function (...args) {
console.log("Fetch started:", args[0]);
try {
const response = await originalFetch.apply(this, args);
console.log("Fetch completed:", response.status);
return response;
} catch (error) {
console.error("Fetch failed:", error);
throw error;
}
};

XHR/Fetch 请求拦截

问题表现

  • 无法捕获 AJAX 请求
  • 需要查看请求参数
  • 需要修改请求

解决方案

1. XHR 断点

1
2
// DevTools → Sources → XHR/fetch Breakpoints
// 添加 URL 包含的关键字,如 "/api/"

2. Hook XMLHttpRequest

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(function () {
const XHR = XMLHttpRequest.prototype;
const open = XHR.open;
const send = XHR.send;

XHR.open = function (method, url) {
this._method = method;
this._url = url;
return open.apply(this, arguments);
};

XHR.send = function (data) {
console.log("XHR Request:", {
method: this._method,
url: this._url,
data: data,
});

this.addEventListener("load", function () {
console.log("XHR Response:", {
status: this.status,
response: this.responseText,
});
});

return send.apply(this, arguments);
};
})();

3. Hook Fetch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
(function () {
const originalFetch = window.fetch;

window.fetch = async function (...args) {
console.log("Fetch Request:", args);

const response = await originalFetch.apply(this, args);

// Clone 响应以避免消费
const clonedResponse = response.clone();
const text = await clonedResponse.text();

console.log("Fetch Response:", {
status: response.status,
body: text,
});

return response;
};
})();

WebAssembly 调试

问题表现

  • WASM 代码难以理解
  • 无法设置断点
  • 变量查看困难

解决方案

1. 使用 WASM Debug Info

1
2
3
4
5
6
7
// 如果 WASM 包含调试信息
// Chrome DevTools 可以显示源码

// 查看 WASM 模块
WebAssembly.instantiate(bytes, imports).then((result) => {
console.log(result.instance.exports);
});

2. 使用 wasmtime/wasmer 调试

1
2
3
4
5
# 使用 wasmtime 运行并调试
wasmtime --invoke main module.wasm

# 使用 wasmer
wasmer run module.wasm

3. Hook WASM 函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 获取 WASM 实例
const instance = wasmInstance;

// Hook 导出函数
const originalFunc = instance.exports.encrypt;
instance.exports.encrypt = function (...args) {
console.log("WASM encrypt called:", args);
const result = originalFunc.apply(this, args);
console.log("WASM encrypt result:", result);
return result;
};

时间相关问题

问题表现

  • 时间戳检测
  • 超时失效
  • 定时器问题

解决方案

1. Hook Date

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// 固定时间
const fixedTime = new Date("2024-01-01 00:00:00").getTime();

const OriginalDate = Date;
window.Date = function (...args) {
if (args.length === 0) {
return new OriginalDate(fixedTime);
}
return new OriginalDate(...args);
};
Date.now = function () {
return fixedTime;
};
Date.prototype = OriginalDate.prototype;

2. Hook setTimeout/setInterval

1
2
3
4
5
const originalSetTimeout = window.setTimeout;
window.setTimeout = function (callback, delay, ...args) {
console.log(`setTimeout called: ${delay}ms`);
return originalSetTimeout(callback, delay, ...args);
};

无限 debugger

问题表现

1
2
3
4
// 反调试代码
setInterval(function () {
debugger;
}, 100);

解决方案

1. 禁用断点

1
2
// Chrome DevTools:
// 点击 "Deactivate breakpoints" 按钮 (Ctrl + F8)

2. 条件断点绕过

1
2
3
4
// 在 debugger 语句上右键 → "Never pause here"

// 或添加条件断点
false; // 永远不暂停

3. 替换 debugger

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 使用 Overrides 或 Requestly
// 将代码中的 debugger 替换为空语句

// 或 Hook Function
const _constructor = Function.prototype.constructor;
Function.prototype.constructor = function (...args) {
if (args.length > 0 && /debugger/.test(args[args.length - 1])) {
return function () {};
}
return _constructor.apply(this, args);
};

调试技巧总结

1. 快捷键

操作Windows/LinuxMac
打开 DevToolsF12Cmd + Opt + I
打开控制台Ctrl + Shift + JCmd + Opt + J
下一步F10F10
进入函数F11F11
跳出函数Shift + F11Shift + F11
继续执行F8F8
禁用断点Ctrl + F8Cmd + F8

2. Console API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 分组日志
console.group("Group 1");
console.log("message 1");
console.log("message 2");
console.groupEnd();

// 表格显示
console.table([
{ name: "a", value: 1 },
{ name: "b", value: 2 },
]);

// 计时
console.time("operation");
// ... 操作
console.timeEnd("operation");

// 计数
console.count("counter"); // counter: 1
console.count("counter"); // counter: 2

// 断言
console.assert(1 === 2, "Should not happen");

3. Performance 调试

1
2
3
4
5
6
7
// 性能标记
performance.mark("start");
// ... 操作
performance.mark("end");
performance.measure("operation", "start", "end");

console.log(performance.getEntriesByType("measure"));

相关章节


相关内容

如果这篇文章对你有帮助,请我喝杯咖啡吧~
+5 支付宝支付宝
+5 微信微信