# 官方 APK 全量逆向档案 — 总览与方法

> **doc-id** `evidence/method` · **层** 证据（方法与总览，本层入口）· **状态** 权威：本层可推翻结论层，冲突时以档案为准并回写 · **建档** 2026-09-21／22 · **修订** 2026-09-26
> 文档系统 [`docs/README.md`](../README.md) · 本层索引在 [`README.md`](README.md)

> 这一套文档是把 `相机 app/` 里三个官方 APK **穷尽式**拆开后的证据层。
> `docs/analysis/*` 是结论，这里是结论背后的每一个类、每一条字符串、每一个参数值。
> 写这套档案的直接原因：之前的分析出现过「逻辑不清楚、字符串没读出来、参数值没读出来」三类问题，
> 所以这次把提取做成脚本 + 全量表格，再在表格之上写解释，任何一条结论都必须能落到 `_work` 里的某一行。
>
> 建档日期：2026-09-21／22。语料与工具版本固定，见 §2 与 `tools/re/README.md`。

---

## 1. 取证对象

| App | APK 文件 | 包名 | 版本 | 体积 | sha256(前16) |
|---|---|---|---|---|---|
| TUWIN | `相机 app/TUWIN.apk` | `com.tuwinsmart.tuwin` | 1.6.4 (250) | 22.7 MiB | `fa7958cf5fa0d811` |
| XTU GO | `相机 app/XTU GO_8.4.3_243_2026-08-19-17-05-43.apk` | `com.gku.xtugo` | 8.4.3_243_2026-08-19-17-05-43 (243) | 145.9 MiB | `ee4b0453c2156551` |
| idGoLive | `相机 app/idGoLive_1.3.6.apk` | `com.id221.golive`（Java 命名空间 `com.icatch.golive`） | 1.3.6 | 153.8 MiB | `7061421287be3005` |

三个包都**未加固、未加密**，jadx 可以直接出 Java 源码。换包或升级到新版本时，先比 `data/_corpus/corpus.md`
里的 sha256，再决定哪些结论要重新取证。

## 2. 方法与工具链

| 步骤 | 工具 | 产物 |
|---|---|---|
| 反编译 dex → Java | jadx 1.5.6（`_work/jadx/`） | `_work/tuwin_src/`、`_work/xtu_src/`、`_work/idgo_live_src/` |
| 反编译失败兜底 | jadx `--show-bad-code` | `_work/xtu_bad/`（XTU 有 127 个类首跑报错） |
| 资源/清单解码 | jadx；TUWIN 改走 `aapt2 dump xmltree` + `aapt2 dump resources` | 见 §3 |
| 结构化提取 | 本仓库 `tools/re/`（9 个零依赖 Node 脚本） | `data/<app>/*` + `_work/re/<app>/*` |

`TUWIN.apk` 用 jadx 跑资源阶段会卡在 “Resetting disk code cache”（两种参数都一样），因此它的清单与资源值
改由 `tools/re/aapt2.mjs` 从 `aapt2` 输出反解，产出形状与另两家一致；源码仍来自 jadx。这个替代路径顺带
补上了一个能力：布局里的 `@0x7f…` 引用会回解成 `string/名字`，见 `data/tuwin/layouts.md`。

**归属判定口径**（`tools/re/lib.mjs`）：

- `OWN` — 确认的厂商包。TUWIN `com.tuwinsmart.*`；XTU `com.gku.*`、`com.icatchtek.*`、顶层 `icatch.*`、`com.example.icatch*`、`com.hisi.*`、`m.mifan.acase.*`；idGoLive `com.icatch.*`、`com.icatchtek.*`、`com.id221.*`、`com.tinyai.*`。
- `UNKNOWN` — 既不是厂商包名也不在已知库清单里（混淆后的应用层藏在这里），**与 OWN 一样逐类展开**。
- `lib` — 已确认的第三方库/平台代码，只清点不逐类分析。

