npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
When to Use
Use this skill when:
- Conducting security assessment of Android APK or AAB files before production release
- Integrating automated mobile security scanning into CI/CD pipelines
- Performing initial triage of Android applications during penetration testing engagements
- Reviewing third-party Android applications for supply chain security risks
Do not use this skill as a replacement for manual code review or dynamic analysis -- MobSF static analysis catches pattern-based vulnerabilities but misses runtime logic flaws.
Prerequisites
- MobSF v4.x installed via Docker (
docker pull opensecurity/mobile-security-framework-mobsf) or local setup - Target Android APK, AAB, or source code ZIP
- Python 3.10+ for MobSF REST API integration
- JADX decompiler (bundled with MobSF) for Java/Kotlin source recovery
- Network access to MobSF web interface (default: http://localhost:8000)
Workflow
Step 1: Deploy MobSF and Obtain API Key
Launch MobSF using Docker for isolated, reproducible scanning:
docker run -it --rm -p 8000:8000 opensecurity/mobile-security-framework-mobsf:latestRetrieve the REST API key from the MobSF web interface at http://localhost:8000/api_docs or from the startup console output. The API key enables programmatic scanning.
Step 2: Upload APK for Static Analysis
Upload the target APK using the MobSF REST API:
curl -F "file=@target_app.apk" http://localhost:8000/api/v1/upload \
-H "Authorization: <API_KEY>"Response includes the hash identifier used for subsequent API calls. MobSF automatically decompiles the APK using JADX, extracts the AndroidManifest.xml, and indexes all resources.
Step 3: Trigger and Retrieve Static Scan Results
Initiate the static scan and retrieve results:
# Trigger scan
curl -X POST http://localhost:8000/api/v1/scan \
-H "Authorization: <API_KEY>" \
-d "scan_type=apk&file_name=target_app.apk&hash=<FILE_HASH>"
# Retrieve JSON report
curl -X POST http://localhost:8000/api/v1/report_json \
-H "Authorization: <API_KEY>" \
-d "hash=<FILE_HASH>"Step 4: Analyze Critical Findings
MobSF static analysis covers these categories mapped to OWASP Mobile Top 10 2024:
Manifest Analysis (M8 - Security Misconfiguration):
- Exported activities, services, receivers, and content providers without permission guards
android:debuggable="true"left enabledandroid:allowBackup="true"enabling data extraction via ADB- Missing
android:networkSecurityConfigfor certificate pinning
Code Analysis (M1 - Improper Credential Usage):
- Hardcoded API keys, passwords, and tokens in Java/Kotlin source
- Insecure SharedPreferences usage for storing sensitive data
- Weak or broken cryptographic implementations (ECB mode, static IV, hardcoded keys)
Network Security (M5 - Insecure Communication):
- Missing certificate pinning configuration
- Custom TrustManagers that accept all certificates
- Cleartext HTTP traffic allowed without exception domains
Binary Analysis (M7 - Insufficient Binary Protections):
- Missing ProGuard/R8 obfuscation
- Native library vulnerabilities (stack canaries, NX bit, PIE)
- Debugger detection absence
Step 5: Generate and Export Reports
Export findings in multiple formats for stakeholder communication:
# PDF report
curl -X POST http://localhost:8000/api/v1/download_pdf \
-H "Authorization: <API_KEY>" \
-d "hash=<FILE_HASH>" -o report.pdf
# JSON for programmatic processing
curl -X POST http://localhost:8000/api/v1/report_json \
-H "Authorization: <API_KEY>" \
-d "hash=<FILE_HASH>" -o report.jsonStep 6: Integrate into CI/CD Pipeline
Add MobSF scanning as a build gate:
# GitHub Actions example
- name: MobSF Static Analysis
run: |
UPLOAD=$(curl -s -F "file=@app/build/outputs/apk/release/app-release.apk" \
http://mobsf:8000/api/v1/upload -H "Authorization: $MOBSF_API_KEY")
HASH=$(echo $UPLOAD | jq -r '.hash')
curl -s -X POST http://mobsf:8000/api/v1/scan \
-H "Authorization: $MOBSF_API_KEY" \
-d "scan_type=apk&file_name=app-release.apk&hash=$HASH"
SCORE=$(curl -s -X POST http://mobsf:8000/api/v1/scorecard \
-H "Authorization: $MOBSF_API_KEY" -d "hash=$HASH" | jq '.security_score')
if [ "$SCORE" -lt 60 ]; then exit 1; fiKey Concepts
| Term | Definition |
|---|---|
| Static Analysis | Examination of application code and resources without executing the program; catches structural and pattern-based vulnerabilities |
| APK Decompilation | Process of recovering Java/Kotlin source from compiled Dalvik bytecode using tools like JADX or apktool |
| AndroidManifest.xml | Configuration file declaring app components, permissions, and security attributes; primary target for manifest analysis |
| Certificate Pinning | Technique binding an app to specific server certificates to prevent man-in-the-middle attacks via rogue CAs |
| ProGuard/R8 | Code obfuscation and shrinking tools that make reverse engineering more difficult by renaming classes and removing unused code |
Tools & Systems
- MobSF: Automated mobile security analysis framework supporting static and dynamic analysis of Android/iOS apps
- JADX: Dex-to-Java decompiler for recovering readable source code from Android APK files
- apktool: Tool for reverse engineering Android APK files, decoding resources to near-original form
- Android Lint: Google's static analysis tool for Android-specific code quality and security issues
- Semgrep: Pattern-based static analysis engine with mobile-specific rule packs for custom vulnerability detection
Common Pitfalls
- Ignoring false positives: MobSF flags patterns like
passwordin variable names even when not storing actual credentials. Triage all HIGH findings manually before reporting. - Missing obfuscated code: Static analysis accuracy drops significantly against obfuscated apps. Supplement with dynamic analysis for apps using DexGuard or custom packers.
- Outdated MobSF rules: Security rules evolve with Android API levels. Ensure MobSF is updated to match the target app's
targetSdkVersion. - Skipping native code analysis: MobSF analyzes Java/Kotlin but has limited coverage of native C/C++ libraries. Use
checksecand manual review for.sofiles.
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md5.5 KB
API Reference: MobSF Android Static Analysis
Libraries Used
| Library | Purpose |
|---|---|
requests |
HTTP client for MobSF REST API v1 |
json |
Parse scan reports and finding data |
os |
Read MOBSF_URL and MOBSF_API_KEY environment variables |
Installation
pip install requests
# MobSF server (Docker)
docker pull opensecurity/mobile-security-framework-mobsf
docker run -it -p 8000:8000 opensecurity/mobile-security-framework-mobsfAuthentication
MobSF uses API key authentication. The default key is shown on the MobSF dashboard:
import requests
import os
MOBSF_URL = os.environ.get("MOBSF_URL", "http://localhost:8000")
MOBSF_KEY = os.environ["MOBSF_API_KEY"]
headers = {"Authorization": MOBSF_KEY}REST API v1 Endpoints
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/upload |
Upload APK, IPA, ZIP, or APPX for analysis |
| POST | /api/v1/scan |
Trigger static analysis on uploaded file |
| GET | /api/v1/report_json |
Get full JSON analysis report |
| POST | /api/v1/download_pdf |
Download PDF report |
| GET | /api/v1/scans |
List recent scans |
| POST | /api/v1/delete_scan |
Delete a scan and its data |
| POST | /api/v1/search |
Search scans by hash or filename |
| POST | /api/v1/compare |
Compare two app scans |
| GET | /api/v1/scorecard |
Get app security scorecard |
| GET | /api/v1/scan_logs |
View live scan logs |
Core Operations
Upload an APK
def upload_apk(file_path):
with open(file_path, "rb") as f:
resp = requests.post(
f"{MOBSF_URL}/api/v1/upload",
files={"file": (os.path.basename(file_path), f, "application/octet-stream")},
headers=headers,
timeout=120,
)
resp.raise_for_status()
result = resp.json()
return result["hash"], result["scan_type"], result["file_name"]
# hash: SHA-256 of the uploaded file
# scan_type: "apk", "ipa", "zip", "appx"Trigger Static Analysis
def start_scan(file_hash, scan_type, file_name):
resp = requests.post(
f"{MOBSF_URL}/api/v1/scan",
data={
"hash": file_hash,
"scan_type": scan_type,
"file_name": file_name,
},
headers=headers,
timeout=600, # Scans can take several minutes
)
resp.raise_for_status()
return resp.json()Retrieve JSON Report
def get_report(file_hash):
resp = requests.post(
f"{MOBSF_URL}/api/v1/report_json",
data={"hash": file_hash},
headers=headers,
timeout=60,
)
resp.raise_for_status()
return resp.json()Extract Key Findings
def extract_findings(report):
findings = {
"security_score": report.get("security_score", "N/A"),
"app_name": report.get("app_name"),
"package_name": report.get("package_name"),
"target_sdk": report.get("target_sdk"),
"min_sdk": report.get("min_sdk"),
"permissions": {
"dangerous": [],
"normal": [],
},
"manifest_issues": [],
"code_issues": [],
"binary_issues": [],
}
# Dangerous permissions
for perm, details in report.get("permissions", {}).items():
status = details.get("status", "normal")
if status == "dangerous":
findings["permissions"]["dangerous"].append(perm)
else:
findings["permissions"]["normal"].append(perm)
# Manifest analysis
for issue in report.get("manifest_analysis", []):
if issue.get("severity") in ("high", "warning"):
findings["manifest_issues"].append({
"title": issue["title"],
"severity": issue["severity"],
"description": issue["description"],
})
# Code analysis
for issue_key, issue_data in report.get("code_analysis", {}).items():
findings["code_issues"].append({
"rule": issue_key,
"severity": issue_data.get("level"),
"description": issue_data.get("description"),
"files": issue_data.get("path", [])[:5],
})
return findingsDownload PDF Report
def download_pdf(file_hash, output_path):
resp = requests.post(
f"{MOBSF_URL}/api/v1/download_pdf",
data={"hash": file_hash},
headers=headers,
timeout=120,
)
resp.raise_for_status()
with open(output_path, "wb") as f:
f.write(resp.content)Compare Two Applications
resp = requests.post(
f"{MOBSF_URL}/api/v1/compare",
data={"hash1": hash_v1, "hash2": hash_v2},
headers=headers,
timeout=120,
)
comparison = resp.json()
# Shows permission changes, new vulnerabilities, code changesOutput Format
{
"file_name": "app-debug.apk",
"app_name": "TestApp",
"package_name": "com.example.testapp",
"security_score": 42,
"target_sdk": "33",
"min_sdk": "24",
"permissions": {
"android.permission.INTERNET": {"status": "normal", "description": "..."},
"android.permission.READ_CONTACTS": {"status": "dangerous", "description": "..."}
},
"manifest_analysis": [
{"title": "Application is debuggable", "severity": "high", "description": "..."}
],
"code_analysis": {
"android_insecure_random": {
"level": "high",
"description": "Insecure Random Number Generator",
"path": ["com/example/CryptoUtils.java"]
}
},
"binary_analysis": [
{"title": "NX bit not set", "severity": "high"}
]
}standards.md2.9 KB
Standards Reference: Android Static Analysis with MobSF
OWASP Mobile Top 10 2024 Mapping
| OWASP ID | Risk | MobSF Coverage |
|---|---|---|
| M1 | Improper Credential Usage | Detects hardcoded API keys, passwords, tokens in source code and resources |
| M2 | Inadequate Supply Chain Security | Identifies third-party library versions with known CVEs |
| M5 | Insecure Communication | Flags missing certificate pinning, cleartext traffic, weak TLS |
| M7 | Insufficient Binary Protections | Checks ProGuard/R8 obfuscation, native binary protections |
| M8 | Security Misconfiguration | Analyzes AndroidManifest.xml for exported components, debug flags, backup settings |
| M9 | Insecure Data Storage | Detects SharedPreferences misuse, world-readable files, SQLite without encryption |
| M10 | Insufficient Cryptography | Identifies ECB mode, static IV, hardcoded encryption keys, weak algorithms |
OWASP MASVS v2.0 Control Mapping
| MASVS Category | Controls | MobSF Static Checks |
|---|---|---|
| MASVS-STORAGE | Sensitive data storage | SharedPreferences analysis, file permission checks, database encryption |
| MASVS-CRYPTO | Cryptographic implementations | Algorithm strength, key management, IV randomness |
| MASVS-AUTH | Authentication mechanisms | Credential storage, biometric implementation review |
| MASVS-NETWORK | Network security | Network security config, certificate pinning, cleartext detection |
| MASVS-PLATFORM | Platform interaction | Intent filter analysis, content provider security, WebView configuration |
| MASVS-CODE | Code quality | Code obfuscation, debug symbols, error handling |
| MASVS-RESILIENCE | Reverse engineering resistance | Root detection, tamper detection, debugger detection |
NIST SP 800-163 Rev 1: Vetting the Security of Mobile Applications
- Section 4.1: Static analysis as mandatory step in mobile app vetting process
- Section 4.2: Automated tools should check for known vulnerability patterns
- Section 5: Integration of vetting into enterprise mobile device management
CWE Mappings for Common MobSF Findings
| CWE ID | Title | MobSF Finding Category |
|---|---|---|
| CWE-312 | Cleartext Storage of Sensitive Information | Hardcoded credentials in source |
| CWE-319 | Cleartext Transmission of Sensitive Information | Missing HTTPS enforcement |
| CWE-327 | Use of Broken Cryptographic Algorithm | Weak crypto detection |
| CWE-330 | Use of Insufficiently Random Values | Static IV, predictable random |
| CWE-532 | Insertion of Sensitive Information into Log File | Logging sensitive data |
| CWE-749 | Exposed Dangerous Method or Function | Exported components without guards |
| CWE-919 | Weaknesses in Mobile Applications | General mobile-specific checks |
| CWE-925 | Improper Verification of Intent by Broadcast Receiver | Unprotected broadcast receivers |
workflows.md3.7 KB
Workflows: Android Static Analysis with MobSF
Workflow 1: Standalone APK Assessment
[Obtain APK] --> [Deploy MobSF Docker] --> [Upload via API] --> [Run Static Scan]
| |
v v
[Verify APK integrity] [Review Manifest Analysis]
[Check signing certificate] [Review Code Analysis]
[Review Binary Analysis]
[Review Network Analysis]
|
v
[Triage HIGH/CRITICAL findings]
[Validate false positives]
[Generate PDF report]Workflow 2: CI/CD Pipeline Integration
[Developer pushes code] --> [Build APK] --> [Upload to MobSF] --> [Static Scan]
|
+-----------+-----------+
| |
[Score >= 60] [Score < 60]
| |
[Pass gate] [Fail build]
[Archive report] [Notify developer]
[Continue pipeline] [Block deployment]Workflow 3: Third-Party App Vetting
[Receive third-party APK] --> [MobSF Static Scan] --> [Automated scoring]
|
v
[Manual review of:]
- Excessive permissions
- Data exfiltration indicators
- Known malware signatures
- C2 communication patterns
|
v
[Risk assessment report]
[Approve/Reject for enterprise use]Workflow 4: Comparative Analysis Across Versions
[APK v1.0] --> [MobSF Scan] --> [Baseline report]
|
[APK v2.0] --> [MobSF Scan] --> [Compare findings] --> [New vulnerabilities introduced?]
| |
v [Yes: Block release]
[Regression report] [No: Approve release]Decision Matrix: When to Escalate
| Finding Severity | MobSF Category | Action |
|---|---|---|
| CRITICAL | Hardcoded production API keys | Immediate key rotation, block release |
| HIGH | Exported activity with sensitive data | Manual verification, fix before release |
| MEDIUM | Missing certificate pinning | Add to sprint backlog, risk acceptance if internal app |
| LOW | Debug logging of non-sensitive data | Track in issue tracker, fix in next release |
| INFO | Missing ProGuard rules | Recommend but do not block |
Scripts 2
agent.py9.0 KB
#!/usr/bin/env python3
"""MobSF Android static analysis agent.
Automates APK upload, static analysis scanning, and report retrieval
via the MobSF REST API. Extracts security findings including manifest
analysis, code analysis, binary analysis, and certificate checks.
"""
import argparse
import json
import os
import sys
import time
from datetime import datetime, timezone
try:
import requests
except ImportError:
print("[!] 'requests' library required: pip install requests", file=sys.stderr)
sys.exit(1)
def get_mobsf_config():
"""Return MobSF server URL and API key from env or defaults."""
server = os.environ.get("MOBSF_URL", "http://localhost:8000")
api_key = os.environ.get("MOBSF_API_KEY", "")
if not api_key:
print("[!] Set MOBSF_API_KEY environment variable", file=sys.stderr)
sys.exit(1)
return server.rstrip("/"), api_key
def upload_apk(server, api_key, apk_path):
"""Upload an APK file to MobSF and return the file hash."""
url = f"{server}/api/v1/upload"
headers = {"Authorization": api_key}
if not os.path.isfile(apk_path):
print(f"[!] File not found: {apk_path}", file=sys.stderr)
sys.exit(1)
print(f"[*] Uploading {os.path.basename(apk_path)} to MobSF...")
with open(apk_path, "rb") as f:
resp = requests.post(
url,
files={"file": (os.path.basename(apk_path), f, "application/octet-stream")},
headers=headers,
timeout=300,
)
resp.raise_for_status()
data = resp.json()
file_hash = data.get("hash", "")
scan_type = data.get("scan_type", "apk")
file_name = data.get("file_name", os.path.basename(apk_path))
print(f"[+] Uploaded: {file_name} (hash: {file_hash}, type: {scan_type})")
return file_hash, scan_type, file_name
def start_scan(server, api_key, file_hash, scan_type, file_name):
"""Trigger static analysis scan on the uploaded APK."""
url = f"{server}/api/v1/scan"
headers = {"Authorization": api_key}
print(f"[*] Starting static analysis scan for {file_name}...")
resp = requests.post(
url,
data={"hash": file_hash, "scan_type": scan_type, "file_name": file_name},
headers=headers,
timeout=600,
)
resp.raise_for_status()
print("[+] Scan complete")
return resp.json()
def get_report(server, api_key, file_hash):
"""Retrieve the JSON report for a scanned APK."""
url = f"{server}/api/v1/report_json"
headers = {"Authorization": api_key}
resp = requests.post(
url,
data={"hash": file_hash},
headers=headers,
timeout=120,
)
resp.raise_for_status()
return resp.json()
def get_scorecard(server, api_key, file_hash):
"""Retrieve the security scorecard for a scanned APK."""
url = f"{server}/api/v1/scorecard"
headers = {"Authorization": api_key}
resp = requests.post(
url,
data={"hash": file_hash},
headers=headers,
timeout=60,
)
resp.raise_for_status()
return resp.json()
def extract_findings(report):
"""Extract structured security findings from MobSF report JSON."""
findings = []
# Manifest analysis
for item in report.get("manifest_analysis", []):
findings.append({
"category": "manifest",
"title": item.get("title", "Unknown"),
"severity": item.get("severity", "info").upper(),
"description": item.get("description", ""),
})
# Code analysis
code_analysis = report.get("code_analysis", {})
if isinstance(code_analysis, dict):
for key, value in code_analysis.items():
if isinstance(value, dict):
findings.append({
"category": "code",
"title": value.get("metadata", {}).get("description", key),
"severity": value.get("metadata", {}).get("severity", "info").upper(),
"description": value.get("metadata", {}).get("cwe", ""),
"files": list(value.get("files", {}).keys())[:5],
})
# Binary analysis
for item in report.get("binary_analysis", []):
findings.append({
"category": "binary",
"title": item.get("name", "Unknown"),
"severity": item.get("severity", "info").upper(),
"description": item.get("description", ""),
})
# Certificate analysis
cert_info = report.get("certificate_analysis", {})
if isinstance(cert_info, dict):
cert_findings = cert_info.get("certificate_findings", [])
for item in cert_findings:
findings.append({
"category": "certificate",
"title": item.get("title", "Certificate finding"),
"severity": item.get("severity", "info").upper(),
"description": item.get("description", ""),
})
# Permissions
permissions = report.get("permissions", {})
dangerous_perms = []
if isinstance(permissions, dict):
for perm, details in permissions.items():
if isinstance(details, dict) and details.get("status") == "dangerous":
dangerous_perms.append(perm)
if dangerous_perms:
findings.append({
"category": "permissions",
"title": f"{len(dangerous_perms)} dangerous permissions declared",
"severity": "WARNING",
"description": ", ".join(dangerous_perms[:10]),
})
return findings
def format_summary(report, findings):
"""Print a human-readable summary of the analysis."""
app_name = report.get("app_name", "Unknown")
package = report.get("package_name", "Unknown")
version = report.get("version_name", "Unknown")
sdk_min = report.get("min_sdk", "?")
sdk_target = report.get("target_sdk", "?")
security_score = report.get("security_score", "N/A")
print(f"\n{'='*60}")
print(f" MobSF Static Analysis Report")
print(f"{'='*60}")
print(f" App Name : {app_name}")
print(f" Package : {package}")
print(f" Version : {version}")
print(f" Min SDK : {sdk_min} | Target SDK: {sdk_target}")
print(f" Security : {security_score}/100")
print(f"{'='*60}")
severity_counts = {}
for f in findings:
sev = f.get("severity", "INFO")
severity_counts[sev] = severity_counts.get(sev, 0) + 1
print(f"\n Findings Summary:")
for sev in ["CRITICAL", "HIGH", "WARNING", "MEDIUM", "INFO", "LOW"]:
if sev in severity_counts:
print(f" {sev:10s}: {severity_counts[sev]}")
print(f"\n Top Findings:")
high_findings = [f for f in findings if f.get("severity") in ("CRITICAL", "HIGH", "WARNING")]
for f in high_findings[:10]:
print(f" [{f['severity']:8s}] [{f['category']:12s}] {f['title']}")
return severity_counts
def main():
parser = argparse.ArgumentParser(
description="MobSF Android static analysis agent - upload, scan, and report"
)
parser.add_argument("--apk", required=True, help="Path to the APK file to analyze")
parser.add_argument("--output", "-o", help="Output JSON report path")
parser.add_argument("--server", help="MobSF server URL (or set MOBSF_URL env var)")
parser.add_argument("--api-key", help="MobSF API key (or set MOBSF_API_KEY env var)")
parser.add_argument("--hash", help="Skip upload; use existing hash to retrieve report")
parser.add_argument("--verbose", "-v", action="store_true", help="Verbose output")
args = parser.parse_args()
if args.server:
os.environ["MOBSF_URL"] = args.server
if args.api_key:
os.environ["MOBSF_API_KEY"] = args.api_key
server, api_key = get_mobsf_config()
if args.hash:
file_hash = args.hash
print(f"[*] Using existing hash: {file_hash}")
else:
file_hash, scan_type, file_name = upload_apk(server, api_key, args.apk)
start_scan(server, api_key, file_hash, scan_type, file_name)
report = get_report(server, api_key, file_hash)
findings = extract_findings(report)
severity_counts = format_summary(report, findings)
output_report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "MobSF",
"apk": args.apk,
"app_name": report.get("app_name", ""),
"package_name": report.get("package_name", ""),
"version": report.get("version_name", ""),
"security_score": report.get("security_score", 0),
"severity_counts": severity_counts,
"findings": findings,
"risk_level": (
"CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
else "HIGH" if severity_counts.get("HIGH", 0) > 0
else "MEDIUM" if severity_counts.get("WARNING", 0) > 0
else "LOW"
),
}
if args.output:
with open(args.output, "w") as f:
json.dump(output_report, f, indent=2)
print(f"\n[+] Report saved to {args.output}")
elif args.verbose:
print(json.dumps(output_report, indent=2))
print(f"\n[*] Risk Level: {output_report['risk_level']}")
if __name__ == "__main__":
main()
process.py8.1 KB
#!/usr/bin/env python3
"""
MobSF Automated Static Analysis Pipeline
Automates APK upload, scanning, and report generation via MobSF REST API.
Designed for CI/CD integration with configurable security score thresholds.
Usage:
python process.py --apk target.apk --api-key <KEY> [--threshold 60] [--output report.json]
"""
import argparse
import json
import sys
import time
import hashlib
from pathlib import Path
from urllib.parse import urljoin
try:
import requests
except ImportError:
print("ERROR: 'requests' package required. Install with: pip install requests")
sys.exit(1)
class MobSFScanner:
"""Interfaces with MobSF REST API for automated static analysis."""
def __init__(self, base_url: str, api_key: str):
self.base_url = base_url.rstrip("/")
self.api_key = api_key
self.session = requests.Session()
self.session.headers.update({"Authorization": api_key})
def upload_apk(self, apk_path: str) -> dict:
"""Upload APK file to MobSF for analysis."""
endpoint = f"{self.base_url}/api/v1/upload"
apk_file = Path(apk_path)
if not apk_file.exists():
raise FileNotFoundError(f"APK not found: {apk_path}")
if not apk_file.suffix.lower() in (".apk", ".aab", ".zip", ".ipa"):
raise ValueError(f"Unsupported file type: {apk_file.suffix}")
with open(apk_path, "rb") as f:
files = {"file": (apk_file.name, f, "application/octet-stream")}
response = self.session.post(endpoint, files=files)
response.raise_for_status()
result = response.json()
print(f"[+] Uploaded: {apk_file.name} (hash: {result.get('hash', 'N/A')})")
return result
def run_static_scan(self, file_hash: str, file_name: str, scan_type: str = "apk") -> dict:
"""Trigger static analysis scan."""
endpoint = f"{self.base_url}/api/v1/scan"
data = {
"scan_type": scan_type,
"file_name": file_name,
"hash": file_hash,
}
response = self.session.post(endpoint, data=data)
response.raise_for_status()
print(f"[+] Static scan completed for: {file_name}")
return response.json()
def get_report_json(self, file_hash: str) -> dict:
"""Retrieve JSON scan report."""
endpoint = f"{self.base_url}/api/v1/report_json"
data = {"hash": file_hash}
response = self.session.post(endpoint, data=data)
response.raise_for_status()
return response.json()
def get_scorecard(self, file_hash: str) -> dict:
"""Retrieve security scorecard."""
endpoint = f"{self.base_url}/api/v1/scorecard"
data = {"hash": file_hash}
response = self.session.post(endpoint, data=data)
response.raise_for_status()
return response.json()
def download_pdf_report(self, file_hash: str, output_path: str) -> str:
"""Download PDF report."""
endpoint = f"{self.base_url}/api/v1/download_pdf"
data = {"hash": file_hash}
response = self.session.post(endpoint, data=data)
response.raise_for_status()
with open(output_path, "wb") as f:
f.write(response.content)
print(f"[+] PDF report saved: {output_path}")
return output_path
def extract_critical_findings(report: dict) -> dict:
"""Extract and categorize critical findings from MobSF report."""
findings = {
"manifest_issues": [],
"code_issues": [],
"network_issues": [],
"binary_issues": [],
"crypto_issues": [],
}
# Manifest analysis
manifest = report.get("manifest_analysis", [])
if isinstance(manifest, list):
for item in manifest:
severity = item.get("stat", item.get("severity", "info"))
if severity.lower() in ("high", "warning"):
findings["manifest_issues"].append({
"title": item.get("title", "Unknown"),
"description": item.get("desc", item.get("description", "")),
"severity": severity,
})
# Code analysis
code_analysis = report.get("code_analysis", {})
if isinstance(code_analysis, dict):
for category, items in code_analysis.items():
if isinstance(items, dict):
metadata = items.get("metadata", {})
severity = metadata.get("severity", "info")
if severity.lower() in ("high", "warning"):
findings["code_issues"].append({
"category": category,
"description": metadata.get("description", ""),
"severity": severity,
"files": list(items.get("files", {}).keys())[:5],
})
# Network security
network = report.get("network_security", [])
if isinstance(network, list):
for item in network:
severity = item.get("severity", "info")
if severity.lower() in ("high", "warning"):
findings["network_issues"].append({
"title": item.get("scope", "Unknown"),
"description": item.get("description", ""),
"severity": severity,
})
return findings
def evaluate_security_score(scorecard: dict, threshold: int) -> bool:
"""Evaluate whether the app meets the minimum security threshold."""
score = scorecard.get("security_score", 0)
print(f"\n[*] Security Score: {score}/100 (threshold: {threshold})")
if score >= threshold:
print("[+] PASS: Application meets minimum security threshold")
return True
else:
print("[-] FAIL: Application does not meet minimum security threshold")
return False
def main():
parser = argparse.ArgumentParser(
description="MobSF Automated Static Analysis Pipeline"
)
parser.add_argument("--apk", required=True, help="Path to APK/AAB file")
parser.add_argument("--api-key", required=True, help="MobSF REST API key")
parser.add_argument(
"--url", default="http://localhost:8000", help="MobSF server URL"
)
parser.add_argument(
"--threshold", type=int, default=60, help="Minimum security score (0-100)"
)
parser.add_argument("--output", default="mobsf_report.json", help="Output report path")
parser.add_argument("--pdf", help="Optional PDF report output path")
parser.add_argument(
"--ci-mode", action="store_true", help="Exit with non-zero code on failure"
)
args = parser.parse_args()
scanner = MobSFScanner(args.url, args.api_key)
# Upload
upload_result = scanner.upload_apk(args.apk)
file_hash = upload_result["hash"]
file_name = Path(args.apk).name
scan_type = "apk" if file_name.endswith(".apk") else "aab"
# Scan
scanner.run_static_scan(file_hash, file_name, scan_type)
# Get reports
report = scanner.get_report_json(file_hash)
scorecard = scanner.get_scorecard(file_hash)
# Extract findings
critical_findings = extract_critical_findings(report)
# Build summary
summary = {
"file": file_name,
"hash": file_hash,
"security_score": scorecard.get("security_score", 0),
"threshold": args.threshold,
"pass": scorecard.get("security_score", 0) >= args.threshold,
"findings_summary": {
"manifest_issues": len(critical_findings["manifest_issues"]),
"code_issues": len(critical_findings["code_issues"]),
"network_issues": len(critical_findings["network_issues"]),
"binary_issues": len(critical_findings["binary_issues"]),
"crypto_issues": len(critical_findings["crypto_issues"]),
},
"critical_findings": critical_findings,
}
# Save JSON report
with open(args.output, "w") as f:
json.dump(summary, f, indent=2)
print(f"[+] Report saved: {args.output}")
# Optional PDF
if args.pdf:
scanner.download_pdf_report(file_hash, args.pdf)
# Evaluate
passed = evaluate_security_score(scorecard, args.threshold)
if args.ci_mode and not passed:
sys.exit(1)
if __name__ == "__main__":
main()