Coverage for kea/app.py: 74%

102 statements  

« 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 

7 

8from typing import TYPE_CHECKING, List 

9if TYPE_CHECKING: 

10 from .core import Setting 

11 

12class App(object): 

13 """ 

14 this class describes an app 

15 """ 

16 

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__) 

25 

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) 

30 

31 self.settings = settings 

32 

33 if not self.settings.is_package: 

34 self._apk_init(app_path) 

35 else: 

36 self._package_init(package_name=app_path) 

37 

38 def _apk_init(self, app_path): 

39 self.app_path = app_path 

40 

41 from loguru import logger 

42 

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() 

56 

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  

65 

66 def get_package_name(self): 

67 """ 

68 get package name of current app 

69 :return: 

70 """ 

71 return self.package_name 

72 

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 

83 

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 

89 

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 

96 

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 

104 

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) 

114 

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) 

127 

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) 

135 

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 

148 

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()]