Coverage for kea/app.py: 74%
102 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-22 16:05 +0800
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-22 16:05 +0800
1import logging
2import re
3import os
4import hashlib
5import subprocess
6from .intent import Intent
8from typing import TYPE_CHECKING, List
9if TYPE_CHECKING:
10 from .core import Setting
12class App(object):
13 """
14 this class describes an app
15 """
17 def __init__(self, app_path, output_dir=None, settings:"Setting" = None):
18 """
19 create an App instance
20 :param app_path: local file path of app
21 :return:
22 """
23 assert app_path is not None
24 self.logger = logging.getLogger(self.__class__.__name__)
26 self.output_dir = output_dir
27 if output_dir is not None:
28 if not os.path.isdir(output_dir):
29 os.makedirs(output_dir)
31 self.settings = settings
33 if not self.settings.is_package:
34 self._apk_init(app_path)
35 else:
36 self._package_init(package_name=app_path)
38 def _apk_init(self, app_path):
39 self.app_path = app_path
41 from loguru import logger
43 # Disable the logging of the androguard module.
44 logger.disable("androguard")
45 from androguard.core.apk import APK
46 self.apk = APK(self.app_path)
47 self.package_name = self.apk.get_package()
48 #Used to get the main activity of the APK (i.e., the first activity that launches when the application starts).
49 self.main_activity = self.apk.get_main_activity()
50 #Get the list of permissions for the application from the self.apk object.
51 self.permissions = self.apk.get_permissions()
52 #Get the list of activities from the self.apk object.
53 self.activities = self.apk.get_activities()
54 self.possible_broadcasts = self.get_possible_broadcasts()
55 self.hashes = self.get_hashes()
57 def _package_init(self, package_name):
58 self.app_path = None
59 self.apk = None
60 self.package_name = package_name
61 self.main_activity = self.dumpsys_main_activity
62 # TODO figure out how to get all activities from package name
63 self.activities = []
64 # TODO parse self.permissions from dumpsys_package_info
66 def get_package_name(self):
67 """
68 get package name of current app
69 :return:
70 """
71 return self.package_name
73 def get_main_activity(self):
74 """
75 get package name of current app
76 :return:
77 """
78 if self.main_activity is not None:
79 return self.main_activity
80 else:
81 self.logger.warning("Cannot get main activity from manifest. Using dumpsys result instead.")
82 return self.dumpsys_main_activity
84 @property
85 def dumpsys_package_info(self):
86 cmd = ["adb", "-s", self.settings.device_serial, "shell", "dumpsys", "package", self.package_name]
87 output = subprocess.check_output(cmd, text=True)
88 return output
90 def dumpsys_activities(self) -> List:
91 match = re.search(r'android.intent.action.MAIN:\s+.*?/(.*?)\s+filter', self.dumpsys_package_info, re.DOTALL)
92 if match:
93 return match.group(1)
94 else:
95 return None
97 @property
98 def dumpsys_main_activity(self):
99 match = re.search(r'android.intent.action.MAIN:\s+.*?/(.*?)\s+filter', self.dumpsys_package_info, re.DOTALL)
100 if match:
101 return match.group(1)
102 else:
103 return None
105 def get_start_intent(self):
106 """
107 get an intent to start the app
108 :return: Intent
109 """
110 package_name = self.get_package_name()
111 if self.get_main_activity():
112 package_name += "/%s" % self.get_main_activity()
113 return Intent(suffix=package_name)
115 def get_start_with_profiling_intent(self, trace_file, sampling=None):
116 """
117 get an intent to start the app with profiling
118 :return: Intent
119 """
120 package_name = self.get_package_name()
121 if self.get_main_activity():
122 package_name += "/%s" % self.get_main_activity()
123 if sampling is not None:
124 return Intent(prefix="start --start-profiler %s --sampling %d" % (trace_file, sampling), suffix=package_name)
125 else:
126 return Intent(prefix="start --start-profiler %s" % trace_file, suffix=package_name)
128 def get_stop_intent(self):
129 """
130 get an intent to stop the app
131 :return: Intent
132 """
133 package_name = self.get_package_name()
134 return Intent(prefix="force-stop", suffix=package_name)
136 def get_possible_broadcasts(self):
137 possible_broadcasts = set()
138 for receiver in self.apk.get_receivers():
139 intent_filters = self.apk.get_intent_filters('receiver', receiver)
140 actions = intent_filters['action'] if 'action' in intent_filters else []
141 categories = intent_filters['category'] if 'category' in intent_filters else []
142 categories.append(None)
143 for action in actions:
144 for category in categories:
145 intent = Intent(prefix='broadcast', action=action, category=category)
146 possible_broadcasts.add(intent)
147 return possible_broadcasts
149 def get_hashes(self, block_size=2 ** 8):
150 """
151 Calculate MD5,SHA-1, SHA-256
152 hashes of APK input file
153 @param block_size:
154 """
155 md5 = hashlib.md5()
156 sha1 = hashlib.sha1()
157 sha256 = hashlib.sha256()
158 f = open(self.app_path, 'rb')
159 while True:
160 data = f.read(block_size)
161 if not data:
162 break
163 md5.update(data)
164 sha1.update(data)
165 sha256.update(data)
166 return [md5.hexdigest(), sha1.hexdigest(), sha256.hexdigest()]