ShruutiiGupta/Proctrace
GitHub: ShruutiiGupta/Proctrace
一款基于 ADB 的 Android 数字取证进程时间线采集与重建 CLI 工具,支持无 root 分层采集、包归因置信度分级及 PDF/JSON 报告输出。
Stars: 1 | Forks: 0
# ProcTrace
一款用于移动取证的能力分层 Android 进程重建工具。
作为**检验人员工作站上的 JVM 桌面 CLI** 运行 —— 目标手机上无需
安装任何内容。它通过标准的 ADB 与连接的设备进行通信,因此无需为每个
制造商单独构建版本:同一个可执行文件即可在 Pixel、Samsung、
Xiaomi 手机或任何其他基于 AOSP 的设备上运行。
## 为什么“无需在手机上安装任何内容”
该工具与设备通信的方式与任何取证的 ADB 工作流相同:
启用 USB debugging + 设备所有者/检验人员在屏幕上接受
RSA 授权提示。该授权步骤是 Android 自身的安全控制,
并非本工具能够或应该绕过的内容。不需要配套的 APK — Android 8+ 将
`ActivityManager.getRunningAppProcesses()` 限制为调用应用自身的进程,
因此应用内的非 root 方案无法替代基于 ADB 的数据采集。
## 前置条件
1. 检验人员工作站上的 **JDK 17 或更高版本**。
使用以下命令检查:`java -version`
2. 你的 `PATH` 中的 **Android platform-tools** (`adb`)。
使用以下命令检查:`adb version`
如果缺少,请从 https://developer.android.com/tools/releases/platform-tools
下载。
3. 目标设备已启用 **USB debugging**,已连接并获得授权:
adb devices
应将其列为 `device`,而不是 `unauthorized` 或 `offline`。如果显示为
`unauthorized`,请解锁手机并在屏幕上接受“允许 USB debugging?”
提示。
4. 对于 Tier 1(最高保真度):目标设备必须已经获取 root 权限。
本工具绝不会尝试对设备进行 root —— 它仅在设备已经拥有 root 权限
且 shell 可以通过 `su -c` 访问该权限时使用它。
你**不需要**单独安装 Gradle 或 Kotlin —— 下面的步骤 1
(`./gradlew`)会在首次运行时自动下载完全匹配的 Gradle 版本。
## 构建与运行
```
cd proctrace
# 1. 编译并运行 unit test suite
./gradlew build
# 2. 从第一个已连接/已授权的设备获取
./gradlew run
```
在 Windows 上,请使用 `gradlew.bat` 而不是 `./gradlew`。
首次运行将下载 Gradle 本身(约 120 MB,一次性操作,需要联网)
以及本项目依赖的 Kotlin/PDFBox 库
(`kotlinx-serialization-json`、`pdfbox`)。此后,构建将完全离线。
成功运行的输出结果:
- 一行摘要信息,包含状态细分(当前运行中 / 已退出 /
未确认)以及**归因置信度细分**:有多少事件与已安装的包匹配(HIGH),
有多少是已知的系统/原生 UID 但无包可匹配(MEDIUM),有多少是应用范围 UID
但无匹配包(LOW),以及有多少具有本工具确实完全无法解析的 UID
(UNATTRIBUTED)—— 这四个数字应大致涵盖每个事件,
因此可以明确看出差距在哪里。
- 打印到控制台的 timeline 表格(PID、PPID、UID、进程、包、
状态、证据来源、归因置信度、**持续时间** —— 对于仍在运行的进程为截至目前的时间,
对于已退出的进程为从开始到结束的时间 —— 以及任何异常标志),此外,
对于每个恢复的进程,还包括:内存(VmRSS/RSS)、活动线程计数、
内核调度状态以及完整的命令行
(仅限 root 层级 —— 如果没有 root 权限,`ps -A` 无法读取另一个 UID 的 `/proc/[pid]/cmdline`)。
- `./output/process_timeline_.json` —— 机器可读的案件文件。
每个条目为 `{event: , durationMillis, durationFormatted}`
—— 持续时间是在写入时计算的,而不是作为原始证据存储在
事件本身上(参见 `ProcessDuration`),因为 RUNNING(运行中)进程的
持续时间在你每次查看时都会发生变化。
- 确认监管链完整性的控制台行
(`Chain of custody intact: true`)
### 命令行选项
```
./gradlew run --args="--pdf --icons --vt"
```
持续捕获现在是**默认行为** —— 上述命令将保持每 15 秒轮询一次(在每个周期打印并写入一份新报告),直到你按下 Ctrl+C。传入 `--once` 可使用旧的单次快照行为。
| 标志 | 效果 |
|---|---|
| `--once` | 执行单次采集流程后退出 —— 退出默认的持续捕获行为。 |
| `--interval=` | 持续捕获周期之间的轮询间隔(默认为 15)。 |
| `--pdf` | 除了 JSON 之外,还会写入一份格式化的、分页的 PDF 报告(`./output/process_timeline_.pdf`)。 |
| `--icons` | 尽力而为:尝试在 PDF 中嵌入每个包的启动器图标。需要你的 `PATH` 中存在 `aapt` **或** `aapt2`(随 Android SDK build-tools 提供)—— 两者都会被尝试,因为许多当前的 SDK 安装仅包含 `aapt2`。每次跳过(缺少工具、矢量图标、拉取失败等)都会向控制台打印一行原因,而不是默默地失败。 |
| `--vt` | 对照 VirusTotal 的*现有*数据库检查每个归因包的已安装 APK(仅限 SHA-256 查询 —— 这绝不会将你的 APK 上传到 VirusTotal;见下文)。 |
| `--vt-api-key=` | VirusTotal API 密钥。也可以通过 `VT_API_KEY` 环境变量设置。免费层级的密钥即可用于查询。 |
| `--timeout=` | 每个 ADB 命令的超时时间(秒)(默认为 45)。如果在进程数过多、包列表庞大或 USB/Wi-Fi ADB 连接缓慢的设备上看到“adb 命令超时”,请提高此值(例如 `--timeout=90`)。 |
| `--help`, `-h` | 打印用法并退出。 |
制造商/型号输入是**完全自动化**的 —— `DeviceProfiler` 会从设备本身读取
`ro.product.manufacturer`/`ro.product.model`/`ro.build.version.sdk`,并且 `OemAdapterRegistry` 会从中选择匹配的适配器(如果有的话)。不需要配置手动的“选择你的手机型号”步骤。
### 关于 VirusTotal 检查 (`--vt`)
此操作执行**只读哈希查询**,这与本工具其余部分的取证严谨性立场一致:
1. 将每个不同的归因包的 APK 从设备中拉取出来(`pm path` +
`adb pull` —— 仅复制出来,绝不修改设备)。
2. 在本地计算其 SHA-256。
3. 查询 `GET /api/v3/files/{sha256}` —— 询问“VirusTotal 以前是否见过这个
确切的文件,各引擎是怎么说的?”
它**不会**将你的 APK 上传到 VirusTotal。如果某个哈希值以前从未被
提交过,你会看到“以前未在 VirusTotal 的数据库中出现过”,
而不是由工具代表你上传二进制文件 —— 上传会在没有单独且明确
决定的情况下将潜在的私有应用发送到设备外,并且每个文件可能需要几分钟的时间。如果你想要完整的
未知文件扫描,请通过 VirusTotal 自己的
上传流程自行提交特定的 APK。
免费的 API 层级被限制为每分钟约 4 次请求;本工具以每 16 秒一次请求的速度进行控制,因此检查多个包需要一些
时间 —— 对于具有大量不同归因包的手机,预计大约每分钟检查一个包。
### 通过 VirusTotal MCP 服务器进行深入调查(可选)
上面的 `--vt` 有意识地只做一件事:针对
VirusTotal 现有数据库进行相同哈希值的查询,仅此而已。如果某个包返回
`FLAGGED`(或者你只是想要比恶意/可疑引擎计数
更多的上下文),并且你将报告拉取到 Claude 对话中以帮助你
处理案件,你可以连接
[`mcp-virustotal`](https://github.com/w0h1v/mcp-virustotal) —— 一个 MCP 服务器,
它为 LLM 客户端提供了用于关系图、沙箱行为
摘要、威胁行为者/集合查询以及自由格式 VT 语料库搜索的工具。
这是一个**你与 ProcTrace 一起运行的独立工具,而不是它的依赖项。**
`mcp-virustotal` 是一个 Node/TypeScript MCP 服务器,它通过 stdio 与 MCP
客户端(Claude Desktop、Claude Code、VS Code+Copilot)进行通信 —— 没有
办法将其 `import` 到这个 Kotlin/JVM CLI 中,也没有理由这样做:这个
项目已经在 `VirusTotalChecker.kt` 中自行进行了直接的 VT REST 调用。在代码级别将两者连接起来,仅仅意味着从 Kotlin 中调用 Node 来执行一个你已经在原生调用的 API。
**设置:**
1. 获取一个 [VirusTotal API 密钥](https://www.virustotal.com/gui/my-apikey)(免费
层级的密钥即可用于查询)。
2. 添加服务器 (Claude Code):
claude mcp add --transport stdio --env VIRUSTOTAL_API_KEY=your-key virustotal -- npx -y @burtthecoder/mcp-virustotal
或者对于 Claude Desktop,添加到 `claude_desktop_config.json`
(macOS 上位于 `~/Library/Application Support/Claude/`,
Windows 上位于 `%APPDATA%\Claude\`):
{
"mcpServers": {
"virustotal": {
"command": "npx",
"args": ["-y", "@burtthecoder/mcp-virustotal"],
"env": { "VIRUSTOTAL_API_KEY": "your-virustotal-api-key" }
}
}
}
保存后重启客户端。
3. 照常运行 ProcTrace(此处 `--vt` 是可选的 —— 你也可以在没有它的情况下拉取新的
哈希值):
./gradlew run --args="--vt"
4. 将案件文件中不同的包/哈希列表提取为可以交给 Claude 的
形式:
python3 scripts/extract_hashes_for_mcp.py ./output/process_timeline_.json
5. 将该输出粘贴到连接了 `virustotal` 的 Claude 聊天中,并要求
它在每个 `sha256` 上运行 `get_file_report` / `get_file_behaviour_summary` —— Claude 将直接调用 MCP 工具,而不是由你
编写单独的 HTTP 请求脚本。
与内置检查的立场相同:这只会查询 VT 已经
见过的哈希值。ProcTrace 和 MCP 服务器都不会上传你的 APK。
### 在没有连接设备的情况下运行(Tier 3 / 静态重建)
将以下任何内容放在项目旁边的 `./artifacts/` 下,然后在没有连接设备的情况下运行 `./gradlew run`:
- `logcat_events.txt` —— `adb logcat -b events -d -v threadtime` 的输出,
先前从设备捕获
- `usagestats.xml` —— 旧版的磁盘上的 usagestats XML 文件(API 23-27 设备)
### 此代码库的验证状态
最初的核心(采集、解析、关联、`CommandGuard`)是使用 Kotlin 2.0.20 编译器编译的,并在之前的测试中针对合成输入进行了测试 —— 请参阅 git 历史记录 / 之前的会话笔记。
本轮的更改 —— dumpsys 去重修复、命名 UID 解析、
UNATTRIBUTED 置信度路径、`--watch`、`--pdf` (PDFBox)、`--icons`
(基于 `aapt`)和 `--vt` (VirusTotal) —— 已经编写完成,并针对现有代码(类型、签名、导入均由人工交叉检查)进行了手动一致性审查,但**在此环境中无法编译**:此沙箱没有出站网络访问权限,因此 Gradle 无法在此处解析新的 `pdfbox` 依赖项。请将你自己机器上的 `./gradlew build` 视为这段代码的第一次真正编译,并反馈任何
无法构建的内容 —— 人工审查的更改与编译器的绿灯保证并不
相同。
## “通用”框架的工作原理
1. **`DeviceProfiler`** 首先在每台设备上运行,并回答三个
问题,无论制造商是谁:root 是否可用,`/proc` 是否受到 `hidepid` 的限制,SDK 级别是什么。这才是设备碎片化的真正核心 —— 它来自于 AOSP/kernel 安全策略,而不是 OEM 定制。
2. **`AcquisitionStrategySelector`** 选择满足前提条件的最高保真度策略:
- `RootProcAcquisitionStrategy` (Tier 1) —— 如果已获取 root 权限,一个批处理的 `su -c` shell 脚本将遍历每个 `/proc/[pid]`
- `ShellPsAcquisitionStrategy` (Tier 2) —— `ps -A` 加上针对某些 OEM 构建版本在 `ps -A` 中遗漏的缓存/后台进程的 `dumpsys` 补充。该补充仅添加 `ps -A` 尚未报告的 PID(并且会对其进行去重),因为 `dumpsys activity processes` 会在多个内部部分列出相同的进程。
- `StaticArtifactAcquisitionStrategy` ( 3) —— 当完全没有实时访问权限时,从先前捕获的 logcat/usagestats 导出中进行历史重建
3. 在任何实时连接的层级(1 或 2)上,**`ProcessTimelineCorrelator`** 会拉取设备的事件日志环形缓冲区,并将 `am_proc_start`/
`am_proc_died` 证据与实时快照合并 —— 这样,仍在运行的
进程会补全其开始时间,而在你连接之前已经退出的
进程仍然会显示开始和结束时间。
4. **`PackageAttributor`** 通过 `pm list packages -U` 将 UID 映射到已安装的包。`ps` 按名称打印的 UID(`root`、`system`、
`shell`、`radio`、...)通过 `AndroidAidMap` 解析,而不是保留为 -1/UNATTRIBUTED —— 只有当 UID 确实完全无法解析时才会真正被归为 UNATTRIBUTED,这与“没有包的已知系统 UID”现在是一个明确区分且单独计数的类别(参见归因覆盖率摘要行)。然后 **`AnomalyDetector`** 会交叉检查整个获取的数据集,以查找孤立父进程、UID 冲突以及与其归因包的 Android 命名约定不匹配的进程名称。
5. **`OemAdapterRegistry`** 仅在设备的制造商需要时加载一个*可选*的适配器(目前包括 Xiaomi 和 Samsung)。大多数设备不需要。无论适配器是否选择使用它进行丰富化,适配器的额外 shell 命令输出都会被记录在监管链中。
6. **`ChainOfCustodyLogger`** 对执行的每个采集操作进行哈希链记录,因此可以检测到对早期日志条目的任何追溯编辑。
到达设备的每个命令都会首先由 `CommandGuard` 进行检查,
它拒绝执行任何无法证明是只读的操作(没有 `rm`、
`pm install`/`uninstall`、`am start`/`force-stop`、`settings put`、写入重定向到文件路径等)—— 这就是为什么无论给定的采集策略要求设备运行什么,采集过程都能保持取证严谨性的原因。
## 添加对新制造商的支持
仅当你发现了通用核心尚未解析的真正特定于 OEM 的工件(例如非标准的日志路径)时,才执行此操作。不要仅因为设备是不同的品牌就创建适配器 —— 首先尝试针对通用核心运行它。
```
class MyOemAdapter : OemAdapter {
override val name = "Vendor Name"
override fun matches(fingerprint: DeviceFingerprint) =
fingerprint.manufacturer.contains("vendorname", ignoreCase = true)
override fun extraShellCommands(): List = listOf("cat /path/to/vendor/log")
override fun enrich(events: List, extraOutputs: Map): List {
// inspect extraOutputs[...] and annotate matching events'
// corroboratingArtifacts, then return events
return events
}
}
```
在 `Main.kt` 的 `OemAdapterRegistry(listOf(...))` 调用中注册它。
## 已知限制
- 仅凭 `pm list packages -U` 无法显示在进程运行后被卸载的包 —— 这正是 Tier 3(logcat/usagestats)存在的 `NO_INSTALLED_PACKAGE` 异常情况要去捕获的内容。
- `am_proc_start`/`am_proc_died` 时间戳在原始事件日志中没有年份字段,因此关联器假定为当前年份 —— 跨越年度界限的捕获需要人工交叉检查。
- Android 9+ (API 28+) 将磁盘上的 `usagestats` 移动到了未公开的 protobuf 格式;本工具不会尝试对其进行逆向工程,而是回退到解析 `dumpsys usagestats` 文本输出(保真度较低,仅限实时查询窗口)。详情请参阅 `UsageStatsParser`。
- `--icons` 需要主机 `PATH` 中存在 `aapt`(或 `aapt2`)(Android SDK build-tools 的一部分)—— 没有它,PDF 将在没有图标的情况下呈现,但在其他方面会正常完成。启动器图标为矢量 ``(日益普遍)的包也会被跳过而不是进行栅格化 —— 这是一个本项目未尝试的、截然不同的渲染子项目。
- `--watch` 采用轮询而不是订阅实时的操作系统事件流 —— 如果一个进程在两次轮询间隔之间启动并完全退出,且它也没有留下 `am_proc_start`/`am_proc_died` 跟踪,则该进程可能会被遗漏。降低 `--interval` 以减少(而不是消除)这种差距。
- `--vt` 仅进行哈希查询(见上文)—— 根据设计,它不会告诉你有关 VirusTotal 从未见过的文件的任何信息。
- 根据设计,HarmonyOS NEXT 设备不受支持 —— 它们不是基于 AOSP 的,并且被明确排除在范围之外,而不是提供不完整的支持。
## 项目结构
```
src/main/kotlin/com/proctrace/
adb/ AdbClient (read-only-enforced shell wrapper + pull()), CommandGuard
profiling/ DeviceProfiler — root/hidepid/SDK capability detection
acquisition/ The three acquisition tiers + the selector
parsing/ ps/proc/logcat/usagestats parsers, AndroidAidMap, timeline correlator
attribution/ UID -> package mapping, anomaly detection
oem/ Optional per-manufacturer adapters (Xiaomi, Samsung)
custody/ Hash-chained chain-of-custody log
security/ VirusTotalChecker — read-only APK hash lookup
report/ Console timeline printer, JSON writer, PdfReportWriter, IconExtractor
CliOptions.kt Command-line flag parsing (--watch/--pdf/--icons/--vt/...)
scripts/
extract_hashes_for_mcp.py Pulls {package, sha256, vt_verdict} out of a
case file for use with the mcp-virustotal MCP
server (see "Deeper investigation..." above)
src/test/kotlin/com/proctrace/
adb/ CommandGuard safety tests
parsing/ ps/proc-batch/correlator/AndroidAidMap parsing tests
attribution/ PackageAttributor confidence-tier tests
```
标签:ADB, Android, DSL, Kotlin, 后台面板检测, 库, 应急响应, 数字取证, 自动化脚本, 进程分析