这样定的理由：XTU 的 App 层重度混淆，按包名白名单会漏；宁可多展开一堆开源库，也不能把相机协议代码留在「未分析」里。
反过来说，`data/<app>/packages.md` 里每个包归哪一档都写明了，读者可以自己复核有没有把某家库误判成厂商代码。

## 3. 覆盖度（本次实际提取的数字，不是估计）

| | TUWIN | XTU GO | idGoLive |
|---|---|---|---|
| 反编译源文件 | 6,501 | 18,195 | 10,685 |
| APK 内条目 | 1,597 | 4,914 | 3,202 |
| 类型（含内部类，全部） | 12,175 | 28,523 | 17,203 |
| 逐类展开的类型（OWN+UNKNOWN） | 3,072 | 2,889 | 1,135 |
| 展开的方法签名 | 18,838 | 22,996 | 9,154 |
| 展开的字段/常量 | 20,315 | 40,030 | 27,118 |
| 厂商侧字符串字面量 | 55,917 | 22,623 | 9,111 |
| HTTP/CGI 面字面量（去重） | 280 | 416 | 279 |
| 带 `%s/%d` 的模板字面量 | 134 | 218 | 15 |
| 资源值条目（所有配置合计） | 21,539 | 52,026 | 34,479 |
| 默认配置 string/array/plurals | 1,051 | 2,463 | 990 |
| 语言/形态配置数 | 135 | 154 | 118 |
| 布局/菜单/配置 XML | 253 | 581 | 344 |
| 原生库 .so | 9 | 31 | 63 |
| .so 符号（.dynsym） | 18,721 | 38,415 | 55,053 |
| .so 内协议相关字符串 | 670 | 1,060 | 4,880 |
| assets 文件 / 其中文本 | 3 / 1 | 194 / 22 | 6 / 4 |

一句话读法：**代码里能静态看到的值，都在 `data/` 里；`data/` 里的值，档案正文都过了一遍。**

## 4. 目录结构

```
docs/evidence/
├── README.md            本层索引（doc-id `evidence`）
├── method.md            ← 本文（方法与总览；doc-id `evidence/method`）
├── tuwin.md             TUWIN 1.6.4：主文档 + 三篇内嵌附录（协议面 / 设置与操作 / 传输·OTA·联网）
├── xtugo.md             XTU GO 8.4.3：主文档 11 节 + 内嵌附录 A-E（海思 CGI / Ambarella 报文 /
│                         SigmaStar 与播放层 / 操作复现表 / 权限·组件·域名）
├── idgolive.md          idGoLive 1.3.6：主文档 9 节 + 内嵌附录 A-D（PTP 属性表 / HTTP 与 TCP 命令 /
│                         原生层与 PTP-IP / UI 与端点判定）
├── operations-matrix.md 三家操作对照与我们的复现清单（我们侧状态台账）
└── data/                生成物，勿手改（重跑方式见 §8 与 data/README.md）
    ├── README.md        生成物契约：怎么读、怎么重跑、边界
    ├── _corpus/corpus.md 语料指纹（体积/sha256/产出规模）
    └── <app>/           每家一目录：tuwin / xtugo / idgolive
        ├── manifest.md            清单全量：包/版本/权限/组件/intent-filter/meta-data
        ├── res-strings-default.md 默认配置全部 string / array / plurals 键值
        ├── res-strings-zh.md      中文与默认不同的条目
        ├── res-locales.md         135/154/118 个语言配置各覆盖了多少条、哪些等于没翻译
        ├── res-inventory.md       资源按类型/限定符/扩展名清点
        ├── layouts.md             每个布局的控件、可见文案、id
        ├── assets-index.md        全部 asset（路径/字节/sha1/预览）
        ├── assets-content.md      文本 asset 全文（菜单定义、LitePal 建表等）
        ├── apk-entries.md         APK 内每个条目（压缩方式/大小/CRC32）
        ├── natives.md             .so 清单：ELF 类别、DT_NEEDED、符号数
        ├── natives-jni.md         Java_* 导出符号 → 对应的 Java native 方法
        ├── packages.md            包归属（OWN/UNKNOWN/lib）与每包类型数
        ├── catalog-http.md        HTTP 路径 / CGI / URL 全量目录
        ├── catalog-templates.md   带格式化槽位的模板全量
        ├── catalog-consts.md      常量字面值，按类分组
        ├── catalog-enums.md       枚举常量与构造实参
        ├── catalog-ports.md       端口/超时/大小/命令号类数值常量
        ├── catalog-settings.md    `*_entries`/`*_values` 配对表（界面选项 ↔ 协议值；没有静态配对时不生成，见 §5）
        ├── api/<包>.md            逐类明细：注解、字段字面值、枚举、方法签名、方法体内字面量
        └── signals-*.tsv          按语义分桶的字面量（scratch 为主，docs 里放 summary）
```

