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, 后台面板检测, 库, 应急响应, 数字取证, 自动化脚本, 进程分析