""" APK Deep Diff Analyzer - AndroidManifest.xml Parser """ import xml.etree.ElementTree as ET from typing import Dict, List, Any, Optional import os class ManifestAnalyzer: """Analyze AndroidManifest.xml content.""" def __init__(self, manifest_path: str): self.manifest_path = manifest_path self.tree = None self.root = None self._parse() def _parse(self): """Parse the manifest XML.""" try: self.tree = ET.parse(self.manifest_path) self.root = self.tree.getroot() except Exception: self.tree = None self.root = None def _get_attr(self, element: ET.Element, attr: str, namespace: str = "android") -> Optional[str]: """Get attribute value with namespace handling.""" ns = "http://schemas.android.com/apk/res/android" val = element.get(f"{{{ns}}}{attr}") if val is None: val = element.get(attr) return val def get_package_info(self) -> Dict[str, Any]: """Get package and application info.""" if self.root is None: return {} info = { "package": self.root.get("package", ""), "versionCode": self._get_attr(self.root, "versionCode"), "versionName": self._get_attr(self.root, "versionName"), } app = self.root.find("application") if app is not None: info["application"] = { "label": self._get_attr(app, "label"), "icon": self._get_attr(app, "icon"), "theme": self._get_attr(app, "theme"), "debuggable": self._get_attr(app, "debuggable"), "networkSecurityConfig": self._get_attr(app, "networkSecurityConfig"), "usesCleartextTraffic": self._get_attr(app, "usesCleartextTraffic"), "allowBackup": self._get_attr(app, "allowBackup"), "extractNativeLibs": self._get_attr(app, "extractNativeLibs"), } # SDK info uses_sdk = self.root.find("uses-sdk") if uses_sdk is not None: info["minSdkVersion"] = self._get_attr(uses_sdk, "minSdkVersion") info["targetSdkVersion"] = self._get_attr(uses_sdk, "targetSdkVersion") info["maxSdkVersion"] = self._get_attr(uses_sdk, "maxSdkVersion") return info def get_permissions(self) -> List[str]: """Get all permissions.""" if self.root is None: return [] permissions = [] for perm in self.root.findall("uses-permission"): name = self._get_attr(perm, "name") if name: permissions.append(name) return sorted(set(permissions)) def get_activities(self) -> List[Dict[str, Any]]: """Get all activities.""" if self.root is None: return [] activities = [] app = self.root.find("application") if app is not None: for activity in app.findall("activity"): name = self._get_attr(activity, "name") if name: acts = { "name": name, "exported": self._get_attr(activity, "exported"), "permission": self._get_attr(activity, "permission"), "intent_filters": [], } for intent in activity.findall("intent-filter"): actions = [a.get("name", "") for a in intent.findall("action")] categories = [c.get("name", "") for c in intent.findall("category")] acts["intent_filters"].append({ "actions": actions, "categories": categories, }) activities.append(acts) return activities def get_services(self) -> List[Dict[str, Any]]: """Get all services.""" if self.root is None: return [] services = [] app = self.root.find("application") if app is not None: for service in app.findall("service"): name = self._get_attr(service, "name") if name: services.append({ "name": name, "exported": self._get_attr(service, "exported"), "permission": self._get_attr(service, "permission"), }) return services def get_receivers(self) -> List[Dict[str, Any]]: """Get all broadcast receivers.""" if self.root is None: return [] receivers = [] app = self.root.find("application") if app is not None: for receiver in app.findall("receiver"): name = self._get_attr(receiver, "name") if name: receivers.append({ "name": name, "exported": self._get_attr(receiver, "exported"), "permission": self._get_attr(receiver, "permission"), }) return receivers def get_providers(self) -> List[Dict[str, Any]]: """Get all content providers.""" if self.root is None: return [] providers = [] app = self.root.find("application") if app is not None: for provider in app.findall("provider"): name = self._get_attr(provider, "name") if name: providers.append({ "name": name, "authorities": self._get_attr(provider, "authorities"), "exported": self._get_attr(provider, "exported"), }) return providers def get_metadata(self) -> List[Dict[str, str]]: """Get application metadata.""" if self.root is None: return [] metadata = [] app = self.root.find("application") if app is not None: for meta in app.findall("meta-data"): name = self._get_attr(meta, "name") value = self._get_attr(meta, "value") if name: metadata.append({"name": name, "value": value or ""}) return metadata def get_full_analysis(self) -> Dict[str, Any]: """Get complete manifest analysis.""" return { "package_info": self.get_package_info(), "permissions": self.get_permissions(), "activities": self.get_activities(), "services": self.get_services(), "receivers": self.get_receivers(), "providers": self.get_providers(), "metadata": self.get_metadata(), } def compare_manifests(orig_manifest: str, mod_manifest: str) -> Dict[str, Any]: """Compare two AndroidManifest.xml files.""" orig = ManifestAnalyzer(orig_manifest) mod = ManifestAnalyzer(mod_manifest) orig_data = orig.get_full_analysis() mod_data = mod.get_full_analysis() result = { "original": orig_data, "modified": mod_data, "changes": {}, } # Package info comparison orig_pkg = orig_data.get("package_info", {}) mod_pkg = mod_data.get("package_info", {}) pkg_changes = {} for key in set(list(orig_pkg.keys()) + list(mod_pkg.keys())): if key == "application": continue o_val = orig_pkg.get(key) m_val = mod_pkg.get(key) if o_val != m_val: pkg_changes[key] = {"original": o_val, "modified": m_val} # Application attributes orig_app = orig_pkg.get("application", {}) mod_app = mod_pkg.get("application", {}) app_changes = {} for key in set(list(orig_app.keys()) + list(mod_app.keys())): o_val = orig_app.get(key) m_val = mod_app.get(key) if o_val != m_val: app_changes[key] = {"original": o_val, "modified": m_val} if pkg_changes or app_changes: result["changes"]["package_info"] = {**pkg_changes, **app_changes} # Permissions orig_perms = set(orig_data.get("permissions", [])) mod_perms = set(mod_data.get("permissions", [])) result["changes"]["permissions"] = { "added": sorted(list(mod_perms - orig_perms)), "removed": sorted(list(orig_perms - mod_perms)), "unchanged": sorted(list(orig_perms & mod_perms)), } # Activities orig_acts = {a["name"]: a for a in orig_data.get("activities", [])} mod_acts = {a["name"]: a for a in mod_data.get("activities", [])} act_changes = { "added": [], "removed": [], "modified": [], } for name, data in mod_acts.items(): if name not in orig_acts: act_changes["added"].append(data) elif data != orig_acts[name]: act_changes["modified"].append({ "name": name, "original": orig_acts[name], "modified": data, }) for name, data in orig_acts.items(): if name not in mod_acts: act_changes["removed"].append(data) result["changes"]["activities"] = act_changes # Services orig_svcs = {s["name"]: s for s in orig_data.get("services", [])} mod_svcs = {s["name"]: s for s in mod_data.get("services", [])} svc_changes = {"added": [], "removed": [], "modified": []} for name, data in mod_svcs.items(): if name not in orig_svcs: svc_changes["added"].append(data) elif data != orig_svcs[name]: svc_changes["modified"].append({"name": name, "original": orig_svcs[name], "modified": data}) for name, data in orig_svcs.items(): if name not in mod_svcs: svc_changes["removed"].append(data) result["changes"]["services"] = svc_changes # Receivers orig_recs = {r["name"]: r for r in orig_data.get("receivers", [])} mod_recs = {r["name"]: r for r in mod_data.get("receivers", [])} rec_changes = {"added": [], "removed": [], "modified": []} for name, data in mod_recs.items(): if name not in orig_recs: rec_changes["added"].append(data) elif data != orig_recs[name]: rec_changes["modified"].append({"name": name, "original": orig_recs[name], "modified": data}) for name, data in orig_recs.items(): if name not in mod_recs: rec_changes["removed"].append(data) result["changes"]["receivers"] = rec_changes # Providers orig_provs = {p["name"]: p for p in orig_data.get("providers", [])} mod_provs = {p["name"]: p for p in mod_data.get("providers", [])} prov_changes = {"added": [], "removed": [], "modified": []} for name, data in mod_provs.items(): if name not in orig_provs: prov_changes["added"].append(data) elif data != orig_provs[name]: prov_changes["modified"].append({"name": name, "original": orig_provs[name], "modified": data}) for name, data in orig_provs.items(): if name not in mod_provs: prov_changes["removed"].append(data) result["changes"]["providers"] = prov_changes # Metadata orig_meta = {m["name"]: m["value"] for m in orig_data.get("metadata", [])} mod_meta = {m["name"]: m["value"] for m in mod_data.get("metadata", [])} meta_changes = { "added": [{"name": k, "value": v} for k, v in mod_meta.items() if k not in orig_meta], "removed": [{"name": k, "value": v} for k, v in orig_meta.items() if k not in mod_meta], "modified": [{"name": k, "original": orig_meta[k], "modified": mod_meta[k]} for k in orig_meta if k in mod_meta and orig_meta[k] != mod_meta[k]], } result["changes"]["metadata"] = meta_changes return result