首页
社区
课程
招聘
[原创] 半年时间,我终于用懂了 eDBG —— eBPF 轻量级 trace tenet 回放伪动调的调试器
发表于: 1天前 799

[原创] 半年时间,我终于用懂了 eDBG —— eBPF 轻量级 trace tenet 回放伪动调的调试器

1天前
799

项目地址:1ddK9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6U0K9r3g2F1P5s2k6T1i4K6u0r3k6f1c8n7c8H3`.`.

搞 Android 逆向的都知道,ptrace 调试器(frida、IDA remote dbsrv)一上来就容易被检测,各种反调试手段层出不穷。花了大半年时间断断续续踩坑,我终于把 eDBG 这套基于 eBPF 的调试 + trace 回放工作流跑通了,感觉确实好用,写篇文章记录一下。

eDBG 是什么

eDBG 是一款基于 eBPF 的轻量级 CLI 调试器,专门针对 Android ARM64 平台。它不使用 ptrace 来附加进程,而是通过内核态的 eBPF uprobe 机制来实现断点和调试,因此能够几乎完全无视用户态的反调试检测。

这个魔改 fork 版 Unicorn Trace 功能 —— 在断点处 dump 上下文,用 Unicorn 引擎做指令级模拟执行,生成 Tenet 兼容的 trace 日志。这意味着你可以在 IDA 里像动态调试一样前进后退地回放执行过程。

为什么用它

相比传统方案(frida stalker、IDA trace、qiling),eDBG 版本的 tracer 有以下好处:

  • 基于 eBPF,基本完全无视反调试 —— uprobe 工作在内核态,用户态的 ptrace 检测、/proc/self/status TracerPid 检查等手段全部失效
  • 下断运行更轻量,崩溃少,内嵌 dump 更快 —— 不需要注入 so,不需要 attach 进程,断点触发后直接在内核态采集寄存器和内存
  • 可以 Unicorn 回放,直接丢给 AI 看 trace —— dump 出来的 regs.json + 内存 bin 可以直接给 LLM 分析执行流(eDBG 本身也支持 MCP 模式直接对接 AI agent)
  • 支持 Tenet 回放调试,相当于 IDA 里伪动调,但更舒服 —— 在 IDA 里可以前进/后退逐条指令查看,还支持内存断点,比真动调更自由

效果展示

Unicorn Trace Dump 全流程

参考仓库图片

Tenet 回放调试

Tenet 内存断点

这是 eDBG + Tenet 最惊艳的能力之一:你可以在 Tenet 的回放中对任意内存地址下断点,当该地址被读写时自动定位到对应的指令。这对于分析加密算法中的 buffer 变化、追踪关键数据流向非常高效。

工作流

一个典型的 eDBG trace 分析流程如下:

┌─────────────────────────────────────────────────────────┐
│  1. IDA 静态分析,确定目标函数入口和出口偏移               │
│     找到 libxxx.so 中目标函数: 入口 0x1234, 出口 0x5678    │
└───────────────────────────┬─────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│  2. 手机端启动 eDBG,设置断点                             │
│     ./eDBG -p com.target.app -l libxxx.so -b 0x1234      │
└───────────────────────────┬─────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│  3. 启动/操作目标 APP,触发断点                           │
│     程序命中 0x1234,eDBG 暂停                            │
└───────────────────────────┬─────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│  4. 执行 trace,dump + Unicorn 模拟执行                   │
│     (eDBG) trace libxxx.so+0x5678 ./trace_out --tenet     │
│                                                           │
│     eDBG 自动:                                            │
│       ① dump 当前寄存器 + 内存段                          │
│       ② Unicorn 模拟执行每条指令                          │
│       ③ 遇到外部调用 → 切回真实调试器执行 → 重新 dump     │
│       ④ 生成 uc.log(反汇编日志)+ tenet.log(回放日志)  │
└───────────────────────────┬─────────────────────────────┘
                            │
                            ▼