`_work/re/<app>/` 里还有未入库的原始表（`own-literals.tsv` 全量、`values-all.tsv` 全量、`natives-symbols.tsv`
全量、解出的 `assets/`、`lib/`），体积大且是 gitignore 的 scratch；需要时按 `tools/re/README.md` 重跑即可。

> **2026-09-22 文档归并**：每份档案原来拆成「主文档 + 若干附录」共 18 个文件，找东西要点来点去；现在把三家
> 的附录整段并回主档案（`tuwin.md` / `xtugo.md` / `idgolive.md` 各自的下半部，标题仍是 `# 附录 A · …`，正文一字未改），
> 自研侧那篇则独立成 `docs/impl/command-surface`，正文里指向旧附录文件名的引用已全部改写。
> `data/` 下的机器表**没有合并**——档案里按名字引用了具体的 `api/<包>.md`，合并会让那些引用失效。
>
> **2026-09-26 分层**：本目录从 `docs/08-官方APK全量逆向档案/` 迁到 `docs/evidence/`，自研侧台账迁到
> `docs/impl/command-surface`；旧编号与新 doc-id 的对照表在 [`docs/README.md`](../README.md) §6。

## 5. 按问题找答案

| 你想知道 | 先看 |
|---|---|
| 某个操作官方到底发了什么 | 对应档案的「操作复现表」；再查 `catalog-http.md` / `catalog-templates.md` 拿原始模板 |
| 某个参数有哪些取值 | 对应档案的「设置项全表」；`catalog-enums.md` + `res-strings-default.md`（entries/values 成对）。注意 `catalog-settings.md` 只有 XTU 11 行 / idGoLive 9 行 / TUWIN 0 行——**这三家的设置选项绝大多数不在静态资源里，而是设备自描述**，这本身就是 `docs/analysis/protocol-matrix` 反复强调的那件事 |
| 某个端口/超时/缓冲区数值 | `catalog-ports.md`，再顺 `api/<包>.md` 找它被谁用 |
| 相机上报了什么事件、字段叫什么 | 对应档案的协议栈小节 + `own-literals.tsv`（scratch） |
| 页面上有哪些入口/文案 | `layouts.md` + `res-strings-zh.md` |
| Java 层看不到、藏在 .so 里的实现 | `natives-jni.md`、`natives-strings.tsv`，再回 `api/` 找调用点 |
| 某条结论有没有证据 | 档案里每条事实都带 `_work/<app>_src/sources/...:行号`；照行号读原文 |

## 6. 边界与已知空白

