ObjC、系统调用与平台 API
平台相关 Pass 把底层 Trace 事件翻译为高层 API 语义。它们回答“发生了什么系统或框架调用、参数是什么、哪个高层方法触发了它”。
target_os 判定与回退
Section titled “target_os 判定与回退”Tenet 从 Trace 头读取 target_os(TraceFormat.h,偏移 0x138,4 字节):
| 值 | 含义 |
|---|---|
| 0 | Unknown(旧版 Trace,该字段尚未引入) |
| 1 | iOS / Darwin |
| 2 | Android |
| 3 | Linux |
target_os = 0 时,Tenet 根据 module_name 路径启发式回退:以 /data/、/system/ 开头或含 .so 的模块推断为 Android;以 /var/、/private/ 开头或含 /Library/、.dylib、.app/ 的模块推断为 iOS;其余保持 Unknown。这种回退是尽力而为的 —— 如果需要准确的平台 API 分类,请确保 qbditrace 录制时写入了正确的 target_os。
Objective-C 消息
Section titled “Objective-C 消息”objc 解析 REC_OBJC(=5)记录种类。它对每条消息发送提取:
- receiver(
objc_msgSend调用时的 x0)—— 消息接收者对象; - selector(x1)—— v8 由 tracer 在线捕获
sel_getName并内联(selector_source=inline);旧 Trace 依次回退到 image、Trace 内存解析,最终显示 hex。selector_resolution为resolved、truncated、read_failed或unresolved; - class name —— 由 receiver 的 isa 指针推导(ObjC runtime 类查找,通过 Trace 内存);
class_resolution同样报告解析状态,无法确定时明确为unknown; - arguments(x2–x7,最多 6 个)—— 每个参数原始值都被记录,当它看起来像用户空间指针时,
objc会尝试解引用为 C 字符串; - return value —— v8
REC_RETVAL捕获返回时刻真实的 x0/x1/NZCV,并按 site/target/source ID 精确关联。v4–v7 没有返回事实,显示return=unknown,不再从后续记录的 x0 推断。objc_msgSend_stret标为stret raw:x0 是隐藏返回缓冲区,不是普通标量返回值。
仅解析四种 msgSend 变体(objc_msgSend、objc_msgSendSuper、objc_msgSendSuper2、objc_msgSend_stret),对应录制时传入 qbditrace_config_t 的地址。ObjcMessage 上的 variant 字段区分具体变体(0 = msgSend,1 = stret,2 = super/super2)。
objc 仅在 Darwin 目标上工作。Android 和 Linux Trace 上该 Pass 直接返回空结果。
./tenet [options] trace.bin --objc./tenet [options] trace.bin --objc --objc-class NSString--objc-class <name> 按类名精确过滤输出。
ObjC × 密码学关联
Section titled “ObjC × 密码学关联”objc_crypto 把 pattern(密码学原语检测)与 objc(围绕每条检测的 ObjC 调用)结合起来。对每个密码学命中,找到最近的包围它的 ObjC 调用栈,回答“哪个用户可见方法(例如 -[NSURLConnection delegate] → -[MyManager connectionDidFinish:])触发了这次 CCCrypt 调用?”
./tenet [options] trace.bin --objc --pattern --objc-crypto该 Pass 也会高亮没有 ObjC 帧包围的密码学调用 —— 那些通常是底层或 C API 路径。
系统调用与 C API 拦截
Section titled “系统调用与 C API 拦截”syscall_intercept 用三个数据源按严格优先级重建高层 API 调用:
| 优先级 | 数据源 | 精度 | 说明 |
|---|---|---|---|
| 1 | REC_CAPICALL |
最高 | Tracer 侧解析:符号名、参数、内联字符串捕获。录制时需开启 enable_capi_intercept |
| 2 | REC_SVC |
中 | 原始 syscall 编号 + 参数,按平台 syscall 表分类 |
| 3 | XFER_CALL 启发式 |
最低 | 跨模块调用/返回对(REC_EVENT 记录标记离开与返回),无符号,可能误归 |
REC_CAPICALL 在录制时设置 qbditrace_config_t::enable_capi_intercept 且显式提供了 capi_funcs[](或依赖 native dlsym 自动解析、Frida 补充 JNI 条目)时产生。每条记录携带:
- 运行时目标地址;
api_id(已知 API 表索引;v8 为uint16_t,BoringSSL 600+ 与 JNI 700+ ID 不会截断);- 参数值(最多 8 个);
str_args位掩码 —— 标记哪些参数是已知字符串指针,tracer 侧直接内联捕获,tenet 无需重建内存。
这是最佳数据源:符号、类型化参数、解引用字符串全部预打包。
REC_SVC(系统调用记录)捕获 supervisor-call 事件及 v8 flags。Darwin 上包括 mach_msg、mach_vm_map、read、write 等;Linux/Android 上包括 ioctl、mmap、mprotect 等。Tenet 按 target_os 选 syscall 表;Darwin 名称会归一化 class prefix,但保留原始编号。
Darwin Mach trap
Section titled “Darwin Mach trap”Darwin SVC 的 x16 为负数时,syscall_intercept 使用 Mach trap 表而非 BSD syscall 表(例如 thread_self_trap、task_for_pid、iokit_user_client_trap、mach_vm_protect)。未映射的 trap 仍显示为 mach_trap_N;正数 BSD syscall 与 class prefix 的既有处理不变。
对于携带发送消息的 mach_msg_trap、mach_msg_overwrite_trap 与 mach_msg2_trap,Tenet 会从可读 Mach 消息头的偏移 20 读取 msgh_id,并解析常用 MIG routine(Mach VM、task、thread、host、port、VM map、clock 系列)。解析成功的摘要形如 mach_vm_protect via mach_msg_trap(msgh_id=0x12c2),JSON 额外给出 mach_msgh_id 与 mach_msg_via。未知 routine ID、纯接收消息或消息头不可读时会优雅降级为 trap 级结果。
对 REC_CAPICALL、REC_SVC 与 ObjC,v8 REC_RETVAL 提供返回时刻真实 x0/x1/NZCV,按 site/target/source ID 精确关联。syscall 失败:Darwin 看 NZCV carry(bit 29),Linux 看 x0 是否在 [-4095,-1];原生 SVC 不把环境 TLS errno 当作有效值。SecKeyRef 等 opaque 对象保持 opaque,不会误解为 key bytes。
XFER_CALL 启发式 基于 REC_EVENT 记录中标记跨模块离开与返回的事件工作。当被追踪代码转移到录制范围外再返回时,tenet 重建一次合成调用。无符号解析,按目标 PC 标记。
拦截的 API 表
Section titled “拦截的 API 表”| 平台 | 分类的 API |
|---|---|
| Darwin (iOS) | CommonCrypto: CCCrypt、CCCryptorCreate、CCCryptorCreateWithMode、CCHmac、摘要/PBKDF 系列 Security.framework: SecKeyCreateSignature、SecKeyRawSign、SecKeyRawVerify、SecKeyEncrypt、SecKeyDecrypt、SecKeyCreateWithData BoringSSL / OpenSSL: EVP cipher 与 digest 系列 |
| Android | BoringSSL: 同上 EVP/SSL 接口,另含 RAND_bytes SQLite: sqlite3_open/sqlite3_exec/sqlite3_close 等 liblog: __android_log_print、__android_log_write JNI: 录制时读取完整 JNINativeInterface_ vtable(RegisterNatives、FindClass、GetMethodID、CallVoidMethod、GetStringUTFChars……共 112 个函数指针),经 JNIEnv vtable 的调用同样可分类 |
| Linux | OpenSSL: EVP_* 系列 系统调用: mmap、mprotect、write、open、ioctl、socket、getrandom 等(需要 REC_SVC 记录;内联 svc 指令按 Linux syscall 名字表命名) |
| Unknown | 启发式 —— 同时尝试 iOS 与 Android 表;质量取决于 Trace 中出现的模块 |
./tenet [options] trace.bin --syscall-intercept./tenet [options] trace.bin --syscall-intercept --syscall-intercept-bytes 128--syscall-intercept-bytes <N> 控制每个指针参数读取的字节数(默认 64)。更大值能捕获更长缓冲区,但增加内存和处理时间。
解读平台 API 结果
Section titled “解读平台 API 结果”每个拦截的调用报告:
- API 名与分类;
- 所有参数值,指针参数在可能时解引用为字符串;
- 调用点 PC 与 inst_id;
- 返回值 —— v8 来自返回时刻的
REC_RETVAL;v4–v7 没有返回事实,显示unknown; - 解析所用的数据源(
REC_CAPICALL/REC_SVC/XFER_CALL)。
多个数据源覆盖同一调用时,只有优先级最高的产生输出 —— tenet 不会跨源重复。
CLI(批处理模式)
Section titled “CLI(批处理模式)”上述每个 Pass 都既有独立 CLI 标志又有 --run-pass 名称,可从脚本驱动:
./tenet [options] trace.bin --objc --objc-class NSString./tenet [options] trace.bin --objc --pattern --objc-crypto./tenet [options] trace.bin --syscall-intercept --syscall-intercept-bytes 128
# 等价地通过通用 runner./tenet [options] trace.bin --run-pass objc./tenet [options] trace.bin --run-pass objc_crypto./tenet [options] trace.bin --run-pass syscall_intercept批处理模式(-c,或选择非交互执行的分析/查询参数)向 stdout 写 JSON。
Tauri 前端
Section titled “Tauri 前端”Tauri 前端通过 WebSocket 请求同一后端 Pass 管线,并在 Platform 分组展示 ObjC 与 syscall 结果。ObjC 结果按类显示消息频率;syscall 结果按 API 显示频率。
直接运行 ./tenet trace.bin 会进入 TUI。带 --objc、--syscall-intercept 等分析参数时,CLI 进入非交互批处理模式并向 stdout 输出 JSON,而不是启动 TUI。
MCP 将平台分析暴露为 objc_messages、syscall_intercept、objc_crypto。objc_messages 对 class 使用精确匹配、对 selector 使用子串匹配;syscall_intercept 用 capture_bytes 控制捕获大小。具体分页字段以 MCP 工具目录 为准。
| 功能 | iOS / Darwin | Android | Linux | Unknown |
|---|---|---|---|---|
| ObjC 消息 | 完整 | 跳过 | 跳过 | 继续尝试(兼容旧 iOS Trace) |
| CommonCrypto / Security / CryptoKit 分类 | 仅分类(需要 REC_CAPICALL) |
N/A | N/A | 启发式 |
| 加密原语检测 | 基于模式(pattern Pass) |
基于模式 | 基于模式 | 基于模式 |
跨源关联(objc_crypto) |
可用 | N/A | N/A | N/A |
| 精确的平台调用返回值 | v8 REC_RETVAL;旧 Trace 为 unknown |
v8 REC_RETVAL;旧 Trace 为 unknown |
v8 REC_RETVAL;旧 Trace 为 unknown |
取决于 Trace 版本 |
录制前置条件:
- ObjC:Trace 必须含
REC_OBJC记录(录制时record |= QBDITRACE_REC_MSGSEND;在qbditrace_config_t中配置objc_msgSend地址); REC_CAPICALL:设置enable_capi_intercept = 1且n_capi_funcs > 0(或依赖 native dlsym 自动解析);否则仅REC_SVC与XFER_CALL可用;REC_SVC:默认在QBDITRACE_REC_DEFAULT(127)中启用;显式record &= ~QBDITRACE_REC_SYSCALL可减小 Trace 体积;- 精确 ObjC/C API/SVC 返回值、内联 ObjC selector 与 16 位 C API ID 需要 v8
HF_CALL_METATrace 及REC_RETVAL;v4–v7 仍可读取,但没有返回事实。
- 判断平台来源 —— 先在 Tauri 桌面应用中打开 Trace,或运行
--stats,确认target_os、模块名、记录 flags。 - iOS Trace —— 先
--objc映射用户可见行为,再--pattern定位密码学原语;配合--objc-crypto把每个原语链接到触发它的 ObjC 方法。 - 提取密码学素材 ——
--syscall-intercept从 CommonCrypto / BoringSSL / JNI 调用中恢复密钥、IV、明文缓冲区。缓冲区超过 64 B 时增大--syscall-intercept-bytes。 - 评估分类可信度 —— 如果 Trace 没有
REC_CAPICALL,结果依赖REC_SVC+XFER_CALL,可能误归或遗漏;查看结果中source字段。
objc 未返回任何消息
Section titled “objc 未返回任何消息”- 明确的 Android/Linux 目标会被跳过;Unknown 会继续尝试解析。若旧 Trace 的
module_name启发式不可靠,请用新版 qbditrace 重新录制。 - 确认 Trace 含
REC_OBJC记录 —— 如果record没含QBDITRACE_REC_MSGSEND,ObjC 记录不会被发射。 - 确认
objc_msgSend(及其 super/stret 变体)已在qbditrace_config_t中配置;tenet 的 Pass 只解析这四个符号。
syscall_intercept 未找到任何 API 调用
Section titled “syscall_intercept 未找到任何 API 调用”- Trace 可能缺少
REC_CAPICALL与REC_SVC,检查 Trace header 中的record。 - 录制时可能未开启
enable_capi_intercept;不开启则只有 syscall 级拦截生效。 - Unknown 目标 Trace 上 API 表可能不匹配 —— 用正确的
target_os重新录制。
返回值为 unknown
Section titled “返回值为 unknown”- Trace 为 v4–v7,或缺少 v8
REC_RETVAL,因此不存在可信的返回时刻事实。请以 v8HF_CALL_META重新录制;Tenet 刻意不从后续记录的 x0 推断返回值。
CLI 与 Tauri 前端结果不一致
Section titled “CLI 与 Tauri 前端结果不一致”两者都通过同一后端 Pass 管线和持久化缓存获得结果,因而相同参数应产生一致结果。如果输出不符,确认 Tauri sidecar 没有加载以前用不同参数生成的缓存;可用 --cache-delete <pass|all> 清除后重跑。