┌─────────────────────────────────────────────────────────┐
│  5. 将 trace 产物 pull 到 PC                              │
│     adb pull /data/local/tmp/trace_out ./                 │
└───────────────────────────┬─────────────────────────────┘
                            │
                ┌───────────┴───────────┐
                ▼                       ▼
┌──────────────────────┐  ┌──────────────────────────────┐
│  6a. IDA + Tenet      │  │  6b. 直接看 uc.log /          │
│  加载 tenet.log       │  │      丢给 AI 分析 trace       │
│  前进后退回放         │  │      或用 local_emu.py 回放   │
│  内存断点追踪数据流   │  │                                │
└──────────────────────┘  └──────────────────────────────┘

命令速查

# 基本调试
./eDBG -p com.target.app -l libxxx.so -b 0x1234
(eDBG) c                                          # 继续运行到断点
(eDBG) s / n                                       # 单步步入 / 步过
(eDBG) info r                                      # 查看寄存器
(eDBG) x SP 128                                    # 查看栈内存

# Trace(核心用法)
(eDBG) trace libxxx.so+0x5678 ./trace_out --tenet  # trace 到目标地址,输出 tenet 日志
(eDBG) trace x30 ./trace_out --tenet               # trace 到 LR(整个函数)

# 内存操作
(eDBG) dump 0x7abc0000 0x4000 mem.bin              # dump 内存到文件
(eDBG) write 0x7abc0000 deadbeef                   # 写内存(patch)

原理简介

eBPF 断点机制

传统调试器(GDB/LLDB/IDA dbsrv)通过 ptrace(PTRACE_ATTACH, pid) 附加到目标进程,这个动作会被用户态的反调试手段轻松检测到(读 TracerPid、检测 SIGTRAP handler 等)。

eDBG 走了完全不同的路线:

  传统方案                          eDBG 方案
  ────────                          ─────────
  用户态 ptrace attach              内核态 eBPF uprobe
       ↓                                ↓
  /proc/pid/status 可见             内核态操作,用户态不可见
  SIGTRAP 信号可检测               无信号,uprobe callback
  进程会被 stop                     uprobe handler 中暂停线程

eBPF uprobe 的原理是在内核中对目标文件的指定偏移注册探针。当任何进程执行到该偏移对应的指令时,内核会触发 uprobe handler,在 handler 中可以读取寄存器、暂停执行等。因为整个过程发生在内核态,用户态程序几乎没有感知。

eDBG 使用 文件+偏移 的断点注册方式,不需要知道目标进程的 PID 或内存布局,可以在目标 APP 启动之前就准备好断点。

Unicorn Trace 的多轮同步机制

单纯的 Unicorn 模拟有个致命问题:遇到外部函数调用(比如 strlenmalloc)就会因为缺少实现而失败。eDBG 的解法非常优雅:

