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, 云安全监控, 可视化界面, 密码学分析, 硬件无关, 网络流量审计, 通知系统, 静态分析