GKLBB

当你经历了暴风雨,你也就成为了暴风雨

导航

动态分析完整方法论

 

动态调试的几种方法

动态调试是指在程序运行过程中对其进行分析和调试的技术,主要有以下几种方法:


一、按调试方式分类

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 为代表)                   │       │
│  │                                                      │       │
│  │  核心能力:                                          │       │
│  │    ① 断点控制(在任意位置暂停程序)                 │       │
│  │    ② 内存检查(查看/搜索/修改任意内存)             │       │
│  │    ③ 栈分析(调用链、局部变量、返回地址)           │       │
│  │    ④ 寄存器分析(参数传递、计算中间值)             │       │
│  │    ⑤ 模块信息(加载地址、偏移计算)                 │       │
│  │    ⑥ 单步执行(逐条指令精确跟踪)                  │       │
│  │                                                      │       │
│  │  特点:精度最高、通用性最强、但需要经验和耐心        │       │
│  └──────────────────────────────────────────────────────┘       │
│                                                                 │
│  最佳实践:先插桩定位,再调试深入                                │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘
 

posted on 2026-02-11 14:45  GKLBB  阅读(47)  评论(0)    收藏  举报