动态分析完整方法论
动态调试的几种方法
动态调试是指在程序运行过程中对其进行分析和调试的技术,主要有以下几种方法:
一、按调试方式分类
1. 断点调试(Breakpoint Debugging)
| 类型 | 说明 |
|---|---|
| 软件断点 | 通过替换指令(如 INT 3 / 0xCC)实现 |
| 硬件断点 | 利用 CPU 调试寄存器(DR0-DR7),数量有限(通常4个) |
| 内存断点 | 监控内存的读/写/执行访问 |
| 条件断点 | 满足特定条件时才触发中断 |
2. 单步跟踪(Single Stepping)
- 步入(Step Into):逐条指令执行,遇到函数调用会进入函数内部
- 步过(Step Over):逐条执行,但跳过函数调用(整个函数当作一步)
- 步出(Step Out):执行到当前函数返回为止
3. 附加调试(Attach)
text
调试器 → 附加到已运行的进程 → 进行调试
- 不需要从头启动程序
- 适用于服务类程序、已运行的进程
4. 远程调试(Remote Debugging)
text
┌──────────┐ 网络连接 ┌──────────┐
│ 调试端PC │ ◄──────────► │ 目标设备 │
│ (IDE/GDB) │ │(gdbserver)│
└──────────┘ └──────────┘
- 常用于嵌入式设备、手机APP、服务器程序
5. 追踪调试(Tracing)
- 指令追踪:记录每条执行的指令
- 函数追踪:记录函数调用关系
- 系统调用追踪:如
strace(Linux)、dtrace(macOS)
二、按应用场景分类
📱 移动端动态调试
| 平台 | 工具 | 方法 |
|---|---|---|
| Android | Frida、IDA、GDB | USB调试/远程附加 |
| iOS | LLDB、Frida、Cycript | SSH连接调试 |
💻 桌面端动态调试
| 平台 | 工具 |
|---|---|
| Windows | OllyDbg、x64dbg、WinDbg、IDA |
| Linux | GDB、EDB、IDA、radare2 |
| macOS | LLDB、IDA |
🌐 Web / 脚本调试
| 类型 | 工具 |
|---|---|
| JavaScript | Chrome DevTools、Node Inspector |
| Python | pdb、PyCharm Debugger |
| Java | JDB、IntelliJ IDEA |
三、按技术原理分类
1. 基于中断机制
text
程序执行 → 触发断点(INT 3) → CPU 产生异常 → OS 通知调试器 → 调试器接管控制
2. 基于 Hook / 插桩(Instrumentation)
- 动态插桩:Frida、DynamoRIO、Pin
- 在运行时注入代码,拦截/修改函数行为
JavaScript
// Frida 示例
Interceptor.attach(targetAddr, {
onEnter: function(args) {
console.log("参数:", args[0]);
},
onLeave: function(retval) {
console.log("返回值:", retval);
}
});
3. 基于虚拟化 / 模拟器
- QEMU:模拟不同架构的 CPU 运行程序
- Unicorn Engine:轻量级 CPU 模拟框架
- 适合分析固件、跨平台调试
4. 基于调试API
- Windows:
DebugActiveProcess()、WaitForDebugEvent() - Linux:
ptrace()系统调用 - macOS:
task_for_pid()+ Mach 异常
四、总结对比
text
动态调试方法
├── 断点调试 ──── 最基础、最常用
├── 单步跟踪 ──── 精细分析逻辑流程
├── 附加调试 ──── 调试已运行进程
├── 远程调试 ──── 嵌入式/移动端场景
├── Hook/插桩 ── 无需断点,灵活监控
├── 追踪调试 ──── 记录执行轨迹
└── 模拟执行 ──── 跨平台/安全分析
实际工作中,通常是多种方法结合使用。例如:先用 Hook 定位关键函数 → 再用断点调试分析具体逻辑 → 配合内存断点追踪数据变化。
总体框架
text
动态分析两大核心手段:
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 手段一:插桩分析(Instrumentation) │
│ → 在目标程序中插入监控代码 │
│ → 自动化收集函数调用、参数、返回值 │
│ → 可修改执行流程 │
│ → 工具:Frida / Xposed / LIEF / DynamoRIO / Pin │
│ → 特点:速度快、自动化程度高、适合批量Hook │
│ │
│ 手段二:手动调试(Debugging) │
│ → 断点 + 单步执行 + 内存/寄存器/栈检查 │
│ → 精确控制程序每一步执行 │
│ → 工具:GDB / LLDB / IDA调试器 / x64dbg / Radare2 │
│ → 特点:精度最高、通用性最强、但速度慢 │
│ │
│ 两者配合: │
│ 插桩 → 快速定位关键函数和数据流 │
│ 调试 → 深入分析关键函数的具体逻辑 │
│ │
└─────────────────────────────────────────────────────────────────┘
第一部分:插桩分析
一、插桩的分类
text
┌────────────────────────────────────────────────────────────────────┐
│ │
│ 按时机分类: │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 静态插桩 │ │ 动态插桩 │ │ 编译时插桩 │ │
│ │ │ │ │ │ │ │
│ │ 修改二进制文件│ │ 运行时注入代码│ │ 编译时插入代码│ │
│ │ 永久性修改 │ │ 不修改文件 │ │ 需要源码 │ │
│ │ │ │ │ │ │ │
│ │ LIEF │ │ Frida │ │ ASAN │ │
│ │ LIEF │ │ DynamoRIO │ │ gcov │ │
│ │ Patchelf │ │ Intel Pin │ │ __sanitizer │ │
│ │ Binary Patch │ │ Valgrind │ │ │ │
│ │ Xposed(安装) │ │ Xposed(运行) │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ 按层级分类: │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 函数级Hook │ │ 指令级插桩 │ │ 系统调用级 │ │
│ │ │ │ │ │ │ │
│ │ 拦截函数入口 │ │ 每条指令前后 │ │ 拦截系统调用 │ │
│ │ 和出口 │ │ 插入回调 │ │ │ │
│ │ │ │ │ │ │ │
│ │ Frida │ │ DynamoRIO │ │ strace │ │
│ │ Inline Hook │ │ Intel Pin │ │ ltrace │ │
│ │ PLT/GOT Hook │ │ Unicorn │ │ seccomp │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────┘
二、Frida 动态插桩详解 ★★★★★
2.1 Frida 架构
text
┌───────────────────────────────────────────────────────┐
│ 分析机(PC) │
│ │
│ ┌─────────────────┐ ┌──────────────────────┐ │
│ │ Python 脚本 │ │ frida CLI │ │
│ │ (控制端) │ │ frida-trace │ │
│ │ │ │ frida-ps │ │
│ │ frida.attach() │ │ │ │
│ │ session.create │ │ │ │
│ │ _script() │ │ │ │
│ └────────┬────────┘ └──────────┬───────────┘ │
│ │ │ │
│ └───────────┬───────────┘ │
│ │ USB / TCP / 本地 │
│ │ │
└───────────────────────┼───────────────────────────────┘
│
┌───────────────────────┼───────────────────────────────┐
│ │ 目标设备/进程 │
│ ▼ │
│ ┌─────────────────┐ │
│ │ frida-server │ │
│ │ (设备端守护进程) │ │
│ └────────┬────────┘ │
│ │ ptrace / 注入 │
│ ▼ │
│ ┌─────────────────┐ │
│ │ 目标进程 │ │
│ │ ┌─────────────┐│ │
│ │ │ frida-agent ││ ← 注入的V8引擎 │
│ │ │ (V8/QuickJS)││ │
│ │ │ ││ │
│ │ │ JS Hook代码 ││ ← 你写的Hook脚本 │
│ │ └─────────────┘│ │
│ └─────────────────┘ │
│ │
└───────────────────────────────────────────────────────┘
2.2 基础连接方式
Bash
# 查看设备上的进程
frida-ps -U # USB 连接的 Android/iOS
frida-ps -H 192.168.1.100 # 远程设备
frida-ps # 本地进程
# 附加到已运行的进程
frida -U -p <PID> -l hook.js # 按 PID 附加
frida -U -n "com.example.app" -l hook.js # 按进程名附加
# Spawn 模式(从头开始监控)★ 推荐
frida -U -f com.example.app -l hook.js --no-pause
# -f = spawn 启动应用
# --no-pause = 注入后自动恢复执行
# Python 控制端
python3 frida_controller.py
Python
# frida_controller.py — Python 控制端模板
import frida
import sys
def on_message(message, data):
if message['type'] == 'send':
print(f"[*] {message['payload']}")
elif message['type'] == 'error':
print(f"[!] {message['stack']}")
# 连接设备
device = frida.get_usb_device()
# Spawn 模式启动
pid = device.spawn(["com.example.app"])
session = device.attach(pid)
# 加载脚本
with open("hook.js", "r") as f:
script = session.create_script(f.read())
script.on('message', on_message)
script.load()
# 恢复进程执行
device.resume(pid)
# 保持运行
print("[*] 按 Ctrl+C 退出")
try:
sys.stdin.read()
except KeyboardInterrupt:
session.detach()
2.3 函数调用流程追踪
JavaScript
// ═══════════════════════════════════════════════════
// trace_call_flow.js — 函数调用流程追踪
// ═══════════════════════════════════════════════════
// ── 1. 追踪指定模块的所有导出函数 ──
var module = Process.findModuleByName("libtarget.so");
var exports = module.enumerateExports();
var indent = 0;
exports.forEach(function(exp) {
if (exp.type === 'function') {
Interceptor.attach(exp.address, {
onEnter: function(args) {
var padding = "│ ".repeat(indent);
console.log(padding + "┌─ " + exp.name + "()");
indent++;
this.startTime = Date.now();
// 保存前几个参数(通用打印)
this.args = [];
for (var i = 0; i < 4; i++) {
this.args.push(args[i]);
}
},
onLeave: function(retval) {
indent--;
var padding = "│ ".repeat(indent);
var elapsed = Date.now() - this.startTime;
console.log(padding + "└─ " + exp.name +
"() → " + retval +
" (" + elapsed + "ms)");
}
});
}
});
// 输出效果:
// ┌─ JNI_OnLoad()
// │ ┌─ init_security()
// │ │ ┌─ load_key()
// │ │ └─ load_key() → 0x1 (2ms)
// │ │ ┌─ setup_anti_debug()
// │ │ └─ setup_anti_debug() → 0x0 (15ms)
// │ └─ init_security() → 0x1 (20ms)
// └─ JNI_OnLoad() → 0x10006 (25ms)
JavaScript
// ── 2. 使用 frida-trace 快速追踪(命令行一键) ──
// 终端直接运行:
// frida-trace -U -f com.example.app -i "AES*" -i "EVP*" -i "*encrypt*" -i "*decrypt*"
// frida-trace -U -f com.example.app -I "libtarget.so" # 追踪整个库
// frida-trace 会自动生成 handler 脚本到 __handlers__/ 目录
// 可以编辑这些脚本添加参数打印逻辑
2.4 函数入参和返回值分析
JavaScript
// ═══════════════════════════════════════════════════
// hook_params_retval.js — 参数和返回值详细分析
// ═══════════════════════════════════════════════════
// ── 通用参数解析器 ──
function dumpArgs(name, args, specs) {
/**
* specs 格式: [{name: "参数名", type: "类型"}, ...]
* 类型支持: ptr, int, uint, str, bytes:N, jstring, jbytearray
*/
console.log("\n╔══ " + name + " ══╗");
specs.forEach(function(spec, i) {
var val = args[i];
var display = "";
switch(spec.type) {
case 'int':
display = val.toInt32();
break;
case 'uint':
display = val.toUInt32 ? val.toUInt32() : "0x" + val.toString(16);
break;
case 'ptr':
display = val;
if (!val.isNull()) {
display += " → " + hexdump(val, {length: 32, ansi: true});
}
break;
case 'str':
display = val.isNull() ? "NULL" : val.readCString();
break;
case 'bool':
display = val.toInt32() ? "true" : "false";
break;
default:
// bytes:N 格式
if (spec.type.startsWith('bytes:')) {
var len = parseInt(spec.type.split(':')[1]);
display = "\n" + hexdump(val, {length: len, ansi: true});
} else {
display = val;
}
}
console.log("║ " + spec.name + " (" + spec.type + "): " + display);
});
console.log("╚══════════════════════╝");
}
// ── 具体函数 Hook 示例 ──
// 1. OpenSSL EVP 加密
var EVP_EncryptInit = Module.findExportByName("libcrypto.so", "EVP_EncryptInit_ex");
if (EVP_EncryptInit) {
Interceptor.attach(EVP_EncryptInit, {
onEnter: function(args) {
this.ctx = args[0];
var cipher = args[1];
var cipher_name = "unknown";
if (!cipher.isNull()) {
// EVP_CIPHER_name 获取算法名
var EVP_CIPHER_nid = Module.findExportByName("libcrypto.so", "EVP_CIPHER_nid");
if (EVP_CIPHER_nid) {
var nid_func = new NativeFunction(EVP_CIPHER_nid, 'int', ['pointer']);
cipher_name = "NID:" + nid_func(cipher);
}
}
var key = args[3];
var iv = args[4];
console.log("\n[EVP_EncryptInit_ex]");
console.log(" 算法: " + cipher_name);
if (!key.isNull()) {
console.log(" 密钥:\n" + hexdump(key, {length: 32}));
}
if (!iv.isNull()) {
console.log(" IV:\n" + hexdump(iv, {length: 16}));
}
console.log(" 调用栈:\n " +
Thread.backtrace(this.context, Backtracer.ACCURATE)
.map(DebugSymbol.fromAddress).join("\n "));
}
});
}
// 2. 自定义函数(通过偏移地址 Hook)
var base = Module.findModuleByName("libtarget.so").base;
// Hook 偏移 0x8500 处的函数
// 已知签名: int verify_signature(const char* data, int len, const char* sig)
Interceptor.attach(base.add(0x8500), {
onEnter: function(args) {
this.data = args[0];
this.len = args[1].toInt32();
this.sig = args[2];
dumpArgs("verify_signature", args, [
{name: "data", type: "str"},
{name: "len", type: "int"},
{name: "sig", type: "bytes:64"}
]);
},
onLeave: function(retval) {
console.log("[verify_signature] 返回值: " + retval.toInt32() +
(retval.toInt32() === 1 ? " ✅ 验证通过" : " ❌ 验证失败"));
}
});
// 3. JNI 函数参数解析(Android)
// Hook native 方法: byte[] encrypt(byte[] data, String key)
var encrypt_addr = base.add(0x9000);
Interceptor.attach(encrypt_addr, {
onEnter: function(args) {
// JNI 调用约定: args[0]=JNIEnv*, args[1]=jobject/jclass, args[2+]=实际参数
var env = args[0];
var obj = args[1];
var jdata = args[2]; // jbyteArray
var jkey = args[3]; // jstring
// 解析 jbyteArray
var JNIEnv = Java.vm.getEnv();
var data_len = JNIEnv.getArrayLength(jdata);
var data_ptr = JNIEnv.getByteArrayElements(jdata, NULL);
console.log("\n[encrypt] JNI 调用:");
console.log(" 输入数据 (" + data_len + " bytes):");
console.log(hexdump(data_ptr, {length: Math.min(data_len, 64)}));
// 解析 jstring
var key_str = JNIEnv.getStringUtfChars(jkey, NULL).readCString();
console.log(" 密钥字符串: " + key_str);
// 保存用于 onLeave
this.env = JNIEnv;
this.jdata = jdata;
},
onLeave: function(retval) {
// 返回值是 jbyteArray
if (!retval.isNull()) {
var result_len = this.env.getArrayLength(retval);
var result_ptr = this.env.getByteArrayElements(retval, NULL);
console.log(" 返回数据 (" + result_len + " bytes):");
console.log(hexdump(result_ptr, {length: Math.min(result_len, 64)}));
}
}
});
2.5 修改执行流程
JavaScript
// ═══════════════════════════════════════════════════
// modify_execution.js — 修改执行流程
// ═══════════════════════════════════════════════════
// ── 1. 修改函数返回值 ──
// 让 is_rooted() 永远返回 false
var base = Module.findModuleByName("libtarget.so").base;
Interceptor.attach(base.add(0x3000), { // is_rooted
onLeave: function(retval) {
console.log("[is_rooted] 原返回值: " + retval + " → 修改为 0 (未root)");
retval.replace(0); // ★ 修改返回值
}
});
// ── 2. 修改函数入参 ──
Interceptor.attach(base.add(0x5000), { // check_license(char* key, int type)
onEnter: function(args) {
// 修改第1个参数为合法密钥
var fake_key = Memory.allocUtf8String("VALID-LICENSE-KEY-12345");
args[0] = fake_key; // ★ 替换参数指针
// 修改第2个参数
args[1] = ptr(1); // ★ type = 1 (premium)
console.log("[check_license] 参数已替换");
}
});
// ── 3. 完全替换函数实现 ──
// 方法 A:Interceptor.replace
var anti_debug = base.add(0x6000);
Interceptor.replace(anti_debug, new NativeCallback(function() {
console.log("[anti_debug] 函数被完全替换,直接返回0");
return 0; // ★ 整个函数被替换,原函数不执行
}, 'int', []));
// 方法 B:使用 NativeFunction 调用原函数 + 修改逻辑
var original_verify = new NativeFunction(base.add(0x7000), 'int', ['pointer', 'int']);
Interceptor.replace(base.add(0x7000), new NativeCallback(function(data, len) {
console.log("[verify] 被调用,数据长度: " + len);
// 可以选择性调用原函数
if (len > 100) {
return original_verify(data, len); // 调用原函数
} else {
return 1; // 短数据直接返回成功
}
}, 'int', ['pointer', 'int']));
// ── 4. 修改内存中的指令 ──
// 将条件跳转 NOP 掉
// 假设 0xA000 处有 CBZ X0, fail_label 需要跳过
Memory.protect(base.add(0xA000), 4, 'rwx'); // 先修改权限
base.add(0xA000).writeU32(0xD503201F); // NOP 指令 (ARM64)
// 或 x86: base.add(0xA000).writeByteArray([0x90, 0x90]); // 两字节 NOP
console.log("[patch] 地址 0xA000 已NOP");
// ── 5. 条件性修改(根据运行状态决定)──
var call_count = 0;
Interceptor.attach(base.add(0xB000), { // validate_token
onEnter: function(args) {
call_count++;
this.token = args[0].readCString();
console.log("[validate_token] 第" + call_count + "次调用, token=" + this.token);
},
onLeave: function(retval) {
if (this.token === "debug_token") {
retval.replace(1); // 特定 token 直接放行
console.log(" → debug token 放行");
}
// 其他 token 保持原结果
}
});
// ── 6. Hook Java 层并修改(Android)──
Java.perform(function() {
var MainActivity = Java.use("com.example.app.MainActivity");
// 修改方法返回值
MainActivity.isPremium.implementation = function() {
console.log("[isPremium] 原始调用,强制返回 true");
return true; // ★ 永远返回 premium
};
// 修改方法参数
MainActivity.login.implementation = function(username, password) {
console.log("[login] 原始: " + username + " / " + password);
return this.login("admin", "bypass123"); // ★ 替换凭据
};
// 调用原始方法并修改结果
MainActivity.getData.implementation = function(id) {
var result = this.getData(id); // 调用原方法
console.log("[getData] 原始结果: " + result);
return result + "_modified"; // ★ 修改返回值
};
});
2.6 综合插桩脚本模板
JavaScript
// ═══════════════════════════════════════════════════
// comprehensive_hook.js — 综合分析脚本模板
// ═══════════════════════════════════════════════════
(function() {
"use strict";
// ═══ 配置区 ═══
var TARGET_MODULE = "libtarget.so";
var LOG_LEVEL = 2; // 0=关闭 1=简要 2=详细 3=含hexdump
// ═══ 工具函数 ═══
function log(level, tag, msg) {
if (level <= LOG_LEVEL) {
var timestamp = new Date().toISOString().substr(11, 12);
console.log("[" + timestamp + "] [" + tag + "] " + msg);
}
}
function safeReadString(ptr, maxLen) {
try {
if (ptr.isNull()) return "NULL";
return ptr.readCString(maxLen || 256);
} catch(e) {
return "<unreadable:" + ptr + ">";
}
}
function safeDump(ptr, len) {
try {
if (ptr.isNull()) return "NULL";
return hexdump(ptr, {length: len, ansi: false});
} catch(e) {
return "<unreadable:" + ptr + ">";
}
}
function getModuleOffset(addr) {
var module = Process.findModuleByAddress(addr);
if (module) {
return module.name + "!" + addr.sub(module.base);
}
return addr.toString();
}
function backtrace(ctx) {
return Thread.backtrace(ctx, Backtracer.ACCURATE)
.map(function(addr) {
return " " + addr + " " + getModuleOffset(addr) +
" " + DebugSymbol.fromAddress(addr);
}).join("\n");
}
// ═══ 等待模块加载 ═══
function waitForModule(name, callback) {
var mod = Process.findModuleByName(name);
if (mod) {
callback(mod);
} else {
var interval = setInterval(function() {
mod = Process.findModuleByName(name);
if (mod) {
clearInterval(interval);
callback(mod);
}
}, 100);
}
}
// ═══ 主逻辑 ═══
waitForModule(TARGET_MODULE, function(mod) {
log(1, "INIT", "模块已加载: " + mod.name +
" @ " + mod.base + " size=" + mod.size);
var base = mod.base;
// ── 阶段1:函数调用流程追踪 ──
// 列出所有导出并按地址排序
var exports = mod.enumerateExports()
.filter(function(e) { return e.type === 'function'; })
.sort(function(a, b) { return a.address.compare(b.address); });
log(1, "INIT", "发现 " + exports.length + " 个导出函数");
// 选择性 Hook 关键函数
var key_functions = [
// {offset: 0x1234, name: "custom_func", params: [...]}
];
exports.forEach(function(exp) {
// 过滤:只 Hook 感兴趣的函数
if (exp.name.match(/encrypt|decrypt|sign|verify|key|init|auth|login|token/i)) {
Interceptor.attach(exp.address, {
onEnter: function(args) {
this._name = exp.name;
this._args = [args[0], args[1], args[2], args[3]];
this._startTime = Date.now();
log(2, "CALL", "→ " + exp.name + "(" +
"arg0=" + args[0] + ", " +
"arg1=" + args[1] + ", " +
"arg2=" + args[2] + ", " +
"arg3=" + args[3] + ")");
if (LOG_LEVEL >= 3) {
log(3, "STACK", "\n" + backtrace(this.context));
}
},
onLeave: function(retval) {
var elapsed = Date.now() - this._startTime;
log(2, "RET", "← " + this._name +
" → " + retval + " (" + elapsed + "ms)");
}
});
log(1, "HOOK", "已Hook: " + exp.name + " @ " + exp.address);
}
});
// ── 阶段2:特定函数的参数详细分析 ──
// 示例:Hook 偏移处的函数
/*
Interceptor.attach(base.add(0xXXXX), {
onEnter: function(args) {
// 详细参数分析
},
onLeave: function(retval) {
// 返回值分析
}
});
*/
// ── 阶段3:执行流程修改 ──
// 反调试绕过
var ptrace_addr = Module.findExportByName(null, "ptrace");
if (ptrace_addr) {
Interceptor.attach(ptrace_addr, {
onEnter: function(args) {
var request = args[0].toInt32();
if (request === 0) { // PTRACE_TRACEME
log(1, "BYPASS", "ptrace(PTRACE_TRACEME) 被拦截");
this._bypass = true;
}
},
onLeave: function(retval) {
if (this._bypass) {
retval.replace(0);
}
}
});
}
// ── 阶段4:数据发送到 Python 控制端 ──
// 使用 send() 将数据发回 Python
// send({type: "key_found", data: "xxxx"});
// 在 Python 端通过 on_message 回调接收
log(1, "INIT", "所有 Hook 已就绪");
});
// ═══ Java 层 Hook(Android)═══
if (Java.available) {
Java.perform(function() {
log(1, "JAVA", "Java 层 Hook 开始");
// 示例 Hook
/*
var TargetClass = Java.use("com.example.TargetClass");
TargetClass.method.implementation = function() { ... };
*/
});
}
})();
第二部分:手动调试
三、调试器完整方法论
3.1 调试器选择
text
┌──────────────┬────────────┬────────────┬──────────────────────────────┐
│ 调试器 │ 平台 │ 架构 │ 适用场景 │
├──────────────┼────────────┼────────────┼──────────────────────────────┤
│ GDB │ Linux │ 全架构 │ Linux/Android Native │
│ │ Android │ │ 最通用,命令行为主 │
├──────────────┼────────────┼────────────┼──────────────────────────────┤
│ GDB + GEF │ Linux │ 全架构 │ GDB增强,自动显示上下文 │
│ GDB + pwndbg │ Linux │ 全架构 │ PWN/漏洞利用专用 │
│ GDB + peda │ Linux │ x86 │ 经典增强 │
├──────────────┼────────────┼────────────┼──────────────────────────────┤
│ LLDB │ macOS/iOS │ 全架构 │ Apple 生态系统 │
│ │ Linux │ │ │
├──────────────┼────────────┼────────────┼──────────────────────────────┤
│ IDA Debugger │ 全平台 │ 全架构 │ 静态+动态一体化 │
│ │ │ │ 远程调试 Android/Linux │
├──────────────┼────────────┼────────────┼──────────────────────────────┤
│ x64dbg │ Windows │ x86/x64 │ Windows 逆向首选 │
│ x32dbg │ Windows │ x86 │ │
├──────────────┼────────────┼────────────┼──────────────────────────────┤
│ OllyDbg │ Windows │ x86 │ 经典但已停更 │
├──────────────┼────────────┼────────────┼──────────────────────────────┤
│ WinDbg │ Windows │ x86/x64 │ 内核调试/崩溃分析 │
├──────────────┼────────────┼────────────┼──────────────────────────────┤
│ r2 (radare2) │ 全平台 │ 全架构 │ 命令行调试+分析一体 │
├──────────────┼────────────┼────────────┼──────────────────────────────┤
│ Cutter │ 全平台 │ 全架构 │ radare2 GUI版 │
└──────────────┴────────────┴────────────┴──────────────────────────────┘
3.2 GDB/GEF 完整调试流程
环境配置
Bash
# 安装 GEF(GDB Enhanced Features)★ 强烈推荐
bash -c "$(curl -fsSL https://gef.blah.cat/sh)"
# 或 pwndbg
git clone https://github.com/pwndbg/pwndbg && cd pwndbg && ./setup.sh
# Android 远程调试
adb push gdbserver /data/local/tmp/
adb shell chmod +x /data/local/tmp/gdbserver
# 在 Android 设备上启动 gdbserver
adb shell /data/local/tmp/gdbserver :1234 --attach <PID>
# PC 端连接
adb forward tcp:1234 tcp:1234
gdb-multiarch
(gdb) target remote :1234
# IDA 远程调试
adb push android_server64 /data/local/tmp/
adb shell chmod +x /data/local/tmp/android_server64
adb shell /data/local/tmp/android_server64 &
adb forward tcp:23946 tcp:23946
# IDA: Debugger → Remote ARM Linux/Android debugger
断点系统
Bash
# ═══════════════════════════════════════════
# GDB 断点完整手册
# ═══════════════════════════════════════════
# ── 1. 软件断点(最常用)──
b main # 在 main 函数入口
b *0x400580 # 在绝对地址
b *($base + 0x1234) # 基址+偏移(PIE程序)
b filename.c:42 # 在源码行号(需调试符号)
b namespace::class::method # C++ 方法
# 条件断点
b *0x400580 if $rdi == 0x41 # 当 RDI == 0x41 时才断
b *0x400580 if strcmp((char*)$rdi, "admin") == 0 # 字符串匹配
b encrypt_data if $rsi > 1024 # 数据长度 > 1024 时断
# 临时断点(触发一次后自动删除)
tb *0x400580
# ── 2. 硬件断点(不修改代码,反反调试)──
hb *0x400580 # 硬件执行断点
# 数量有限(x86最多4个),但不会被反调试检测
# ── 3. 数据断点(Watchpoint)★★★ 数据流分析利器 ──
watch *(int*)0x601040 # 当该地址被写入时中断
rwatch *(int*)0x601040 # 当该地址被读取时中断
awatch *(int*)0x601040 # 当该地址被读或写时中断
watch *(char[32]*)0x601040 # 监控32字节区域的写入
# 条件数据断点
watch *(int*)0x601040 if *(int*)0x601040 == 0xdeadbeef
# ── 4. Catchpoint(捕获系统事件)──
catch syscall open # 捕获 open 系统调用
catch syscall write # 捕获 write 系统调用
catch signal SIGSEGV # 捕获段错误信号
catch throw # 捕获 C++ 异常抛出
catch fork # 捕获 fork 调用
# ── 5. 断点管理 ──
info breakpoints # i b 列出所有断点
disable 3 # 禁用第3个断点
enable 3 # 启用
delete 3 # 删除
delete # 删除所有
# 断点命令(触发时自动执行)★★★
b *0x400580
commands
silent
printf "encrypt called: data=%p len=%d\n", $rdi, $esi
x/32xb $rdi
bt 5
continue
end
# 这样每次触发断点时自动打印信息并继续,不会停下来
内存分析
Bash
# ═══════════════════════════════════════════
# 内存检查完整命令
# ═══════════════════════════════════════════
# ── 1. 查看内存内容 ──
# x 命令格式: x/NFU address
# N = 数量, F = 格式, U = 单位大小
# F: x=十六进制 d=十进制 s=字符串 i=指令 c=字符
# U: b=字节 h=半字(2B) w=字(4B) g=双字(8B)
x/32xb $rdi # 从 RDI 开始显示 32 个字节(hex)
x/8xw $rsp # 从 RSP 开始显示 8 个 DWORD
x/4xg $rsp # 从 RSP 开始显示 4 个 QWORD
x/s $rdi # 以字符串显示
x/10i $rip # 反汇编后面 10 条指令
x/20xb 0x7fff1234 # 查看绝对地址
# ── 2. 搜索内存 ──
# 搜索字节序列
find 0x400000, 0x500000, 0x41, 0x42, 0x43, 0x44 # 搜索 "ABCD"
find /b 0x400000, +0x100000, 0xde, 0xad, 0xbe, 0xef # 搜索 deadbeef
find /s 0x400000, +0x100000, "password" # 搜索字符串
# GEF 增强搜索
search-pattern "password" # 搜索所有内存段
search-pattern 0xdeadbeef # 搜索整数
# ── 3. 内存映射 ──
info proc mappings # 查看进程内存映射
# 或
vmmap # GEF 命令(更美观)
# 或
shell cat /proc/<pid>/maps # 直接读取 /proc
# 输出:
# Start End Offset Perm Path
# 0x0000555555554000 0x0000555555558000 0x000000 r-x /path/binary
# 0x0000555555758000 0x0000555555759000 0x004000 r-- /path/binary
# 0x0000555555759000 0x000055555575a000 0x005000 rw- /path/binary
# 0x00007ffff7a0d000 0x00007ffff7bcd000 0x000000 r-x /lib/libc.so.6
# 0x00007ffffffde000 0x00007ffffffff000 0x000000 rw- [stack]
# ── 4. 内存修改 ──
set *(int*)0x601040 = 0x12345678 # 写入 4 字节
set {char[5]}0x601040 = "ABCD" # 写入字符串
set $rax = 0 # 修改寄存器
set $rip = 0x400600 # 修改执行位置 ★
# ── 5. 内存转储 ──
dump binary memory output.bin 0x400000 0x401000 # 转储内存区域到文件
dump binary value output.bin *(char[1024]*)$rdi # 转储指针指向的数据
栈分析
Bash
# ═══════════════════════════════════════════
# 栈分析
# ═══════════════════════════════════════════
# ── 1. 调用栈回溯 ──
bt # 完整调用栈
bt 10 # 最近 10 层
bt full # 含局部变量
# 切换栈帧
frame 3 # 切换到第 3 帧
up # 上一帧(调用者)
down # 下一帧(被调用者)
info frame # 当前帧详细信息
info args # 当前函数参数
info locals # 当前局部变量
# ── 2. 栈内容检查 ──
x/32xg $rsp # 从栈顶显示 32 个 QWORD
x/64xg $rbp-0x100 # 从 RBP 往下查看局部变量区
# GEF/pwndbg 自动栈显示
stack 20 # 显示栈顶 20 个条目
telescope $rsp 20 # 智能栈显示(自动解引用)
# ── 3. 栈帧布局分析 ──
# 手动分析栈帧
# 对照反汇编中的 sub rsp, 0xN0 确定帧大小
# [rbp+0x10] → 第2个栈参数(如果有)
# [rbp+0x08] → 返回地址
# [rbp+0x00] → saved RBP
# [rbp-0x08] → Canary(如果有)
# [rbp-0x10] → 第1个局部变量
# [rbp-0x??] → 其他局部变量
# ...
# [rsp] → 栈顶
寄存器分析
Bash
# ═══════════════════════════════════════════
# 寄存器检查与修改
# ═══════════════════════════════════════════
# ── 1. 查看寄存器 ──
info registers # i r 所有通用寄存器
info all-registers # 包含浮点/SIMD寄存器
info registers rax rbx # 指定寄存器
# GEF 自动显示(每步都显示)
context # 手动刷新上下文显示
# 各寄存器在函数调用中的含义(x86-64 System V ABI)
# RDI = 第1个参数 RSI = 第2个参数
# RDX = 第3个参数 RCX = 第4个参数
# R8 = 第5个参数 R9 = 第6个参数
# RAX = 返回值 RSP = 栈指针
# RBP = 帧指针 RIP = 指令指针
# ARM64 ABI
# X0-X7 = 参数1-8 X0 = 返回值
# SP = 栈指针 PC = 程序计数器
# LR(X30) = 返回地址 FP(X29) = 帧指针
# ── 2. 修改寄存器 ──
set $rax = 1 # 修改 RAX
set $rdi = 0x41414141 # 修改第1个参数
set $rip = 0x400600 # ★ 修改执行位置(跳过代码)
set $eflags &= ~(1<<6) # 清除 ZF 标志(改变条件跳转结果)
set $eflags |= (1<<6) # 设置 ZF 标志
加载模块分析
Bash
# ═══════════════════════════════════════════
# 加载模块地址和大小
# ═══════════════════════════════════════════
# ── 1. 列出所有加载的共享库 ──
info sharedlibrary # 或 info shared
# 输出:
# From To Syms Name
# 0x00007ffff7dd5000 0x00007ffff7df5000 Yes /lib/ld-linux.so
# 0x00007ffff7a0d000 0x00007ffff7bcd000 Yes /lib/libc.so.6
# 0x00007ffff7800000 0x00007ffff7900000 Yes libtarget.so
# GEF 增强
vmmap # 完整内存映射
xinfo $rip # 查看当前地址属于哪个模块
# ── 2. 计算模块基址(PIE 程序)──
# 方法1: 从 /proc/pid/maps 获取
shell cat /proc/$(pidof target)/maps | grep libtarget
# 方法2: GDB 中
info proc mappings | grep libtarget
# 方法3: 设置基址变量方便使用
set $base = 0x7ffff7800000
b *($base + 0x1234) # 使用基址+偏移设置断点
# ── 3. 模块内偏移计算 ──
# 当前 RIP 在模块内的偏移
p/x $rip - $base
# IDA/Ghidra 中的地址 → GDB 中的地址
# GDB地址 = 模块基址 + IDA中的偏移
# 例: IDA 中函数在 0x1234, 基址 0x7ffff7800000
# GDB 地址 = 0x7ffff7801234
执行控制
Bash
# ═══════════════════════════════════════════
# 执行控制完整命令
# ═══════════════════════════════════════════
# ── 1. 基本执行 ──
r # run 运行程序
r arg1 arg2 # 带参数运行
c # continue 继续执行
kill # 终止程序
# ── 2. 单步执行 ──
si # stepi 单步一条指令(进入call)
ni # nexti 单步一条指令(跳过call)★常用
si 10 # 执行 10 条指令
s # step 单步一行源码(进入函数)
n # next 单步一行源码(跳过函数)
# ── 3. 跳出/跳到 ──
finish # 执行到当前函数返回 ★常用
until *0x400600 # 执行到指定地址
advance *0x400600 # 同上,但更智能
# ── 4. 跳过代码 ──
set $rip = 0x400620 # ★ 直接跳过一段代码
jump *0x400620 # 跳转到地址继续执行
# ── 5. 反向调试(需要 record 支持)──
record # 开始记录执行
reverse-stepi # 反向单步
reverse-continue # 反向继续到上一个断点
reverse-nexti # 反向跳过
# ── 6. 自动化执行脚本 ──
# GDB 脚本文件 (commands.gdb)
# source commands.gdb
# 示例 commands.gdb:
set pagination off
set logging on
set logging file trace.log
b *($base + 0x1234)
commands
silent
printf "func called: arg1=%p arg2=%d\n", $rdi, $esi
x/32xb $rdi
bt 3
c
end
b *($base + 0x5678)
commands
silent
printf "func2 returns: %d\n", $eax
c
end
run
3.3 GEF/pwndbg 增强显示
text
GEF 每次中断时自动显示的上下文:
┌──────────────────── registers ────────────────────────┐
│ $rax : 0x0000000000000001 │
│ $rbx : 0x0000000000000000 │
│ $rcx : 0x00007ffff7b04000 → "/lib/libc.so.6" │ ← 自动解引用
│ $rdx : 0x0000000000000020 │
│ $rsp : 0x00007fffffffe350 → 0x00000000deadbeef │ ← 自动解引用
│ $rbp : 0x00007fffffffe3a0 → 0x00007fffffffe3d0 │
│ $rsi : 0x00007fffffffe100 → "Hello World" │ ← 自动识别字符串
│ $rdi : 0x0000555555558040 → <g_buffer> │
│ $rip : 0x0000555555555200 → <encrypt+0x30> │
│ $eflags: [ZERO carry PARITY adjust sign ...] │
├──────────────────── stack ─────────────────────────────┤
│ 0x00007fffffffe350│+0x0000: 0x00000000deadbeef │
│ 0x00007fffffffe358│+0x0008: 0x00007ffff7a0d000 → ... │
│ 0x00007fffffffe360│+0x0010: 0x0000000000000020 │
│ 0x00007fffffffe368│+0x0018: 0x0000555555555300 → <main+0x50>│ ← 返回地址
│ ... │
├──────────────────── code ──────────────────────────────┤
│ 0x5555555551f0 <encrypt+0x20>: mov rsi, rbx │
│ 0x5555555551f3 <encrypt+0x23>: mov edx, 0x20 │
│ 0x5555555551f8 <encrypt+0x28>: call AES_encrypt │
│ → 0x555555555200 <encrypt+0x30>: test eax, eax │ ← 当前位置
│ 0x555555555202 <encrypt+0x32>: jne 0x555555555220│
│ 0x555555555204 <encrypt+0x34>: mov rdi, r12 │
│ 0x555555555207 <encrypt+0x37>: call log_error │
├──────────────────── threads ───────────────────────────┤
│ [#0] Id 1, stopped at 0x555555555200 in encrypt() │
└────────────────────────────────────────────────────────┘
3.4 Android 远程调试完整配置
Bash
# ═══════════════════════════════════════════
# Android GDB 远程调试配置
# ═══════════════════════════════════════════
# ── 步骤1: 准备 gdbserver ──
# 使用 NDK 自带的 gdbserver
# 路径: $NDK/prebuilt/android-arm64/gdbserver/gdbserver
adb push $NDK/prebuilt/android-arm64/gdbserver/gdbserver /data/local/tmp/
adb shell chmod 755 /data/local/tmp/gdbserver
# ── 步骤2: 找到目标进程 ──
adb shell ps -A | grep com.example.app
# u0_a123 12345 ... com.example.app
# ── 步骤3: 附加 gdbserver ──
adb shell su -c "/data/local/tmp/gdbserver :1234 --attach 12345"
# Attached; pid = 12345
# Listening on port 1234
# ── 步骤4: 端口转发 ──
adb forward tcp:1234 tcp:1234
# ── 步骤5: PC 端连接 ──
gdb-multiarch # 或 aarch64-linux-gnu-gdb
(gdb) set architecture aarch64
(gdb) target remote :1234
# ── 步骤6: 加载符号(如果有)──
(gdb) set solib-search-path ./symbols/
(gdb) info sharedlibrary
# ── 步骤7: 找到目标库的基址 ──
(gdb) shell cat /proc/12345/maps | grep libtarget.so
# 7a3b000000-7a3b100000 r-xp ... libtarget.so
(gdb) set $base = 0x7a3b000000
# ── 步骤8: 设置断点 ──
(gdb) b *($base + 0x1234) # IDA 中的偏移
(gdb) c
3.5 IDA 远程调试配置
text
IDA Pro 远程调试 Android:
步骤1: 推送 IDA 调试服务器
adb push android_server64 /data/local/tmp/
adb shell chmod 755 /data/local/tmp/android_server64
adb shell su -c "/data/local/tmp/android_server64 -p 23946"
步骤2: 端口转发
adb forward tcp:23946 tcp:23946
步骤3: IDA 中配置
Debugger → Select Debugger → Remote ARM Linux/Android debugger
Debugger → Process Options:
┌────────────────────────────────────────────┐
│ Hostname: localhost │
│ Port: 23946 │
│ Input file: /data/app/.../lib/libtarget.so│
└────────────────────────────────────────────┘
步骤4: 附加进程
Debugger → Attach to process → 选择目标进程
步骤5: 重新基址(Rebase)
IDA 自动检测加载地址并重新基址
或手动: Edit → Segments → Rebase program
优势:
★ 静态分析(注释/类型/函数名)与动态调试完全统一
★ 可以直接在反编译伪代码上设断点
★ 鼠标悬停变量自动显示当前值
四、x64dbg 调试(Windows)
text
x64dbg 完整功能:
┌─────────────────────────────────────────────────────────────┐
│ x64dbg 界面布局 │
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌─ 反汇编窗口 ──────────────────────────────────────┐ │
│ │ 00401000 push ebp │ │
│ │ 00401001 mov ebp, esp │ │
│ │ 00401003 sub esp, 40 │ │
│ │►00401006 call 00402000 ← 当前执行位置 │ │
│ │ 0040100B test eax, eax │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌─ 寄存器 ────────┐ ┌─ 栈 ────────────────────────┐ │
│ │ EAX: 00000001 │ │ ESP→ 0019FE00: 00401234 │ │
│ │ EBX: 00000000 │ │ 0019FE04: 00000040 │ │
│ │ ECX: 7FFE0304 │ │ 0019FE08: 0019FE50 │ │
│ │ EDX: 00000020 │ │ ... │ │
│ │ EIP: 00401006 │ │ │ │
│ │ ESP: 0019FE00 │ │ │ │
│ │ EBP: 0019FE40 │ │ │ │
│ └─────────────────┘ └─────────────────────────────┘ │
│ │
│ ┌─ 内存转储 ────────────────────────────────────────┐ │
│ │ 00601000: 48 65 6C 6C 6F 20 57 6F Hello Wo │ │
│ │ 00601008: 72 6C 64 00 41 42 43 44 rld.ABCD │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ ┌─ 模块列表 ────────────────────────────────────────┐ │
│ │ base size module │ │
│ │ 00400000 00050000 target.exe │ │
│ │ 77D10000 001A0000 ntdll.dll │ │
│ │ 75A30000 00100000 kernel32.dll │ │
│ └───────────────────────────────────────────────────┘ │
│ │
│ 快捷键: │
│ F2=断点 F7=步入 F8=步过 F9=运行 Ctrl+F9=运行到返回 │
│ Ctrl+G=跳转地址 Ctrl+F=搜索 Ctrl+B=搜索字节 │
│ │
└─────────────────────────────────────────────────────────────┘
五、调试实战场景
场景一:分析加密函数的密钥
Bash
# 目标:找到 AES 加密使用的密钥
# 步骤1: 通过字符串/导入表定位加密函数
# 在 IDA 中找到 AES_set_encrypt_key 的交叉引用
# 步骤2: 在加密函数入口设断点
(gdb) set $base = 0x7a3b000000
(gdb) b *($base + 0x8500) # encrypt_data 函数入口
# 步骤3: 触发加密操作(在APP中执行相关功能)
(gdb) c
# Breakpoint 1 hit
# 步骤4: 查看参数
(gdb) info registers
# x0 = 0x7b4c001000 ← 待加密数据指针
# x1 = 0x00000040 ← 数据长度 (64)
# x2 = 0x7a3b012340 ← 密钥指针 ★
# 步骤5: 转储密钥
(gdb) x/32xb $x2
# 0x7a3b012340: 0x2b 0x7e 0x15 0x16 0x28 0xae 0xd2 0xa6
# 0xab 0xf7 0x15 0x88 0x09 0xcf 0x4f 0x3c
# 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88
# 0x99 0xaa 0xbb 0xcc 0xdd 0xee 0xff 0x00
# 步骤6: 追溯密钥来源(设数据断点)
(gdb) delete # 删除所有断点
(gdb) watch *(char*)0x7a3b012340 # 监控密钥地址的写入
(gdb) run # 重新运行
# 当密钥被写入时会中断
# Watchpoint hit
(gdb) bt # 查看调用栈 → 找到密钥生成函数
场景二:绕过反调试
Bash
# 常见反调试手段及绕过
# ── 1. ptrace 检测 ──
# 程序调用 ptrace(PTRACE_TRACEME) 检测是否被调试
(gdb) catch syscall ptrace
(gdb) c
# Catchpoint hit: ptrace
(gdb) set $rax = 0 # 伪造返回值为成功
(gdb) c
# 或用 Frida:
# Interceptor.replace(ptrace_addr, new NativeCallback(() => 0, 'int', [...]));
# ── 2. /proc/self/status 检测 ──
# 读取 TracerPid 字段检测调试器
(gdb) catch syscall openat
(gdb) commands
silent
if strcmp((char*)$rsi, "/proc/self/status") == 0
printf "检测到 /proc/self/status 读取\n"
end
c
end
# ── 3. 时间检测 ──
# 通过 clock_gettime 检测执行时间异常
# 方法: Hook clock_gettime 使其返回递增的假时间
# 或在检测代码处 NOP 掉比较指令
场景三:修改程序执行流程
Bash
# 目标:跳过 license 验证
# 步骤1: 找到验证函数
# check_license() 返回 0=失败 1=成功
# 步骤2: 在验证函数返回处设断点
(gdb) b *($base + 0x3456) # check_license 的 ret 指令
# 步骤3: 修改返回值
(gdb) c
# Breakpoint hit at ret instruction
(gdb) set $rax = 1 # 强制返回"成功"
(gdb) c
# 步骤4: 永久修改(patch 内存)
# 将 check_license 开头改为: mov eax, 1; ret
(gdb) set *(unsigned int*)($base + 0x3400) = 0x000001b8 # mov eax, 1
(gdb) set *(unsigned char*)($base + 0x3404) = 0xc3 # ret
# 步骤5: 或修改条件跳转
# 原始: je fail_label (0x74 XX)
# 修改: jne fail_label (0x75 XX) 或 nop (0x90 0x90)
(gdb) set *(unsigned char*)($base + 0x3500) = 0x75 # je → jne
# 或
(gdb) set *(unsigned short*)($base + 0x3500) = 0x9090 # je → nop nop
六、插桩 vs 调试选择策略
text
┌──────────────────────┬──────────────────────┬────────────────────────┐
│ 场景 │ 推荐方法 │ 原因 │
├──────────────────────┼──────────────────────┼────────────────────────┤
│ 快速了解调用流程 │ Frida 批量 Hook │ 一键追踪所有函数 │
│ 获取加密密钥/参数 │ Frida Hook 加密API │ 自动化、不需要单步 │
│ 绕过反调试/root检测 │ Frida replace │ 灵活替换函数实现 │
│ 修改返回值/参数 │ Frida onLeave │ 代码简单、即时生效 │
│ │ │ │
│ 分析复杂算法逻辑 │ GDB/IDA 手动调试 │ 需要单步跟踪每条指令 │
│ 追踪内存变化 │ GDB watchpoint │ 硬件断点精确追踪 │
│ 分析崩溃原因 │ GDB core dump │ 查看崩溃时完整状态 │
│ 理解未知数据结构 │ GDB 内存检查 │ 需要反复查看内存布局 │
│ 内核/驱动分析 │ GDB/WinDbg │ Frida 无法hook内核 │
│ │ │ │
│ 最佳实践 │ Frida + GDB 配合 │ Frida定位 → GDB深入 │
└──────────────────────┴──────────────────────┴────────────────────────┘
text
典型工作流:
┌───────────────────────────────────────────────────────┐
│ Step 1: Frida 快速扫描 │
│ → 批量Hook目标库所有导出函数 │
│ → 触发目标功能 │
│ → 从日志中定位关键函数(哪些被调用了) │
│ │
│ Step 2: Frida 定点Hook │
│ → 对关键函数详细Hook参数/返回值 │
│ → 获取密钥、输入输出数据 │
│ → 大致理解数据流 │
│ │
│ Step 3: GDB 精确调试(如需要) │
│ → 对复杂算法函数下断点 │
│ → 单步执行理解具体逻辑 │
│ → watchpoint 追踪数据变化 │
│ → 分析控制流和分支条件 │
│ │
│ Step 4: 修改执行流程 │
│ → Frida 动态修改(不改文件) │
│ → 或 GDB patch内存 │
│ → 或 静态 patch 二进制文件 │
│ │
└───────────────────────────────────────────────────────┘
七、核心总结
text
┌─────────────────────────────────────────────────────────────────┐
│ │
│ 动态分析 = 在程序运行时观察和干预其行为 │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 插桩分析(Frida 为代表) │ │
│ │ │ │
│ │ 核心能力: │ │
│ │ ① 函数调用流程追踪(谁调谁、什么顺序) │ │
│ │ ② 参数和返回值捕获(输入输出是什么) │ │
│ │ ③ 执行流程修改(改参数、改返回值、替换函数) │ │
│ │ │ │
│ │ 特点:快速、自动化、适合批量分析 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ 手动调试(GDB/IDA/x64dbg 为代表) │ │
│ │ │ │
│ │ 核心能力: │ │
│ │ ① 断点控制(在任意位置暂停程序) │ │
│ │ ② 内存检查(查看/搜索/修改任意内存) │ │
│ │ ③ 栈分析(调用链、局部变量、返回地址) │ │
│ │ ④ 寄存器分析(参数传递、计算中间值) │ │
│ │ ⑤ 模块信息(加载地址、偏移计算) │ │
│ │ ⑥ 单步执行(逐条指令精确跟踪) │ │
│ │ │ │
│ │ 特点:精度最高、通用性最强、但需要经验和耐心 │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ 最佳实践:先插桩定位,再调试深入 │
│ │
└─────────────────────────────────────────────────────────────────┘
免责声明
本文档所有内容仅供安全研究、学术交流与技术学习使用,严禁用于任何未经授权的逆向破解、网络攻击、隐私窃取、恶意软件开发及其他违反《中华人民共和国网络安全法》《数据安全法》等法律法规的行为,使用者应确保已获得目标软件权利人的合法授权并自行承担因使用本文档内容所产生的一切法律责任与后果,作者不对任何直接或间接损害承担任何责任,继续阅读即视为您已知悉并同意上述全部条款。
浙公网安备 33010602011771号