Skip to content

Phase 9: Android line completion (multi-.so / MANIFEST-010 / MASVS / symbol attribution) - #23

Merged
sp3arm4n merged 4 commits into
mainfrom
claude-code
May 10, 2026
Merged

Phase 9: Android line completion (multi-.so / MANIFEST-010 / MASVS / symbol attribution)#23
sp3arm4n merged 4 commits into
mainfrom
claude-code

Conversation

@sp3arm4n

Copy link
Copy Markdown
Owner

Summary

  • 9-1 --apk-lib all extracts every .so under the selected ABI and runs lief on each; JNI bridge correlation now matches against the union of every analysed library's exports so symbols in libssl.so / libsqlcipher.so / etc. no longer show as unmatched
  • 9-2 MANIFEST-010 surfaces the previously-captured base_trusts_user_certs flag (high) with a five-step mitmproxy CA install PoC
  • 9-3 new rule_taxonomy.py maps every shipped rule to an OWASP MASVS bucket; HTML report renders a category overview between the severity bar and the per-finding cards (framework key is generic so Phase 8 PE / CWE buckets can reuse the same grid)
  • 9-4 co-locality string attribution: classify each JNI export name into category buckets, intersect with NativeStringHints buckets, attach up to 12 evidence strings per symbol; HTML bridges table shows up to 3 chips per row with a header count

Test plan

  • 747 → 796 unit tests passing locally (+49 across multi-.so, MANIFEST-010, taxonomy, and symbol attribution)
  • Round-trip via to_dict / from_dict for new fields (additional_so_metas, additional_so_paths, strings_by_symbol)
  • analysis_cache.SCHEMA_VERSION bumped 2 → 4 so multi-lib and symbol-attribution payloads cannot be replayed by older caches
  • HTML report renders new sections; existing find-card / zero-PoC tests anchor lookups inside findings-section so taxonomy pills no longer interfere
  • Manual: rerun apk-batch-audit skill against sample/apk/ after merge to validate behavior on real APKs (--apk-lib all parity, MANIFEST-010 zero-fire on release builds, MASVS grouping, attributed-string chips on JNI exports)

🤖 Generated with Claude Code

sp3arm4n and others added 4 commits May 10, 2026 15:40
Extract every .so under the selected ABI when --apk-lib all is passed,
run lief on each, and correlate JNI bridges against the union of every
analysed library's exports. Native string hints are now built from the
merged string pool so URLs / paths / crypto tokens embedded in
secondary libraries (libssl.so, libsqlcipher.so, etc.) are no longer
silently dropped.

The primary .so still fills so_meta / extracted_so_path for backward
compat; the rest goes into new fields:

  AndroidAnalysis.additional_so_metas: list[BinaryMeta]
  AndroidAnalysis.additional_so_paths: list[str]

Also adds AndroidAnalysis.all_so_metas convenience property.

Per-extra-lib parse failure is downgraded to a warning so one bad .so
doesn't abort the whole report. The "X siblings skipped — re-run with
--apk-lib X" warning is suppressed in multi-lib mode (it would be
incorrect there).

apk_extractor gains extract_all_native_libs() returning every .so path
under an ABI, sorted lex. analysis_cache schema bumps 2 → 3 so an
older single-lib payload can't satisfy a multi-lib request from cache.

CLI accepts --apk-lib all (case-insensitive); cache_replay_blocker
forces a fresh run when multi-lib data is missing from the hit.

Tests +12: extract_all_native_libs (3), TestAnalyzeApkMultiLib (5)
covering populate, union exports, warning suppression, single-lib
warning preserved, graceful degradation; round-trip preserves
additional_so_metas; existing 747 tests untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…se 9-2)

NetworkSecurityConfigMeta.base_trusts_user_certs was captured by Phase
7-3 but no rule fired on it — apps that opt user-installed CAs into
the trust chain are MITM-able with a mitmproxy CA install + Wi-Fi
proxy, no root or app patch required, and that gate was being silently
ignored.

MANIFEST-010 (high) surfaces the flag with an actionable description
and a five-step PoC bundled into the same .sh export as the other
mitmproxy recipes:

  1. mitmproxy --listen-port 8080
  2. adb push ~/.mitmproxy/mitmproxy-ca-cert.cer to /sdcard/
  3. install via Settings > Security > Install from device storage
  4. set device Wi-Fi manual proxy to host:8080
  5. am start the app and observe TLS-decrypted traffic

PoC notes warn that Android 7+ defaults user-CA trust to OFF, so this
rule only fires on explicit opt-in (almost always a debug NSC that
slipped into release).

