SDK Neutralizer v4: JSON registry with 29 SDKs and registry-scan.py
Replaces hardcoded smali patterns with a data-driven SDK registry: 29 JSON definitions covering ad networks, mediation, attribution, and analytics SDKs (123 entry points, 156 ad operations, 30 deep patterns, 64 manifest components). - Add plugins/.../sdk-neutralizer/registry/ with _schema.json + 29 SDK files (Adjust, Braze, CleverTap, Guru Fusion, Mintegral, Mixpanel, MobileFuse, Moloco, PubMatic, TradPlus, plus the prior 19) - Add registry-scan.py to consume the registry and emit targets-file + manifest-components-file consumed by neutralize.sh - Extend neutralize.sh with --no-builtin-targets, --targets-file, --manifest-components-file, --package, --cleanup-backups - Extend find-ads.sh and find-trackers.sh with --summary and --json output - Mark python3 as INSTALL_OPTIONAL in check-neutralize-deps.sh (fallback to builtin hardcoded targets when unavailable) - Bump plugin and marketplace to 1.3.1 - Add .gitignore for __pycache__/, *.pyc, *.pyo - Update CLAUDE.md, SKILL.md, neutralize.md to reflect registry-driven flow Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3d68cf392f
commit
6891a3a8a2
|
|
@ -7,14 +7,14 @@
|
|||
},
|
||||
"metadata": {
|
||||
"description": "Claude Code plugins for Android reverse engineering, tracker detection, ad SDK analysis, and SDK neutralization for enterprise deployment",
|
||||
"version": "1.2.0"
|
||||
"version": "1.3.1"
|
||||
},
|
||||
"plugins": [
|
||||
{
|
||||
"name": "android-reverse-engineering",
|
||||
"source": "./plugins/android-reverse-engineering",
|
||||
"description": "Decompile Android APK/JAR/AAR with jadx, trace call flows, extract APIs, analyze tracker/ad SDKs for privacy auditing, and neutralize SDK entry points for enterprise deployment.",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.1",
|
||||
"author": {
|
||||
"name": "Simone Avogadro"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
__pycache__/
|
||||
*.pyc
|
||||
*.pyo
|
||||
19
CLAUDE.md
19
CLAUDE.md
|
|
@ -21,7 +21,8 @@ A Claude Code Skill (plugin) for Android reverse engineering, API extraction, an
|
|||
- `plugins/android-reverse-engineering/skills/android-reverse-engineering/` — Core RE skill (5-phase workflow, references, scripts)
|
||||
- `plugins/android-reverse-engineering/skills/tracker-analysis/` — Tracker/analytics SDK detection skill (4-phase workflow, references, find-trackers.sh)
|
||||
- `plugins/android-reverse-engineering/skills/ad-analysis/` — Advertising SDK detection skill (3-phase workflow, references, find-ads.sh)
|
||||
- `plugins/android-reverse-engineering/skills/sdk-neutralizer/` — SDK neutralization skill (6-phase workflow, references, decode-apk.sh, neutralize.sh, merge-splits.sh, rebuild-apk.sh)
|
||||
- `plugins/android-reverse-engineering/skills/sdk-neutralizer/` — SDK neutralization skill (6-phase workflow, references, decode-apk.sh, neutralize.sh, registry-scan.py, merge-splits.sh, rebuild-apk.sh)
|
||||
- `plugins/android-reverse-engineering/skills/sdk-neutralizer/registry/` — SDK registry (29 JSON files defining neutralization targets, manifest components, protected patterns)
|
||||
|
||||
## Key Scripts
|
||||
|
||||
|
|
@ -61,13 +62,20 @@ bash find-ads.sh <source-dir> [--admob|--unity|--ironsource|--applovin|--faceboo
|
|||
SDK neutralizer scripts under `plugins/android-reverse-engineering/skills/sdk-neutralizer/scripts/`:
|
||||
|
||||
```bash
|
||||
# Check neutralization dependencies (including apktool >= 2.9.0)
|
||||
# Check neutralization dependencies (including apktool >= 2.9.0, Python 3.6+ optional)
|
||||
bash check-neutralize-deps.sh
|
||||
|
||||
# Decode APK or XAPK (for XAPK: decodes base APK, preserves splits in .xapk-origin/)
|
||||
bash decode-apk.sh <file.apk|file.xapk> [-o <decoded-dir>]
|
||||
|
||||
# Scan decoded APK against SDK registry (generates targets-file + manifest-components-file)
|
||||
# Depth: 1=entry_points only, 2=+ad_operations, 3=+deep_patterns
|
||||
python3 registry-scan.py <decoded-dir> --registry <registry-path> --depth 1|2|3 --category ads|trackers|all --output-dir <decoded-dir>
|
||||
|
||||
# Neutralize SDK entry points in decoded APK (dry-run first)
|
||||
# Registry-driven mode (preferred):
|
||||
bash neutralize.sh <decoded-dir> --no-builtin-targets --targets-file <decoded-dir>/registry-targets.txt --manifest-components-file <decoded-dir>/registry-manifest.txt [--dry-run] [--package <path>]
|
||||
# Fallback (builtin targets):
|
||||
bash neutralize.sh <decoded-dir> [--ads|--trackers|--all] [--dry-run] [--no-backup] [--no-manifest] [--targets-file <file>] [--replay] [--no-save-manifest]
|
||||
|
||||
# Merge XAPK splits into decoded base for single APK output (optional, for XAPK input)
|
||||
|
|
@ -77,6 +85,13 @@ bash merge-splits.sh <decoded-dir> [--abi <abi>] [--all-abis] [--skip-resources]
|
|||
bash rebuild-apk.sh <decoded-dir> [--auto-keystore|--debug-key|--keystore <file>] [-o <output>] [--no-sign] [--no-res] [--zipalign] [--single-apk]
|
||||
```
|
||||
|
||||
SDK registry under `plugins/android-reverse-engineering/skills/sdk-neutralizer/registry/`:
|
||||
|
||||
- 29 SDK JSON files + `_schema.json` schema definition
|
||||
- Covers: AdMob, Unity Ads, IronSource, AppLovin, Meta AN, Vungle, InMobi, Chartboost, Pangle, BidMachine, Smaato, PubNative, Ogury, Fyber, Amazon APS, Facebook, Firebase Analytics, Firebase Crashlytics, AppsFlyer, Adjust, Braze, CleverTap, Guru Fusion, Mintegral, Mixpanel, MobileFuse, Moloco, PubMatic, TradPlus
|
||||
- Each JSON defines: packages, entry_points, ad_operations, deep_patterns, manifest_components, protected_patterns
|
||||
- `registry-scan.py` consumes these JSONs to generate neutralization targets
|
||||
|
||||
## Architecture
|
||||
|
||||
**Plugin structure follows Claude Code skill conventions:**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "android-reverse-engineering",
|
||||
"version": "1.2.0",
|
||||
"version": "1.3.1",
|
||||
"description": "Decompile Android APK/JAR/AAR with jadx, trace call flows, extract APIs, analyze tracker/ad SDKs for privacy auditing, and neutralize SDK entry points for enterprise deployment.",
|
||||
"author": {
|
||||
"name": "Simone Avogadro"
|
||||
|
|
|
|||
|
|
@ -60,6 +60,25 @@ This searches for ad SDK calls **only in app code** (excluding library packages
|
|||
|
||||
Use the reference documents in `${CLAUDE_PLUGIN_ROOT}/skills/ad-analysis/references/` for SDK-specific patterns.
|
||||
|
||||
### Step 3b: Filter false positives
|
||||
|
||||
When analyzing the results, distinguish real SDK presence from false positives:
|
||||
|
||||
- **HIGH confidence** — SDK-specific classes/imports are present (e.g., `MobileAds.initialize()`, `import com.unity3d.ads.UnityAds`). The SDK is definitely integrated.
|
||||
- **MEDIUM confidence** — Only generic ad format names matched (e.g., `InterstitialAd`, `BannerView`, `RewardedAd`). These class names may be from the actual SDK or from custom wrappers. Verify by checking the full import path.
|
||||
- **LOW confidence** — Only generic strings matched (e.g., "banner", "interstitial" in comments or unrelated code). Check the context.
|
||||
|
||||
**Quick verification**: For any SDK flagged as detected, check if its package directory actually exists:
|
||||
```bash
|
||||
# Example: verify Unity Ads is really present
|
||||
find "$SOURCE_DIR" -path "*/com/unity3d/ads" -type d
|
||||
```
|
||||
|
||||
Use `--summary` for a quick confidence-scored overview before diving into raw output:
|
||||
```bash
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-analysis/scripts/find-ads.sh "$SOURCE_DIR" --summary
|
||||
```
|
||||
|
||||
### Step 4: Produce report
|
||||
|
||||
Generate a structured report with:
|
||||
|
|
|
|||
|
|
@ -48,6 +48,25 @@ For each SDK found in the sweep, perform deep analysis:
|
|||
|
||||
Use the reference documents in `${CLAUDE_PLUGIN_ROOT}/skills/tracker-analysis/references/` for SDK-specific patterns.
|
||||
|
||||
### Step 3b: Filter false positives
|
||||
|
||||
When analyzing the results, distinguish real SDK presence from false positives:
|
||||
|
||||
- **HIGH confidence** — SDK-specific classes/imports are present (e.g., `FirebaseAnalytics.getInstance()`, `import com.adjust.sdk.Adjust`). The SDK is definitely integrated.
|
||||
- **MEDIUM confidence** — Only generic method names matched (e.g., `.track(`, `.logEvent(`, `.identify(`). These are common method names used by many libraries, not just tracker SDKs. Verify by checking if the actual SDK package exists in the source tree.
|
||||
- **LOW confidence** — Only string matches (e.g., the word "amplitude" in a comment or unrelated context). Check the surrounding code — if it's not an SDK import/call, it's a false positive.
|
||||
|
||||
**Quick verification**: For any SDK flagged as detected, check if its package directory actually exists:
|
||||
```bash
|
||||
# Example: verify Firebase is really present, not just a string match
|
||||
find "$SOURCE_DIR" -path "*/com/google/firebase/analytics" -type d
|
||||
```
|
||||
|
||||
Use `--summary` for a quick confidence-scored overview before diving into raw output:
|
||||
```bash
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/tracker-analysis/scripts/find-trackers.sh "$SOURCE_DIR" --summary
|
||||
```
|
||||
|
||||
### Step 4: Produce report
|
||||
|
||||
Generate a structured report with:
|
||||
|
|
|
|||
|
|
@ -76,35 +76,104 @@ Verify the decoded directory contains `smali/` and `AndroidManifest.xml` (the sc
|
|||
|
||||
If the output includes `XAPK_ORIGIN:<path>`, inform the user: "This is an XAPK (split APK bundle). The base APK has been decoded for neutralization, and all split APKs are preserved. During rebuild, all APKs (base + splits) will be re-signed with the same key and reassembled into a new XAPK."
|
||||
|
||||
### Step 5: Identify targets
|
||||
### Step 5: Identify targets — Registry Scan
|
||||
|
||||
Run entry point detection to find which SDK calls exist in the app code:
|
||||
The decoded directory contains smali bytecode. Use `registry-scan.py` to match against the SDK registry (29 SDKs, 123 entry points, 156 ad operations).
|
||||
|
||||
**5a. Run registry scan:**
|
||||
|
||||
```bash
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ad-analysis/scripts/find-ads.sh "${DECODED_DIR}" --entrypoints
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/tracker-analysis/scripts/find-trackers.sh "${DECODED_DIR}" --entrypoints
|
||||
python3 ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/registry-scan.py "${DECODED_DIR}" \
|
||||
--registry "${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/registry/" \
|
||||
--depth 1 --category all \
|
||||
--output-dir "${DECODED_DIR}"
|
||||
```
|
||||
|
||||
Present the results and ask the user what to neutralize:
|
||||
- **Ads only** (`--ads`)
|
||||
- **Trackers only** (`--trackers`)
|
||||
- **Both** (`--all`, recommended)
|
||||
Parse stdout:
|
||||
- `MATCHED:` lines — present as a table (SDK name, category, target count)
|
||||
- `UNKNOWN_PACKAGE:` lines — candidates for Step 5c
|
||||
- `REGISTRY_TARGETS:` / `REGISTRY_MANIFEST:` — paths to generated files
|
||||
|
||||
### Step 6: Dry-run preview
|
||||
**Depth levels**: Ask the user which depth to use:
|
||||
- **Depth 1** (default, safest): only SDK init/start methods
|
||||
- **Depth 2**: + ad load/show/cache methods
|
||||
- **Depth 3**: + bulk-stub internal packages (aggressive, version-dependent)
|
||||
|
||||
Always run a dry-run first so the user can review what will be patched:
|
||||
If the user requests depth 2 or 3, re-run with `--depth 2` or `--depth 3`.
|
||||
|
||||
**Fallback** (if Python 3 not available): use builtin hardcoded detection:
|
||||
```bash
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh "${DECODED_DIR}" --all --dry-run
|
||||
```
|
||||
|
||||
Show the user the list of methods that would be patched and manifest components that would be disabled. Ask for explicit confirmation before proceeding. Remind the user about possible side effects for any SDK where the stub could cause breakage (especially `getInstance()` returning null).
|
||||
**5b. Identify wrapper frameworks:**
|
||||
|
||||
Search for non-SDK packages that invoke known SDK methods — these are wrapper/bridge classes. Use Grep (auto-approved) to find invocations from app code:
|
||||
|
||||
```
|
||||
Grep: pattern="invoke-.*Lcom/google/android/gms/ads|invoke-.*Lcom/unity3d/ads|invoke-.*Lcom/ironsource|invoke-.*Lcom/applovin"
|
||||
path=<decoded-dir>/smali*/
|
||||
```
|
||||
|
||||
Filter to non-SDK packages to identify wrappers. If found, add as `--package` targets.
|
||||
|
||||
**5c. Unknown SDK discovery (if registry reported UNKNOWN_PACKAGE candidates):**
|
||||
|
||||
For significant unknown packages (10+ classes, proper naming), use Claude Code built-in tools:
|
||||
|
||||
1. **Glob** to list main classes in the package
|
||||
2. **Grep** for SDK patterns: `\.method.*(init|initialize|start|load|show)`, `const-string.*http`
|
||||
3. **Read** key classes to understand the API
|
||||
4. Classify: ads SDK, tracker, utility, or app code
|
||||
|
||||
Present unknown candidates as a table. If the user wants deep analysis:
|
||||
|
||||
**5d. Deep analysis (opt-in, requires user confirmation):**
|
||||
|
||||
Ask: "I found N unknown SDK candidates. Want me to research them via web search?"
|
||||
|
||||
For each confirmed candidate:
|
||||
1. Web search for the package name
|
||||
2. Read main smali classes for public API
|
||||
3. Propose treatment and generate custom targets
|
||||
|
||||
**5e. Compile and confirm:**
|
||||
|
||||
Present the complete target summary (registry + custom + wrappers) and ask for confirmation:
|
||||
- `--ads` / `--trackers` / `--all`
|
||||
- Which SDKs to include/exclude
|
||||
|
||||
### Step 6: Dry-run preview
|
||||
|
||||
Always run a dry-run first. Use registry-driven mode when available:
|
||||
|
||||
```bash
|
||||
# Registry-driven mode (preferred)
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh "${DECODED_DIR}" \
|
||||
--no-builtin-targets --dry-run \
|
||||
--targets-file "${DECODED_DIR}/registry-targets.txt" \
|
||||
--manifest-components-file "${DECODED_DIR}/registry-manifest.txt"
|
||||
|
||||
# Fallback (no Python)
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh "${DECODED_DIR}" --all --dry-run
|
||||
```
|
||||
|
||||
If wrapper packages were found, add `--package` flags. If custom targets were generated, append them to the targets file first.
|
||||
|
||||
Show the user what will be patched. Ask for explicit confirmation. Remind about side effects.
|
||||
|
||||
### Step 7: Neutralize
|
||||
|
||||
Apply the neutralization:
|
||||
|
||||
```bash
|
||||
# Registry-driven mode (preferred)
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh "${DECODED_DIR}" \
|
||||
--no-builtin-targets \
|
||||
--targets-file "${DECODED_DIR}/registry-targets.txt" \
|
||||
--manifest-components-file "${DECODED_DIR}/registry-manifest.txt"
|
||||
|
||||
# Fallback (no Python)
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh "${DECODED_DIR}" --all
|
||||
```
|
||||
|
||||
|
|
@ -112,7 +181,20 @@ Parse the `PATCHED:` and `MANIFEST_DISABLED:` output lines for the report.
|
|||
|
||||
### Step 8: Rebuild & sign
|
||||
|
||||
**Before rebuilding**, ask the user their signing preference:
|
||||
**If the input was an XAPK**, ask the user how they want the output:
|
||||
|
||||
> The original input was an XAPK (split APK bundle). How would you like to rebuild?
|
||||
>
|
||||
> 1. **Merged single APK** (recommended for sideloading) — merges split contents into one APK, installable with standard `adb install`. May be missing some locale/density resources.
|
||||
> 2. **XAPK bundle** (preserves original structure) — requires `adb install-multiple` to install. All splits preserved exactly.
|
||||
|
||||
If the user chooses option 1, run `merge-splits.sh` before rebuilding:
|
||||
|
||||
```bash
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/merge-splits.sh "${DECODED_DIR}"
|
||||
```
|
||||
|
||||
**Then ask the user their signing preference**:
|
||||
|
||||
> How would you like to sign the rebuilt APK?
|
||||
>
|
||||
|
|
@ -136,10 +218,11 @@ bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/rebuild-apk.sh "${DECO
|
|||
Parse the output for:
|
||||
- `KEYSTORE_USED:<path>` — which keystore was used
|
||||
- `KEYSTORE_SOURCE:<source>` — how it was resolved (debug-standard, debug-previous, debug-generated, custom)
|
||||
- `KEYSTORE_ALIAS:<alias>` — the key alias used for signing
|
||||
- `SPLIT_SIGNED:<filename>` — each re-signed split APK (XAPK only)
|
||||
- `XAPK_ASSEMBLED:<path>` — final XAPK output (XAPK only)
|
||||
|
||||
For XAPK output: inform the user that install requires `adb install-multiple` or a split APK installer (SAI).
|
||||
For XAPK output: inform the user that install requires `adb install-multiple` (unzip the XAPK first, then `adb install-multiple *.apk`).
|
||||
|
||||
### Step 9: Report & next steps
|
||||
|
||||
|
|
@ -151,7 +234,7 @@ Include in the report:
|
|||
- **Install command**: `adb install <path>` for APK, `adb install-multiple <base.apk> <splits...>` for XAPK
|
||||
|
||||
Tell the user what they can do next:
|
||||
- **Test thoroughly**: for APK: "Install via `adb install <apk>`"; for XAPK: "Install via `adb install-multiple` or use SAI (Split APKs Installer)" — test for crashes, especially features tied to ads or analytics
|
||||
- **Test thoroughly**: for APK: "Install via `adb install <apk>`"; for XAPK: "Unzip the XAPK, then install via `adb install-multiple *.apk`" — test for crashes, especially features tied to ads or analytics
|
||||
- **Verify**: "I can re-run entry point detection on the rebuilt APK to confirm neutralization"
|
||||
- **Custom targets**: "If the app uses obfuscated SDK calls, provide a targets file for additional patching"
|
||||
- **Deep analysis**: "Run `/find-trackers` or `/find-ads` for full SDK analysis"
|
||||
|
|
|
|||
|
|
@ -28,10 +28,14 @@ Options:
|
|||
--manifest Search only for AndroidManifest.xml ad markers
|
||||
--entrypoints Search only for ad SDK calls in app code (excludes library packages)
|
||||
--all Search all patterns (default)
|
||||
--summary Output a compact summary table with confidence scoring
|
||||
--json Output results as machine-readable JSON
|
||||
-h, --help Show this help message
|
||||
|
||||
Output:
|
||||
Results are printed as file:line:match for easy navigation.
|
||||
Default: Results are printed as file:line:match for easy navigation.
|
||||
--summary: Compact table with SDK | Sections | File Matches | Confidence | Status
|
||||
--json: JSON object with per-SDK detection results and confidence scores
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
|
@ -53,6 +57,8 @@ SEARCH_CONSENT=false
|
|||
SEARCH_MANIFEST=false
|
||||
SEARCH_ENTRYPOINTS=false
|
||||
SEARCH_ALL=true
|
||||
OUTPUT_SUMMARY=false
|
||||
OUTPUT_JSON=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
|
|
@ -72,6 +78,8 @@ while [[ $# -gt 0 ]]; do
|
|||
--manifest) SEARCH_MANIFEST=true; SEARCH_ALL=false; shift ;;
|
||||
--entrypoints) SEARCH_ENTRYPOINTS=true; SEARCH_ALL=false; shift ;;
|
||||
--all) SEARCH_ALL=true; shift ;;
|
||||
--summary) OUTPUT_SUMMARY=true; shift ;;
|
||||
--json) OUTPUT_JSON=true; shift ;;
|
||||
-h|--help) usage ;;
|
||||
-*) echo "Error: Unknown option $1" >&2; usage ;;
|
||||
*) SOURCE_DIR="$1"; shift ;;
|
||||
|
|
@ -90,23 +98,99 @@ fi
|
|||
|
||||
GREP_OPTS="-rn --include=*.java --include=*.kt"
|
||||
|
||||
# Summary/JSON mode
|
||||
if [[ "$OUTPUT_SUMMARY" == true ]] || [[ "$OUTPUT_JSON" == true ]]; then
|
||||
SUMMARY_MODE=true
|
||||
SEARCH_ALL=true
|
||||
else
|
||||
SUMMARY_MODE=false
|
||||
fi
|
||||
|
||||
declare -A SDK_CLASS_MATCHES
|
||||
declare -A SDK_STRING_MATCHES
|
||||
declare -A SDK_SECTIONS_HIT
|
||||
|
||||
section() {
|
||||
echo
|
||||
echo "==== $1 ===="
|
||||
echo
|
||||
if [[ "$SUMMARY_MODE" == true ]]; then
|
||||
CURRENT_SECTION="$1"
|
||||
else
|
||||
echo
|
||||
echo "==== $1 ===="
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
run_grep() {
|
||||
local pattern="$1"
|
||||
# shellcheck disable=SC2086
|
||||
grep $GREP_OPTS -E "$pattern" "$SOURCE_DIR" 2>/dev/null || true
|
||||
local result
|
||||
result=$(grep $GREP_OPTS -E "$pattern" "$SOURCE_DIR" 2>/dev/null || true)
|
||||
if [[ "$SUMMARY_MODE" == true ]]; then
|
||||
if [[ -n "$result" ]]; then
|
||||
local count
|
||||
count=$(echo "$result" | wc -l)
|
||||
_tally_section_result "$count"
|
||||
fi
|
||||
else
|
||||
echo "$result"
|
||||
fi
|
||||
}
|
||||
|
||||
run_grep_xml() {
|
||||
local pattern="$1"
|
||||
grep -rn --include="*.xml" -E "$pattern" "$SOURCE_DIR" 2>/dev/null || true
|
||||
local result
|
||||
result=$(grep -rn --include="*.xml" -E "$pattern" "$SOURCE_DIR" 2>/dev/null || true)
|
||||
if [[ "$SUMMARY_MODE" == true ]]; then
|
||||
if [[ -n "$result" ]]; then
|
||||
local count
|
||||
count=$(echo "$result" | wc -l)
|
||||
_tally_section_result "$count"
|
||||
fi
|
||||
else
|
||||
echo "$result"
|
||||
fi
|
||||
}
|
||||
|
||||
_tally_section_result() {
|
||||
local count="$1"
|
||||
local sdk=""
|
||||
local confidence="class"
|
||||
|
||||
case "$CURRENT_SECTION" in
|
||||
"AdMob"*) sdk="AdMob" ;;
|
||||
"Unity Ads"*) sdk="UnityAds" ;;
|
||||
"IronSource"*) sdk="IronSource" ;;
|
||||
"AppLovin"*) sdk="AppLovin" ;;
|
||||
"Meta"*|"Meta AN"*) sdk="MetaAN" ;;
|
||||
"Vungle"*) sdk="Vungle" ;;
|
||||
"InMobi"*) sdk="InMobi" ;;
|
||||
"Chartboost"*) sdk="Chartboost" ;;
|
||||
"Pangle"*) sdk="Pangle" ;;
|
||||
"Mintegral"*) sdk="Mintegral" ;;
|
||||
"Cross-SDK"*) sdk="CrossSDK"; confidence="string" ;;
|
||||
"Mediation"*) sdk="Mediation"; confidence="class" ;;
|
||||
"Consent"*) sdk="Consent"; confidence="class" ;;
|
||||
"AndroidManifest"*) sdk="Manifest"; confidence="class" ;;
|
||||
"Entry Points"*) sdk="EntryPoints"; confidence="class" ;;
|
||||
*) sdk="Other"; confidence="string" ;;
|
||||
esac
|
||||
|
||||
if [[ "$confidence" == "class" ]]; then
|
||||
SDK_CLASS_MATCHES["$sdk"]=$(( ${SDK_CLASS_MATCHES["$sdk"]:-0} + count ))
|
||||
else
|
||||
SDK_STRING_MATCHES["$sdk"]=$(( ${SDK_STRING_MATCHES["$sdk"]:-0} + count ))
|
||||
fi
|
||||
|
||||
local existing="${SDK_SECTIONS_HIT["$sdk"]:-}"
|
||||
if [[ -n "$existing" ]]; then
|
||||
SDK_SECTIONS_HIT["$sdk"]="${existing}, ${CURRENT_SECTION}"
|
||||
else
|
||||
SDK_SECTIONS_HIT["$sdk"]="${CURRENT_SECTION}"
|
||||
fi
|
||||
}
|
||||
|
||||
CURRENT_SECTION=""
|
||||
|
||||
# --- AdMob / Google Mobile Ads ---
|
||||
if [[ "$SEARCH_ALL" == true || "$SEARCH_ADMOB" == true ]]; then
|
||||
section "AdMob — Initialization"
|
||||
|
|
@ -285,12 +369,102 @@ if [[ "$SEARCH_ALL" == true || "$SEARCH_ENTRYPOINTS" == true ]]; then
|
|||
ENTRYPOINT_PATTERN='(MobileAds\.initialize|MobileAds\.setRequestConfiguration|InterstitialAd\.load|RewardedAd\.load|RewardedInterstitialAd\.load|AppOpenAd\.load|AdView\.loadAd|AdLoader\.Builder|UnityAds\.initialize|UnityAds\.load|UnityAds\.show|IronSource\.init|IronSource\.loadInterstitial|IronSource\.showInterstitial|IronSource\.showRewardedVideo|IronSource\.loadBanner|AppLovinSdk\.getInstance|AppLovinSdk\.initializeSdk|MaxInterstitialAd|MaxRewardedAd|MaxAdView|AudienceNetworkAds\.initialize|Vungle\.init|Vungle\.loadAd|Vungle\.playAd|InMobiSdk\.init|Chartboost\.startWithAppId|TTAdSdk\.init|PAGSdk\.init|MBridgeSDKFactory\.getMBridgeSDK|LevelPlayInterstitialAd|LevelPlayRewardedAd|LevelPlayBannerAdView)'
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
grep -rn --include="*.java" --include="*.kt" "${EXCLUDE_DIRS[@]}" -E "$ENTRYPOINT_PATTERN" "$SOURCE_DIR" 2>/dev/null || true
|
||||
|
||||
echo
|
||||
echo "NOTE: Only calls from app code are shown above. Library-internal calls are excluded."
|
||||
echo "If no results appear, ad SDKs may only be invoked internally via mediation adapters."
|
||||
local ep_result
|
||||
ep_result=$(grep -rn --include="*.java" --include="*.kt" "${EXCLUDE_DIRS[@]}" -E "$ENTRYPOINT_PATTERN" "$SOURCE_DIR" 2>/dev/null || true)
|
||||
if [[ "$SUMMARY_MODE" == true ]]; then
|
||||
if [[ -n "$ep_result" ]]; then
|
||||
local count
|
||||
count=$(echo "$ep_result" | wc -l)
|
||||
_tally_section_result "$count"
|
||||
fi
|
||||
else
|
||||
echo "$ep_result"
|
||||
echo
|
||||
echo "NOTE: Only calls from app code are shown above. Library-internal calls are excluded."
|
||||
echo "If no results appear, ad SDKs may only be invoked internally via mediation adapters."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== Search complete ==="
|
||||
# =====================================================================
|
||||
# Summary / JSON output
|
||||
# =====================================================================
|
||||
|
||||
if [[ "$SUMMARY_MODE" == true ]]; then
|
||||
AD_SDKS=("AdMob" "UnityAds" "IronSource" "AppLovin" "MetaAN" "Vungle" "InMobi" "Chartboost" "Pangle" "Mintegral")
|
||||
|
||||
if [[ "$OUTPUT_JSON" == true ]]; then
|
||||
printf '{\n "ad_sdks": [\n'
|
||||
first=true
|
||||
for sdk in "${AD_SDKS[@]}"; do
|
||||
class_count=${SDK_CLASS_MATCHES["$sdk"]:-0}
|
||||
string_count=${SDK_STRING_MATCHES["$sdk"]:-0}
|
||||
total=$((class_count + string_count))
|
||||
|
||||
if [[ $class_count -gt 0 ]]; then
|
||||
confidence="HIGH"
|
||||
elif [[ $string_count -gt 0 ]]; then
|
||||
confidence="MEDIUM"
|
||||
else
|
||||
confidence="NONE"
|
||||
fi
|
||||
|
||||
[[ "$confidence" == "NONE" ]] && continue
|
||||
|
||||
if [[ "$first" == true ]]; then
|
||||
first=false
|
||||
else
|
||||
printf ',\n'
|
||||
fi
|
||||
|
||||
sections="${SDK_SECTIONS_HIT["$sdk"]:-}"
|
||||
printf ' {"sdk": "%s", "class_matches": %d, "string_matches": %d, "total_matches": %d, "confidence": "%s", "sections": "%s"}' \
|
||||
"$sdk" "$class_count" "$string_count" "$total" "$confidence" "$sections"
|
||||
done
|
||||
printf '\n ]\n}\n'
|
||||
else
|
||||
echo
|
||||
echo "=== Ad SDK Detection Summary ==="
|
||||
echo
|
||||
printf "%-15s | %6s | %7s | %5s | %-10s | %s\n" "SDK" "Class" "String" "Total" "Confidence" "Status"
|
||||
printf "%-15s-|-%6s-|-%7s-|-%5s-|-%-10s-|-%s\n" "---------------" "------" "-------" "-----" "----------" "--------"
|
||||
|
||||
for sdk in "${AD_SDKS[@]}"; do
|
||||
class_count=${SDK_CLASS_MATCHES["$sdk"]:-0}
|
||||
string_count=${SDK_STRING_MATCHES["$sdk"]:-0}
|
||||
total=$((class_count + string_count))
|
||||
|
||||
if [[ $class_count -gt 0 ]]; then
|
||||
confidence="HIGH"
|
||||
status="DETECTED"
|
||||
elif [[ $string_count -gt 0 ]]; then
|
||||
confidence="MEDIUM"
|
||||
status="LIKELY"
|
||||
else
|
||||
confidence="NONE"
|
||||
status="NOT FOUND"
|
||||
fi
|
||||
|
||||
printf "%-15s | %6d | %7d | %5d | %-10s | %s\n" \
|
||||
"$sdk" "$class_count" "$string_count" "$total" "$confidence" "$status"
|
||||
done
|
||||
|
||||
ep_count=${SDK_CLASS_MATCHES["EntryPoints"]:-0}
|
||||
if [[ $ep_count -gt 0 ]]; then
|
||||
echo
|
||||
echo "Entry points in app code: $ep_count match(es)"
|
||||
fi
|
||||
mediation_count=${SDK_CLASS_MATCHES["Mediation"]:-0}
|
||||
if [[ $mediation_count -gt 0 ]]; then
|
||||
echo "Mediation adapters: $mediation_count match(es)"
|
||||
fi
|
||||
consent_count=${SDK_CLASS_MATCHES["Consent"]:-0}
|
||||
if [[ $consent_count -gt 0 ]]; then
|
||||
echo "Consent framework: $consent_count match(es)"
|
||||
fi
|
||||
echo
|
||||
echo "Confidence: HIGH = SDK-specific classes found, MEDIUM = only generic/string matches, NONE = not detected"
|
||||
fi
|
||||
else
|
||||
echo
|
||||
echo "=== Search complete ==="
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -21,6 +21,9 @@ Options:
|
|||
--deobf Enable deobfuscation of names
|
||||
--no-res Skip resource decoding (faster, code-only)
|
||||
--engine ENGINE Decompiler engine: jadx, fernflower, or both (default: jadx)
|
||||
--timeout SECS Global timeout in seconds (default: 600). 0 = no timeout.
|
||||
--threads N Number of decompiler threads (passed as -j N to jadx)
|
||||
--heap SIZE JVM heap size (e.g., 4g, 8g). Default: auto-sized by input file size.
|
||||
-h, --help Show this help message
|
||||
|
||||
Engines:
|
||||
|
|
@ -49,6 +52,9 @@ DEOBF=false
|
|||
NO_RES=false
|
||||
ENGINE="jadx"
|
||||
INPUT_FILE=""
|
||||
TIMEOUT=600
|
||||
THREADS=""
|
||||
HEAP_SIZE=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
|
|
@ -56,6 +62,9 @@ while [[ $# -gt 0 ]]; do
|
|||
--deobf) DEOBF=true; shift ;;
|
||||
--no-res) NO_RES=true; shift ;;
|
||||
--engine) ENGINE="$2"; shift 2 ;;
|
||||
--timeout) TIMEOUT="$2"; shift 2 ;;
|
||||
--threads) THREADS="$2"; shift 2 ;;
|
||||
--heap) HEAP_SIZE="$2"; shift 2 ;;
|
||||
-h|--help) usage ;;
|
||||
-*) echo "Error: Unknown option $1" >&2; usage ;;
|
||||
*) INPUT_FILE="$1"; shift ;;
|
||||
|
|
@ -98,6 +107,25 @@ if [[ -z "$OUTPUT_DIR" ]]; then
|
|||
OUTPUT_DIR="${BASENAME}-decompiled"
|
||||
fi
|
||||
|
||||
# --- Auto-size JVM heap based on input file size ---
|
||||
if [[ -z "$HEAP_SIZE" ]]; then
|
||||
file_size_bytes=$(stat -c%s "$INPUT_FILE_ABS" 2>/dev/null || stat -f%z "$INPUT_FILE_ABS" 2>/dev/null || echo 0)
|
||||
file_size_mb=$((file_size_bytes / 1048576))
|
||||
if [[ "$file_size_mb" -gt 100 ]]; then
|
||||
HEAP_SIZE="6g"
|
||||
elif [[ "$file_size_mb" -gt 30 ]]; then
|
||||
HEAP_SIZE="4g"
|
||||
else
|
||||
HEAP_SIZE="2g"
|
||||
fi
|
||||
echo "Auto-sized JVM heap: -Xmx${HEAP_SIZE} (input: ${file_size_mb}MB)"
|
||||
fi
|
||||
|
||||
# Export JVM heap for jadx and fernflower
|
||||
export JAVA_OPTS="-Xmx${HEAP_SIZE}"
|
||||
# jadx also reads JADX_OPTS
|
||||
export JADX_OPTS="-Xmx${HEAP_SIZE}"
|
||||
|
||||
# --- XAPK handling ---
|
||||
# XAPK is a ZIP containing one or more APKs, optional OBB files, and a manifest.json.
|
||||
# We extract it, find all APKs inside, and decompile each one.
|
||||
|
|
@ -106,6 +134,7 @@ XAPK_APK_FILES=()
|
|||
|
||||
if [[ "$ext_lower" == "xapk" ]]; then
|
||||
XAPK_EXTRACTED_DIR=$(mktemp -d "${TMPDIR:-/tmp}/xapk-extract-XXXXXX")
|
||||
trap 'rm -rf "$XAPK_EXTRACTED_DIR"' EXIT
|
||||
echo "=== Extracting XAPK archive ==="
|
||||
unzip -qo "$INPUT_FILE_ABS" -d "$XAPK_EXTRACTED_DIR"
|
||||
|
||||
|
|
@ -179,10 +208,22 @@ run_jadx() {
|
|||
[[ "$DEOBF" == true ]] && args+=("--deobf")
|
||||
[[ "$NO_RES" == true ]] && args+=("--no-res")
|
||||
args+=("--show-bad-code")
|
||||
[[ -n "$THREADS" ]] && args+=("-j" "$THREADS")
|
||||
args+=("$INPUT_FILE_ABS")
|
||||
|
||||
echo "Running: jadx ${args[*]}"
|
||||
jadx "${args[@]}"
|
||||
if [[ "$TIMEOUT" -gt 0 ]] 2>/dev/null; then
|
||||
if ! timeout "${TIMEOUT}s" jadx "${args[@]}"; then
|
||||
local exit_code=$?
|
||||
if [[ $exit_code -eq 124 ]]; then
|
||||
echo "WARNING: jadx timed out after ${TIMEOUT}s. Partial output may be available." >&2
|
||||
else
|
||||
return $exit_code
|
||||
fi
|
||||
fi
|
||||
else
|
||||
jadx "${args[@]}"
|
||||
fi
|
||||
|
||||
echo "jadx output: $out_dir/sources/"
|
||||
if [[ -d "$out_dir/sources" ]]; then
|
||||
|
|
@ -237,8 +278,19 @@ run_fernflower() {
|
|||
ff_args+=("$jar_to_decompile")
|
||||
ff_args+=("$out_dir")
|
||||
|
||||
echo "Running: java -jar $ff_jar ${ff_args[*]}"
|
||||
java -jar "$ff_jar" "${ff_args[@]}"
|
||||
echo "Running: java -Xmx${HEAP_SIZE} -jar $ff_jar ${ff_args[*]}"
|
||||
if [[ "$TIMEOUT" -gt 0 ]] 2>/dev/null; then
|
||||
if ! timeout "${TIMEOUT}s" java "-Xmx${HEAP_SIZE}" -jar "$ff_jar" "${ff_args[@]}"; then
|
||||
local exit_code=$?
|
||||
if [[ $exit_code -eq 124 ]]; then
|
||||
echo "WARNING: Fernflower timed out after ${TIMEOUT}s. Partial output may be available." >&2
|
||||
else
|
||||
return $exit_code
|
||||
fi
|
||||
fi
|
||||
else
|
||||
java "-Xmx${HEAP_SIZE}" -jar "$ff_jar" "${ff_args[@]}"
|
||||
fi
|
||||
|
||||
# Fernflower outputs a JAR containing .java files — extract it
|
||||
local result_jar="$out_dir/$(basename "$jar_to_decompile")"
|
||||
|
|
@ -363,11 +415,14 @@ if [[ "$ext_lower" == "xapk" ]]; then
|
|||
apk_name=$(basename "$apk_file" .apk)
|
||||
echo
|
||||
echo "======================================================"
|
||||
decompile_single "$apk_file" "$OUTPUT_DIR/$apk_name" "$apk_name.apk"
|
||||
if ! decompile_single "$apk_file" "$OUTPUT_DIR/$apk_name" "$apk_name.apk"; then
|
||||
echo "WARNING: Failed to decompile $apk_name.apk, continuing with remaining APKs..." >&2
|
||||
fi
|
||||
done
|
||||
|
||||
# Cleanup extracted XAPK
|
||||
rm -rf "$XAPK_EXTRACTED_DIR"
|
||||
trap - EXIT
|
||||
|
||||
echo
|
||||
echo "=== XAPK decompilation complete ==="
|
||||
|
|
|
|||
|
|
@ -98,86 +98,135 @@ If the input is an XAPK, inform the user that it's a split APK bundle. Let them
|
|||
|
||||
### Phase 3: Identify Targets
|
||||
|
||||
Target identification has three sub-phases. The goal is to find all SDK entry points to neutralize while minimizing manual approval prompts.
|
||||
Target identification has four sub-phases. The goal is to combine deterministic registry matching with heuristic discovery for maximum coverage.
|
||||
|
||||
#### Phase 3a — Built-in Catalog Detection
|
||||
**Depth levels** control how aggressively SDKs are neutralized:
|
||||
- **Depth 1** (default, safest): Only SDK entry points (init, start). Disables SDK initialization.
|
||||
- **Depth 2**: Entry points + ad operations (load, show, cache). Safety net if init stub is bypassed.
|
||||
- **Depth 3** (most aggressive): All above + deep patterns (bulk-stub internal packages). Version-dependent.
|
||||
|
||||
Use `neutralize.sh --dry-run` to detect known SDK targets. This is more reliable than `find-ads.sh`/`find-trackers.sh` because it searches smali directly (not Java source) and matches the exact patterns that will be patched.
|
||||
Ask the user which depth level to use. Default to depth 1 unless they request more.
|
||||
|
||||
**Action**: Run dry-run detection.
|
||||
#### Phase 3a — Registry Scan (Known SDKs)
|
||||
|
||||
Run `registry-scan.py` to match the decoded APK against the SDK registry (29 SDKs, 123 entry points, 156 ad operations, 30 deep patterns).
|
||||
|
||||
**Action**: Run registry scan.
|
||||
|
||||
```bash
|
||||
python3 ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/registry-scan.py "<decoded-dir>" \
|
||||
--registry "${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/registry/" \
|
||||
--depth 1 --category all \
|
||||
--output-dir "<decoded-dir>"
|
||||
```
|
||||
|
||||
Parse stdout for:
|
||||
- `MATCHED:<sdk_id>:<display_name>:<category>:<n_targets>` — matched SDK with target count
|
||||
- `UNKNOWN_PACKAGE:<package>:<class_count>` — unknown packages (candidates for Phase 3b)
|
||||
- `REGISTRY_TARGETS:<path>` — generated targets file for neutralize.sh
|
||||
- `REGISTRY_MANIFEST:<path>` — generated manifest components file
|
||||
|
||||
Present matched SDKs as a table:
|
||||
|
||||
| SDK | Category | Depth | Targets | Manifest Components |
|
||||
|---|---|---|---|---|
|
||||
| Google AdMob | ads | 1 | 2 entry points | 3 components |
|
||||
| Firebase Analytics | analytics | 1 | 8 entry points | 7 components |
|
||||
| AppsFlyer | attribution | 1 | 19 entry points | 3 components |
|
||||
| ... | | | | |
|
||||
|
||||
If the user requests **depth 2 or 3**, re-run registry-scan.py with the higher depth level.
|
||||
|
||||
**Fallback**: If Python 3 is not available, fall back to the builtin catalog:
|
||||
```bash
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh <decoded-dir> --all --dry-run
|
||||
```
|
||||
|
||||
Parse the output for:
|
||||
- `DRY_RUN:WOULD_PATCH:` lines — smali methods that would be stubbed
|
||||
- `DRY_RUN:WOULD_DISABLE:` lines — manifest components that would be disabled
|
||||
|
||||
**NOTE**: Do NOT use `find-ads.sh` or `find-trackers.sh` here — those scripts search Java/Kotlin source (`.java`, `.kt`), not smali. The decoded directory from Phase 2 contains only smali bytecode, so those scripts will find nothing.
|
||||
|
||||
#### Phase 3b — Custom/Proprietary SDK Discovery (if needed)
|
||||
#### Phase 3b — Unknown SDK Discovery
|
||||
|
||||
Activate this sub-phase when:
|
||||
- The user mentions a specific SDK not in the built-in catalog (e.g., `guru/ads/fusion`, `com/proprietary/analytics`)
|
||||
- The dry-run found few or no targets but the user expects more
|
||||
- The user asks to find "all" trackers/ads, including custom/proprietary ones
|
||||
- `registry-scan.py` reported `UNKNOWN_PACKAGE:` candidates
|
||||
- The user asks to discover SDKs beyond the registry
|
||||
- Few matches in Phase 3a but the user expects more
|
||||
|
||||
**CRITICAL — Use Claude Code built-in tools, NOT bash commands.** Glob, Grep, and Read are auto-approved and require no manual user approval. Using `bash find`, `bash grep`, or `bash head` forces the user to approve each command individually.
|
||||
The registry scan automatically filters unknowns:
|
||||
- Excludes obfuscated packages (single-letter names like `a/`, `b/c/`)
|
||||
- Excludes known utility libraries (okhttp, retrofit, gson, protobuf, kotlinx, androidx, etc.)
|
||||
- Excludes the app's own package (from AndroidManifest)
|
||||
- Only includes packages with 10+ classes and 3+ name segments
|
||||
|
||||
**Discovery workflow using built-in tools:**
|
||||
**CRITICAL — Use Claude Code built-in tools, NOT bash commands.** Glob, Grep, and Read are auto-approved.
|
||||
|
||||
1. **Find smali files by package pattern** — use Glob:
|
||||
**Discovery workflow** for each unknown package candidate:
|
||||
|
||||
1. **List main classes** — use Glob:
|
||||
```
|
||||
Glob: **/smali*/com/guru/**/*.smali
|
||||
Glob: **/smali*/com/proprietary/analytics/**/*.smali
|
||||
Glob: **/smali*/com/vendor/sdk/**/*.smali
|
||||
```
|
||||
|
||||
2. **Find SDK method signatures** — use Grep on the matched files:
|
||||
2. **Search for SDK patterns** — use Grep:
|
||||
```
|
||||
Grep: pattern="^\.method.*(init|track|log|show|load|send|report|emit)"
|
||||
Grep: pattern="^\.method.*getInstance"
|
||||
Grep: pattern="\.method.*(init|initialize|start|load|show)" path=<smali-dir>/com/vendor/sdk/
|
||||
Grep: pattern="const-string.*http" path=<smali-dir>/com/vendor/sdk/
|
||||
```
|
||||
|
||||
3. **Find SDK invocations from app code** — use Grep on the app's own smali:
|
||||
3. **Check manifest** for components (activities, services, providers, receivers) with that package.
|
||||
|
||||
4. **Classify**: "probable SDK ads", "probable SDK tracker", "utility library", "app code"
|
||||
|
||||
Present results as a table:
|
||||
|
||||
| Package | Classes | SDK Patterns | Classification | Suggested Action |
|
||||
|---|---|---|---|---|
|
||||
| `com/vendor/analytics` | 45 | init, logEvent, URL endpoints | Tracker | Deep analysis |
|
||||
| `com/vendor/mediator` | 120 | no direct init | Mediator/wrapper | Check SDK refs |
|
||||
| `org/example/util` | 8 | no SDK patterns | Utility library | Ignore |
|
||||
|
||||
#### Phase 3c — Deep Analysis (opt-in, explicit confirmation required)
|
||||
|
||||
**IMPORTANT**: Before proceeding, present the candidates and ask:
|
||||
> "I identified N SDK candidates for deep analysis. This involves web search and smali reverse engineering. Which ones should I analyze? (list numbers or 'all')"
|
||||
|
||||
**Only after user confirmation**, for each selected SDK:
|
||||
|
||||
1. **Web search**: Search for the package name to identify the SDK:
|
||||
- `"com.vendor.sdk" android SDK`
|
||||
- `site:maven.org "com.vendor.sdk"`
|
||||
- `"com.vendor.sdk" gradle dependency`
|
||||
|
||||
2. **Read main smali classes**: Identify the public API (init, config, entry points).
|
||||
|
||||
3. **Propose to the user**: "This appears to be **X SDK** version Y, used for Z. Entry points found: `init()`, `start()`, `logEvent()`. Neutralize it?"
|
||||
|
||||
4. **If confirmed**, generate entry for targets file:
|
||||
```
|
||||
Grep: pattern="invoke-(static|virtual|direct).*Lcom/guru/ads/"
|
||||
Grep: pattern="invoke-(static|virtual|direct).*Lcom/proprietary/analytics/"
|
||||
# [X SDK] discovered via deep analysis
|
||||
com/vendor/sdk/MainClass:init
|
||||
com/vendor/sdk/MainClass:start
|
||||
```
|
||||
|
||||
4. **Examine specific files** — use Read to inspect method bodies and confirm they are SDK entry points worth neutralizing.
|
||||
5. **Optionally**, propose a draft registry JSON entry for future inclusion.
|
||||
|
||||
**Build the custom targets file** with discovered entry points, one per line, in the format:
|
||||
#### Phase 3d — Compile & Confirm
|
||||
|
||||
```
|
||||
<smali-class-path>:<method-name>
|
||||
```
|
||||
Merge all target sources:
|
||||
- **Registry targets** from Phase 3a (`registry-targets.txt`)
|
||||
- **Custom discovery** from Phase 3b/3c (append to `custom-targets.txt`)
|
||||
- **User-provided** `--targets-file` if any
|
||||
|
||||
For example:
|
||||
```
|
||||
smali/com/guru/ads/fusion/FusionAd.smali:initialize
|
||||
smali/com/guru/ads/fusion/FusionAd.smali:showAd
|
||||
smali/com/guru/ads/fusion/FusionTracker.smali:trackEvent
|
||||
```
|
||||
Present the complete summary:
|
||||
|
||||
**Action**: Write the targets file.
|
||||
| SDK | Category | Source | Depth | Targets | Manifest Components |
|
||||
|---|---|---|---|---|---|
|
||||
| Google AdMob | ads | Registry | 1 | 2 methods | 3 components |
|
||||
| Firebase Analytics | analytics | Registry | 1 | 8 methods | 7 components |
|
||||
| guru/ads/fusion | ads | Discovery | - | 3 methods | 0 components |
|
||||
| ... | | | | | |
|
||||
|
||||
```
|
||||
Write: <decoded-dir>/custom-targets.txt
|
||||
```
|
||||
**Ads vs Trackers distinction**: Always present ads and trackers separately — they have different implications (revenue impact vs privacy).
|
||||
|
||||
#### Phase 3c — Compile Target List and Confirm
|
||||
|
||||
Present a summary table of all targets to the user:
|
||||
|
||||
| SDK | Category | Source | Entry Points |
|
||||
|---|---|---|---|
|
||||
| AdMob | Ads | Built-in catalog | 5 methods, 2 components |
|
||||
| Firebase Analytics | Trackers | Built-in catalog | 3 methods, 1 component |
|
||||
| guru/ads/fusion | Ads | Custom discovery | 3 methods |
|
||||
| ... | | | |
|
||||
|
||||
Ask which categories/SDKs to neutralize:
|
||||
Ask for final confirmation:
|
||||
- Which categories/SDKs to neutralize
|
||||
- `--ads` — only ad SDKs
|
||||
- `--trackers` — only tracker/analytics SDKs
|
||||
- `--all` — both (default)
|
||||
|
|
@ -187,34 +236,55 @@ Ask which categories/SDKs to neutralize:
|
|||
|
||||
Run the neutralization script. **Always run a dry-run first** to preview changes.
|
||||
|
||||
**Action**: Dry-run, then apply.
|
||||
#### Registry-driven mode (preferred, requires Python 3.6+)
|
||||
|
||||
Uses `registry-targets.txt` and `registry-manifest.txt` generated by Phase 3a. The `--no-builtin-targets` flag disables hardcoded targets to rely entirely on the registry.
|
||||
|
||||
```bash
|
||||
# Preview changes (no files modified)
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh <decoded-dir> --all --dry-run
|
||||
# Preview (dry-run)
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh <decoded-dir> \
|
||||
--no-builtin-targets --dry-run \
|
||||
--targets-file <decoded-dir>/registry-targets.txt \
|
||||
--manifest-components-file <decoded-dir>/registry-manifest.txt
|
||||
|
||||
# Apply changes (with backups)
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh <decoded-dir> --all
|
||||
# Apply
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh <decoded-dir> \
|
||||
--no-builtin-targets \
|
||||
--targets-file <decoded-dir>/registry-targets.txt \
|
||||
--manifest-components-file <decoded-dir>/registry-manifest.txt
|
||||
```
|
||||
|
||||
**If Phase 3b produced custom targets**, add `--targets-file` to both commands:
|
||||
**If Phase 3b/3c produced custom targets**, add a second `--targets-file` or append to the registry file:
|
||||
|
||||
```bash
|
||||
# Preview with custom targets
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh <decoded-dir> --all --dry-run --targets-file <decoded-dir>/custom-targets.txt
|
||||
# Append custom targets to registry targets
|
||||
cat <decoded-dir>/custom-targets.txt >> <decoded-dir>/registry-targets.txt
|
||||
```
|
||||
|
||||
# Apply with custom targets
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh <decoded-dir> --all --targets-file <decoded-dir>/custom-targets.txt
|
||||
#### Fallback mode (no Python)
|
||||
|
||||
If Python 3 is not available, use the builtin hardcoded targets:
|
||||
|
||||
```bash
|
||||
# Preview
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh <decoded-dir> --all --dry-run
|
||||
|
||||
# Apply
|
||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/sdk-neutralizer/scripts/neutralize.sh <decoded-dir> --all
|
||||
```
|
||||
|
||||
Parse the output for `PATCHED:` and `MANIFEST_DISABLED:` lines to build the report.
|
||||
|
||||
Options:
|
||||
- `--ads` / `--trackers` / `--all` — target selection
|
||||
#### Options reference
|
||||
|
||||
- `--no-builtin-targets` — skip hardcoded target functions, rely on `--targets-file` + `--manifest-components-file`
|
||||
- `--targets-file <file>` — load targets from file (registry-scan.py output or custom)
|
||||
- `--manifest-components-file <file>` — load manifest components from file (registry-scan.py output)
|
||||
- `--ads` / `--trackers` / `--all` — target selection (for builtin mode)
|
||||
- `--dry-run` — preview only
|
||||
- `--no-backup` — skip `.smali.bak` creation
|
||||
- `--no-manifest` — skip manifest patching
|
||||
- `--targets-file <file>` — additional custom targets
|
||||
- `--package <path>` — neutralize all methods in a package recursively
|
||||
- `--replay` — replay patches from a previous `neutralize-manifest.json` (useful after re-decode)
|
||||
- `--no-save-manifest` — skip saving `neutralize-manifest.json`
|
||||
|
||||
|
|
@ -231,7 +301,7 @@ Rebuild the decoded directory back into a signed APK (or XAPK if the original wa
|
|||
> How would you like to rebuild the neutralized app?
|
||||
>
|
||||
> 1. **Merged single APK** (recommended for sideloading) — merges split contents into one APK, installable with standard `adb install`. May be missing some locale/density resources.
|
||||
> 2. **XAPK bundle** (preserves original structure) — requires `adb install-multiple` or SAI app to install. All splits preserved exactly.
|
||||
> 2. **XAPK bundle** (preserves original structure) — requires `adb install-multiple` to install. All splits preserved exactly.
|
||||
|
||||
**If the user chooses option 1 (merged single APK):**
|
||||
|
||||
|
|
@ -376,7 +446,7 @@ If the original input was an XAPK and the user chose merged single APK output, i
|
|||
- Signed with: auto-detected debug key / generated debug key / custom keystore
|
||||
- Keystore used: `<path>` (source: `KEYSTORE_SOURCE:` value)
|
||||
- Install via: `adb install <path>` (APK / merged APK) or `adb install-multiple <base.apk> <split1.apk> ...` (XAPK)
|
||||
- For XAPK: can also use SAI (Split APKs Installer) or unzip and `adb install-multiple *.apk`
|
||||
- For XAPK: unzip the XAPK and run `adb install-multiple *.apk`
|
||||
```
|
||||
|
||||
**Next steps to suggest:**
|
||||
|
|
|
|||
|
|
@ -0,0 +1,188 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"$id": "sdk-registry-entry.schema.json",
|
||||
"title": "SDK Neutralization Registry Entry",
|
||||
"description": "Defines which public API methods of a third-party SDK can be safely neutralized (stubbed) in a decompiled Android APK.",
|
||||
"type": "object",
|
||||
"required": ["sdk_id", "display_name", "vendor", "category", "packages", "targets"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string"
|
||||
},
|
||||
"sdk_id": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z0-9-]+$",
|
||||
"description": "Unique lowercase identifier for this SDK (used as filename stem)"
|
||||
},
|
||||
"display_name": {
|
||||
"type": "string",
|
||||
"description": "Human-readable SDK name"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"vendor": {
|
||||
"type": "string"
|
||||
},
|
||||
"category": {
|
||||
"type": "string",
|
||||
"enum": ["ads", "analytics", "attribution", "crash_reporting", "ads_mediation", "social"]
|
||||
},
|
||||
"known_versions": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "SDK versions tested against (for reference only)"
|
||||
},
|
||||
"packages": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"minItems": 1,
|
||||
"description": "Root Java/Kotlin packages of this SDK"
|
||||
},
|
||||
"obfuscation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"public_api": {
|
||||
"type": "string",
|
||||
"enum": ["readable", "mixed", "obfuscated"]
|
||||
},
|
||||
"internals": {
|
||||
"type": "string",
|
||||
"enum": ["readable", "mixed", "obfuscated"]
|
||||
},
|
||||
"internal_prefix": {
|
||||
"type": "string",
|
||||
"description": "If known, the obfuscation prefix pattern (e.g. 'zz', single letters)"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"targets": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"entry_points": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/class_target" },
|
||||
"description": "Level 1: SDK init methods. Stubbing these disables the entire SDK."
|
||||
},
|
||||
"ad_operations": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/class_target" },
|
||||
"description": "Level 2: load/show/cache methods. Safety net if init stub is bypassed."
|
||||
},
|
||||
"deep_patterns": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/deep_pattern" },
|
||||
"description": "Level 3: Broad internal patterns. Use for well-known SDK internals."
|
||||
}
|
||||
}
|
||||
},
|
||||
"manifest_components": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/manifest_component" },
|
||||
"description": "AndroidManifest components to disable/remove"
|
||||
},
|
||||
"protected_patterns": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/protected_pattern" },
|
||||
"description": "Methods/patterns that must NEVER be stubbed in this SDK"
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"class_target": {
|
||||
"type": "object",
|
||||
"required": ["class", "methods"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"class": {
|
||||
"type": "string",
|
||||
"description": "Fully qualified Java class name (dot-separated)"
|
||||
},
|
||||
"methods": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/definitions/method_target" },
|
||||
"minItems": 1
|
||||
}
|
||||
}
|
||||
},
|
||||
"method_target": {
|
||||
"type": "object",
|
||||
"required": ["name", "stub"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Method name"
|
||||
},
|
||||
"signature": {
|
||||
"type": "string",
|
||||
"description": "Smali-style signature or '*' for all overloads"
|
||||
},
|
||||
"stub": {
|
||||
"type": "string",
|
||||
"enum": ["return-void", "return-false/0", "return-null"],
|
||||
"description": "Stub type to apply"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"deep_pattern": {
|
||||
"type": "object",
|
||||
"required": ["package_glob", "rule"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"package_glob": {
|
||||
"type": "string",
|
||||
"description": "Glob pattern for packages (e.g. 'com.google.android.gms.internal.ads.**')"
|
||||
},
|
||||
"rule": {
|
||||
"type": "string",
|
||||
"enum": ["stub_all_void", "stub_all_concrete", "stub_all_non_getter"],
|
||||
"description": "How aggressively to stub matched classes"
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"manifest_component": {
|
||||
"type": "object",
|
||||
"required": ["type", "class", "action"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["activity", "service", "receiver", "provider"]
|
||||
},
|
||||
"class": {
|
||||
"type": "string"
|
||||
},
|
||||
"action": {
|
||||
"type": "string",
|
||||
"enum": ["disable", "remove"]
|
||||
},
|
||||
"note": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"protected_pattern": {
|
||||
"type": "object",
|
||||
"required": ["pattern", "reason"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"pattern": {
|
||||
"type": "string",
|
||||
"description": "Glob or regex pattern for methods to protect"
|
||||
},
|
||||
"reason": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "adjust",
|
||||
"display_name": "Adjust",
|
||||
"description": "Adjust mobile attribution and analytics SDK",
|
||||
"vendor": "Adjust",
|
||||
"category": "attribution",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.adjust.sdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "readable"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.adjust.sdk.Adjust",
|
||||
"methods": [
|
||||
{ "name": "onCreate", "signature": "*", "stub": "return-void", "note": "Main SDK init" },
|
||||
{ "name": "trackEvent", "signature": "*", "stub": "return-void", "note": "Event tracking" },
|
||||
{ "name": "setEnabled", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "addSessionCallbackParameter", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "addSessionPartnerParameter", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.adjust.sdk.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Internal SDK classes"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "receiver",
|
||||
"class": "com.adjust.sdk.AdjustReferrerReceiver",
|
||||
"action": "disable",
|
||||
"note": "Install referrer receiver"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "amazon-ads",
|
||||
"display_name": "Amazon Ads",
|
||||
"description": "Amazon Mobile Ads SDK (APS / TAM)",
|
||||
"vendor": "Amazon",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.amazon.device.ads"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.amazon.device.ads.AdRegistration",
|
||||
"methods": [
|
||||
{ "name": "setAppKey", "signature": "*", "stub": "return-void", "note": "SDK config — all overloads" },
|
||||
{ "name": "enableLogging", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "enableTesting", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setMRAIDPolicy", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setMRAIDSupportedVersions", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setAdNetworkInfo", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.amazon.device.ads.DTBAdRequest",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.amazon.device.ads.DTBAdActivity",
|
||||
"action": "disable",
|
||||
"note": "Ad display activity"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "applovin",
|
||||
"display_name": "AppLovin / MAX",
|
||||
"description": "AppLovin SDK and MAX mediation platform",
|
||||
"vendor": "AppLovin",
|
||||
"category": "ads_mediation",
|
||||
"known_versions": ["13.3.0"],
|
||||
"packages": [
|
||||
"com.applovin.sdk",
|
||||
"com.applovin.mediation",
|
||||
"com.applovin.impl",
|
||||
"com.applovin.adview"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated",
|
||||
"internal_prefix": "single-letter"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.applovin.sdk.AppLovinSdk",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "Main SDK init" },
|
||||
{ "name": "initializeSdk", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.applovin.mediation.MaxInterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "showAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.applovin.mediation.MaxRewardedAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "showAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.applovin.mediation.MaxAdView",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "startAutoRefresh", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "stopAutoRefresh", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.applovin.impl.**",
|
||||
"rule": "stub_all_concrete",
|
||||
"note": "Internal obfuscated implementation — version-dependent"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.applovin.mediation.adapters.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Third-party adapters bundled with MAX"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.applovin.adview.AppLovinFullscreenActivity",
|
||||
"action": "disable",
|
||||
"note": "Fullscreen ad display activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.applovin.sdk.AppLovinWebViewActivity",
|
||||
"action": "disable",
|
||||
"note": "WebView-based ad activity"
|
||||
},
|
||||
{
|
||||
"type": "provider",
|
||||
"class": "com.applovin.sdk.AppLovinInitProvider",
|
||||
"action": "disable",
|
||||
"note": "Auto-init ContentProvider"
|
||||
}
|
||||
],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getActivity()*",
|
||||
"reason": "Returns Activity reference — null causes NPE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "appsflyer",
|
||||
"display_name": "AppsFlyer",
|
||||
"description": "AppsFlyer mobile attribution and analytics SDK",
|
||||
"vendor": "AppsFlyer",
|
||||
"category": "attribution",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.appsflyer"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "mixed",
|
||||
"internals": "obfuscated",
|
||||
"internal_prefix": "AF"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.appsflyer.internal.AFa1uSDK",
|
||||
"methods": [
|
||||
{ "name": "start", "signature": "*", "stub": "return-void", "note": "Main SDK start — all overloads" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.appsflyer.unity.AppsFlyerAndroidWrapper",
|
||||
"methods": [
|
||||
{ "name": "initSdk", "signature": "*", "stub": "return-void", "note": "Unity wrapper init" },
|
||||
{ "name": "startTracking", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "logEvent", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setCustomerUserId", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setCurrencyCode", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setAdditionalData", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "sendPushNotificationData", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setUserEmails", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setDebug", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "getConversionData", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "attributeAndOpenStore", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "recordLocation", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "recordCrossPromoteImpression", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setPhoneNumber", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setSharingFilterForPartners", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setPartnerData", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setDisableNetworkData", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "performLatestCPI", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.appsflyer.internal.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Internal obfuscated classes (AF* prefix) — 649+ methods, V1-proven safe"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.appsflyer.share.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Sharing module — 11 methods, V1-proven safe"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.appsflyer.unity.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Unity wrapper — 99+ methods, V1-proven safe"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "receiver",
|
||||
"class": "com.appsflyer.MultipleInstallBroadcastReceiver",
|
||||
"action": "disable",
|
||||
"note": "Install referrer receiver"
|
||||
},
|
||||
{
|
||||
"type": "receiver",
|
||||
"class": "com.appsflyer.SingleInstallBroadcastReceiver",
|
||||
"action": "disable",
|
||||
"note": "Single install referrer receiver"
|
||||
},
|
||||
{
|
||||
"type": "receiver",
|
||||
"class": "com.appsflyer.internal.AFSingleInstallBroadcastReceiver",
|
||||
"action": "disable",
|
||||
"note": "Internal single install referrer receiver"
|
||||
}
|
||||
],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getActivity()*",
|
||||
"reason": "Returns Activity reference — null causes NPE"
|
||||
},
|
||||
{
|
||||
"pattern": "*.getApplicationContext()*",
|
||||
"reason": "Returns Context — null causes NPE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "bidmachine",
|
||||
"display_name": "BidMachine",
|
||||
"description": "BidMachine programmatic ad SDK",
|
||||
"vendor": "BidMachine",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"io.bidmachine"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "io.bidmachine.BidMachine",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "Main SDK init" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "io.bidmachine.interstitial.InterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "io.bidmachine.rewarded.RewardedAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "io.bidmachine.banner.BannerView",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "provider",
|
||||
"class": "io.bidmachine.BidMachineInitProvider",
|
||||
"action": "disable",
|
||||
"note": "Auto-init ContentProvider"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "braze",
|
||||
"display_name": "Braze",
|
||||
"description": "Braze (formerly Appboy) customer engagement SDK",
|
||||
"vendor": "Braze",
|
||||
"category": "analytics",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.braze",
|
||||
"com.appboy"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "readable"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.braze.Braze",
|
||||
"methods": [
|
||||
{ "name": "configure", "signature": "*", "stub": "return-void", "note": "SDK configuration" },
|
||||
{ "name": "logCustomEvent", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "changeUser", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "logPurchase", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.appboy.Appboy",
|
||||
"methods": [
|
||||
{ "name": "configure", "signature": "*", "stub": "return-void", "note": "Legacy Appboy init" },
|
||||
{ "name": "logCustomEvent", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "changeUser", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "service",
|
||||
"class": "com.braze.push.BrazeFirebaseMessagingService",
|
||||
"action": "disable",
|
||||
"note": "Firebase messaging service for push notifications"
|
||||
}
|
||||
],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getInstance()*",
|
||||
"reason": "Singleton accessor — null causes NPE if called by app code"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "chartboost",
|
||||
"display_name": "Chartboost",
|
||||
"description": "Chartboost ad SDK",
|
||||
"vendor": "Chartboost",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.chartboost.sdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.chartboost.sdk.Chartboost",
|
||||
"methods": [
|
||||
{ "name": "startWithAppId", "signature": "*", "stub": "return-void", "note": "Main SDK init" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.chartboost.sdk.ads.Interstitial",
|
||||
"methods": [
|
||||
{ "name": "cache", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.chartboost.sdk.ads.Rewarded",
|
||||
"methods": [
|
||||
{ "name": "cache", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.chartboost.sdk.ads.Banner",
|
||||
"methods": [
|
||||
{ "name": "cache", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "detach", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.chartboost.sdk.internal.**",
|
||||
"rule": "stub_all_concrete",
|
||||
"note": "Internal obfuscated classes"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.chartboost.sdk.CBImpressionActivity",
|
||||
"action": "disable",
|
||||
"note": "Ad impression display activity"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "clevertap",
|
||||
"display_name": "CleverTap",
|
||||
"description": "CleverTap customer engagement and analytics SDK",
|
||||
"vendor": "CleverTap",
|
||||
"category": "analytics",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.clevertap.android.sdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "readable"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.clevertap.android.sdk.CleverTapAPI",
|
||||
"methods": [
|
||||
{ "name": "pushEvent", "signature": "*", "stub": "return-void", "note": "Push analytics event" },
|
||||
{ "name": "onUserLogin", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "pushProfile", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "recordEvent", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "recordScreen", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "pushNotificationClickedEvent", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "pushNotificationViewedEvent", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "receiver",
|
||||
"class": "com.clevertap.android.sdk.pushnotification.CTPushNotificationReceiver",
|
||||
"action": "disable",
|
||||
"note": "Push notification receiver"
|
||||
},
|
||||
{
|
||||
"type": "service",
|
||||
"class": "com.clevertap.android.sdk.pushnotification.CTNotificationIntentService",
|
||||
"action": "disable",
|
||||
"note": "Notification intent service"
|
||||
}
|
||||
],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getDefaultInstance()*",
|
||||
"reason": "Singleton accessor — null causes NPE if called by app code"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,147 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "facebook",
|
||||
"display_name": "Facebook SDK",
|
||||
"description": "Meta SDK — core, ads (Audience Network), login, share, gaming services",
|
||||
"vendor": "Meta",
|
||||
"category": "social",
|
||||
"known_versions": ["17.0.2"],
|
||||
"packages": [
|
||||
"com.facebook",
|
||||
"com.facebook.ads",
|
||||
"com.facebook.login",
|
||||
"com.facebook.share",
|
||||
"com.facebook.appevents",
|
||||
"com.facebook.gamingservices",
|
||||
"com.facebook.bolts",
|
||||
"com.facebook.internal",
|
||||
"com.facebook.unity"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "readable"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.facebook.FacebookSdk",
|
||||
"methods": [
|
||||
{ "name": "sdkInitialize", "signature": "*", "stub": "return-void", "note": "Main SDK init — all overloads" },
|
||||
{ "name": "fullyInitialize", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setApplicationId", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setClientToken", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setAutoLogAppEventsEnabled", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setAdvertiserIDCollectionEnabled", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setAutoInitEnabled", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setIsDebugEnabled", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.facebook.ads.AudienceNetworkAds",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "Audience Network init" },
|
||||
{ "name": "buildInitSettings", "signature": "*", "stub": "return-null" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.facebook.ads.internal.dynamicloading.DynamicLoaderFactory",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "Dynamic loader init" },
|
||||
{ "name": "getDynamicLoader", "signature": "*", "stub": "return-null" },
|
||||
{ "name": "makeLoaderUnsafe", "signature": "*", "stub": "return-null" },
|
||||
{ "name": "doCallInitialize", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setFallbackMode", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setUseLegacyClassLoader", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.facebook.ads.internal.api.InitSettingsBuilder",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.facebook.unity.FB",
|
||||
"methods": [
|
||||
{ "name": "Init", "signature": "*", "stub": "return-void", "note": "Unity bridge init" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.facebook.ads.internal.api.InterstitialAdApi",
|
||||
"methods": [
|
||||
{ "name": "buildLoadAdConfig", "signature": "*", "stub": "return-null" },
|
||||
{ "name": "buildShowAdConfig", "signature": "*", "stub": "return-null" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.facebook.ads.internal.api.RewardedInterstitialAdApi",
|
||||
"methods": [
|
||||
{ "name": "buildLoadAdConfig", "signature": "*", "stub": "return-null" },
|
||||
{ "name": "buildShowAdConfig", "signature": "*", "stub": "return-null" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.facebook.appevents.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Event tracking — 670+ methods, all V1-proven safe"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.facebook.internal.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Internal utilities — 460+ methods, V1-proven safe"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.facebook.login.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Login flow — 392+ methods, safe if app does not use FB Login as feature"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.facebook.ads.internal.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Ads internal — 313+ methods, V1-proven safe"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.facebook.share.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Share API — 263+ methods, V1-proven safe"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.facebook.gamingservices.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Gaming services — 181+ methods, V1-proven safe"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.facebook.bolts.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Task framework — 66 methods, V1-proven safe"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.facebook.unity.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "FB Unity wrappers — 147+ methods, V1-proven safe"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "provider",
|
||||
"class": "com.facebook.internal.FacebookInitProvider",
|
||||
"action": "disable",
|
||||
"note": "Auto-init ContentProvider"
|
||||
}
|
||||
],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getActivity()*",
|
||||
"reason": "Returns Activity reference — null causes NPE"
|
||||
},
|
||||
{
|
||||
"pattern": "*.getApplicationContext()*",
|
||||
"reason": "Returns Context — null causes NPE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "firebase-analytics",
|
||||
"display_name": "Firebase Analytics",
|
||||
"description": "Google Firebase Analytics (Google Analytics for Firebase)",
|
||||
"vendor": "Google",
|
||||
"category": "analytics",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.google.firebase.analytics"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.google.firebase.analytics.FirebaseAnalytics",
|
||||
"methods": [
|
||||
{ "name": "logEvent", "signature": "*", "stub": "return-void", "note": "Log analytics event" },
|
||||
{ "name": "setUserId", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setUserProperty", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setAnalyticsCollectionEnabled", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setCurrentScreen", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setDefaultEventParameters", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "resetAnalyticsData", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setSessionTimeoutDuration", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.google.android.gms.measurement.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Analytics measurement internals"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "provider",
|
||||
"class": "com.google.firebase.provider.FirebaseInitProvider",
|
||||
"action": "disable",
|
||||
"note": "Firebase auto-init ContentProvider"
|
||||
},
|
||||
{
|
||||
"type": "service",
|
||||
"class": "com.google.android.gms.measurement.AppMeasurementService",
|
||||
"action": "disable",
|
||||
"note": "Analytics measurement service"
|
||||
},
|
||||
{
|
||||
"type": "receiver",
|
||||
"class": "com.google.android.gms.measurement.AppMeasurementReceiver",
|
||||
"action": "disable",
|
||||
"note": "Analytics measurement receiver"
|
||||
},
|
||||
{
|
||||
"type": "provider",
|
||||
"class": "com.google.android.gms.measurement.AppMeasurementContentProvider",
|
||||
"action": "disable",
|
||||
"note": "Analytics measurement content provider"
|
||||
},
|
||||
{
|
||||
"type": "receiver",
|
||||
"class": "com.google.android.gms.measurement.AppMeasurementInstallReferrerReceiver",
|
||||
"action": "disable",
|
||||
"note": "Install referrer tracking receiver"
|
||||
},
|
||||
{
|
||||
"type": "service",
|
||||
"class": "com.google.android.gms.measurement.AppMeasurementJobService",
|
||||
"action": "disable",
|
||||
"note": "Analytics background job service"
|
||||
},
|
||||
{
|
||||
"type": "receiver",
|
||||
"class": "com.google.firebase.iid.FirebaseInstanceIdReceiver",
|
||||
"action": "disable",
|
||||
"note": "Firebase Instance ID receiver"
|
||||
}
|
||||
],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getInstance()*",
|
||||
"reason": "Singleton accessor — null causes NPE if called by app code"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "firebase-crashlytics",
|
||||
"display_name": "Firebase Crashlytics",
|
||||
"description": "Google Firebase Crashlytics crash reporting",
|
||||
"vendor": "Google",
|
||||
"category": "crash_reporting",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.google.firebase.crashlytics"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.google.firebase.crashlytics.FirebaseCrashlytics",
|
||||
"methods": [
|
||||
{ "name": "log", "signature": "*", "stub": "return-void", "note": "Log message" },
|
||||
{ "name": "recordException", "signature": "*", "stub": "return-void", "note": "Record non-fatal exception" },
|
||||
{ "name": "setUserId", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setCustomKey", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setCrashlyticsCollectionEnabled", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "sendUnsentReports", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "deleteUnsentReports", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getInstance()*",
|
||||
"reason": "Singleton accessor — null causes NPE if called by app code"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "fyber",
|
||||
"display_name": "Fyber / Digital Turbine",
|
||||
"description": "Fyber (Inneractive / Digital Turbine) ad SDK",
|
||||
"vendor": "Digital Turbine",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.fyber.inneractive.sdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.fyber.inneractive.sdk.external.InneractiveAdManager",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "Main SDK init" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.fyber.inneractive.sdk.external.InneractiveAdRequest",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "google-admob",
|
||||
"display_name": "Google AdMob",
|
||||
"description": "Google Mobile Ads SDK",
|
||||
"vendor": "Google",
|
||||
"category": "ads",
|
||||
"known_versions": ["24.4.0"],
|
||||
"packages": [
|
||||
"com.google.android.gms.ads"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated",
|
||||
"internal_prefix": "zz"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.google.android.gms.ads.MobileAds",
|
||||
"methods": [
|
||||
{
|
||||
"name": "initialize",
|
||||
"signature": "(Landroid/content/Context;)V",
|
||||
"stub": "return-void",
|
||||
"note": "Main SDK init — disables entire ad pipeline"
|
||||
},
|
||||
{
|
||||
"name": "initialize",
|
||||
"signature": "(Landroid/content/Context;Lcom/google/android/gms/ads/initialization/OnInitializationCompleteListener;)V",
|
||||
"stub": "return-void",
|
||||
"note": "Init with callback"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.google.android.gms.ads.AdLoader",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "loadAds", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.google.android.gms.ads.rewarded.RewardedAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.google.android.gms.ads.interstitial.InterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.google.android.gms.ads.appopen.AppOpenAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.google.android.gms.ads.AdView",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "resume", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "pause", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.google.android.gms.ads.nativead.NativeAd",
|
||||
"methods": [
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.google.android.gms.ads.rewarded.RewardedInterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.google.android.gms.internal.ads.**",
|
||||
"rule": "stub_all_concrete",
|
||||
"note": "Internal obfuscated classes (zz* prefix) — safe but version-dependent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "provider",
|
||||
"class": "com.google.android.gms.ads.MobileAdsInitProvider",
|
||||
"action": "disable",
|
||||
"note": "Auto-init ContentProvider — starts SDK before Application.onCreate"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.google.android.gms.ads.AdActivity",
|
||||
"action": "disable",
|
||||
"note": "Fullscreen ad display activity"
|
||||
},
|
||||
{
|
||||
"type": "service",
|
||||
"class": "com.google.android.gms.ads.AdService",
|
||||
"action": "disable",
|
||||
"note": "Background ad service"
|
||||
}
|
||||
],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getActivity()*",
|
||||
"reason": "Returns Activity reference — null causes NPE"
|
||||
},
|
||||
{
|
||||
"pattern": "*.getApplicationContext()*",
|
||||
"reason": "Returns Context — null causes NPE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "guru-fusion",
|
||||
"display_name": "Guru Fusion Ads",
|
||||
"description": "Guru Fusion Ads SDK — ad mediation wrapper for AdMob, IronSource, AppLovin MAX",
|
||||
"vendor": "Guru",
|
||||
"category": "ads_mediation",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"guru.ads.fusion"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "readable"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "guru.ads.fusion.core.GuruAdsSdk",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "Main SDK init with callback" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "guru.ads.fusion.admob.GuruAdMobAdsSdk",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "AdMob backend init" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "guru.ads.fusion.ironsource.GuruIronSourceAdsSdk",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "IronSource backend init" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "guru.ads.fusion.max.GuruMaxAdsSdk",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "AppLovin MAX backend init" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "guru.ads.fusion.admob.GuruAdMobInterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "guru.ads.fusion.admob.GuruAdMobRewardedAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "guru.ads.fusion.admob.GuruAdMobBannerAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "guru.ads.fusion.ironsource.GuruIronSourceInterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "guru.ads.fusion.ironsource.GuruIronSourceRewardedAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "guru.ads.fusion.max.GuruMaxInterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "guru.ads.fusion.max.GuruMaxRewardedAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "guru.ads.fusion.max.GuruMaxBannerAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "guru.ads.fusion.engine.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Ad engine internals"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "inmobi",
|
||||
"display_name": "InMobi",
|
||||
"description": "InMobi ad SDK",
|
||||
"vendor": "InMobi",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.inmobi.sdk",
|
||||
"com.inmobi.ads"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.inmobi.sdk.InMobiSdk",
|
||||
"methods": [
|
||||
{ "name": "init", "signature": "*", "stub": "return-void", "note": "Main SDK init — all overloads" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.inmobi.ads.InMobiInterstitial",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.inmobi.ads.InMobiBanner",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "ironsource",
|
||||
"display_name": "IronSource / LevelPlay",
|
||||
"description": "IronSource mediation SDK (also known as LevelPlay)",
|
||||
"vendor": "Unity (IronSource)",
|
||||
"category": "ads_mediation",
|
||||
"known_versions": ["8.11.0"],
|
||||
"packages": [
|
||||
"com.ironsource.mediationsdk",
|
||||
"com.ironsource.sdk",
|
||||
"com.ironsource.adapters"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated",
|
||||
"internal_prefix": "single-letter"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.ironsource.mediationsdk.IronSource",
|
||||
"methods": [
|
||||
{ "name": "init", "signature": "*", "stub": "return-void", "note": "Main SDK init — all overloads" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.ironsource.mediationsdk.IronSource",
|
||||
"methods": [
|
||||
{ "name": "loadInterstitial", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "showInterstitial", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "loadRewardedVideo", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "showRewardedVideo", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "loadBanner", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroyBanner", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setConsent", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setMetaData", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isInterstitialReady", "signature": "*", "stub": "return-false/0", "note": "Always report no ad available" },
|
||||
{ "name": "isRewardedVideoAvailable", "signature": "*", "stub": "return-false/0", "note": "Always report no ad available" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.ironsource.mediationsdk.sdk.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Internal SDK classes — obfuscated, version-dependent"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.ironsource.adapters.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Third-party ad network adapters bundled with IronSource"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.ironsource.sdk.controller.InterstitialActivity",
|
||||
"action": "disable",
|
||||
"note": "Interstitial ad display activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.ironsource.sdk.controller.ControllerActivity",
|
||||
"action": "disable",
|
||||
"note": "SDK controller activity"
|
||||
},
|
||||
{
|
||||
"type": "provider",
|
||||
"class": "com.ironsource.lifecycle.IronsourceLifecycleProvider",
|
||||
"action": "disable",
|
||||
"note": "Lifecycle auto-init provider"
|
||||
}
|
||||
],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getActivity()*",
|
||||
"reason": "Returns Activity reference — null causes NPE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "meta-audience-network",
|
||||
"display_name": "Meta Audience Network",
|
||||
"description": "Meta Audience Network (Facebook Ads) — ad-specific subset. Full SDK coverage in facebook.json.",
|
||||
"vendor": "Meta",
|
||||
"category": "ads",
|
||||
"known_versions": ["6.20.0"],
|
||||
"packages": [
|
||||
"com.facebook.ads"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "readable"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.facebook.ads.AudienceNetworkAds",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "Audience Network init" },
|
||||
{ "name": "isInitialized", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.facebook.ads.InterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isAdLoaded", "signature": "*", "stub": "return-false/0" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.facebook.ads.RewardedVideoAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isAdLoaded", "signature": "*", "stub": "return-false/0" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.facebook.ads.AdView",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.facebook.ads.NativeAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.facebook.ads.internal.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Ads internals — 313+ methods, V1-proven safe"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.facebook.ads.AudienceNetworkActivity",
|
||||
"action": "disable",
|
||||
"note": "Audience Network ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.facebook.ads.InterstitialAdActivity",
|
||||
"action": "disable",
|
||||
"note": "Interstitial ad display activity"
|
||||
},
|
||||
{
|
||||
"type": "provider",
|
||||
"class": "com.facebook.ads.AudienceNetworkContentProvider",
|
||||
"action": "disable",
|
||||
"note": "Audience Network content provider"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "mintegral",
|
||||
"display_name": "Mintegral",
|
||||
"description": "Mintegral ad SDK",
|
||||
"vendor": "Mintegral",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.mbridge.msdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.mbridge.msdk.out.MBridgeSDKFactory",
|
||||
"methods": [
|
||||
{ "name": "getMBridgeSDK", "signature": "*", "stub": "return-null", "note": "Factory — returns SDK singleton" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.mbridge.msdk.MBridgeSDK",
|
||||
"methods": [
|
||||
{ "name": "init", "signature": "*", "stub": "return-void", "note": "Main SDK init — all overloads" },
|
||||
{ "name": "initAsync", "signature": "*", "stub": "return-void", "note": "Async SDK init — all overloads" },
|
||||
{ "name": "preload", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.mbridge.msdk.out.MBInterstitialHandler",
|
||||
"methods": [
|
||||
{ "name": "preload", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.mbridge.msdk.out.MBRewardVideoHandler",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.mbridge.msdk.out.MBBannerView",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "loadFromBid", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "release", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.mbridge.msdk.out.MBNewInterstitialHandler",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "loadFormSelfFilling", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.mbridge.msdk.out.MBBidRewardVideoHandler",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.mbridge.msdk.out.MBBidInterstitialVideoHandler",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.mbridge.msdk.foundation.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Internal foundation classes"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.mbridge.msdk.activity.MBCommonActivity",
|
||||
"action": "disable",
|
||||
"note": "Common ad display activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.mbridge.msdk.reward.player.MBRewardVideoActivity",
|
||||
"action": "disable",
|
||||
"note": "Legacy reward video activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.mbridge.msdk.newreward.player.MBRewardVideoActivity",
|
||||
"action": "disable",
|
||||
"note": "New reward video activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.mbridge.msdk.out.LoadingActivity",
|
||||
"action": "disable",
|
||||
"note": "Ad loading activity"
|
||||
},
|
||||
{
|
||||
"type": "receiver",
|
||||
"class": "com.mbridge.msdk.foundation.same.broadcast.NetWorkChangeReceiver",
|
||||
"action": "disable",
|
||||
"note": "Network change broadcast receiver"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "mixpanel",
|
||||
"display_name": "Mixpanel",
|
||||
"description": "Mixpanel analytics SDK for mobile",
|
||||
"vendor": "Mixpanel",
|
||||
"category": "analytics",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.mixpanel.android"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "readable"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.mixpanel.android.mpmetrics.MixpanelAPI",
|
||||
"methods": [
|
||||
{ "name": "track", "signature": "*", "stub": "return-void", "note": "Track event" },
|
||||
{ "name": "trackMap", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "identify", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "timeEvent", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "registerSuperProperties", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "flush", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "alias", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "reset", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "optOutTracking", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "optInTracking", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "service",
|
||||
"class": "com.mixpanel.android.mpmetrics.MixpanelFCMMessagingService",
|
||||
"action": "disable",
|
||||
"note": "FCM push notification service"
|
||||
}
|
||||
],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getInstance()*",
|
||||
"reason": "Singleton accessor — null causes NPE if called by app code"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "mobilefuse",
|
||||
"display_name": "MobileFuse",
|
||||
"description": "MobileFuse ad SDK",
|
||||
"vendor": "MobileFuse",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.mobilefuse.sdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.mobilefuse.sdk.MobileFuse",
|
||||
"methods": [
|
||||
{ "name": "init", "signature": "*", "stub": "return-void", "note": "Main SDK init — all overloads" },
|
||||
{ "name": "initSdkServices", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.mobilefuse.sdk.MobileFuseInterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "showAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isLoaded", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.mobilefuse.sdk.MobileFuseRewardedAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "showAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isLoaded", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.mobilefuse.sdk.MobileFuseBannerAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.mobilefuse.sdk.MobileFuseNativeAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.mobilefuse.sdk.internal.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Internal SDK classes"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "provider",
|
||||
"class": "com.mobilefuse.sdk.MobileFuseSdkInitProvider",
|
||||
"action": "disable",
|
||||
"note": "Auto-init ContentProvider"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.mobilefuse.sdk.MobileFuseFullscreenActivity",
|
||||
"action": "disable",
|
||||
"note": "Fullscreen ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.mobilefuse.sdk.MobileFuseFullscreenTransparentActivity",
|
||||
"action": "disable",
|
||||
"note": "Transparent fullscreen ad overlay"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.mobilefuse.sdk.experimental.NativeInterstitialActivity",
|
||||
"action": "disable",
|
||||
"note": "Native interstitial activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.mobilefuse.sdk.ad.rendering.splashad.MobileFuseSplashAdActivity",
|
||||
"action": "disable",
|
||||
"note": "Splash ad activity"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "moloco",
|
||||
"display_name": "Moloco",
|
||||
"description": "Moloco programmatic ad SDK",
|
||||
"vendor": "Moloco",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.moloco.sdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "mixed",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.moloco.sdk.Init",
|
||||
"methods": [
|
||||
{ "name": "registerAllExtensions", "signature": "*", "stub": "return-void", "note": "Protobuf init registration" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.moloco.sdk.internal.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Internal SDK classes"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.moloco.sdk.xenoss.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Ad renderer internals"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.moloco.sdk.xenoss.sdkdevkit.android.adrenderer.internal.templates.renderer.fullscreen.FullscreenWebviewActivity",
|
||||
"action": "disable",
|
||||
"note": "Fullscreen webview ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.moloco.sdk.xenoss.sdkdevkit.android.adrenderer.internal.vast.VastActivity",
|
||||
"action": "disable",
|
||||
"note": "VAST video ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.moloco.sdk.xenoss.sdkdevkit.android.adrenderer.internal.staticrenderer.StaticAdActivity",
|
||||
"action": "disable",
|
||||
"note": "Static ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.moloco.sdk.xenoss.sdkdevkit.android.adrenderer.internal.mraid.MraidActivity",
|
||||
"action": "disable",
|
||||
"note": "MRAID ad activity"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "ogury",
|
||||
"display_name": "Ogury",
|
||||
"description": "Ogury ad and consent SDK",
|
||||
"vendor": "Ogury",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.ogury.sdk",
|
||||
"com.ogury.ed",
|
||||
"com.ogury.cm",
|
||||
"com.ogury.core"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.ogury.sdk.Ogury",
|
||||
"methods": [
|
||||
{ "name": "start", "signature": "*", "stub": "return-void", "note": "Main SDK init" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.ogury.ed.OguryInterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isLoaded", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.ogury.ed.OguryOptinVideoAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isLoaded", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.ogury.ed.OguryBannerAdView",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "pangle",
|
||||
"display_name": "Pangle",
|
||||
"description": "Pangle (ByteDance / TikTok) ad SDK",
|
||||
"vendor": "ByteDance",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.bytedance.sdk.openadsdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.bytedance.sdk.openadsdk.api.init.PAGSdk",
|
||||
"methods": [
|
||||
{ "name": "init", "signature": "*", "stub": "return-void", "note": "Main SDK init" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.bytedance.sdk.openadsdk.api.interstitial.PAGInterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.bytedance.sdk.openadsdk.api.reward.PAGRewardedAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.bytedance.sdk.openadsdk.api.banner.PAGBannerAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.bytedance.sdk.openadsdk.core.**",
|
||||
"rule": "stub_all_concrete",
|
||||
"note": "Internal implementation"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.bytedance.sdk.openadsdk.activity.TTFullScreenVideoActivity",
|
||||
"action": "disable",
|
||||
"note": "Fullscreen video ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.bytedance.sdk.openadsdk.activity.TTRewardVideoActivity",
|
||||
"action": "disable",
|
||||
"note": "Rewarded video ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.bytedance.sdk.openadsdk.activity.TTInterstitialActivity",
|
||||
"action": "disable",
|
||||
"note": "Interstitial ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.bytedance.sdk.openadsdk.activity.TTAdActivity",
|
||||
"action": "disable",
|
||||
"note": "Generic ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.bytedance.sdk.openadsdk.activity.TTDelegateActivity",
|
||||
"action": "disable",
|
||||
"note": "Delegate activity for ad interactions"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "pubmatic",
|
||||
"display_name": "PubMatic OpenWrap",
|
||||
"description": "PubMatic OpenWrap SDK for programmatic advertising",
|
||||
"vendor": "PubMatic",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.pubmatic.sdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.pubmatic.sdk.openwrap.core.POBBid",
|
||||
"methods": [
|
||||
{ "name": "invalidate", "signature": "*", "stub": "return-void", "note": "Invalidate bid" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.pubmatic.sdk.rewardedad.POBRewardedAd",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.pubmatic.sdk.nativead.POBNativeAdView",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.pubmatic.sdk.openwrap.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "OpenWrap core engine internals"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.pubmatic.sdk.webrendering.mraid.POBVideoPlayerActivity",
|
||||
"action": "disable",
|
||||
"note": "Video player activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.pubmatic.sdk.webrendering.ui.POBFullScreenActivity",
|
||||
"action": "disable",
|
||||
"note": "Fullscreen ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.pubmatic.sdk.common.browser.POBInternalBrowserActivity",
|
||||
"action": "disable",
|
||||
"note": "Internal browser activity"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "pubnative",
|
||||
"display_name": "PubNative / HyBid",
|
||||
"description": "PubNative HyBid SDK for programmatic advertising",
|
||||
"vendor": "Verve Group",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"net.pubnative.lite.sdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "mixed"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "net.pubnative.lite.sdk.HyBid",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "Main SDK init" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "net.pubnative.lite.sdk.interstitial.HyBidInterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "net.pubnative.lite.sdk.rewarded.HyBidRewardedAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "smaato",
|
||||
"display_name": "Smaato",
|
||||
"description": "Smaato ad SDK",
|
||||
"vendor": "Smaato",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.smaato.sdk"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.smaato.sdk.core.SmaatoSdk",
|
||||
"methods": [
|
||||
{ "name": "init", "signature": "*", "stub": "return-void", "note": "Main SDK init — all overloads" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.smaato.sdk.interstitial.Interstitial",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "showAd", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.smaato.sdk.rewarded.RewardedInterstitial",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "showAd", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.smaato.sdk.banner.BannerView",
|
||||
"methods": [
|
||||
{ "name": "loadAd", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "receiver",
|
||||
"class": "com.smaato.sdk.core.SmaatoBroadcastReceiver",
|
||||
"action": "disable",
|
||||
"note": "Broadcast receiver for ad events"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "tradplus",
|
||||
"display_name": "TradPlus",
|
||||
"description": "TradPlus ad mediation SDK",
|
||||
"vendor": "TradPlus",
|
||||
"category": "ads_mediation",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.tradplus.ads"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.tradplus.ads.open.TradPlusSdk",
|
||||
"methods": [
|
||||
{ "name": "initSdk", "signature": "*", "stub": "return-void", "note": "Main SDK init" },
|
||||
{ "name": "setDebugMode", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setGDPRDataCollection", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setCCPADoNotSell", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setCOPPAIsAgeRestrictedUser", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "setEUTraffic", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "checkCurrentArea", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.tradplus.ads.core.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Core ad management internals"
|
||||
},
|
||||
{
|
||||
"package_glob": "com.tradplus.ads.mgr.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Ad manager internals"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.tradplus.ads.mgr.interstitial.views.InterNativeActivity",
|
||||
"action": "disable",
|
||||
"note": "Fullscreen native interstitial activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.tradplus.ads.mgr.interstitial.views.InterNativeAPI26Activity",
|
||||
"action": "disable",
|
||||
"note": "Native interstitial for API 26+"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.tradplus.crosspro.ui.CPAdActivity",
|
||||
"action": "disable",
|
||||
"note": "Cross-promotion ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.tradplus.crosspro.ui.ApkConfirmDialogActivity",
|
||||
"action": "disable",
|
||||
"note": "APK install confirm dialog"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.tradplus.crosspro.ui.ApkConfirmDialogCNActivity",
|
||||
"action": "disable",
|
||||
"note": "APK install confirm dialog (CN)"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "unity-ads",
|
||||
"display_name": "Unity Ads",
|
||||
"description": "Unity Ads SDK for in-app advertising",
|
||||
"vendor": "Unity",
|
||||
"category": "ads",
|
||||
"known_versions": ["4.15.0"],
|
||||
"packages": [
|
||||
"com.unity3d.ads",
|
||||
"com.unity3d.services"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "readable"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.unity3d.ads.UnityAds",
|
||||
"methods": [
|
||||
{ "name": "initialize", "signature": "*", "stub": "return-void", "note": "Main SDK init — all overloads" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.unity3d.ads.UnityAds",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void", "note": "Load ad placement — all overloads" },
|
||||
{ "name": "show", "signature": "*", "stub": "return-void", "note": "Show ad — all overloads" },
|
||||
{ "name": "isInitialized", "signature": "*", "stub": "return-false/0" },
|
||||
{ "name": "isReady", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": [
|
||||
{
|
||||
"package_glob": "com.unity3d.services.**",
|
||||
"rule": "stub_all_void",
|
||||
"note": "Unity Services internal — readable, stable across versions"
|
||||
}
|
||||
]
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.unity3d.ads.adunit.AdUnitActivity",
|
||||
"action": "disable",
|
||||
"note": "Legacy ad unit activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.unity3d.ads.adunit.AdUnitTransparentActivity",
|
||||
"action": "disable",
|
||||
"note": "Legacy transparent ad overlay"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.unity3d.services.ads.adunit.AdUnitActivity",
|
||||
"action": "disable",
|
||||
"note": "Services ad unit activity (newer SDK)"
|
||||
}
|
||||
],
|
||||
"protected_patterns": [
|
||||
{
|
||||
"pattern": "*.getActivity()*",
|
||||
"reason": "Returns Activity reference — null causes NPE"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"$schema": "./_schema.json",
|
||||
"sdk_id": "vungle",
|
||||
"display_name": "Vungle / Liftoff",
|
||||
"description": "Vungle (Liftoff) ad SDK",
|
||||
"vendor": "Liftoff",
|
||||
"category": "ads",
|
||||
"known_versions": [],
|
||||
"packages": [
|
||||
"com.vungle.ads"
|
||||
],
|
||||
"obfuscation": {
|
||||
"public_api": "readable",
|
||||
"internals": "obfuscated"
|
||||
},
|
||||
"targets": {
|
||||
"entry_points": [
|
||||
{
|
||||
"class": "com.vungle.ads.VungleAds",
|
||||
"methods": [
|
||||
{ "name": "init", "signature": "*", "stub": "return-void", "note": "Main SDK init" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"ad_operations": [
|
||||
{
|
||||
"class": "com.vungle.ads.InterstitialAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "play", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "canPlayAd", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.vungle.ads.RewardedAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "play", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "canPlayAd", "signature": "*", "stub": "return-false/0" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"class": "com.vungle.ads.BannerAd",
|
||||
"methods": [
|
||||
{ "name": "load", "signature": "*", "stub": "return-void" },
|
||||
{ "name": "destroy", "signature": "*", "stub": "return-void" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"deep_patterns": []
|
||||
},
|
||||
"manifest_components": [
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.vungle.warren.ui.VungleActivity",
|
||||
"action": "disable",
|
||||
"note": "Legacy Vungle (warren) ad activity"
|
||||
},
|
||||
{
|
||||
"type": "activity",
|
||||
"class": "com.vungle.ads.internal.ui.VungleActivity",
|
||||
"action": "disable",
|
||||
"note": "New Vungle ads activity"
|
||||
}
|
||||
],
|
||||
"protected_patterns": []
|
||||
}
|
||||
|
|
@ -128,6 +128,26 @@ else
|
|||
missing_optional+=("zip")
|
||||
fi
|
||||
|
||||
# --- Python 3.6+ (optional, for registry-scan.py) ---
|
||||
if command -v python3 &>/dev/null; then
|
||||
py_version=$(python3 -c 'import sys; print(f"{sys.version_info.major}.{sys.version_info.minor}")' 2>/dev/null)
|
||||
py_major=$(echo "$py_version" | cut -d. -f1)
|
||||
py_minor=$(echo "$py_version" | cut -d. -f2)
|
||||
if [[ "$py_major" -ge 3 ]] && [[ "$py_minor" -ge 6 ]]; then
|
||||
echo "[OK] Python $py_version (for registry-scan.py — SDK registry scanning)"
|
||||
else
|
||||
echo "[WARN] Python $py_version found but 3.6+ required for registry-scan.py"
|
||||
echo " Without Python 3.6+, neutralize.sh falls back to builtin hardcoded targets."
|
||||
echo " Install: apt install python3 / brew install python3"
|
||||
missing_optional+=("python3")
|
||||
fi
|
||||
else
|
||||
echo "[WARN] python3 not found (optional — required for registry-scan.py SDK registry scanning)"
|
||||
echo " Without python3, neutralize.sh falls back to builtin hardcoded targets."
|
||||
echo " Install: apt install python3 / brew install python3"
|
||||
missing_optional+=("python3")
|
||||
fi
|
||||
|
||||
# --- Machine-readable summary ---
|
||||
echo
|
||||
if [[ ${#missing_required[@]} -gt 0 ]]; then
|
||||
|
|
|
|||
|
|
@ -115,6 +115,7 @@ if [[ "$ext_lower" == "xapk" ]]; then
|
|||
IS_XAPK=true
|
||||
echo "=== Extracting XAPK archive ==="
|
||||
XAPK_TMPDIR=$(mktemp -d "${TMPDIR:-/tmp}/xapk-decode-XXXXXX")
|
||||
trap cleanup_xapk EXIT
|
||||
unzip -qo "$INPUT_FILE_ABS" -d "$XAPK_TMPDIR"
|
||||
|
||||
# Show manifest.json if present
|
||||
|
|
|
|||
|
|
@ -118,8 +118,10 @@ for split_apk in "$XAPK_ORIGIN_DIR/splits/"*.apk; do
|
|||
[[ -f "$split_apk" ]] || continue
|
||||
split_name=$(basename "$split_apk")
|
||||
|
||||
# Check if it's an ABI split by looking for lib/ inside
|
||||
if unzip -l "$split_apk" 2>/dev/null | grep -q "lib/[^/]*/"; then
|
||||
# Check if it's an ABI split — filename pattern first, then content-based fallback
|
||||
if [[ "$split_name" =~ ^config\.(arm64_v8a|armeabi_v7a|x86|x86_64)\.apk$ ]]; then
|
||||
abi_splits+=("$split_apk")
|
||||
elif unzip -l "$split_apk" 2>/dev/null | grep -qE '\blib/[a-z0-9_-]+/.*\.so'; then
|
||||
abi_splits+=("$split_apk")
|
||||
# Check if it's a feature split (contains classes.dex or AndroidManifest.xml with split name)
|
||||
elif unzip -l "$split_apk" 2>/dev/null | grep -qE "(classes[0-9]*\.dex|^.*AndroidManifest\.xml)" && \
|
||||
|
|
@ -221,7 +223,7 @@ else
|
|||
for so_file in "$TMPDIR_NATIVE/lib/$abi/"*; do
|
||||
[[ -f "$so_file" ]] || continue
|
||||
cp "$so_file" "$DECODED_DIR/lib/$abi/"
|
||||
(( lib_count++ ))
|
||||
(( lib_count++ )) || true
|
||||
done
|
||||
ok "Merged $lib_count native libraries for $abi"
|
||||
echo "MERGE_ABI:$abi ($lib_count native libraries)"
|
||||
|
|
@ -265,7 +267,7 @@ else
|
|||
# Count extracted files (excluding directories)
|
||||
file_count=0
|
||||
while IFS= read -r -d '' f; do
|
||||
(( file_count++ ))
|
||||
(( file_count++ )) || true
|
||||
done < <(find "$TMPDIR_RES" -type f -print0 2>/dev/null)
|
||||
|
||||
if (( file_count == 0 )); then
|
||||
|
|
@ -287,7 +289,7 @@ else
|
|||
target="$DECODED_DIR/res/$subdir_name/$(basename "$res_file")"
|
||||
if [[ ! -f "$target" ]]; then
|
||||
cp "$res_file" "$target"
|
||||
(( res_copied++ ))
|
||||
(( res_copied++ )) || true
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
|
@ -303,7 +305,7 @@ else
|
|||
if [[ ! -f "$target" ]]; then
|
||||
mkdir -p "$(dirname "$target")"
|
||||
cp "$asset_file" "$target"
|
||||
(( assets_copied++ ))
|
||||
(( assets_copied++ )) || true
|
||||
fi
|
||||
done < <(find "$TMPDIR_RES/assets" -type f -print0 2>/dev/null)
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -27,12 +27,32 @@ Options:
|
|||
--trackers Neutralize only tracker/analytics SDK entry points
|
||||
--all Neutralize both ads and trackers (default)
|
||||
--dry-run Show what would be patched without modifying files
|
||||
--backup Create .smali.bak backups before patching (default)
|
||||
--no-backup Do not create backup files
|
||||
--backup Create .smali.bak backups before patching
|
||||
--no-backup Do not create backup files (default)
|
||||
--cleanup-backups Remove all .smali.bak files from the decoded directory
|
||||
--manifest Patch AndroidManifest.xml to disable SDK components (default)
|
||||
--no-manifest Skip manifest patching
|
||||
--targets-file <file> Load additional targets from a file (one per line:
|
||||
<smali-class>:<method-name>)
|
||||
--targets-file <file> Load additional targets from a file (one per line).
|
||||
Format: <class-path>:<method-name>
|
||||
com/example/MyClass:methodName (searches all smali dirs)
|
||||
com/example/pkg/**:* (wildcard — all methods in package)
|
||||
com/example/Class:* (all methods in class)
|
||||
L prefix and ; suffix are auto-stripped:
|
||||
Lcom/example/MyClass;:init (also valid)
|
||||
Lines starting with # are ignored.
|
||||
--manifest-components-file <file>
|
||||
Load additional manifest components from a file.
|
||||
Format: one per line, class_substring|sdk_name
|
||||
Components are appended to the builtin lists.
|
||||
--no-builtin-targets Skip hardcoded patch_ad_targets() and patch_tracker_targets().
|
||||
Use when relying entirely on --targets-file (e.g., from
|
||||
registry-scan.py). Builtin manifest components are also
|
||||
skipped; use --manifest-components-file for registry-driven
|
||||
manifest patching.
|
||||
--package <path> Neutralize ALL methods in a smali package recursively.
|
||||
Stubs every non-abstract, non-native, non-constructor
|
||||
method. Can be specified multiple times.
|
||||
Example: --package guru/ads --package com/appsflyer
|
||||
--replay Replay patches from a previous neutralize-manifest.json
|
||||
--save-manifest Save neutralize-manifest.json after patching (default)
|
||||
--no-save-manifest Do not save neutralize-manifest.json
|
||||
|
|
@ -57,9 +77,13 @@ NEUTRALIZE_ADS=false
|
|||
NEUTRALIZE_TRACKERS=false
|
||||
NEUTRALIZE_ALL=true
|
||||
DRY_RUN=false
|
||||
DO_BACKUP=true
|
||||
DO_BACKUP=false
|
||||
DO_CLEANUP_BACKUPS=false
|
||||
DO_MANIFEST=true
|
||||
TARGETS_FILE=""
|
||||
MANIFEST_COMPONENTS_FILE=""
|
||||
NO_BUILTIN_TARGETS=false
|
||||
PACKAGE_PATHS=()
|
||||
DO_REPLAY=false
|
||||
DO_SAVE_MANIFEST=true
|
||||
|
||||
|
|
@ -71,6 +95,7 @@ while [[ $# -gt 0 ]]; do
|
|||
--dry-run) DRY_RUN=true; shift ;;
|
||||
--backup) DO_BACKUP=true; shift ;;
|
||||
--no-backup) DO_BACKUP=false; shift ;;
|
||||
--cleanup-backups) DO_CLEANUP_BACKUPS=true; shift ;;
|
||||
--manifest) DO_MANIFEST=true; shift ;;
|
||||
--no-manifest) DO_MANIFEST=false; shift ;;
|
||||
--targets-file)
|
||||
|
|
@ -80,6 +105,21 @@ while [[ $# -gt 0 ]]; do
|
|||
exit 1
|
||||
fi
|
||||
TARGETS_FILE="$1"; shift ;;
|
||||
--manifest-components-file)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Error: --manifest-components-file requires a file argument" >&2
|
||||
exit 1
|
||||
fi
|
||||
MANIFEST_COMPONENTS_FILE="$1"; shift ;;
|
||||
--no-builtin-targets) NO_BUILTIN_TARGETS=true; shift ;;
|
||||
--package)
|
||||
shift
|
||||
if [[ $# -eq 0 ]]; then
|
||||
echo "Error: --package requires a package path argument" >&2
|
||||
exit 1
|
||||
fi
|
||||
PACKAGE_PATHS+=("$1"); shift ;;
|
||||
--replay) DO_REPLAY=true; shift ;;
|
||||
--save-manifest) DO_SAVE_MANIFEST=true; shift ;;
|
||||
--no-save-manifest) DO_SAVE_MANIFEST=false; shift ;;
|
||||
|
|
@ -169,39 +209,101 @@ patch_method() {
|
|||
found = 0
|
||||
}
|
||||
|
||||
# Match .method line containing our target method name
|
||||
/^\.method / && $0 ~ "[ ;]" method "\\(" {
|
||||
# count_param_registers(descriptor_params, is_static)
|
||||
# Counts the number of registers required for method parameters.
|
||||
# Instance methods have an implicit "this" (p0) taking 1 register.
|
||||
# J (long) and D (double) each take 2 registers; all others take 1.
|
||||
function count_param_registers(params, is_static, i, c, count) {
|
||||
count = 0
|
||||
if (!is_static) count = 1 # p0 = this
|
||||
i = 1
|
||||
while (i <= length(params)) {
|
||||
c = substr(params, i, 1)
|
||||
if (c == "J" || c == "D") {
|
||||
count += 2; i++
|
||||
} else if (c == "L") {
|
||||
count += 1
|
||||
# Skip to ";"
|
||||
while (i <= length(params) && substr(params, i, 1) != ";") i++
|
||||
i++ # skip the ";"
|
||||
} else if (c == "[") {
|
||||
# Array: skip all leading "[", then the base type
|
||||
while (i <= length(params) && substr(params, i, 1) == "[") i++
|
||||
c = substr(params, i, 1)
|
||||
if (c == "L") {
|
||||
count += 1
|
||||
while (i <= length(params) && substr(params, i, 1) != ";") i++
|
||||
i++
|
||||
} else {
|
||||
# Primitive array
|
||||
count += 1; i++
|
||||
}
|
||||
} else {
|
||||
# Primitive: Z, B, C, S, I, F
|
||||
count += 1; i++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
# Match .method line containing our target method name (skip abstract/native)
|
||||
/^\.method / && !/ abstract / && !/ native / && $0 ~ "[ ;]" method "\\(" {
|
||||
in_target = 1
|
||||
found = 1
|
||||
|
||||
# Extract return type from method descriptor
|
||||
# The descriptor is the last part: (params)ReturnType
|
||||
# Determine if static
|
||||
is_static = ($0 ~ / static /) ? 1 : 0
|
||||
|
||||
# Extract descriptor: everything between "(" and ")" is params,
|
||||
# everything after ")" is return type
|
||||
line = $0
|
||||
# Find the closing paren and get what follows
|
||||
idx = index(line, ")")
|
||||
if (idx > 0) {
|
||||
ret_type = substr(line, idx + 1)
|
||||
# Remove trailing whitespace
|
||||
open_idx = index(line, "(")
|
||||
close_idx = index(line, ")")
|
||||
if (open_idx > 0 && close_idx > open_idx) {
|
||||
params = substr(line, open_idx + 1, close_idx - open_idx - 1)
|
||||
ret_type = substr(line, close_idx + 1)
|
||||
gsub(/[[:space:]]+$/, "", ret_type)
|
||||
} else {
|
||||
params = ""
|
||||
ret_type = "V"
|
||||
}
|
||||
|
||||
# Determine stub type
|
||||
# Count registers needed for parameters (including "this" for instance methods)
|
||||
param_regs = count_param_registers(params, is_static)
|
||||
|
||||
# Determine stub type and registers needed for the stub itself
|
||||
if (ret_type == "V") {
|
||||
stub_type = "return-void"
|
||||
stub_body = " .registers 1\n\n return-void"
|
||||
stub_regs = 0 # return-void needs no registers beyond params
|
||||
} else if (ret_type == "Z" || ret_type == "I" || ret_type == "S" || \
|
||||
ret_type == "B" || ret_type == "C" || ret_type == "F") {
|
||||
stub_type = "const/4+return"
|
||||
stub_body = " .registers 1\n\n const/4 v0, 0x0\n\n return v0"
|
||||
stub_regs = 1 # needs v0
|
||||
} else if (ret_type == "J" || ret_type == "D") {
|
||||
stub_type = "const-wide+return-wide"
|
||||
stub_body = " .registers 2\n\n const-wide/16 v0, 0x0\n\n return-wide v0"
|
||||
stub_regs = 2 # needs v0, v1
|
||||
} else {
|
||||
# Object or array return type (L...; or [...)
|
||||
stub_type = "const/4+return-object"
|
||||
stub_body = " .registers 1\n\n const/4 v0, 0x0\n\n return-object v0"
|
||||
stub_regs = 1 # needs v0
|
||||
}
|
||||
|
||||
# Total registers = max(param_regs, stub_regs + param_regs)
|
||||
# In Dalvik, .registers = local_vars + param_registers
|
||||
# We need stub_regs local vars + param_regs for parameters
|
||||
total_regs = stub_regs + param_regs
|
||||
if (total_regs < 1) total_regs = 1 # minimum 1 register
|
||||
|
||||
# Build stub body
|
||||
if (ret_type == "V") {
|
||||
stub_body = " .registers " total_regs "\n\n return-void"
|
||||
} else if (ret_type == "J" || ret_type == "D") {
|
||||
stub_body = " .registers " total_regs "\n\n const-wide/16 v0, 0x0\n\n return-wide v0"
|
||||
} else if (ret_type == "Z" || ret_type == "I" || ret_type == "S" || \
|
||||
ret_type == "B" || ret_type == "C" || ret_type == "F") {
|
||||
stub_body = " .registers " total_regs "\n\n const/4 v0, 0x0\n\n return v0"
|
||||
} else {
|
||||
stub_body = " .registers " total_regs "\n\n const/4 v0, 0x0\n\n return-object v0"
|
||||
}
|
||||
|
||||
if (dry_run == "true") {
|
||||
|
|
@ -232,21 +334,24 @@ patch_method() {
|
|||
|
||||
# Outside target method — print line unchanged
|
||||
{ print }
|
||||
' "$file" > "$tmp_file" 2> >(while IFS= read -r line; do
|
||||
echo "$line"
|
||||
if [[ "$line" == PATCHED:* ]]; then
|
||||
METHODS_PATCHED=$((METHODS_PATCHED + 1))
|
||||
patched=true
|
||||
echo "$line" >> "$PATCH_LOG_FILE"
|
||||
elif [[ "$line" == DRY_RUN:WOULD_PATCH:* ]]; then
|
||||
METHODS_PATCHED=$((METHODS_PATCHED + 1))
|
||||
patched=true
|
||||
fi
|
||||
done)
|
||||
' "$file" > "$tmp_file" 2>"${tmp_file}.stderr"
|
||||
|
||||
# Process stderr in the parent shell (not a subshell) so variables propagate
|
||||
if grep -q '^PATCHED:\|^DRY_RUN:WOULD_PATCH:' "${tmp_file}.stderr" 2>/dev/null; then
|
||||
patched=true
|
||||
local patch_count
|
||||
patch_count=$(grep -c '^PATCHED:\|^DRY_RUN:WOULD_PATCH:' "${tmp_file}.stderr")
|
||||
METHODS_PATCHED=$((METHODS_PATCHED + patch_count))
|
||||
# Append PATCHED lines to the patch log (not DRY_RUN lines)
|
||||
grep '^PATCHED:' "${tmp_file}.stderr" >> "$PATCH_LOG_FILE" 2>/dev/null || true
|
||||
fi
|
||||
# Emit stderr lines to stderr for user visibility
|
||||
cat "${tmp_file}.stderr" >&2
|
||||
rm -f "${tmp_file}.stderr"
|
||||
|
||||
if [[ "$DRY_RUN" == false ]] && [[ "$patched" == true ]]; then
|
||||
if [[ "$DO_BACKUP" == true ]]; then
|
||||
cp "$file" "${file}.bak"
|
||||
[[ -f "${file}.bak" ]] || cp "$file" "${file}.bak"
|
||||
fi
|
||||
mv "$tmp_file" "$file"
|
||||
else
|
||||
|
|
@ -282,6 +387,92 @@ find_and_patch() {
|
|||
done
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# patch_all_methods() — Stub ALL non-abstract, non-native, non-constructor
|
||||
# methods in a smali file
|
||||
#
|
||||
# Arguments:
|
||||
# $1 — smali file path
|
||||
# $2 — SDK name (for reporting)
|
||||
# =====================================================================
|
||||
|
||||
patch_all_methods() {
|
||||
local file="$1"
|
||||
local sdk_name="$2"
|
||||
|
||||
if [[ ! -f "$file" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# Extract class descriptor from the .class line
|
||||
local class_desc
|
||||
class_desc=$(grep -m1 '^\.class ' "$file" | grep -oP 'L[^ ;]+;' | head -1)
|
||||
if [[ -z "$class_desc" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# Extract all method names that are patchable:
|
||||
# - Not abstract, not native
|
||||
# - Not <init> or <clinit> (constructors)
|
||||
local methods
|
||||
methods=$(awk '
|
||||
/^\.method / {
|
||||
# Skip abstract and native methods
|
||||
if ($0 ~ / abstract / || $0 ~ / native /) next
|
||||
# Extract method name from descriptor
|
||||
# Format: .method [access] methodName(params)RetType
|
||||
match($0, /[ ]([^ (]+)\(/, arr)
|
||||
if (arr[1] != "" && arr[1] != "<init>" && arr[1] != "<clinit>") {
|
||||
print arr[1]
|
||||
}
|
||||
}
|
||||
' "$file" | sort -u)
|
||||
|
||||
if [[ -z "$methods" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
while IFS= read -r method_name; do
|
||||
[[ -z "$method_name" ]] && continue
|
||||
patch_method "$file" "$method_name" "$sdk_name" "$class_desc"
|
||||
done <<< "$methods"
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# patch_packages() — Neutralize all methods in specified packages
|
||||
# =====================================================================
|
||||
|
||||
patch_packages() {
|
||||
if [[ ${#PACKAGE_PATHS[@]} -eq 0 ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
for pkg_path in "${PACKAGE_PATHS[@]}"; do
|
||||
# Normalize: remove trailing slashes
|
||||
pkg_path="${pkg_path%/}"
|
||||
local sdk_label="Package:${pkg_path}"
|
||||
|
||||
echo "--- Neutralizing package: $pkg_path ---"
|
||||
local pkg_file_count=0
|
||||
|
||||
for smali_dir in "${SMALI_DIRS[@]}"; do
|
||||
local pkg_dir="$smali_dir/$pkg_path"
|
||||
if [[ ! -d "$pkg_dir" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
# Find all .smali files recursively
|
||||
while IFS= read -r -d '' smali_file; do
|
||||
patch_all_methods "$smali_file" "$sdk_label"
|
||||
pkg_file_count=$((pkg_file_count + 1))
|
||||
done < <(find "$pkg_dir" -name "*.smali" -print0 2>/dev/null)
|
||||
done
|
||||
|
||||
echo " Processed $pkg_file_count smali file(s) in $pkg_path"
|
||||
done
|
||||
echo
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
# SDK Target Lists
|
||||
# =====================================================================
|
||||
|
|
@ -317,12 +508,12 @@ patch_ad_targets() {
|
|||
|
||||
# IronSource / LevelPlay
|
||||
find_and_patch "com/ironsource/mediationsdk/IronSource" \
|
||||
"init,loadInterstitial,showInterstitial,showRewardedVideo,loadBanner" \
|
||||
"init,loadInterstitial,showInterstitial,showRewardedVideo,loadRewardedVideo,loadBanner,showISDemandOnlyInterstitial,showISDemandOnlyRewardedVideo" \
|
||||
"IronSource" "Lcom/ironsource/mediationsdk/IronSource;"
|
||||
|
||||
# AppLovin / MAX
|
||||
find_and_patch "com/applovin/sdk/AppLovinSdk" \
|
||||
"getInstance,initializeSdk" \
|
||||
"getInstance,initialize,initializeSdk" \
|
||||
"AppLovin" "Lcom/applovin/sdk/AppLovinSdk;"
|
||||
|
||||
# Meta Audience Network
|
||||
|
|
@ -334,6 +525,9 @@ patch_ad_targets() {
|
|||
find_and_patch "com/vungle/warren/Vungle" \
|
||||
"init,loadAd,playAd" \
|
||||
"Vungle" "Lcom/vungle/warren/Vungle;"
|
||||
find_and_patch "com/vungle/ads/VungleAds" \
|
||||
"init" \
|
||||
"Vungle" "Lcom/vungle/ads/VungleAds;"
|
||||
find_and_patch "com/vungle/ads/VungleInterstitial" \
|
||||
"load,show" \
|
||||
"Vungle" "Lcom/vungle/ads/VungleInterstitial;"
|
||||
|
|
@ -355,24 +549,64 @@ patch_ad_targets() {
|
|||
"load" \
|
||||
"InMobi" "Lcom/inmobi/ads/InMobiBanner;"
|
||||
|
||||
# Chartboost
|
||||
# Chartboost (legacy API)
|
||||
find_and_patch "com/chartboost/sdk/Chartboost" \
|
||||
"startWithAppId,cacheInterstitial,showInterstitial,cacheRewardedVideo,showRewardedVideo" \
|
||||
"Chartboost" "Lcom/chartboost/sdk/Chartboost;"
|
||||
# Chartboost (new ads API)
|
||||
find_and_patch "com/chartboost/sdk/ads/Interstitial" \
|
||||
"cache,show" \
|
||||
"Chartboost" "Lcom/chartboost/sdk/ads/Interstitial;"
|
||||
find_and_patch "com/chartboost/sdk/ads/Rewarded" \
|
||||
"cache,show" \
|
||||
"Chartboost" "Lcom/chartboost/sdk/ads/Rewarded;"
|
||||
|
||||
# Pangle / TikTok (legacy API)
|
||||
find_and_patch "com/bytedance/sdk/openadsdk/TTAdSdk" \
|
||||
"init" \
|
||||
"Pangle" "Lcom/bytedance/sdk/openadsdk/TTAdSdk;"
|
||||
# Pangle new API
|
||||
find_and_patch "com/pgl/sys/ces/PAGSdk" \
|
||||
find_and_patch "com/bytedance/sdk/openadsdk/api/init/PAGSdk" \
|
||||
"init" \
|
||||
"Pangle" "Lcom/pgl/sys/ces/PAGSdk;"
|
||||
"Pangle" "Lcom/bytedance/sdk/openadsdk/api/init/PAGSdk;"
|
||||
|
||||
# Mintegral
|
||||
find_and_patch "com/mbridge/msdk/MBridgeSDKFactory" \
|
||||
"getMBridgeSDK" \
|
||||
"Mintegral" "Lcom/mbridge/msdk/MBridgeSDKFactory;"
|
||||
find_and_patch "com/mbridge/msdk/MBridgeSDK" \
|
||||
"init,initAsync,preload" \
|
||||
"Mintegral" "Lcom/mbridge/msdk/MBridgeSDK;"
|
||||
|
||||
# BidMachine
|
||||
find_and_patch "io/bidmachine/BidMachine" \
|
||||
"initialize" \
|
||||
"BidMachine" "Lio/bidmachine/BidMachine;"
|
||||
|
||||
# Smaato
|
||||
find_and_patch "com/smaato/sdk/core/SmaatoSdk" \
|
||||
"init" \
|
||||
"Smaato" "Lcom/smaato/sdk/core/SmaatoSdk;"
|
||||
|
||||
# Verve / HyBid (PubNative)
|
||||
find_and_patch "net/pubnative/lite/sdk/HyBid" \
|
||||
"initialize" \
|
||||
"Verve" "Lnet/pubnative/lite/sdk/HyBid;"
|
||||
|
||||
# Ogury
|
||||
find_and_patch "com/ogury/sdk/Ogury" \
|
||||
"start" \
|
||||
"Ogury" "Lcom/ogury/sdk/Ogury;"
|
||||
|
||||
# Fyber / DT Exchange (InnerActive)
|
||||
find_and_patch "com/fyber/inneractive/sdk/external/InneractiveAdManager" \
|
||||
"initialize" \
|
||||
"Fyber" "Lcom/fyber/inneractive/sdk/external/InneractiveAdManager;"
|
||||
|
||||
# Amazon APS
|
||||
find_and_patch "com/amazon/device/ads/AdRegistration" \
|
||||
"setAppKey,enableTesting,enableLogging" \
|
||||
"AmazonAPS" "Lcom/amazon/device/ads/AdRegistration;"
|
||||
}
|
||||
|
||||
patch_tracker_targets() {
|
||||
|
|
@ -423,6 +657,16 @@ patch_tracker_targets() {
|
|||
find_and_patch "com/flurry/android/FlurryAgent" \
|
||||
"logEvent,setUserId,onStartSession,onEndSession" \
|
||||
"Flurry" "Lcom/flurry/android/FlurryAgent;"
|
||||
|
||||
# Facebook SDK core
|
||||
find_and_patch "com/facebook/FacebookSdk" \
|
||||
"sdkInitialize,fullyInitialize,setAutoInitEnabled,setAutoLogAppEventsEnabled" \
|
||||
"Facebook" "Lcom/facebook/FacebookSdk;"
|
||||
|
||||
# Firebase Crashlytics
|
||||
find_and_patch "com/google/firebase/crashlytics/FirebaseCrashlytics" \
|
||||
"log,recordException,setUserId,setCustomKey,setCrashlyticsCollectionEnabled" \
|
||||
"Firebase" "Lcom/google/firebase/crashlytics/FirebaseCrashlytics;"
|
||||
}
|
||||
|
||||
# =====================================================================
|
||||
|
|
@ -486,11 +730,22 @@ patch_manifest() {
|
|||
|
||||
local -a components_to_disable=()
|
||||
|
||||
if [[ "$NEUTRALIZE_ALL" == true ]] || [[ "$NEUTRALIZE_ADS" == true ]]; then
|
||||
components_to_disable+=("${AD_COMPONENTS[@]}")
|
||||
if [[ "$NO_BUILTIN_TARGETS" == false ]]; then
|
||||
if [[ "$NEUTRALIZE_ALL" == true ]] || [[ "$NEUTRALIZE_ADS" == true ]]; then
|
||||
components_to_disable+=("${AD_COMPONENTS[@]}")
|
||||
fi
|
||||
if [[ "$NEUTRALIZE_ALL" == true ]] || [[ "$NEUTRALIZE_TRACKERS" == true ]]; then
|
||||
components_to_disable+=("${TRACKER_COMPONENTS[@]}")
|
||||
fi
|
||||
fi
|
||||
if [[ "$NEUTRALIZE_ALL" == true ]] || [[ "$NEUTRALIZE_TRACKERS" == true ]]; then
|
||||
components_to_disable+=("${TRACKER_COMPONENTS[@]}")
|
||||
|
||||
# Load additional components from file (registry-scan.py output)
|
||||
if [[ -n "$MANIFEST_COMPONENTS_FILE" ]] && [[ -f "$MANIFEST_COMPONENTS_FILE" ]]; then
|
||||
while IFS= read -r line || [[ -n "$line" ]]; do
|
||||
[[ -z "$line" ]] && continue
|
||||
[[ "$line" == \#* ]] && continue
|
||||
components_to_disable+=("$line")
|
||||
done < "$MANIFEST_COMPONENTS_FILE"
|
||||
fi
|
||||
|
||||
if [[ "$DO_BACKUP" == true ]] && [[ "$DRY_RUN" == false ]]; then
|
||||
|
|
@ -553,6 +808,8 @@ patch_custom_targets() {
|
|||
[[ "$line" == \#* ]] && continue
|
||||
|
||||
# Format: Lcom/example/Class;:methodName or com/example/Class:methodName
|
||||
# Wildcards: com/example/pkg/**:* (all methods in package)
|
||||
# com/example/Class:* (all methods in class)
|
||||
local class_part="${line%%:*}"
|
||||
local method_part="${line##*:}"
|
||||
|
||||
|
|
@ -565,9 +822,42 @@ patch_custom_targets() {
|
|||
class_part="${class_part#L}"
|
||||
class_part="${class_part%;}"
|
||||
|
||||
# Build class descriptor for reporting
|
||||
local class_desc="L${class_part};"
|
||||
# Handle package wildcard: com/example/pkg/** — treat as --package
|
||||
if [[ "$class_part" == *"/**" ]]; then
|
||||
local pkg_path="${class_part%/**}"
|
||||
echo " Wildcard target: neutralizing package $pkg_path"
|
||||
for smali_dir in "${SMALI_DIRS[@]}"; do
|
||||
local pkg_dir="$smali_dir/$pkg_path"
|
||||
if [[ ! -d "$pkg_dir" ]]; then
|
||||
continue
|
||||
fi
|
||||
while IFS= read -r -d '' smali_file; do
|
||||
if [[ "$method_part" == "*" ]]; then
|
||||
patch_all_methods "$smali_file" "Custom"
|
||||
else
|
||||
local file_class_desc
|
||||
file_class_desc=$(grep -m1 '^\.class ' "$smali_file" | grep -oP 'L[^ ;]+;' | head -1)
|
||||
patch_method "$smali_file" "$method_part" "Custom" "${file_class_desc:-Lunknown;}"
|
||||
fi
|
||||
done < <(find "$pkg_dir" -name "*.smali" -print0 2>/dev/null)
|
||||
done
|
||||
continue
|
||||
fi
|
||||
|
||||
# Handle class wildcard: com/example/Class:* — all methods in one class
|
||||
if [[ "$method_part" == "*" ]]; then
|
||||
local class_desc="L${class_part};"
|
||||
for smali_dir in "${SMALI_DIRS[@]}"; do
|
||||
local smali_file="$smali_dir/$class_part.smali"
|
||||
if [[ -f "$smali_file" ]]; then
|
||||
patch_all_methods "$smali_file" "Custom"
|
||||
fi
|
||||
done
|
||||
continue
|
||||
fi
|
||||
|
||||
# Standard target: specific class + method
|
||||
local class_desc="L${class_part};"
|
||||
find_and_patch "$class_part" "$method_part" "Custom" "$class_desc"
|
||||
done < "$TARGETS_FILE"
|
||||
}
|
||||
|
|
@ -630,6 +920,19 @@ if [[ "$DO_REPLAY" == true ]]; then
|
|||
echo
|
||||
fi
|
||||
|
||||
# =====================================================================
|
||||
# Cleanup backups (if requested)
|
||||
# =====================================================================
|
||||
|
||||
if [[ "$DO_CLEANUP_BACKUPS" == true ]]; then
|
||||
bak_count=0
|
||||
while IFS= read -r -d '' bakfile; do
|
||||
rm -f "$bakfile"
|
||||
bak_count=$((bak_count + 1))
|
||||
done < <(find "$DECODED_DIR" -name "*.smali.bak" -print0 2>/dev/null)
|
||||
echo "Cleaned up $bak_count .smali.bak file(s) from $DECODED_DIR"
|
||||
fi
|
||||
|
||||
# =====================================================================
|
||||
# Main
|
||||
# =====================================================================
|
||||
|
|
@ -644,16 +947,21 @@ echo "Smali directories: ${SMALI_DIRS[*]}"
|
|||
echo
|
||||
|
||||
# Patch SDK targets
|
||||
if [[ "$NEUTRALIZE_ALL" == true ]] || [[ "$NEUTRALIZE_ADS" == true ]]; then
|
||||
echo "--- Neutralizing Ad SDK entry points ---"
|
||||
patch_ad_targets
|
||||
if [[ "$NO_BUILTIN_TARGETS" == true ]]; then
|
||||
echo "--- Skipping builtin targets (--no-builtin-targets) ---"
|
||||
echo
|
||||
fi
|
||||
else
|
||||
if [[ "$NEUTRALIZE_ALL" == true ]] || [[ "$NEUTRALIZE_ADS" == true ]]; then
|
||||
echo "--- Neutralizing Ad SDK entry points ---"
|
||||
patch_ad_targets
|
||||
echo
|
||||
fi
|
||||
|
||||
if [[ "$NEUTRALIZE_ALL" == true ]] || [[ "$NEUTRALIZE_TRACKERS" == true ]]; then
|
||||
echo "--- Neutralizing Tracker SDK entry points ---"
|
||||
patch_tracker_targets
|
||||
echo
|
||||
if [[ "$NEUTRALIZE_ALL" == true ]] || [[ "$NEUTRALIZE_TRACKERS" == true ]]; then
|
||||
echo "--- Neutralizing Tracker SDK entry points ---"
|
||||
patch_tracker_targets
|
||||
echo
|
||||
fi
|
||||
fi
|
||||
|
||||
# Patch custom targets
|
||||
|
|
@ -663,6 +971,12 @@ if [[ -n "$TARGETS_FILE" ]]; then
|
|||
echo
|
||||
fi
|
||||
|
||||
# Patch packages (--package flag)
|
||||
if [[ ${#PACKAGE_PATHS[@]} -gt 0 ]]; then
|
||||
echo "--- Neutralizing packages ---"
|
||||
patch_packages
|
||||
fi
|
||||
|
||||
# Patch manifest
|
||||
patch_manifest
|
||||
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ Output:
|
|||
VERIFY_OK:<output-apk>
|
||||
KEYSTORE_USED:<path>
|
||||
KEYSTORE_SOURCE:debug-standard|debug-previous|debug-generated|custom
|
||||
KEYSTORE_ALIAS:<alias>
|
||||
SPLIT_SIGNED:<filename> (XAPK only)
|
||||
XAPK_ASSEMBLED:<output-xapk> (XAPK only)
|
||||
EOF
|
||||
|
|
@ -231,6 +232,8 @@ build_apk() {
|
|||
local no_res_flag="${1:-}"
|
||||
# Clean previous build artifacts
|
||||
rm -rf "$DECODED_DIR/dist/" 2>/dev/null || true
|
||||
# Remove .smali.bak files that cause apktool warnings during rebuild
|
||||
find "$DECODED_DIR" -name "*.smali.bak" -delete 2>/dev/null || true
|
||||
if apktool b $no_res_flag "$DECODED_DIR" 2>&1; then
|
||||
return 0
|
||||
fi
|
||||
|
|
@ -387,6 +390,7 @@ fi
|
|||
|
||||
echo "KEYSTORE_USED:$KEYSTORE"
|
||||
echo "KEYSTORE_SOURCE:$KEYSTORE_SOURCE"
|
||||
echo "KEYSTORE_ALIAS:$KEY_ALIAS"
|
||||
|
||||
info "Signing APK with $SIGNER..."
|
||||
|
||||
|
|
@ -559,7 +563,6 @@ echo
|
|||
echo "WARNING: Play Integrity / SafetyNet will FAIL — expected for enterprise sideloading."
|
||||
if [[ "$IS_XAPK" == true ]] && [[ "$SINGLE_APK" == false ]]; then
|
||||
echo "Install via: adb install-multiple <base.apk> <split1.apk> <split2.apk> ..."
|
||||
echo " or: use a split APK installer (e.g., SAI — Split APKs Installer)"
|
||||
echo " or: unzip the XAPK and run: adb install-multiple *.apk"
|
||||
elif [[ "$IS_XAPK" == true ]] && [[ "$SINGLE_APK" == true ]]; then
|
||||
echo "Install via: adb install $OUTPUT"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,517 @@
|
|||
#!/usr/bin/env python3
|
||||
"""registry-scan.py — Scan decoded APK against SDK registry for neutralization targets.
|
||||
|
||||
Loads SDK registry JSONs, scans smali directories for matching packages,
|
||||
and generates targets-file + manifest-components-file for neutralize.sh.
|
||||
Also discovers unknown SDK packages not covered by the registry.
|
||||
|
||||
Requires Python 3.6+. No external dependencies.
|
||||
|
||||
Exit codes:
|
||||
0 — success (matches found)
|
||||
1 — error (invalid input, missing files)
|
||||
2 — no matches found
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from fnmatch import fnmatch
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# Constants
|
||||
# =====================================================================
|
||||
|
||||
# Category mapping for --category filter
|
||||
ADS_CATEGORIES = {"ads", "ads_mediation"}
|
||||
TRACKER_CATEGORIES = {"analytics", "attribution", "crash_reporting", "social"}
|
||||
|
||||
# Known non-SDK library packages to exclude from unknown discovery
|
||||
KNOWN_LIBRARY_PACKAGES = {
|
||||
"android", "androidx", "kotlin", "kotlinx",
|
||||
"com/google/protobuf", "com/google/gson", "com/google/common",
|
||||
"com/google/android/material", "com/google/android/play",
|
||||
"com/google/android/exoplayer", "com/google/android/exoplayer2",
|
||||
"com/google/android/datatransport", "com/google/android/recaptcha",
|
||||
"com/google/crypto", "com/google/flatbuffers",
|
||||
"com/squareup/okhttp3", "com/squareup/okhttp", "com/squareup/moshi",
|
||||
"com/squareup/wire", "com/squareup/picasso",
|
||||
"okhttp3", "okio",
|
||||
"retrofit2", "retrofit",
|
||||
"com/jakewharton",
|
||||
"dagger", "com/google/dagger",
|
||||
"javax/inject", "javax/annotation",
|
||||
"io/reactivex", "io/reactivex/rxjava3",
|
||||
"com/bumptech/glide",
|
||||
"com/airbnb/lottie",
|
||||
"org/json", "org/intellij", "org/jetbrains",
|
||||
"org/apache",
|
||||
"com/google/firebase/components", "com/google/firebase/inject",
|
||||
"com/google/firebase/encoders", "com/google/firebase/installations",
|
||||
"com/google/firebase/sessions", "com/google/firebase/messaging",
|
||||
"com/google/firebase/datatransport",
|
||||
"com/google/android/gms/base", "com/google/android/gms/common",
|
||||
"com/google/android/gms/tasks", "com/google/android/gms/flags",
|
||||
"com/google/android/gms/dynamic", "com/google/android/gms/dynamite",
|
||||
"com/google/android/gms/security", "com/google/android/gms/cloudmessaging",
|
||||
"com/google/android/gms/phenotype",
|
||||
"com/google/android/ump",
|
||||
"bolts",
|
||||
"com/github",
|
||||
}
|
||||
|
||||
# Minimum class count to consider a package as a potential SDK
|
||||
MIN_CLASSES_FOR_UNKNOWN = 10
|
||||
|
||||
# Minimum package depth (segments) to consider as SDK (not obfuscated)
|
||||
MIN_PACKAGE_DEPTH = 3
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# Registry loading
|
||||
# =====================================================================
|
||||
|
||||
def load_registry(registry_dir):
|
||||
"""Load all SDK JSON files from registry directory."""
|
||||
sdks = []
|
||||
registry_path = Path(registry_dir)
|
||||
|
||||
if not registry_path.is_dir():
|
||||
print(f"Error: Registry directory not found: {registry_dir}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
for json_file in sorted(registry_path.glob("*.json")):
|
||||
if json_file.name.startswith("_"):
|
||||
continue
|
||||
try:
|
||||
with open(json_file, "r", encoding="utf-8") as f:
|
||||
sdk = json.load(f)
|
||||
sdks.append(sdk)
|
||||
except (json.JSONDecodeError, IOError) as e:
|
||||
print(f"Warning: Skipping {json_file.name}: {e}", file=sys.stderr)
|
||||
|
||||
return sdks
|
||||
|
||||
|
||||
def filter_sdks_by_category(sdks, category):
|
||||
"""Filter SDKs by category: ads, trackers, or all."""
|
||||
if category == "all":
|
||||
return sdks
|
||||
elif category == "ads":
|
||||
return [s for s in sdks if s.get("category") in ADS_CATEGORIES]
|
||||
elif category == "trackers":
|
||||
return [s for s in sdks if s.get("category") in TRACKER_CATEGORIES]
|
||||
return sdks
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# Smali scanning
|
||||
# =====================================================================
|
||||
|
||||
def find_smali_dirs(decoded_dir):
|
||||
"""Find all smali directories (multidex support)."""
|
||||
decoded = Path(decoded_dir)
|
||||
dirs = sorted(decoded.glob("smali*"))
|
||||
return [d for d in dirs if d.is_dir()]
|
||||
|
||||
|
||||
def get_app_package(decoded_dir):
|
||||
"""Extract app package from AndroidManifest.xml."""
|
||||
manifest = Path(decoded_dir) / "AndroidManifest.xml"
|
||||
if not manifest.exists():
|
||||
return None
|
||||
try:
|
||||
content = manifest.read_text(encoding="utf-8")
|
||||
match = re.search(r'<manifest[^>]+package="([^"]+)"', content)
|
||||
if match:
|
||||
return match.group(1).replace(".", "/")
|
||||
except IOError:
|
||||
pass
|
||||
return None
|
||||
|
||||
|
||||
def scan_packages(smali_dirs):
|
||||
"""Scan smali directories and return a map of package -> class count."""
|
||||
package_classes = defaultdict(int)
|
||||
|
||||
for smali_dir in smali_dirs:
|
||||
for smali_file in smali_dir.rglob("*.smali"):
|
||||
rel = smali_file.relative_to(smali_dir)
|
||||
parts = rel.parts[:-1] # directory parts = package
|
||||
if parts:
|
||||
package = "/".join(parts)
|
||||
package_classes[package] += 1
|
||||
|
||||
return package_classes
|
||||
|
||||
|
||||
def check_package_exists(smali_dirs, package_dot):
|
||||
"""Check if a dot-separated package exists in any smali directory."""
|
||||
package_path = package_dot.replace(".", "/")
|
||||
for smali_dir in smali_dirs:
|
||||
pkg_dir = smali_dir / package_path
|
||||
if pkg_dir.is_dir():
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def find_sdk_matches(sdks, smali_dirs):
|
||||
"""Match registry SDKs against smali directories. Returns list of (sdk, matched_packages)."""
|
||||
matches = []
|
||||
for sdk in sdks:
|
||||
matched_pkgs = []
|
||||
for pkg in sdk.get("packages", []):
|
||||
if check_package_exists(smali_dirs, pkg):
|
||||
matched_pkgs.append(pkg)
|
||||
if matched_pkgs:
|
||||
matches.append((sdk, matched_pkgs))
|
||||
return matches
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# Target generation
|
||||
# =====================================================================
|
||||
|
||||
def java_to_smali_class(java_class):
|
||||
"""Convert Java class name to smali path: com.example.Foo -> com/example/Foo"""
|
||||
return java_class.replace(".", "/")
|
||||
|
||||
|
||||
def generate_targets(sdk, depth):
|
||||
"""Generate targets-file lines for a matched SDK at given depth level.
|
||||
|
||||
Depth 1: entry_points only
|
||||
Depth 2: entry_points + ad_operations
|
||||
Depth 3: entry_points + ad_operations + deep_patterns
|
||||
"""
|
||||
lines = []
|
||||
targets = sdk.get("targets", {})
|
||||
protected = sdk.get("protected_patterns", [])
|
||||
sdk_name = sdk.get("display_name", sdk.get("sdk_id", "Unknown"))
|
||||
|
||||
# Collect protected method patterns
|
||||
protected_methods = set()
|
||||
for pp in protected:
|
||||
pattern = pp.get("pattern", "")
|
||||
# Extract method name from patterns like "*.getActivity()*"
|
||||
m = re.search(r'\*\.(\w+)\(', pattern)
|
||||
if m:
|
||||
protected_methods.add(m.group(1))
|
||||
|
||||
# Level 1: entry_points
|
||||
for class_target in targets.get("entry_points", []):
|
||||
cls = java_to_smali_class(class_target["class"])
|
||||
for method in class_target.get("methods", []):
|
||||
method_name = method["name"]
|
||||
if method_name in protected_methods:
|
||||
continue
|
||||
lines.append(f"# [{sdk_name}] entry_point")
|
||||
lines.append(f"{cls}:{method_name}")
|
||||
|
||||
# Level 2: ad_operations
|
||||
if depth >= 2:
|
||||
for class_target in targets.get("ad_operations", []):
|
||||
cls = java_to_smali_class(class_target["class"])
|
||||
for method in class_target.get("methods", []):
|
||||
method_name = method["name"]
|
||||
if method_name in protected_methods:
|
||||
continue
|
||||
lines.append(f"# [{sdk_name}] ad_operation")
|
||||
lines.append(f"{cls}:{method_name}")
|
||||
|
||||
# Level 3: deep_patterns (package wildcards)
|
||||
if depth >= 3:
|
||||
for dp in targets.get("deep_patterns", []):
|
||||
glob_pattern = dp["package_glob"]
|
||||
# Convert "com.example.pkg.**" -> "com/example/pkg/**:*"
|
||||
pkg_path = glob_pattern.replace(".", "/").rstrip("*").rstrip("/")
|
||||
lines.append(f"# [{sdk_name}] deep_pattern: {dp.get('rule', 'stub_all_void')}")
|
||||
lines.append(f"{pkg_path}/**:*")
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
def generate_manifest_components(sdk):
|
||||
"""Generate manifest-components-file lines for a matched SDK."""
|
||||
lines = []
|
||||
sdk_name = sdk.get("display_name", sdk.get("sdk_id", "Unknown"))
|
||||
for comp in sdk.get("manifest_components", []):
|
||||
cls = comp["class"]
|
||||
lines.append(f"{cls}|{sdk_name}")
|
||||
return lines
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# Unknown package discovery
|
||||
# =====================================================================
|
||||
|
||||
def is_obfuscated_package(package):
|
||||
"""Check if a package looks obfuscated (single-letter segments, too short)."""
|
||||
parts = package.split("/")
|
||||
# Single-letter segments are obfuscated (a/, b/c/, etc.)
|
||||
if all(len(p) <= 2 for p in parts):
|
||||
return True
|
||||
# First two segments are single letters
|
||||
if len(parts) >= 2 and len(parts[0]) <= 1 and len(parts[1]) <= 1:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def is_known_library(package):
|
||||
"""Check if a package matches known non-SDK libraries."""
|
||||
for known in KNOWN_LIBRARY_PACKAGES:
|
||||
if package == known or package.startswith(known + "/"):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def find_unknown_packages(package_classes, matched_sdk_packages, app_package):
|
||||
"""Find packages that might be unknown SDKs.
|
||||
|
||||
Filters:
|
||||
- Exclude matched SDK packages
|
||||
- Exclude app package
|
||||
- Exclude known libraries (androidx, kotlin, okhttp, etc.)
|
||||
- Exclude obfuscated packages (single-letter names)
|
||||
- Minimum class count threshold
|
||||
- Minimum package depth (3+ segments)
|
||||
"""
|
||||
# Build set of all matched SDK root packages (as paths)
|
||||
sdk_roots = set()
|
||||
for pkg_dot in matched_sdk_packages:
|
||||
sdk_roots.add(pkg_dot.replace(".", "/"))
|
||||
|
||||
unknowns = []
|
||||
|
||||
# Aggregate class counts at the 3-segment level for better grouping
|
||||
aggregated = defaultdict(int)
|
||||
for pkg, count in package_classes.items():
|
||||
parts = pkg.split("/")
|
||||
if len(parts) >= 3:
|
||||
root = "/".join(parts[:3])
|
||||
else:
|
||||
root = pkg
|
||||
aggregated[root] += count
|
||||
|
||||
for package, class_count in sorted(aggregated.items(), key=lambda x: -x[1]):
|
||||
# Check minimum class count
|
||||
if class_count < MIN_CLASSES_FOR_UNKNOWN:
|
||||
continue
|
||||
|
||||
# Check package depth
|
||||
parts = package.split("/")
|
||||
if len(parts) < MIN_PACKAGE_DEPTH:
|
||||
continue
|
||||
|
||||
# Skip obfuscated
|
||||
if is_obfuscated_package(package):
|
||||
continue
|
||||
|
||||
# Skip known libraries
|
||||
if is_known_library(package):
|
||||
continue
|
||||
|
||||
# Skip app package
|
||||
if app_package and (package == app_package or package.startswith(app_package + "/")):
|
||||
continue
|
||||
|
||||
# Skip matched SDK packages
|
||||
is_matched = False
|
||||
for sdk_root in sdk_roots:
|
||||
if package == sdk_root or package.startswith(sdk_root + "/"):
|
||||
is_matched = True
|
||||
break
|
||||
if is_matched:
|
||||
continue
|
||||
|
||||
unknowns.append((package, class_count))
|
||||
|
||||
return unknowns
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# Report generation
|
||||
# =====================================================================
|
||||
|
||||
def generate_report(matches, unknowns, depth, category):
|
||||
"""Generate JSON report of scan results."""
|
||||
report = {
|
||||
"scan_config": {
|
||||
"depth": depth,
|
||||
"category": category,
|
||||
},
|
||||
"matched_sdks": [],
|
||||
"unknown_packages": [],
|
||||
}
|
||||
|
||||
for sdk, matched_pkgs in matches:
|
||||
targets = sdk.get("targets", {})
|
||||
n_entry = sum(len(ct.get("methods", [])) for ct in targets.get("entry_points", []))
|
||||
n_ops = sum(len(ct.get("methods", [])) for ct in targets.get("ad_operations", []))
|
||||
n_deep = len(targets.get("deep_patterns", []))
|
||||
n_manifest = len(sdk.get("manifest_components", []))
|
||||
|
||||
# Count targets at current depth
|
||||
n_targets = n_entry
|
||||
if depth >= 2:
|
||||
n_targets += n_ops
|
||||
if depth >= 3:
|
||||
n_targets += n_deep
|
||||
|
||||
report["matched_sdks"].append({
|
||||
"sdk_id": sdk["sdk_id"],
|
||||
"display_name": sdk["display_name"],
|
||||
"category": sdk["category"],
|
||||
"matched_packages": matched_pkgs,
|
||||
"targets_count": n_targets,
|
||||
"manifest_components_count": n_manifest,
|
||||
"depth_breakdown": {
|
||||
"entry_points": n_entry,
|
||||
"ad_operations": n_ops,
|
||||
"deep_patterns": n_deep,
|
||||
},
|
||||
})
|
||||
|
||||
for package, class_count in unknowns:
|
||||
report["unknown_packages"].append({
|
||||
"package": package,
|
||||
"class_count": class_count,
|
||||
})
|
||||
|
||||
return report
|
||||
|
||||
|
||||
# =====================================================================
|
||||
# Main
|
||||
# =====================================================================
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Scan decoded APK against SDK registry for neutralization targets."
|
||||
)
|
||||
parser.add_argument("decoded_dir", help="Path to apktool-decoded APK directory")
|
||||
parser.add_argument(
|
||||
"--registry", required=True,
|
||||
help="Path to SDK registry directory containing JSON files"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--depth", type=int, choices=[1, 2, 3], default=1,
|
||||
help="Neutralization depth: 1=entry_points, 2=+ad_operations, 3=+deep_patterns (default: 1)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--category", choices=["ads", "trackers", "all"], default="all",
|
||||
help="Filter SDKs by category (default: all)"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output-dir",
|
||||
help="Output directory for generated files (default: decoded-dir)"
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
decoded_dir = args.decoded_dir
|
||||
output_dir = args.output_dir or decoded_dir
|
||||
|
||||
# Validate decoded directory
|
||||
if not os.path.isdir(decoded_dir):
|
||||
print(f"Error: Directory not found: {decoded_dir}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Find smali directories
|
||||
smali_dirs = find_smali_dirs(decoded_dir)
|
||||
if not smali_dirs:
|
||||
print(f"Error: No smali/ directory found in {decoded_dir}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Load and filter registry
|
||||
sdks = load_registry(args.registry)
|
||||
if not sdks:
|
||||
print("Error: No SDK entries loaded from registry", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
sdks = filter_sdks_by_category(sdks, args.category)
|
||||
|
||||
# Scan for package existence
|
||||
package_classes = scan_packages(smali_dirs)
|
||||
app_package = get_app_package(decoded_dir)
|
||||
|
||||
# Find SDK matches
|
||||
matches = find_sdk_matches(sdks, smali_dirs)
|
||||
|
||||
# Collect all matched SDK packages for unknown discovery
|
||||
all_matched_packages = set()
|
||||
for sdk, matched_pkgs in matches:
|
||||
for pkg in sdk.get("packages", []):
|
||||
all_matched_packages.add(pkg)
|
||||
|
||||
# Generate targets file
|
||||
targets_lines = []
|
||||
targets_lines.append("# Auto-generated by registry-scan.py")
|
||||
targets_lines.append(f"# Depth: {args.depth} | Category: {args.category}")
|
||||
targets_lines.append("")
|
||||
|
||||
for sdk, matched_pkgs in matches:
|
||||
sdk_targets = generate_targets(sdk, args.depth)
|
||||
if sdk_targets:
|
||||
targets_lines.extend(sdk_targets)
|
||||
targets_lines.append("")
|
||||
|
||||
# Generate manifest components file
|
||||
manifest_lines = []
|
||||
for sdk, matched_pkgs in matches:
|
||||
manifest_lines.extend(generate_manifest_components(sdk))
|
||||
|
||||
# Find unknown packages
|
||||
unknowns = find_unknown_packages(package_classes, all_matched_packages, app_package)
|
||||
|
||||
# Generate report
|
||||
report = generate_report(matches, unknowns, args.depth, args.category)
|
||||
|
||||
# Write output files
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
|
||||
targets_path = os.path.join(output_dir, "registry-targets.txt")
|
||||
with open(targets_path, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(targets_lines) + "\n")
|
||||
|
||||
manifest_path = os.path.join(output_dir, "registry-manifest.txt")
|
||||
with open(manifest_path, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(manifest_lines) + "\n")
|
||||
|
||||
report_path = os.path.join(output_dir, "registry-report.json")
|
||||
with open(report_path, "w", encoding="utf-8") as f:
|
||||
json.dump(report, f, indent=2, ensure_ascii=False)
|
||||
f.write("\n")
|
||||
|
||||
# Machine-readable stdout
|
||||
for sdk, matched_pkgs in matches:
|
||||
targets = sdk.get("targets", {})
|
||||
n_entry = sum(len(ct.get("methods", [])) for ct in targets.get("entry_points", []))
|
||||
n_ops = sum(len(ct.get("methods", [])) for ct in targets.get("ad_operations", []))
|
||||
n_deep = len(targets.get("deep_patterns", []))
|
||||
n_targets = n_entry
|
||||
if args.depth >= 2:
|
||||
n_targets += n_ops
|
||||
if args.depth >= 3:
|
||||
n_targets += n_deep
|
||||
print(f"MATCHED:{sdk['sdk_id']}:{sdk['display_name']}:{sdk['category']}:{n_targets}")
|
||||
|
||||
for package, class_count in unknowns:
|
||||
print(f"UNKNOWN_PACKAGE:{package}:{class_count}")
|
||||
|
||||
print(f"REGISTRY_TARGETS:{targets_path}")
|
||||
print(f"REGISTRY_MANIFEST:{manifest_path}")
|
||||
|
||||
if not matches:
|
||||
sys.exit(2)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -26,10 +26,14 @@ Options:
|
|||
--manifest Search only for AndroidManifest.xml markers
|
||||
--entrypoints Search only for tracker SDK calls in app code (excludes library packages)
|
||||
--all Search all patterns (default)
|
||||
--summary Output a compact summary table with confidence scoring
|
||||
--json Output results as machine-readable JSON
|
||||
-h, --help Show this help message
|
||||
|
||||
Output:
|
||||
Results are printed as file:line:match for easy navigation.
|
||||
Default: Results are printed as file:line:match for easy navigation.
|
||||
--summary: Compact table with SDK | Sections | File Matches | Confidence | Status
|
||||
--json: JSON object with per-SDK detection results and confidence scores
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
|
@ -49,6 +53,8 @@ SEARCH_ENDPOINTS=false
|
|||
SEARCH_MANIFEST=false
|
||||
SEARCH_ENTRYPOINTS=false
|
||||
SEARCH_ALL=true
|
||||
OUTPUT_SUMMARY=false
|
||||
OUTPUT_JSON=false
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
|
|
@ -66,6 +72,8 @@ while [[ $# -gt 0 ]]; do
|
|||
--manifest) SEARCH_MANIFEST=true; SEARCH_ALL=false; shift ;;
|
||||
--entrypoints) SEARCH_ENTRYPOINTS=true; SEARCH_ALL=false; shift ;;
|
||||
--all) SEARCH_ALL=true; shift ;;
|
||||
--summary) OUTPUT_SUMMARY=true; shift ;;
|
||||
--json) OUTPUT_JSON=true; shift ;;
|
||||
-h|--help) usage ;;
|
||||
-*) echo "Error: Unknown option $1" >&2; usage ;;
|
||||
*) SOURCE_DIR="$1"; shift ;;
|
||||
|
|
@ -84,23 +92,102 @@ fi
|
|||
|
||||
GREP_OPTS="-rn --include=*.java --include=*.kt"
|
||||
|
||||
# Summary/JSON mode: redirect output to temp file, count results per SDK
|
||||
if [[ "$OUTPUT_SUMMARY" == true ]] || [[ "$OUTPUT_JSON" == true ]]; then
|
||||
SUMMARY_MODE=true
|
||||
# Force --all for summary mode to get complete picture
|
||||
SEARCH_ALL=true
|
||||
else
|
||||
SUMMARY_MODE=false
|
||||
fi
|
||||
|
||||
# Associative arrays for summary tracking (SDK -> match count, sections with hits)
|
||||
declare -A SDK_CLASS_MATCHES # SDK-specific class/import matches (HIGH confidence)
|
||||
declare -A SDK_STRING_MATCHES # SDK string/generic matches (MEDIUM confidence)
|
||||
declare -A SDK_SECTIONS_HIT # comma-separated list of sections with hits
|
||||
|
||||
section() {
|
||||
echo
|
||||
echo "==== $1 ===="
|
||||
echo
|
||||
if [[ "$SUMMARY_MODE" == true ]]; then
|
||||
CURRENT_SECTION="$1"
|
||||
else
|
||||
echo
|
||||
echo "==== $1 ===="
|
||||
echo
|
||||
fi
|
||||
}
|
||||
|
||||
run_grep() {
|
||||
local pattern="$1"
|
||||
# shellcheck disable=SC2086
|
||||
grep $GREP_OPTS -E "$pattern" "$SOURCE_DIR" 2>/dev/null || true
|
||||
local result
|
||||
result=$(grep $GREP_OPTS -E "$pattern" "$SOURCE_DIR" 2>/dev/null || true)
|
||||
if [[ "$SUMMARY_MODE" == true ]]; then
|
||||
if [[ -n "$result" ]]; then
|
||||
local count
|
||||
count=$(echo "$result" | wc -l)
|
||||
# Determine which SDK this section belongs to and confidence level
|
||||
_tally_section_result "$count"
|
||||
fi
|
||||
else
|
||||
echo "$result"
|
||||
fi
|
||||
}
|
||||
|
||||
run_grep_xml() {
|
||||
local pattern="$1"
|
||||
grep -rn --include="*.xml" -E "$pattern" "$SOURCE_DIR" 2>/dev/null || true
|
||||
local result
|
||||
result=$(grep -rn --include="*.xml" -E "$pattern" "$SOURCE_DIR" 2>/dev/null || true)
|
||||
if [[ "$SUMMARY_MODE" == true ]]; then
|
||||
if [[ -n "$result" ]]; then
|
||||
local count
|
||||
count=$(echo "$result" | wc -l)
|
||||
_tally_section_result "$count"
|
||||
fi
|
||||
else
|
||||
echo "$result"
|
||||
fi
|
||||
}
|
||||
|
||||
# Maps section name to SDK and tallies results
|
||||
_tally_section_result() {
|
||||
local count="$1"
|
||||
local sdk=""
|
||||
local confidence="class" # default to HIGH (class-level match)
|
||||
|
||||
case "$CURRENT_SECTION" in
|
||||
"Firebase Analytics"*) sdk="Firebase" ;;
|
||||
"Adjust"*) sdk="Adjust" ;;
|
||||
"AppsFlyer"*) sdk="AppsFlyer" ;;
|
||||
"Mixpanel"*) sdk="Mixpanel" ;;
|
||||
"Amplitude"*) sdk="Amplitude" ;;
|
||||
"Segment"*) sdk="Segment" ;;
|
||||
"Braze"*) sdk="Braze" ;;
|
||||
"CleverTap"*) sdk="CleverTap" ;;
|
||||
"Flurry"*) sdk="Flurry" ;;
|
||||
"Generic"*) sdk="Generic"; confidence="string" ;;
|
||||
"Known Tracker Endpoints"*) sdk="Endpoints"; confidence="class" ;;
|
||||
"AndroidManifest"*) sdk="Manifest"; confidence="class" ;;
|
||||
"Entry Points"*) sdk="EntryPoints"; confidence="class" ;;
|
||||
*) sdk="Other"; confidence="string" ;;
|
||||
esac
|
||||
|
||||
if [[ "$confidence" == "class" ]]; then
|
||||
SDK_CLASS_MATCHES["$sdk"]=$(( ${SDK_CLASS_MATCHES["$sdk"]:-0} + count ))
|
||||
else
|
||||
SDK_STRING_MATCHES["$sdk"]=$(( ${SDK_STRING_MATCHES["$sdk"]:-0} + count ))
|
||||
fi
|
||||
|
||||
# Track which sections had hits
|
||||
local existing="${SDK_SECTIONS_HIT["$sdk"]:-}"
|
||||
if [[ -n "$existing" ]]; then
|
||||
SDK_SECTIONS_HIT["$sdk"]="${existing}, ${CURRENT_SECTION}"
|
||||
else
|
||||
SDK_SECTIONS_HIT["$sdk"]="${CURRENT_SECTION}"
|
||||
fi
|
||||
}
|
||||
|
||||
CURRENT_SECTION=""
|
||||
|
||||
# --- Firebase Analytics ---
|
||||
if [[ "$SEARCH_ALL" == true || "$SEARCH_FIREBASE" == true ]]; then
|
||||
section "Firebase Analytics — Initialization"
|
||||
|
|
@ -257,12 +344,103 @@ if [[ "$SEARCH_ALL" == true || "$SEARCH_ENTRYPOINTS" == true ]]; then
|
|||
ENTRYPOINT_PATTERN='(FirebaseAnalytics\.getInstance|FirebaseAnalytics\.newInstance|FirebaseApp\.initializeApp|\.logEvent\s*\(|\.setUserId\s*\(|\.setUserProperty\s*\(|\.setAnalyticsCollectionEnabled|Adjust\.onCreate|Adjust\.trackEvent|AdjustConfig\s*\(|AppsFlyerLib\.getInstance|\.init\s*\(.*AF_DEV_KEY|\.start\s*\(.*AppsFlyerLib|MixpanelAPI\.getInstance|\.track\s*\(.*MixpanelAPI|\.identify\s*\(.*Mixpanel|Amplitude\.getInstance|\.logEvent\s*\(.*Amplitude|Analytics\.with\s*\(|\.track\s*\(.*Segment|\.identify\s*\(.*Segment|Braze\.configure|\.logCustomEvent\s*\(|\.changeUser\s*\(.*Braze|CleverTapAPI\.getDefaultInstance|\.pushEvent\s*\(|\.onUserLogin\s*\(|FlurryAgent\.logEvent|FlurryAgent\.setUserId|FlurryAgent\.Builder)'
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
grep -rn --include="*.java" --include="*.kt" "${EXCLUDE_DIRS[@]}" -E "$ENTRYPOINT_PATTERN" "$SOURCE_DIR" 2>/dev/null || true
|
||||
|
||||
echo
|
||||
echo "NOTE: Only calls from app code are shown above. Library-internal calls are excluded."
|
||||
echo "If no results appear, tracker SDKs may only be initialized internally (e.g., via ContentProvider)."
|
||||
local ep_result
|
||||
ep_result=$(grep -rn --include="*.java" --include="*.kt" "${EXCLUDE_DIRS[@]}" -E "$ENTRYPOINT_PATTERN" "$SOURCE_DIR" 2>/dev/null || true)
|
||||
if [[ "$SUMMARY_MODE" == true ]]; then
|
||||
if [[ -n "$ep_result" ]]; then
|
||||
local count
|
||||
count=$(echo "$ep_result" | wc -l)
|
||||
_tally_section_result "$count"
|
||||
fi
|
||||
else
|
||||
echo "$ep_result"
|
||||
echo
|
||||
echo "NOTE: Only calls from app code are shown above. Library-internal calls are excluded."
|
||||
echo "If no results appear, tracker SDKs may only be initialized internally (e.g., via ContentProvider)."
|
||||
fi
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "=== Search complete ==="
|
||||
# =====================================================================
|
||||
# Summary / JSON output
|
||||
# =====================================================================
|
||||
|
||||
if [[ "$SUMMARY_MODE" == true ]]; then
|
||||
# Known tracker SDKs to report on (exclude meta categories)
|
||||
TRACKER_SDKS=("Firebase" "Adjust" "AppsFlyer" "Mixpanel" "Amplitude" "Segment" "Braze" "CleverTap" "Flurry")
|
||||
|
||||
if [[ "$OUTPUT_JSON" == true ]]; then
|
||||
# JSON output
|
||||
printf '{\n "trackers": [\n'
|
||||
first=true
|
||||
for sdk in "${TRACKER_SDKS[@]}"; do
|
||||
class_count=${SDK_CLASS_MATCHES["$sdk"]:-0}
|
||||
string_count=${SDK_STRING_MATCHES["$sdk"]:-0}
|
||||
total=$((class_count + string_count))
|
||||
|
||||
# Determine confidence
|
||||
if [[ $class_count -gt 0 ]]; then
|
||||
confidence="HIGH"
|
||||
elif [[ $string_count -gt 0 ]]; then
|
||||
confidence="MEDIUM"
|
||||
else
|
||||
confidence="NONE"
|
||||
fi
|
||||
|
||||
[[ "$confidence" == "NONE" ]] && continue
|
||||
|
||||
if [[ "$first" == true ]]; then
|
||||
first=false
|
||||
else
|
||||
printf ',\n'
|
||||
fi
|
||||
|
||||
sections="${SDK_SECTIONS_HIT["$sdk"]:-}"
|
||||
printf ' {"sdk": "%s", "class_matches": %d, "string_matches": %d, "total_matches": %d, "confidence": "%s", "sections": "%s"}' \
|
||||
"$sdk" "$class_count" "$string_count" "$total" "$confidence" "$sections"
|
||||
done
|
||||
printf '\n ]\n}\n'
|
||||
else
|
||||
# Summary table output
|
||||
echo
|
||||
echo "=== Tracker Detection Summary ==="
|
||||
echo
|
||||
printf "%-15s | %6s | %7s | %5s | %-10s | %s\n" "SDK" "Class" "String" "Total" "Confidence" "Status"
|
||||
printf "%-15s-|-%6s-|-%7s-|-%5s-|-%-10s-|-%s\n" "---------------" "------" "-------" "-----" "----------" "--------"
|
||||
|
||||
for sdk in "${TRACKER_SDKS[@]}"; do
|
||||
class_count=${SDK_CLASS_MATCHES["$sdk"]:-0}
|
||||
string_count=${SDK_STRING_MATCHES["$sdk"]:-0}
|
||||
total=$((class_count + string_count))
|
||||
|
||||
if [[ $class_count -gt 0 ]]; then
|
||||
confidence="HIGH"
|
||||
status="DETECTED"
|
||||
elif [[ $string_count -gt 0 ]]; then
|
||||
confidence="MEDIUM"
|
||||
status="LIKELY"
|
||||
else
|
||||
confidence="NONE"
|
||||
status="NOT FOUND"
|
||||
fi
|
||||
|
||||
printf "%-15s | %6d | %7d | %5d | %-10s | %s\n" \
|
||||
"$sdk" "$class_count" "$string_count" "$total" "$confidence" "$status"
|
||||
done
|
||||
|
||||
# Additional info
|
||||
ep_count=${SDK_CLASS_MATCHES["EntryPoints"]:-0}
|
||||
if [[ $ep_count -gt 0 ]]; then
|
||||
echo
|
||||
echo "Entry points in app code: $ep_count match(es)"
|
||||
fi
|
||||
manifest_count=${SDK_CLASS_MATCHES["Manifest"]:-0}
|
||||
if [[ $manifest_count -gt 0 ]]; then
|
||||
echo "Manifest markers: $manifest_count match(es)"
|
||||
fi
|
||||
echo
|
||||
echo "Confidence: HIGH = SDK-specific classes found, MEDIUM = only generic/string matches, NONE = not detected"
|
||||
fi
|
||||
else
|
||||
echo
|
||||
echo "=== Search complete ==="
|
||||
fi
|
||||
|
|
|
|||
Loading…
Reference in New Issue