Unicorn 模拟执行
    │
    ├── 指令在 bound 范围内 → 继续模拟,记录 trace
    │
    └── PC 跳出 bound(外部调用)→ 暂停模拟
            │
            ├── 在 LR 处设置临时断点
            ├── 用真实调试器 continue,让程序真实执行外部函数
            ├── 断点命中后重新 dump 寄存器 + 内存
            └── 开始新一轮 Unicorn 模拟(Round N+1

每一轮的 dump 和 trace 日志都会保存,最终合并成完整的 uc_combine.logtenet_combine.log。这个设计让 Unicorn Trace 可以处理几乎所有包含外部调用的真实代码。

Tenet 内存断点

Tenet 是 IDA 的一个时间旅行调试插件。加载 eDBG 生成的 tenet.log 后,你可以:

  • 前进/后退逐条指令回放
  • 查看每条指令执行前后的寄存器和内存变化
  • 对任意内存地址设置读/写断点 —— 当回放到该地址被访问时自动停下

这个内存断点能力是分析加密/混淆代码的利器。比如你知道明文在地址 A,密文在地址 B,对这两个地址下内存断点,就能快速定位到加密函数的关键操作。

推荐使用特化版 Tenet:343K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6U0K9r3g2F1P5s2k6T1i4K6u0r3g2r3g2F1k6i4c8Q4x3X3c8u0c8p5p5&6i4K6u0W2x3l9`.`. ,支持 dump 内存加载,在 IDA 中还原完整动调体验。

小技巧

1. 先断后调,永远记住 -b

eDBG 不支持随时暂停,必须 通过 -b 或运行时 b 命令在目标位置先下断点。养成习惯:在 IDA 里确定好偏移再启动。

2. trace 整个函数用 x30

(eDBG) trace x30 ./trace_out --tenet

x30 就是 LR(链接寄存器),trace 到 LR 相当于 trace 整个当前函数。这是最常用的 trace 方式。

3. 手动指定 bound 避免不必要的外部调用同步

如果目标代码跨多个段,自动 bound 可能不准,导致频繁触发外部调用同步(每次同步都会慢一些)。手动指定 --bound 可以减少中断:

(eDBG) trace 0x5678 ./out --tenet --bound 0x1000 0x9000

4. dump 产物可以离线回放

trace 产出的 dump_*/regs.json + 内存 bin 文件可以用 Unicorn-Tracelocal_emu.py 离线回放,不需要手机,不需要再跑一次。适合反复调试同一段代码:

from local_emu import run_all_continuous
run_all_continuous(dump_path="./trace_out/dump_xxx", debug_switch=True)

5. 调试会跳过系统库

libc.solibart.so 这类被大量进程加载的库会自动跳过,但会局限在本库中,可以过滤很多无用逻辑且提高效率

缺点与局限

说优点也要说缺点,eDBG 并不完美:

  • 异常类反调试会有问题 —— eDBG 无视的是常规的 ptrace/signal 检测,但如果目标使用了基于异常处理流程的反调试(比如故意触发 SIGSEGV 走 signal handler 来执行关键逻辑),eDBG 的 uprobe 机制可能会干扰异常传递链路,导致行为异常
  • 过多的外部调用会很慢 —— 每次 Unicorn 遇到外部调用都需要切回真实调试器执行、重新 dump 内存、重启模拟,如果目标函数密集调用外部函数(比如大量 strlen/memcpy/JNI 调用),trace 速度会急剧下降
  • 在外部调用少的 VM trace 场景很有优势 —— 反过来说,对于自定义 VM、纯计算类混淆(ollvm handler、白盒加密循环等)这种外部调用很少的代码,eDBG 的 trace 效率非常高,这是它的最佳使用场景
  • 仅支持 Android ARM64 —— 需要 root + 内核 5.10+,覆盖面有限

相关工具与参考

工具 说明
eDBG 本文主角,eBPF 调试器
eDBG fork 原仓库 eBPF 调试器
Unicorn-Trace 源项目,IDA 插件版本 + 离线回放工具链
Tenet-IDA9.0 特化版 Tenet,支持 dump 内存加载
Tenet (原版) IDA 时间旅行调试插件
stackplz eDBG 的参考项目,eBPF trace 工具
pwndbg eDBG CLI 界面的灵感来源
Unicorn Engine CPU 模拟引擎
KernelSU 推荐的 root 方案

延伸阅读:


如果觉得有用,去 GitHub 给 eDBG 点个 Star 吧。


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 10
支持
分享
最新回复 (3)
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
test大佬我记得之前说过uprobe处理混淆的so不太好
1天前
0
雪    币: 217
活跃值: (300)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
动调混淆会有啥问题吗,目前我用还没遇到什么问题。如果有问题也可以用 ida 版本的插件 dump
21小时前
0
雪    币: 104
活跃值: (8552)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
4
tql
16小时前
0
游客
登录 | 注册 方可回帖
返回