#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""Android permission analysis module for detecting critical permissions."""
# #!/usr/bin/env python3
# # -*- coding: utf-8 -*-
#
# # Copyright (C) {{ year }} Dexray Insight Contributors
# #
# # This file is part of Dexray Insight - Android APK Security Analysis Tool
# #
# # Licensed under the Apache License, Version 2.0 (the "License");
# # you may not use this file except in compliance with the License.
# # You may obtain a copy of the License at
# #
# # http://www.apache.org/licenses/LICENSE-2.0
# #
# # Unless required by applicable law or agreed to in writing, software
# # distributed under the License is distributed on an "AS IS" BASIS,
# # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# # See the License for the specific language governing permissions and
# # limitations under the License.
import logging
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from ..core.base_classes import AnalysisContext
from ..core.base_classes import AnalysisStatus
from ..core.base_classes import BaseAnalysisModule
from ..core.base_classes import BaseResult
from ..core.base_classes import register_module
try:
from androguard.core.bytecodes.apk import APK
except ImportError:
from androguard.core.apk import APK
@dataclass
class PermissionAnalysisResult(BaseResult):
"""Result class for permission analysis."""
all_permissions: list[str] = None
critical_permissions: list[str] = None
permissions_used: int = 0
critical_permissions_found: int = 0
def __post_init__(self):
"""Initialize default values for optional fields."""
if self.all_permissions is None:
self.all_permissions = []
if self.critical_permissions is None:
self.critical_permissions = []
def to_dict(self) -> dict[str, Any]:
"""Convert result to dictionary."""
base_dict = super().to_dict()
base_dict.update(
{
"all_permissions": self.all_permissions,
"critical_permissions": self.critical_permissions,
"permissions_used": self.permissions_used,
"critical_permissions_found": self.critical_permissions_found,
}
)
return base_dict
[docs]
@register_module("permission_analysis")
class PermissionAnalysisModule(BaseAnalysisModule):
"""Permission analysis module for detecting critical Android permissions."""
# Default critical permissions list
DEFAULT_CRITICAL_PERMISSIONS = [
"SEND_SMS",
"SEND_SMS_NO_CONFIRMATION",
"CALL_PHONE",
"RECEIVE_SMS",
"RECEIVE_MMS",
"READ_SMS",
"WRITE_SMS",
"RECEIVE_WAP_PUSH",
"READ_CONTACTS",
"WRITE_CONTACTS",
"READ_PROFILE",
"WRITE_PROFILE",
"READ_CALENDAR",
"WRITE_CALENDAR",
"READ_USER_DICTIONARY",
"READ_HISTORY_BOOKMARKS",
"WRITE_HISTORY_BOOKMARKS",
"ACCESS_FINE_LOCATION",
"ACCESS_COARSE_LOCATION",
"ACCESS_MOCK_LOCATION",
"USE_SIP",
"GET_ACCOUNTS",
"AUTHENTICATE_ACCOUNTS",
"USE_CREDENTIALS",
"MANAGE_ACCOUNTS",
"RECORD_AUDIO",
"CAMERA",
"PROCESS_OUTGOING_CALLS",
"READ_PHONE_STATE",
"WRITE_EXTERNAL_STORAGE",
"READ_EXTERNAL_STORAGE",
"WRITE_SETTINGS",
"GET_TASKS",
"SYSTEM_ALERT_WINDOW",
"SET_ANIMATION_SCALE",
"PERSISTENT_ACTIVITY",
"MOUNT_UNMOUNT_FILESYSTEMS",
"MOUNT_FORMAT_FILESYSTEMS",
"WRITE_APN_SETTINGS",
"SUBSCRIBED_FEEDS_WRITE",
"READ_LOGS",
"SET_DEBUG_APP",
"SET_PROCESS_LIMIT",
"SET_ALWAYS_FINISH",
"SIGNAL_PERSISTENT_PROCESSES",
"REQUEST_INSTALL_PACKAGES",
"ADD_VOICEMAIL",
"ACCEPT_HANDOVER",
"ANSWER_PHONE_CALLS",
"BODY_SENSORS",
"READ_CALL_LOG",
"READ_PHONE_NUMBERS",
"WRITE_CALL_LOG",
"ACCESS_BACKGROUND_LOCATION",
"ACCESS_MEDIA_LOCATION",
"ACTIVITY_RECOGNITION",
"MANAGE_EXTERNAL_STORAGE",
"READ_PRECISE_PHONE_STATE",
"BLUETOOTH_ADVERTISE",
"BLUETOOTH_CONNECT",
"BLUETOOTH_SCAN",
"BODY_SENSORS_BACKGROUND",
"NEARBY_WIFI_DEVICES",
"POST_NOTIFICATIONS",
"READ_MEDIA_AUDIO",
"READ_MEDIA_IMAGES",
"READ_MEDIA_VIDEO",
"READ_MEDIA_VISUAL_USER_SELECTED",
"UWB_RANGING",
]
[docs]
def __init__(self, config: dict[str, Any]):
"""Initialize PermissionAnalysisModule with configuration."""
super().__init__(config)
self.logger = logging.getLogger(__name__)
self.critical_permissions_file = config.get("critical_permissions_file")
self.use_default_list = config.get("use_default_critical_list", True)
self.critical_permissions = self._load_critical_permissions()
[docs]
def get_dependencies(self) -> list[str]:
"""No dependencies for permission analysis."""
return []
def _load_critical_permissions(self) -> list[str]:
"""Load critical permissions from file or use default list."""
if self.critical_permissions_file:
try:
path = Path(self.critical_permissions_file)
if path.exists():
with open(path) as f:
content = f.read().strip()
# Support both line-separated and comma-separated formats
if "," in content:
permissions = [p.strip() for p in content.split(",")]
else:
permissions = content.split()
self.logger.info(
f"Loaded {len(permissions)} critical permissions from {self.critical_permissions_file}"
)
return permissions
else:
self.logger.warning(f"Critical permissions file not found: {self.critical_permissions_file}")
except Exception as e:
self.logger.error(f"Failed to load critical permissions file: {str(e)}")
if self.use_default_list:
self.logger.info(
f"Using default critical permissions list with {len(self.DEFAULT_CRITICAL_PERMISSIONS)} permissions"
)
return self.DEFAULT_CRITICAL_PERMISSIONS
return []
[docs]
def analyze(self, apk_path: str, context: AnalysisContext) -> PermissionAnalysisResult:
"""
Perform permission analysis on the APK.
Args:
apk_path: Path to the APK file
context: Analysis context
Returns:
PermissionAnalysisResult with analysis results
"""
start_time = time.time()
try:
# Use existing androguard object if available
if context.androguard_obj:
# Try to get permissions from the existing androguard object
# This would need to be adapted based on the actual interface
all_permissions = [] # Placeholder - would extract from androguard_obj
else:
# Create new APK instance
apk = APK(apk_path)
all_permissions = apk.get_permissions()
# Find critical permissions
found_critical_permissions = []
for permission in all_permissions:
for critical_permission in self.critical_permissions:
if critical_permission in permission:
found_critical_permissions.append(permission)
break
execution_time = time.time() - start_time
return PermissionAnalysisResult(
module_name=self.name,
status=AnalysisStatus.SUCCESS,
execution_time=execution_time,
all_permissions=all_permissions,
critical_permissions=found_critical_permissions,
permissions_used=len(all_permissions),
critical_permissions_found=len(found_critical_permissions),
)
except Exception as e:
execution_time = time.time() - start_time
self.logger.error(f"Permission analysis failed: {str(e)}")
return PermissionAnalysisResult(
module_name=self.name,
status=AnalysisStatus.FAILURE,
execution_time=execution_time,
error_message=str(e),
all_permissions=[],
critical_permissions=[],
permissions_used=0,
critical_permissions_found=0,
)
[docs]
def validate_config(self) -> bool:
"""Validate module configuration."""
if self.critical_permissions_file:
path = Path(self.critical_permissions_file)
if not path.exists():
self.logger.warning(f"Critical permissions file does not exist: {self.critical_permissions_file}")
if not self.critical_permissions:
self.logger.warning("No critical permissions loaded")
return False
return True