Tests +5: positive trigger (high severity, NSC config), false flag
ignored, no NSC object => no fire, NSC field default => no fire,
PoC artifact shape (mitmproxy + cer push + am start). PER_RULE_BUILDERS
coverage matrix updated to include MANIFEST-010 alongside 001..006.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a single-source-of-truth rule taxonomy and a category overview
panel to the HTML audit report.

src/venomhook/rule_taxonomy.py is the new module — every audit rule
maps to a category dataclass (key + label + framework). Today the
mapping covers all 10 manifest rules (001..010) and all 6 code rules
into nine MASVS buckets:

  MASVS-NETWORK-1     MANIFEST-002, MANIFEST-010, CODE-001
  MASVS-PLATFORM-1    MANIFEST-004, 005, 006
  MASVS-PLATFORM-2    CODE-002 (WebView)
  MASVS-STORAGE-1     MANIFEST-003, CODE-005, CODE-006
  MASVS-CRYPTO-1      CODE-003
  MASVS-PRIVACY-1     MANIFEST-007 (perms)
  MASVS-PRIVACY-2     CODE-004 (cred logs)
  MASVS-ARCH-9        MANIFEST-008, 009 (sdk)
  MASVS-RESILIENCE-1  MANIFEST-001 (debug)

categorize_findings() takes any iterable of objects with rule_id +
severity attrs (duck-typed so Phase 8 PE-* findings reuse the same
helper without a models.py dependency cycle), returns CategoryGroup
records sorted by category key with each group's pills sorted by
severity rank.

audit_html_report inlines a new ``.taxonomy`` grid CSS block and
renders the overview panel right after warnings, before the per-rule
findings cards. Each card shows framework / category key / human
label / per-rule pills coloured by severity. The section auto-omits
when no rule has a mapping (clean app or a future rule set with no
taxonomy entries yet).

A coverage test locks every shipped MANIFEST-001..010 + CODE-001..006
rule_id into CATEGORY_BY_RULE so adding a future rule without an
entry fails the build rather than silently disappearing from the
overview.

Tests +17:
  - test_rule_taxonomy.py (13): mapping presence, unknown→None, group
    sort, severity sort, malformed object handling, dataclass hash
  - test_audit_html_report.py (5): taxonomy section presence/absence,
    pill severity class, ordering before findings, code-rule contribution
  - Two pre-existing tests (finding-card order, zero-poc card lookup)
    updated to anchor their searches inside findings-section since the
    taxonomy pills now mention every rule_id earlier in the document

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-locality heuristic that surfaces "this exported native is in the
same category as embedded artefacts" — a first-pass operator hint
that points at high-yield Frida targets without claiming static xref
accuracy.

True per-symbol RVA-based attribution requires disassembling .text
(ADRP+ADD on ARM64, RIP-rel LEA on x86-64) — a Capstone-class task
that's out of scope. We deliver the pragmatic alternative instead:

  classify_symbol_name(name) -> frozenset[str]
    Tokenize JNI / camelCase names, match against per-bucket needles
    ("crypt"/"aes" -> crypto, "log"/"trace" -> debug, etc.). Returns
    empty when the name is opaque (single-char ProGuard outputs,
    Java_a_b_c stripping, generic names) — safer to under-attribute
    than mislead.

  attribute_strings_by_symbol_name(symbols, hints, cap=12)
    For every classifiable symbol, collect the subset of hint strings
    whose buckets intersect the symbol's name buckets. Capped per
    symbol so the report stays compact.

Pipeline integration:
  - AndroidAnalysis.strings_by_symbol: dict[str, list[str]]
  - Populated at the end of analyze_apk when so_meta + hints exist
  - Bridge-matched JNI symbols are the primary candidate set;
    falls back to every Java_* export when no bridges (caller used
    --no-jadx)

HTML rendering:
  - Bridges section header reports "{N}개 브리지에 문자열 단서 첨부"
  - Each bridge row shows up to 3 attributed strings as inline pills
    so triage is one screen, not two clicks

Cache schema bumps 3 -> 4 (v3 payloads have empty strings_by_symbol;
replaying v3 for a fresh request would mis-represent the report).

Tests +15:
  test_native_strings.py (13): symbol classification, word-boundary
    safety (key vs monkey), camelCase split, ProGuard returns empty,
    co-locality crypto/url separation, cap respected, multi-category
    symbols collect from each matching bucket.
  test_android_pipeline.py (2): pipeline populates strings_by_symbol
    when bridges match crypto-named exports; empty when hints are
    irrelevant.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sp3arm4n
sp3arm4n merged commit 36417d4 into main May 10, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant