-
-
Notifications
You must be signed in to change notification settings - Fork 1
feat: Add Android APK analyzer for security scanning #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # Mobile App Analyzer | ||
|
|
||
| This tool analyzes Android APK files for potential security issues, including high-risk permissions, hardcoded secrets, and indicators of phishing or scams. | ||
|
|
||
| ## Features | ||
|
|
||
| * **APK Decompilation:** Uses `apktool` to decompile the APK for source code analysis. | ||
| * **Permission Analysis:** Extracts and identifies high-risk permissions from the `AndroidManifest.xml`. | ||
| * **Secret Scanning:** Scans the decompiled code for hardcoded secrets like API keys and private keys. | ||
| * **Scam Detection:** Analyzes text content in the app's files for suspicious URLs, phishing keywords, and other scam indicators. | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| * Python 3.x | ||
| * `apktool`: Must be installed and available in your system's PATH. | ||
| * `aapt`: Must be installed and available in your system's PATH. | ||
|
|
||
| You can typically install these tools on a Debian-based system (like Ubuntu) with: | ||
| `sudo apt-get install apktool aapt` | ||
|
|
||
| ## How to Run | ||
|
|
||
| 1. Navigate to the root directory of this repository. | ||
| 2. Run the analyzer from your terminal, passing the path to the APK file you want to analyze: | ||
|
|
||
| ```bash | ||
| python mobile_analyzer_main.py /path/to/your/app.apk | ||
| ``` | ||
|
|
||
| ### Options | ||
|
|
||
| * `--keep-files`: Use this flag to prevent the script from deleting the `decompiled_apk` directory after the analysis is complete. This is useful for debugging or manual inspection. | ||
|
|
||
| ```bash | ||
| python mobile_analyzer_main.py /path/to/your/app.apk --keep-files | ||
| ``` | ||
|
|
||
| ## How to Interpret the Output | ||
|
|
||
| The tool will print a report to the console with the following sections: | ||
|
|
||
| * **High-Risk Permissions Found:** A list of permissions that could potentially be abused to access sensitive user data or control the device. | ||
| * **Potential Secrets Found:** A list of files that may contain hardcoded sensitive data. Review these files carefully. | ||
| * **Scam Indicators Found:** A list of files containing suspicious URLs, keywords, or other patterns that might indicate phishing or other scams. | ||
|
|
||
| ## Limitations | ||
|
|
||
| * **Android Only:** This tool currently only supports Android APK files. iOS app analysis is not supported. | ||
| * **Static Analysis Only:** The analysis is purely static (it only examines the code and files). It does not run the app or monitor its behavior at runtime. | ||
| * **Not Foolproof:** This tool uses patterns and heuristics to find potential issues. It is not guaranteed to find all vulnerabilities, and it may produce false positives. Always use your judgment and, if possible, combine this with other security testing methods. | ||
|
|
||
| ## Disclaimer | ||
|
|
||
| This tool is for educational and research purposes only. The user is responsible for any use of this tool. Do not use it to analyze apps for which you do not have permission. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,192 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import subprocess | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| # HACK: This is not ideal, but the project is structured as a collection of | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # top-level scripts and not as a single installable package. This allows us | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # to import modules from sibling directories. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| from sensitive_data_scanner.scanner import scan_directory as scan_for_secrets | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| from social_media_analyzer.scam_detector import analyze_text_for_scams | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Based on Android documentation and security best practices. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # These permissions grant access to sensitive user data or system control. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| HIGH_RISK_PERMISSIONS = [ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.READ_CALENDAR", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.WRITE_CALENDAR", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.CAMERA", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.READ_CONTACTS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.WRITE_CONTACTS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.GET_ACCOUNTS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.ACCESS_FINE_LOCATION", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.ACCESS_COARSE_LOCATION", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.RECORD_AUDIO", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.READ_PHONE_STATE", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.READ_PHONE_NUMBERS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.CALL_PHONE", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.ANSWER_PHONE_CALLS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.ADD_VOICEMAIL", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.USE_SIP", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.PROCESS_OUTGOING_CALLS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.READ_CALL_LOG", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.WRITE_CALL_LOG", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "com.android.voicemail.permission.ADD_VOICEMAIL", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.BODY_SENSORS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.SEND_SMS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.RECEIVE_SMS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.READ_SMS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.RECEIVE_WAP_PUSH", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.RECEIVE_MMS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.READ_EXTERNAL_STORAGE", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.WRITE_EXTERNAL_STORAGE", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.SYSTEM_ALERT_WINDOW", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.WRITE_SETTINGS", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.REQUEST_INSTALL_PACKAGES", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| "android.permission.ACCESS_BACKGROUND_LOCATION", | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| ] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| def decompile_apk(apk_path, output_dir): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Decompiles an APK file using apktool. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| apk_path (str): The path to the APK file. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| output_dir (str): The directory to store the decompiled code. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| bool: True if decompilation was successful, False otherwise. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not os.path.exists(apk_path): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Error: APK file not found at {apk_path}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Decompiling {apk_path} to {output_dir}...") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Using -f to force overwrite the output directory if it exists | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| command = ["apktool", "d", "-f", apk_path, "-o", output_dir] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = subprocess.run(command, capture_output=True, text=True, check=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("Decompilation successful.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| except subprocess.CalledProcessError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("Error during decompilation:") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(e.stderr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| except FileNotFoundError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("Error: 'apktool' not found. Make sure it is installed and in your PATH.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+70
to
+79
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): No timeout specified for subprocess calls. Subprocesses may hang; add a timeout to subprocess.run for reliability.
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_permissions(apk_path): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Extracts permissions from an APK's AndroidManifest.xml using aapt. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| apk_path (str): The path to the APK file. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| list: A list of permissions found in the APK. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not os.path.exists(apk_path): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Error: APK file not found at {apk_path}") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Extracting permissions from {apk_path}...") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| command = ["aapt", "dump", "permissions", apk_path] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Regex for permission extraction may miss edge cases. The current regex may fail if the aapt output format changes. Please update the regex to handle variations in whitespace or formatting. Suggested implementation: print(f"Extracting permissions from {apk_path}...")
try:
command = ["aapt", "dump", "permissions", apk_path]
result = subprocess.run(command, capture_output=True, text=True, check=True)
# Updated regex to handle variations in whitespace and formatting
import re
permission_pattern = re.compile(r'^\s*permission(?:\s*:\s*|\s+)([\w\.\-]+)', re.MULTILINE)
permissions = permission_pattern.findall(result.stdout)If the function is expected to return the list of permissions, ensure you add |
||||||||||||||||||||||||||||||||||||||||||||||||||||
| result = subprocess.run(command, capture_output=True, text=True, check=True) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'. Source: opengrep |
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Regex to find package permissions | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| permissions = re.findall(r"uses-permission: name='([^']*)'", result.stdout) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Found {len(permissions)} permissions.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return permissions | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| except subprocess.CalledProcessError as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("Error extracting permissions:") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(e.stderr) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| except FileNotFoundError: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("Error: 'aapt' not found. Make sure it is installed and in your PATH.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| def check_high_risk_permissions(permissions): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Checks a list of permissions against the high-risk permissions list. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| permissions (list): The list of permissions from an APK. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| list: A list of high-risk permissions found in the APK. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| found_high_risk = [] | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for perm in permissions: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if perm in HIGH_RISK_PERMISSIONS: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| found_high_risk.append(perm) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+122
to
+126
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (code-quality): Convert for loop into list comprehension (
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Found {len(found_high_risk)} high-risk permissions.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return found_high_risk | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| def scan_for_sensitive_data(decompiled_dir): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Scans a directory for sensitive data using the sensitive_data_scanner module. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| decompiled_dir (str): The path to the directory of decompiled code. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| dict: A dictionary of findings. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("\nScanning for sensitive data...") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| findings = scan_for_secrets(decompiled_dir) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if findings: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Found sensitive data in {len(findings)} files.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("No sensitive data found.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| return findings | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| def scan_for_scam_indicators(decompiled_dir): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Scans files in a directory for scam indicators using the scam_detector module. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Args: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| decompiled_dir (str): The path to the directory of decompiled code. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| Returns: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| dict: A dictionary of findings, where keys are file paths. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("\nScanning for scam indicators (phishing, suspicious URLs)...") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| all_findings = {} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Extensions of text-like files to scan. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Smali, xml, and yml are common in decompiled APKs. | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| scan_extensions = {'.smali', '.xml', '.yml', '.yaml', '.json', '.html', '.js', '.txt'} | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| for root, _, files in os.walk(decompiled_dir): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| for filename in files: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Check if the file has one of the scannable extensions | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if not any(filename.endswith(ext) for ext in scan_extensions): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| filepath = os.path.join(root, filename) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| with open(filepath, 'r', encoding='utf-8', errors='ignore') as f: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| content = f.read() | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Skip very large files to avoid performance issues | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if len(content) > 1000000: # 1MB limit | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| analysis_result = analyze_text_for_scams(content) | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| if analysis_result.get("indicators_found"): | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| all_findings[filepath] = analysis_result | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| except Exception: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Ignore files that can't be read for any reason | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| if all_findings: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print(f"Found scam indicators in {len(all_findings)} files.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| print("No scam indicators found.") | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
| return all_findings | ||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,77 @@ | ||||||||||||||||||||||||||||||||
| import argparse | ||||||||||||||||||||||||||||||||
| import os | ||||||||||||||||||||||||||||||||
| import shutil | ||||||||||||||||||||||||||||||||
| import sys | ||||||||||||||||||||||||||||||||
| from mobile_analyzer import analyzer | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def main(): | ||||||||||||||||||||||||||||||||
| parser = argparse.ArgumentParser(description="Analyze an Android APK for security vulnerabilities.") | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (code-quality): Use named expression to simplify assignment and conditional [×4] ( |
||||||||||||||||||||||||||||||||
| parser.add_argument("apk_path", help="The path to the APK file to analyze.") | ||||||||||||||||||||||||||||||||
| parser.add_argument("--keep-files", action="store_true", help="Keep the decompiled files after analysis.") | ||||||||||||||||||||||||||||||||
| args = parser.parse_args() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| apk_path = args.apk_path | ||||||||||||||||||||||||||||||||
| if not os.path.exists(apk_path): | ||||||||||||||||||||||||||||||||
| print(f"Error: APK file not found at {apk_path}") | ||||||||||||||||||||||||||||||||
| sys.exit(1) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Create a directory for the decompiled code | ||||||||||||||||||||||||||||||||
| output_dir = "decompiled_apk" | ||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion: Hardcoded output directory may cause conflicts. Allow users to specify the output directory or generate a unique temporary directory to avoid conflicts when running multiple analyses or if the directory already exists. Suggested implementation: import tempfile
parser.add_argument("apk_path", help="The path to the APK file to analyze.")
parser.add_argument("--keep-files", action="store_true", help="Keep the decompiled files after analysis.")
parser.add_argument("--output-dir", type=str, default=None, help="Directory to store decompiled files. If not specified, a temporary directory will be used.")
args = parser.parse_args()
apk_path = args.apk_path
if not os.path.exists(apk_path):
print(f"Error: APK file not found at {apk_path}")
sys.exit(1)
# Create a directory for the decompiled code
if args.output_dir:
output_dir = args.output_dir
if os.path.exists(output_dir):
shutil.rmtree(output_dir)
os.makedirs(output_dir)
else:
output_dir = tempfile.mkdtemp(prefix="decompiled_apk_") print(f"--- Starting analysis for {os.path.basename(apk_path)} ---")
print(f"Decompiled code will be stored in: {output_dir}")
# 1. Decompile the APK
if not analyzer.decompile_apk(apk_path, output_dir):
print("Failed to decompile APK. Aborting analysis.")
sys.exit(1)If there is code later in the file that deletes the output directory (e.g., when --keep-files is not set), ensure it works with both user-specified and temporary directories. You may want to print a message to the user if a temporary directory is used and files are deleted. |
||||||||||||||||||||||||||||||||
| if os.path.exists(output_dir): | ||||||||||||||||||||||||||||||||
| shutil.rmtree(output_dir) | ||||||||||||||||||||||||||||||||
| os.makedirs(output_dir) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): Potential race condition when removing and creating output directory. Another process could create the directory between its removal and recreation, causing issues. Consider using tempfile.TemporaryDirectory or an atomic approach. Suggested implementation: import tempfile
# Create a temporary directory for the decompiled code
with tempfile.TemporaryDirectory(prefix="decompiled_apk_") as output_dir:
print(f"--- Starting analysis for {os.path.basename(apk_path)} ---")
# 1. Decompile the APK
if not analyzer.decompile_apk(apk_path, output_dir):
print("Failed to decompile APK. Aborting analysis.")
sys.exit(1)You will need to indent all code that uses |
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| print(f"--- Starting analysis for {os.path.basename(apk_path)} ---") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # 1. Decompile the APK | ||||||||||||||||||||||||||||||||
| if not analyzer.decompile_apk(apk_path, output_dir): | ||||||||||||||||||||||||||||||||
| print("Failed to decompile APK. Aborting analysis.") | ||||||||||||||||||||||||||||||||
| sys.exit(1) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # 2. Analyze permissions | ||||||||||||||||||||||||||||||||
| print("\n--- Analyzing Permissions ---") | ||||||||||||||||||||||||||||||||
| all_permissions = analyzer.get_permissions(apk_path) | ||||||||||||||||||||||||||||||||
| if all_permissions: | ||||||||||||||||||||||||||||||||
| high_risk_permissions = analyzer.check_high_risk_permissions(all_permissions) | ||||||||||||||||||||||||||||||||
| if high_risk_permissions: | ||||||||||||||||||||||||||||||||
| print("\n[!] High-Risk Permissions Found:") | ||||||||||||||||||||||||||||||||
| for perm in high_risk_permissions: | ||||||||||||||||||||||||||||||||
| print(f" - {perm}") | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| print("\nNo high-risk permissions found.") | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| print("\nCould not extract permissions.") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # 3. Scan for sensitive data | ||||||||||||||||||||||||||||||||
| print("\n--- Scanning for Hardcoded Secrets ---") | ||||||||||||||||||||||||||||||||
| sensitive_data = analyzer.scan_for_sensitive_data(output_dir) | ||||||||||||||||||||||||||||||||
| if sensitive_data: | ||||||||||||||||||||||||||||||||
| print("\n[!] Potential Secrets Found:") | ||||||||||||||||||||||||||||||||
| for file, findings in sensitive_data.items(): | ||||||||||||||||||||||||||||||||
| print(f" - In file: {file}") | ||||||||||||||||||||||||||||||||
| for finding_type, matches in findings.items(): | ||||||||||||||||||||||||||||||||
| print(f" - {finding_type}: {len(matches)} found") | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| print("\nNo hardcoded secrets found.") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # 4. Scan for scam indicators | ||||||||||||||||||||||||||||||||
| print("\n--- Scanning for Phishing and Scam Indicators ---") | ||||||||||||||||||||||||||||||||
| scam_indicators = analyzer.scan_for_scam_indicators(output_dir) | ||||||||||||||||||||||||||||||||
| if scam_indicators: | ||||||||||||||||||||||||||||||||
| print("\n[!] Scam Indicators Found:") | ||||||||||||||||||||||||||||||||
| for file, result in scam_indicators.items(): | ||||||||||||||||||||||||||||||||
| print(f" - In file: {file}") | ||||||||||||||||||||||||||||||||
| for indicator in result["indicators_found"]: | ||||||||||||||||||||||||||||||||
| print(f" - {indicator}") | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| print("\nNo scam indicators found.") | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| # Clean up the decompiled files | ||||||||||||||||||||||||||||||||
| if not args.keep_files: | ||||||||||||||||||||||||||||||||
| print("\nCleaning up temporary files...") | ||||||||||||||||||||||||||||||||
| shutil.rmtree(output_dir) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| print("\n--- Analysis Complete ---") | ||||||||||||||||||||||||||||||||
|
Comment on lines
+69
to
+74
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. suggestion (bug_risk): No error handling for cleanup step. Wrap shutil.rmtree in a try-except block to handle potential exceptions if the directory is locked or files are in use.
Suggested change
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if __name__ == "__main__": | ||||||||||||||||||||||||||||||||
| main() | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
security (python.lang.security.audit.dangerous-subprocess-use-audit): Detected subprocess function 'run' without a static string. If this data can be controlled by a malicious actor, it may be an instance of command injection. Audit the use of this call to ensure it is not controllable by an external resource. You may consider using 'shlex.escape()'.
Source: opengrep