Android application analysis tool aimed at security researchers.
Downloads apps directly from Google Play, detects and attempts to bypass
anti-root/RASP protections (DexGuard, Arxan, Appdome, Promon, RootBeer), decompiles them, extracts hardcoded secrets and endpoints,
analyzes insecure manifest configurations, and launches OSINT reconnaissance on the package ID,
domains, endpoints and extracted secrets (subdomains via crt.sh, public leaks on GitHub/Postman/FOFA/Wayback and optional web searches).
Findings go through an optional LLM-powered false positive filter (ai-review).
All results are consolidated into a technical PDF report ready for reporting.
Nutcracker is intended for security research, penetration testing, and educational purposes only. Use this tool exclusively on applications you own or have explicit written authorization to test. Unauthorized analysis of third-party applications may violate local laws, international regulations, and app store terms of service. The authors assume no liability for misuse or any damage caused by this tool. Use responsibly.
- Downloads APKs from Google Play (via apkeep + AAS token), APKPure or direct URL
- App Bundle (AAB) support: split detection and
adb install-multiple - Static protection detection: DexGuard, Arxan, Appdome, RootBeer, Promon Shield, etc.
- Smart analytics SDK filtering (AppMetrica, AppsFlyer, etc.) to avoid false positives
- Dynamic deobfuscation via
frida_server,gadgetorfart, depending on the configured pipeline - Optional Frida Gadget instrumentation as an embedded fallback path
- SAST scanner: semgrep (OWASP MASTG) + 38 internal regex rules (
sast_scanfeature) - Configurable leak/secret search: internal HC rules + apkleaks + gitleaks on decompiled code and original APK
- Optional OSINT module: subdomains via crt.sh, public leaks on GitHub/Postman/FOFA/Shodan/Wayback, false-positive filter and optional web searches via DuckDuckGo
- AI Review (
ai-review): LLM-powered false positive filter — reviews each finding, tags FPs with_fp: true(preserved in JSON for audit), downgrades low-confidence findings severity; auto-regenerates PDF - AndroidManifest.xml analysis: dangerous permissions, exported components,
network security configand insecure configurations - MASVS v2 compliance scoring: 24-control evaluation with numeric pass/fail count (e.g.
10/24 controls) - Complete PDF report: cover page, MASVS compliance, protections, misconfigurations, OSINT, leaks, and SAST vulnerabilities
- Batch mode to scan multiple apps in sequence
- Modules controllable via feature flags in
config.yaml decompilation: jadxpipeline option forces static-only analysis — disables Frida/emulator even when DexGuard is detected
brew install apkeep # download APKs from Google Play / APKPure
brew install jadx # decompile APKs to Java + XML
brew install apktool # unpack/repack APKs (required for gadget_inject)
brew install semgrep # static analysis (OWASP MASTG rules)
brew install android-platform-tools # adb# Base tools
sudo apt update
sudo apt install -y openjdk-21-jre-headless jadx apktool adb curl
# semgrep (via pipx recommended)
python3 -m pip install --user pipx
python3 -m pipx ensurepath
pipx install semgrep
# apkeep (official binary — direct download, no archive)
APKEEP_VERSION="1.0.0"
curl -L -o /tmp/apkeep \
"https://github.com/EFForg/apkeep/releases/download/${APKEEP_VERSION}/apkeep-x86_64-unknown-linux-gnu"
sudo install /tmp/apkeep /usr/local/bin/apkeep
apkeep --versionFor other distros (Fedora/Arch), install the equivalent packages for
openjdk,jadx,apktoolandadb, and keepapkeepfrom its official release.
# Java 11+ required. Example with OpenJDK:
brew install openjdk@21Tested version: openjdk 23.0.1
Install from Android Studio or with sdkmanager.
The tool automatically detects the SDK at ~/Library/Android/sdk (macOS).
Required components:
# From Android Studio → SDK Manager, or with sdkmanager:
sdkmanager "platform-tools" # adb
sdkmanager "emulator" # AVD emulator
sdkmanager "build-tools;34.0.0" # apksigner, zipalign
sdkmanager "system-images;android-34;google_apis;arm64-v8a" # AVD image
avdmanager create avd -n nutcracker_avd -k "system-images;android-34;google_apis;arm64-v8a"
apksignerandzipalignare required for APK Bundle patching and for Frida Gadget injection. They can be found at~/Library/Android/sdk/build-tools/<ver>/.
git clone <repo>
cd nutcracker
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt| Package | Purpose |
|---|---|
androguard |
Static APK analysis (DEX, manifest, strings) |
click |
CLI |
rich |
Terminal output with colors and spinners |
pyyaml |
Read config.yaml |
fpdf2 |
PDF report generation |
loguru |
Structured logging |
requests |
HTTP (download frida-server, internal communication) |
Note:
frida,frida-tools,frida-dexdump,semgrepandapkleaksare system tools (pip or Homebrew), not project dependencies. They are validated withshutil.which()before use; if not installed, the corresponding module is skipped with a warning.
This mode runs nutcracker inside Docker and uses an emulator/device connected on the host.
This is the recommended option for Windows + WSL.
docker compose build
docker compose run --rm nutcrackerInside the container:
adb devices
frida-ls-devicesIf nothing appears, restart ADB on the host (Windows/Linux/macOS):
adb kill-server
adb start-server
adb devicespython nutcracker.py analyze downloads/app.apk- The emulator typically runs on Windows, not inside WSL.
- The container connects to the host's
adb serverviaADB_SERVER_SOCKET=tcp:host.docker.internal:5037. - If Frida cannot resolve
-D emulator-xxxx, use-U(the project pipeline already handles this for emulators).
OWASP MASTG rules for jadx-decompiled code come from: mindedsecurity/semgrep-rules-android-security
git clone https://github.com/mindedsecurity/semgrep-rules-android-security \
semgrep_rules_android
# To update:
git -C ./semgrep_rules_android pullThe path is configured in config.yaml:
sast:
engine: auto # auto | semgrep | regex | none
config: "p/secrets ./semgrep_rules_android/rules"Note: The
p/android,p/secretsandp/owasp-top-tenprofiles are no longer available in the semgrep public registry (HTTP 404 since ~2025). Use the local rules instead.
apkeep requires a long-lived AAS token to download from Google Play.
Important:
setup-tokenrequires a real device or an emulator without Google Play (i.e.google_apisimage, notgoogle_play). Play Store-protected AVDs block the token extraction flow.
# Interactive assistant (step-by-step guided on the device):
python nutcracker.py setup-token
# Optional: choose device and method
python nutcracker.py setup-token --serial emulator-5554 --method autosource .venv/bin/activate
# Analyze a local APK:
python nutcracker.py analyze downloads/app.apk
# Download and analyze from Google Play (URL or package ID):
python nutcracker.py scan 'https://play.google.com/store/apps/details?id=com.example.app'
python nutcracker.py analyze com.example.app
# Batch scan from a package list:
python nutcracker.py batch packages.txtLaunches an already-installed app using the last bypass script generated for that package:
# Use the most recent bypass script for the package:
python nutcracker.py launch com.example.app
# Specify a particular emulator:
python nutcracker.py launch com.example.app --serial emulator-5554
# Specify a script manually:
python nutcracker.py launch com.example.app --script frida_scripts/bypass_com.example.app_....js
# Pass the APK path directly (extracts the package from the filename):
python nutcracker.py launch downloads/com.example.app/com.example.app.apkThe command:
- Restarts frida-server on the device (kills any existing process).
- Runs
adb rootto obtain contextu:r:su:s0— required on Android 14 so frida-server can read/sys/fs/selinux/policy. - Launches the app via
frida -f <package> -l <script>with the bypass script.
Use config.yaml.example as the source of truth.
The recommended practice is to copy that file to config.yaml and adjust only the values you need.
google_play:
email: "you@gmail.com"
aas_token: "aas_et/..."
downloader:
output_dir: "./downloads"
keep_apk: true
reports:
output_dir: "./reports"
save_json: false
save_pdf: true
features: # Feature flags: enable or disable modules
anti_root_analysis: true # Anti-root protection detection
decompilation: true # Decompilation (jadx or runtime, depending on pipeline)
manifest_scan: true # Insecure manifest configuration analysis
sast_scan: false # SAST scanner (semgrep + regex)
leak_scan: true # Leak/secret scanner
osint_scan: true # OSINT: subdomains and public leaks
report_pdf: true # Generate PDF report
report_json: false # Generate JSON report
sast: # SAST scanner settings
engine: auto # auto | semgrep | regex | none
config: "p/secrets ./semgrep_rules_android/rules"
leak_scan:
native: true # Internal HC rules on decompiled code
apkleaks: true # apkleaks on the original APK
gitleaks: true # gitleaks on decompiled code
osint:
crt_sh: true # Subdomain enumeration via crt.sh
github_search: true # Search for public leaks on GitHub
github_token: '' # Optional PAT for the Code Search API
fofa_search: false # Search for exposed assets on FOFA
fofa_key: '' # FOFA API key for search/all
postman_search: true # Search for public Postman collections
execute_dorks: false # Optional web searches via DuckDuckGo
dork_engines:
- duckduckgo
dork_max_per_engine: 5 # Maximum web queries per engine
dork_max_results_per_dork: 5 # Maximum results per query
wayback_search: true # Search historical URLs on archive.org
wayback_limit_per_domain: 200 # Maximum archived URLs per domain
wayback_filter_interesting: true # Filter to sensitive paths/queries
strategies:
anti_root_engine: native # Anti-root detection engine: native | apkid
show_emulator: true
runtime_target: emulator # auto | emulator | device
default_emulator_avd: ""
default_device_id: ""
frida_host: "" # host:port for Frida TCP
frida_server_version: "" # explicit frida-server version
pipelines:
protected: # Apps with detected protection
decompilation: runtime # runtime | jadx (jadx = static-only, disables Frida)
fallback_jadx: true # If runtime fails, try jadx
runtime_methods:
- frida_server
- gadget
- fart
unprotected: # Apps without protection
decompilation_jadx: true # Direct static decompilation
# LLM-powered false positive filter (runs as a post-hook after analysis)
post_hooks: [ai-review]
ai_review:
batch_size: 8 # Findings per LLM request
context_lines: 4 # Source lines of context sent to LLM
regen_pdf: true # Regenerate PDF after filtering
llm:
model: deepseek-v4-pro # Any OpenAI-compatible model
api_key: "sk-..."
base_url: "https://api.deepseek.com"
provider: openai
max_tokens: 4096
timeout: 120
auto:
unattended: true # Unattended mode (no manual intervention)
batch:
list_file: "" # Optional list file for batch mode
stop_on_error: falseAPK
└─► Install on AVD emulator
├─► frida-dexdump (primary strategy: dumps DEX from memory)
│ └─► fails →
├─► Frida Gadget inject (if pipeline.protected includes gadget)
│ └─► fails →
└─► FART (classloader hook via Frida script)
└─► jadx → scan → PDF
The PDF cover page shows a single risk score (0–100, higher = worse) and a letter grade derived from weighted deductions across all finding categories:
| Factor | Deduction | Cap |
|---|---|---|
| No protection detected | −30 | — |
| Protection bypassed (RASP) | −20 | — |
| Each CRITICAL vulnerability | −15 | −45 |
| Each HIGH vulnerability | −8 | −24 |
| Each MEDIUM vulnerability | −3 | −12 |
| Each LOW vulnerability | −1 | −5 |
| Each hardcoded secret / leak | −4 | −20 |
| Each manifest misconfiguration | −2 | −10 |
| Each CVE CRITICAL (Shodan) | −8 | −15 (assets total) |
| Each CVE HIGH (Shodan) | −4 | ↑ |
Grade thresholds (score = 100 − deductions, floor 0):
| Score | Grade | Risk Level |
|---|---|---|
| 85–100 | A | MINIMAL |
| 70–84 | B | LOW |
| 50–69 | C | MEDIUM |
| 30–49 | D | HIGH |
| 0–29 | F | CRITICAL |
Note: MASVS v2 compliance uses a separate numeric count (
10/24 controls passed). It is not included in the risk score — it measures regulatory compliance, not operational risk.
| Section | Description |
|---|---|
| Cover | Risk score (0–100), letter grade (A–F), overall risk level and findings breakdown by category |
| MASVS v2 Compliance | 24-control pass/fail evaluation — numeric count only (10/24 controls), no letter grade |
| Protections | Detected vs bypassed protections (8 detectors) |
| Misconfigurations | AndroidManifest.xml analysis: debuggable, allowBackup, cleartext, exported components and dangerous permissions |
| OSINT | Own domains, subdomains and public leaks (GitHub, Postman, FOFA, Shodan, Wayback) |
| Leaks | Hardcoded secrets: API keys, tokens, URLs, AWS/Firebase credentials |
| Vulnerabilities | semgrep + regex findings classified by severity (only if sast_scan: true and files were scanned) |
An optional LLM-powered post-hook that filters false positives from findings:
# Runs automatically after scan if configured in post_hooks:
post_hooks: [ai-review]
# Or manually:
python nutcracker.py ai-review com.example.app
python nutcracker.py ai-review com.example.app --dry-run # preview onlyHow it works:
- Sends findings in batches to the configured LLM (any OpenAI-compatible provider)
- Each finding is classified as
TRUE_POSITIVE,FALSE_POSITIVEorDOWNGRADE - FPs are tagged
_fp: truein the JSON — never deleted — so the audit trail is preserved - Downgrades reduce severity (e.g.
high→infofor URL-only findings) - URL-valued findings (
HC007/HC008with only a URL as matched text) are automatically degraded toinfoeven without LLM review - PDF is regenerated with only true positives visible
Detectors implement multiple filtering layers:
- Anti-root: Whitelist of 30+ analytics SDK namespaces (AppMetrica, AppsFlyer, Adjust, etc.). Root-check strings from SDKs are not counted as app-level protection.
- DexGuard: Requires vendor signature (guardsquare, arxan) as mandatory evidence. Multidex + high entropy without vendor sig is not reported.
- Leaks (regex): Ignore patterns for HC002 (passwords), HC006 (crypto keys), AUTH001 (tokens in logs) that filter framework constants.
- Leaks (apkleaks): Post-filtering of noisy categories and FP patterns (JWT versions, X.509, Facebook SDK signatures).
When apkeep downloads only the base split (base.apk), the tool:
- Detects additional splits in the same package folder
- Uses
adb install-multiplewith all splits (excludes_patched,_unsigned,_resignartifacts) - If no local splits exist: patches the binary
AndroidManifest.xmlto overriderequiredSplitTypesand reinstalls
On Android 14 (API 34), frida-server needs SELinux context u:r:su:s0 to read
/sys/fs/selinux/policy during spawn. Without this context, frida throws InvocationTargetException.
The launch command and the automatic pipeline run adb root before starting frida-server.
Requires an AVD with a google_apis image (not google_play) or root access on a physical device.
When the app doesn't start with monkey (native-level anti-tampering, emulator detection), the system automatically tries:
am start— more reliable alternative to monkey for apps with restrictions in the intent handlerfrida-dexdump -f(spawn mode) — pauses the app before any code runs, including anti-tampering. Requires an active frida-server.
See ROADMAP.md for pending tasks: OSINT improvements, iOS/IPA support and partial migration to Go.
Nutcracker auto-discovers plugins: any subdirectory inside nutcracker_core/plugins/
that exposes a register(cli) function is loaded at startup.
git clone https://github.com/<user>/<plugin-repo> nutcracker_core/plugins/<name>
# requirements.txt is installed automatically on first use| Plugin | Command | Description |
|---|---|---|
aipwn |
nutcracker aipwn <package> |
Autonomous LLM-powered Frida bypass agent |
aireview |
nutcracker ai-review <package> |
LLM-powered false positive filter |
A plugin is a Python package (a folder with __init__.py) placed inside
nutcracker_core/plugins/. The only required contract is a top-level
register(cli) function — everything else is optional.
nutcracker_core/plugins/myplugin/
├── __init__.py # required
└── requirements.txt # optional — auto-installed if import fails
cli is the root click.Group of nutcracker.py. Use it to attach one or
more subcommands:
# nutcracker_core/plugins/myplugin/__init__.py
from __future__ import annotations
import click
def register(cli: click.Group) -> None:
@cli.command("my-command")
@click.argument("package")
@click.option("--verbose", "-v", is_flag=True)
def my_command(package: str, verbose: bool) -> None:
"""Short description shown in nutcracker --help."""
click.echo(f"Running myplugin on {package}")After adding the file, the command is available immediately:
python nutcracker.py my-command com.example.app
python nutcracker.py --help # shows my-command in the listfrom nutcracker_core.config import load_config, get as cfg_get
def register(cli: click.Group) -> None:
@cli.command("my-command")
@click.argument("package")
def my_command(package: str) -> None:
config = load_config()
api_key = cfg_get(config, "llm.api_key", default="")
timeout = cfg_get(config, "llm.timeout", default=60)
...You can also add your own block to config.yaml:
# config.yaml
myplugin:
output_dir: "./myplugin_output"
max_items: 10output_dir = cfg_get(config, "myplugin.output_dir", default="./myplugin_output")Post-hooks let you run code automatically after scan / analyze / batch
without modifying nutcracker.py:
from nutcracker_core.plugins import register_post_hook
def _after_analysis(package, result, vuln_scan, config):
# Runs after every scan/analyze that produces a result
print(f"[myplugin] {package} — {len(vuln_scan.findings)} findings")
def _after_batch(packages, config):
# Runs once after a full batch completes
print(f"[myplugin] batch done — {len(packages)} apps")
def register(cli: click.Group) -> None:
register_post_hook("after_analysis", _after_analysis)
register_post_hook("after_batch", _after_batch)Available events:
| Event | When | kwargs |
|---|---|---|
after_analysis |
After every scan / analyze run |
package, result, vuln_scan, config |
after_batch |
Once after batch finishes |
packages (list[str]), config |
Create requirements.txt next to __init__.py. If the plugin fails to import
due to missing packages, the loader automatically runs pip install -q -r requirements.txt
and retries the import.
# nutcracker_core/plugins/myplugin/requirements.txt
httpx>=0.27
rich>=13
# nutcracker_core/plugins/myplugin/__init__.py
from __future__ import annotations
from pathlib import Path
import click
from nutcracker_core.plugins import register_post_hook
def _after_analysis(package, result, vuln_scan, config):
out = Path("./myplugin_output") / f"{package}.txt"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_text(f"{len(vuln_scan.findings)} findings\n")
def register(cli: click.Group) -> None:
register_post_hook("after_analysis", _after_analysis)
@cli.command("my-command")
@click.argument("package")
def my_command(package: str) -> None:
"""Run myplugin manually on PACKAGE."""
click.echo(f"myplugin: {package}")nutcracker/
├── nutcracker.py # Main CLI (click)
├── config.yaml # Local configuration
├── config.yaml.example # Configuration template
├── setup.sh # Quick install script
├── requirements.txt # Python dependencies
├── docker-compose.yml # Docker environment for hybrid execution
├── Dockerfile # Project base image
├── docs/assets/ # Logo and README assets
├── downloads/ # Downloaded APKs
├── decompiled/ # Code decompiled by jadx / frida-dexdump
├── frida_scripts/ # Generated Frida bypass scripts
├── reports/ # Generated PDFs and JSON reports
├── semgrep_rules_android/ # OWASP MASTG rules
├── tools/ # Auxiliary utilities
└── nutcracker_core/
├── __init__.py # Main package
├── analyzer.py # Main static analysis (androguard)
├── apk_tools.py # APK manipulation and installation utilities
├── config.py # config.yaml loading and access
├── device.py # Devices, SDK, Frida and adb utilities
├── downloader.py # Download APKs (Google Play / APKPure / direct URL)
├── decompiler.py # jadx interface
├── deobfuscator.py # FART flow for physical device
├── frida_bypass.py # Frida scripts (bypass, FART)
├── manifest_analyzer.py # AndroidManifest.xml and insecure configuration analysis
├── masvs.py # MASVS v2 compliance scoring (24 controls, numeric pass/fail)
├── osint.py # Subdomains, public leaks, Wayback and optional web searches
├── pdf_reporter.py # PDF report generation (fpdf2)
├── pipeline.py # End-to-end analysis pipeline
├── reporter.py # JSON reports and console output
├── runtime.py # Dynamic analysis orchestration
├── string_extractor.py # APK string extraction
├── vuln_scanner.py # Semgrep + regex + apkleaks + gitleaks
├── plugins/
│ ├── __init__.py # Plugin loader + post-hook registry
│ ├── aireview/ # ai-review plugin: LLM-powered false positive filter
└── detectors/
├── __init__.py # Detectors subpackage export
├── appdome.py # Appdome detector
├── base.py # Common base for detectors
├── certificate_pinning.py # Certificate pinning detector
├── dexguard.py # DexGuard / Arxan detector (requires vendor signature)
├── libraries.py # Anti-root library detector (classes only, no strings)
├── magisk.py # Magisk / SuperSU / KernelSU / Frida detector
├── safetynet.py # SafetyNet / Play Integrity API detector
└── manual_checks.py # Manual checks (with analytics SDK filtering)
This project is licensed under the MIT License.