- **只读静态分析。** 不主动连别人的设备、不做利用；动态验证只发生在自有设备/自建模拟器上。方法声明沿用 `docs/analysis/network-api` §0。
- **不搬运代码。** 档案记录事实、常量、控制流描述与引用，重实现按干净室路线自己写（参见 `docs/design/roadmap` 的架构约束）。
- **静态定不来的东西明确留白**，各档案末尾有「未解之谜」小节列出需要的补证手段（抓包、真机、IDA）。目前已知的硬空白：idGoLive 的 PTP-IP 端口号（3195/3196 属推测）、各家 TLS 证书固定与否的运行时行为、云端接口的真实鉴权链。
- **混淆**：XTU 的 App 层符号不可读，靠 `UNKNOWN` 桶 + 字面量反推；这会让部分「这个方法叫什么」只能回答「它在 `m/gku/xtugo/…` 的某处，做了 X」。
- **jadx 反编译失败的方法**：全语料共 58 个文件带 `JADX ERROR`，其中**厂商侧 3 个**：
  `m/mifan/acase/icatch/IcatchProtocol.java`（`getFileList(int,int)` 因 RegionMaker 溢出没出 Java）、
  `com/gku/actioncam/hisilicon/dv/dlg/DlgForProcessActivity.java`、
  `com/gku/actioncam/hisilicon/dv/localimage/ImageEditLib/ImageEditActivity.java`。
  jadx 在这些方法位置留了 smali 伪码，`tools/re/code.mjs` 会把里面的字符串与整数常量捞回来，
  在 `own-literals.tsv` 里以 `via=bytecode` 标记（例：`IcatchProtocol` 失败块里的 `playbackClient`、`listFiles(...)`、`15`）。
  其余失败文件全在 kotlinx / androidx / exoplayer / bugly / luck 等库代码里，厂商侧没有整类丢失。
  `_work/xtu_bad/`（`--show-bad-code` 版）用于对照，条目集合与 `_work/xtu_src/` 完全一致。

## 7. 与既有文档的关系

- 全部文档的地图（分层、状态、引用约定、维护规矩）在 [`docs/README.md`](../README.md)；本层是结论层的证据层。
- `docs/analysis/apk-overview`（首次逆向报告）：概览，本次深挖后其取值细节以本层为准；三份档案各有「对既有文档的纠正」小节。
- `docs/analysis/protocol-matrix`（品牌型号与协议矩阵）：结论层速查表，本层是它的证据；本层发现的错要回写它，不留两份互相矛盾的表。
- `docs/analysis/ota-and-gaps`（固件与功能差距）、`docs/analysis/network-api`（网络 API 与安全评估）、`docs/analysis/ble-and-vpn`（蓝牙配网与 VPN）：都从这三家取证，本层补充其取值细节并修正域名判定（反编译包里有域名字符串 ≠ 是真在用的 API）。
- 实现层（`composeApp/src/*/**.kt` 的 profile / protocol 插件）应以本层的表为验收清单：表里每一行要么已实现，要么在 `docs/evidence/operations-matrix` 里被显式标记为「不做」。

## 8. 复跑与校验

```sh
node tools/re/run-all.mjs        # 三家全跑，约 3–5 分钟
node tools/re/run-all.mjs xtugo  # 只跑一家
node tools/re/check-refs.mjs          # doc-id 引用 + § 小节号 + 层索引孤儿检查
node tools/re/check-links.mjs docs     # 相对链接是否可达
node tools/re/check-citations.mjs docs # 本层每条 `文件:行号` 引用能否定位（找不到/越界 → 退出码非 0）
node tools/re/check-tables.mjs docs    # 表格列数（GFM 里裸 `|` 会把一行劈成多列）
node tools/re/canonicalize-citations.mjs <doc.md> <appKey> --apply   # 把裸文件名引用补成全路径
```

前置条件、每个提取器的输入输出、以及踩过的坑（Git Bash `unzip` 的 `*` 不跨 `/`、jadx 复制 store-only `.so`
会截断、Kotlin `@Metadata` 二进制字面量污染等）写在 `tools/re/README.md`；生成物本身的契约在
[`data/README.md`](data/README.md)。

引用校验不是装饰：这一轮它抓出了 4 处把 `HaisiPreviewModel.java` 误写成 `HisiPreviewModel.java`、
9 处把 jadx 内联的匿名类写成 `Foo$AnonymousClass1.java`（这种文件根本不存在）、
以及 1 处引用了从未存在过的 `docs/02-功能-设备连接流程`（旧编号时代的产物，已修）。**文档里的行号必须机器核过，不能靠人眼。**
