droidsaw/droidsaw-apk
GitHub: droidsaw/droidsaw-apk
一个纯 Rust 实现的 Android APK/AAB/XAPK 容器解析与安全分析库,提供签名验证、密码学弱点检测、YARA 扫描和 SBOM 生成等能力。
Stars: 2 | Forks: 1
# droidsaw-apk
[droidsaw](https://github.com/droidsaw/droidsaw) 工作区的 APK 和 AAB 解析器及安全分析工具。它负责解码容器并遍历其中的每一层 —— `AndroidManifest.xml`(二进制 XML)、`resources.arsc`、ELF 共享库、APK 签名块(v1 PKCS#7 + v2 / v3 / v4)、v4 附属文件、AAB protobuf、baseline profiles、网络安全配置、原始资源 —— 并输出类型化的 `Finding` 记录。纯 Rust 编写,BSD-3-Clause 许可证。
## 签名分析
`signing_info()` 返回一个包含现有每种签名方案的 `SigningInfo`:
| Scheme | Parser | Verifier |
|---|---|---|
| v1 (JAR / PKCS#7) | `signing::SigningInfo::from_pkcs7` | 完整的 CMS `SignedData` 验证:证书链,SHA-1 / SHA-256 / SHA-384 / SHA-512 摘要,按 RFC 5652 §5.4 规范排列的 SignedAttributes |
| v2 | `signing::detect_signing_block` | block ID `0x7109871a`,按 AOSP 进行摘要验证,签名者证书链 |
| v3 | 相同路径,block ID `0xf05368c0` | 轮换血缘;v3 → v2 → v1 证书绑定 |
| v4 | `signing::SigningInfo::attach_v4_sidecar` | `.apk.idsig` Merkle-tree 根 + 签名者证书验证 |
| source stamp | source-stamp 解析器 | 针对 `META-INF/com.android.stamp.{type,source}` 交叉检查公钥绑定 |
`signing/verify.rs` 包含算法调度器:基于 SHA-2 系列的 RSA(PKCS#1 v1.5 + PSS),P-256 / P-384 上的 ECDSA,DSA-SHA2-256(块算法 `0x0301`)。仅使用 RustCrypto 验证器 —— 不包含密钥生成和签名路径。
`signing/subject.rs` 根据 RFC 4514 解析主题 DN,并带有完整的 `\X` 转义处理(`,`、`=`、`\`、`+`、`<`、`>`、`#`、`;`、前导 `#`、尾随空格)。
`signer_allowlist.rs` 包含控制 `NO_LAUNCHER_INTENT_FILTER` 重新分类的已知签名者表 —— 由已知供应商签名的、不包含 launcher activity 的 deep-link 工具,与缺少该组件的未知包所生成的发现是不同的。
### 密码学因式分解攻击
`signing/factoring.rs` 通过 `signing::extract_key_material` 针对从证书链提取的 RSA 模数运行四种独立的攻击:
- **ROCA 指纹** (CVE-2017-15361) —— 通过 Howgrave-Graham 生成器集检查进行 Infineon RSALib 弱密钥检测;素数表上限为 271。
- **Fermat 因式分解** —— 紧密素数模数检测,具有可配置的迭代边界。
- **Wiener 攻击** —— 小 `d` RSA 私钥指数漏洞(作用于 `(N, e)`)。
- **Batch-GCD** —— 小型密钥语料库使用成对 (`batch_gcd`),大型语料库使用乘积余数树 (`batch_gcd_quasilinear`)。
因式分解堆栈独立于解析路径 —— 调用者从任意数量的 APK 中收集 `KeyMaterial`,然后在整个集合中运行批量攻击。
## 容器分析
| 面 | 模块 | 产出内容 |
|---|---|---|
| 二进制 XML 解码器 | `binary_xml.rs` | 保留 lossy-flag 的字符串池,带有深度和广度限制的元素树 |
| `AndroidManifest.xml` | `manifest.rs` | 权限、组件、intent filters、deep links、导出组件风险概况 |
| `resources.arsc` | `resources.rs` | 资源表、`ResTable_config` 解析、资源遮蔽检测、type-id 零值拒绝 |
| AAB protobuf | `aab.rs` | base + feature 模块,保留 lossy-attribute,XML 节点递归深度上限 |
| baseline profiles | `baseline_profile.rs` | 带有 zlib 炸弹上限的 `.prof` / `.profm` 解析 |
| v4 sidecar | `v4_sidecar.rs` | `.apk.idsig` Merkle 根 |
| ELF 原生库 | `elf.rs` | 加固标志、JNI exports、`.init_array`、RWX 段、导入符号、section-header 边界 |
| `META-INF/MANIFEST.MF` | `manifest_mf.rs` | 带有位置 `=` 验证的 base64 属性解码 |
| 网络安全配置 | `network_security.rs` | 明文传输、pinning、trust anchors |
| accessibility XML | `accessibility.rs` | 声明的 accessibility 服务配置 |
| 资产熵 | `asset_format.rs` + `apk/asset_entropy_audit.rs` | 每个资产的 Shannon 熵 + 格式分类、加密资产指纹 |
| 混淆指标 | `obfuscation.rs` | R8 / ProGuard / 商业壳信号 |
| SBOM / SDK 清单 | `sdk_inventory.rs`, `firebase.rs`, `backend_vendor.rs` | 捆绑的 SDK + 后端供应商清单 |
### ZIP 结构异常
`apk/anomaly.rs` 包含 EOCD 类检测器。APPNOTE.TXT §4.4.21–§4.4.24 是规范标准:
- `DoubleEocd { count, offsets }` —— 存在多个 End-of-Central-Directory 记录。
- `SuspiciousEocdComment` —— EOCD 注释字段带有意外字节(打包器走私向量)。
- `NonZeroDiskNumber` —— 单磁盘压缩包上的 disk-number / disk-with-CD / disk-entries 字段非零。
夹具 `packer_double_eocd`、`packer_eocd_comment_false_positive`、`packer_eocd_disk_number_nonzero` 和 `packer_eocd_magic_flood` 固定了检测器的行为。
## YARA 扫描
`yara_scan.rs` 是 YARA-X(Rust 移植版)的集成。`rules/` 下有四个内置规则包:
- `antianalysis.yar`
- `credential.yar`
- `crypto.yar`
- `packer.yar`
暴露接口:
- `bundled_rules()` / `bundled_credential_rules()` —— 嵌入的规则集。
- `compile_rules_str_restricted()` —— 针对调用者提供的规则的指令策略执行器。
- `load_rules_from_dir()` —— 加载并编译自定义规则目录。
- `scan_bytes_with_scanner_budgeted()` —— 带有 `YaraMatchBudget` 上限的扫描;在截断时发出溢出的 `Finding`。
`yara_verify.rs` 包含基于来源的误报抑制机制 —— 在已知供应商 SDK 或已知干净库 namespace 内的匹配不会生成发现。`detect_library_namespaces` 使用 Aho-Corasick(对 APK 缓冲区进行单次遍历)。
## 架构
```
src/
├── apk/ orchestrates per-entry audits (security, anomaly, classifier, JNI, …)
├── signing/ der_walker · factoring · keyinfo · oids · subject · types · verify
├── manifest.rs AndroidManifest model
├── binary_xml.rs AXML decoder
├── resources.rs ARSC parser
├── elf.rs ELF parser
├── aab.rs AAB protobuf
├── yara_scan.rs · yara_verify.rs
└── …flat src/ layout, ~29 files
```
仅供测试的特性标志(`test-construct-verified`、`test-support`)对 `Verified` 构造函数和 `Apk::empty_for_testing()` 进行限制,使得生产环境的调用者无法绕过验证器或解析器入口点。
## 正确性
### 对抗性输入不产生 panic
crate 根目录在每个非测试模块上拒绝 panic lint 下限:
```
#![forbid(unsafe_code)]
#![cfg_attr(not(test), deny(
clippy::unwrap_used, clippy::expect_used, clippy::panic,
clippy::unreachable, clippy::todo, clippy::arithmetic_side_effects,
clippy::indexing_slicing, clippy::string_slice, clippy::as_conversions,
/* …17 more lints… */
))]
```
参见 `src/lib.rs` 第 72–117 行。解析器路径返回类型化的 `Err(ApkError)`(参见 `src/error.rs`);切片访问通过 `.get(i).ok_or(…)` 进行,算术运算通过 `checked_*` 进行。由 `cargo clippy` 和 libFuzzer 运行强制执行。
### Fuzz 覆盖率
`fuzz/fuzz_targets/` 下的 25 个 libFuzzer 目标覆盖了解析器面:
- **容器与格式解析器:** `fuzz_apk_parse`、`fuzz_xapk_parse`、`fuzz_aab_decode_xml_node`、`fuzz_binary_xml`、`fuzz_arsc`、`fuzz_baseline_profile`、`fuzz_v4_sidecar`
- **签名与密码学:** `fuzz_signing_block`、`fuzz_detect_signing_block`、`fuzz_v1_pkcs7`、`fuzz_subject_dn`
- **Native 与 DEX:** `fuzz_elf_parse`、`fuzz_apk_dex_extractor`、`fuzz_dex_sdk_versions_fallback`
- **分析与扫描:** `fuzz_obfuscation_analyze`、`fuzz_yara_prefilter`、`fuzz_yara_scan_with_skip`、`scan_tail`
- **Manifest 与辅助项:** `fuzz_manifest_heuristics`、`fuzz_parse_multiple`
- **异常检测:** `fuzz_v1_orphan_zip_entry`
- **差分与辅助项:** `parser_differential_arsc`、`parser_differential_axml`、`parser_differential_axml_permissive_recovery_combinatorial`
- **其他:** `fuzz_aab_decode_xml_node`(AAB XML)、`fuzz_v1_signing_garbage`、通过嵌入式 harness 实现的 `fuzz_v4_signed_data_layout`
新的解析器路径上线时会附带其 fuzz 目标;由 `tests/regenerate_fuzz_seeds.rs` 重新生成在 `tests/fixtures/adversarial/` 下跟踪的语料库种子。
### 夹具棘轮
`tests/fixtures/apk_matrix/` 包含 28 个命名的夹具:`aab_base_module`、`aab_feature_module`、`debuggable_true`、`exported_component_heavy`、`locale_fallback_manifest`、`manifest_not_first`、`many_permissions`、`minimal_valid`、`multi_native_libs`、`multidex`、`packer_aab_slash_bomb`、`packer_double_eocd`、`packer_eocd_comment_false_positive`、`packer_eocd_disk_number_nonzero`、`packer_eocd_magic_flood`、`packer_zip_comment_smuggle`、`unknown_chunk_arsc`、`v1_signed`、`v1_signed_signedattrs_unsorted`、`v1_signed_tampered`、`v1_signing_garbage`、`v4_signed`、`with_baseline_profile`、`with_dex`、`with_native_lib`、`with_raw_manifest`。棘轮是 `tests/fixture_ratchet.rs`;回归的夹具会导致构建中断。
在本地验证:
```
cargo test -p droidsaw-apk fixture_ratchet
cargo test -p droidsaw-apk typed_errors
```
### Kani harness
`proofs/` 下的七个证明文件包含总共 21 个 harness(由 `#[cfg(kani)]` 控制):
- `aab_xml_depth.rs` —— AAB protobuf XML 节点递归深度上限。
- `der_set_member_cmp.rs` —— DER SET 成员比较的正确性。
- `manifest_mf_base64_positional.rs` —— `MANIFEST.MF` base64 尾随 `=` 位置不变性。
- `resources_type_id_nonzero.rs` —— `resources.arsc` type-id 零值拒绝。
- `restableconfig_parse_bound.rs` —— `ResTable_config` 解析消耗字节数的边界。
- `subject_dn_escape.rs` —— 有界 DN 输入上 RFC 4514 §2.4 转义的完整性。
- `v4_signed_data_layout.rs` —— v4 签名块布局和数据边界验证。
## 输入
| 格式 | 入口点 |
|---|---|
| `.apk` | `Apk::parse(path)` |
| `.xapk` | `Apk::parse_xapk(path)` |
| `.aab` (Play bundle) | `Apk::parse_aab(path)` |
| 拆分 APK 集合(base + config splits) | `Apk::parse_multiple(&paths)` |
在 APK 内部发现的 DEX 字节码和 Hermes 包作为 `DexEntry` / `AabStringRecord` 暴露出来;这些层的反编译存在于 `droidsaw-dex` 和 `droidsaw-hermes` 中。
## 输出
`Apk::parse` 返回一个类型化的 [`Apk`] 结构体,包含已解析的 manifest、资源表、签名块、ELF 元数据、DEX 统计信息、资产熵和 AAB 字符串池。`Apk::audit_security()` 发出包含严重性、种类、层级和详细信息的 [`Finding`] 记录。不包含 `println!`,对对抗性输入不产生 panic。
## 性能
在 APK 解析和审计的热点路径,使用 `Vec::with_capacity` 替换了已知增长上限(来自中央目录的条目数,来自签名块的签名者数)的集合上的 `Vec::new()`。预分配由 `droidsaw-common` 中的 `guard::bound_count` 控制,该函数在使用前根据切片的字节长度对计数进行交叉检查。
## 工作区
此 crate 是 `droidsaw` 工作区的一个成员。有关跨层流水线(DEX → Java,Hermes → JavaScript,跨 React Native bridge 的污点分析),请参阅顶层的 droidsaw crate。
从 `droidsaw-common` 公开重新导出:`Finding`、`Layer`、`Severity`、`EntropyPattern`、`EntropyProfile`、`entropy_profile`、`shannon_entropy`。
## 许可证
BSD-3-Clause。
标签:Android, APK解析, DSL, Rust, SBOM, 云安全监控, 可视化界面, 密码学分析, 硬件无关, 网络流量审计, 通知系统, 静态分析