def _extract_custom_signature_from(self, string: str) -> Sequence[str]: if not hasattr(self, "_signatures"): self._generic_signature = Signature() signature = self._generic_signature.get_matches_in_string(string) if signature != "": return [signature] return []
def _extract_and_set_signatures_from(self, string: str): """ Extract eventual signatures from a string and set the correspondent attribute. :param string: The string from which extracting the eventual signatures. """ if not hasattr(self, "_shell"): self._generic_signature = Signature() match = self._generic_signature.get_matches_in_string(string) if match != "" and match not in self._custom_signatures: self._custom_signatures.append(match)
def parse_signatures(signature: Signature, strings: List, min_string_len: Optional[bool] = None) -> List: signatures = [] for string in strings: if min_string_len is None or len(string) > min_string_len: match, is_valid = signature.search(string) if is_valid and match is not None and match != "": signatures.append(match) return sorted(signatures)
class TestSignature(unittest.TestCase): """ Test Signature parser. """ sut = Signature() @parameterized.expand([ ["apk", True], ["root", True], ["hack", True], ["esploid", True], ["tattoo", True], ["AdMob", True], ["http://www.domain.com", False], ["adbd", False], ["/system/bin", False], ["VersionConstants.java", False], ]) def test_is_valid(self, raw_string, expected): result = self.sut.is_valid(raw_string) self.assertEqual(expected, result) @parameterized.expand([ [ "\"mOnRootMePleaseDialogClickListener", "\"mOnRootMePleaseDialogClickListener", True ], ["str_root_already_rooted", "str_root_already_rooted", True], ["tv_gen_exploit_msg", "tv_gen_exploit_msg", True], ["tattoo_hack_g6561203.ko", "tattoo_hack_g6561203.ko", True], ["6ixxx", "6ixxx", True], [ "#preserveType: %b, type: %s, obj: %s", "#preserveType: %b, type: %s, obj: %s", True ], [ "Mozilla/5.0 (Linux; U; Android %s) Version/3.0.4 Mobile Safari/523.12.2 (AdMob-ANDROID-%s)", "Mozilla/5.0 (Linux; U; Android %s) Version/3.0.4 Mobile Safari/523.12.2 (AdMob-ANDROID-%s)", True ], [ "8Lcom/corner23/android/universalandroot/UniversalAndroot;", "8Lcom/corner23/android/universalandroot/UniversalAndroot;", True ], [" - no match - ", None, False], ["http://www.domain.com", None, False], ["chmod 777", None, False], ]) def test_search(self, pattern, expected_match, expected_is_valid): match, is_valid = self.sut.search(pattern) self.assertEqual(match, expected_match) self.assertEqual(is_valid, expected_is_valid)
class Dex(File, DexInterface): """ Parser implementation for Android DEX file. """ __DEX_FILE_REGEX = ".*\\.dex$" def __init__(self, filepath: str, filename: str, string_processing: bool = True, logger=logger): super(Dex, self).__init__(filepath, filename) self.logger = logger self.logger.debug("Init Dex on %s, filename=%s, string_processing=%s", filepath, filename, string_processing) self._filename = filename self._strings = [] # type: List[str] self._urls = [] # type: List[str] self._shell_commands = [] # type: List[str] self._custom_signatures = [] # type: List[str] self._extract_and_set_strings() if string_processing: self._extract_and_set_urls() self._extract_and_set_shell_commands() # self. _extract_and_set_signatures() def _extract_and_set_strings(self): self.logger.debug("Extracting strings...") command = "strings " + self.get_file_path() process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None, shell=True) strings = filter(lambda string: string != "", (string.strip() for string in process.communicate() [0].decode("utf-8").splitlines())) self._strings = sorted(strings) self.logger.debug("%d strings extracted", len(self._strings)) def _extract_and_set_urls(self): self.logger.debug("Extracting URLs from strings...") urls = (url for string in self._strings for url in self._extract_urls_from(string)) self._urls = sorted(urls) self.logger.debug("%s URLs extracted from strings", len(self._urls)) def _extract_and_set_shell_commands(self): self.logger.debug("Extracting shell commands from strings...") shell_commands = ( command for string in self._strings for command in self._extract_shell_commands_from(string)) self._shell_commands = sorted(shell_commands) self.logger.debug("%s shell commands extracted from strings", len(self._shell_commands)) def _extract_and_set_signatures(self): self.logger.debug("Extracting signatures from strings...") custom_signatures = ( signature for string in self._strings for signature in self._extract_custom_signature_from(string)) self._custom_signatures = sorted(custom_signatures) self.logger.debug("%s signatures extracted from strings", len(self._custom_signatures)) def _extract_urls_from(self, string: str) -> Sequence[str]: if not hasattr(self, "_uri"): self._uri_signature = URISignature() if len(string) > 6: uri = self._uri_signature.get_matches_in_string(string) if uri != "": return [uri] return [] def _extract_shell_commands_from(self, string: str) -> Sequence[str]: if not hasattr(self, "_shell"): self._shell_signature = ShellCommandSignature() command = self._shell_signature.get_matches_in_string(string) if command != "": return [command] return [] def _extract_custom_signature_from(self, string: str) -> Sequence[str]: if not hasattr(self, "_signatures"): self._generic_signature = Signature() signature = self._generic_signature.get_matches_in_string(string) if signature != "": return [signature] return [] @staticmethod def looks_like_a_dex(filename: str) -> bool: return bool(re.search(Dex.__DEX_FILE_REGEX, filename)) def dump(self) -> Dict: dump = super(Dex, self).dump() dump["urls"] = self._urls dump["shell_commands"] = self._shell_commands # TODO: improve custom signatures parsing performance (commented in the meanwhile because far too slow) # dump["custom_signatures"] = self._custom_signatures dump["strings"] = self.get_strings() return dump def get_strings(self) -> List: return self._strings def get_urls(self) -> List: return self._urls def get_shell_commands(self) -> List: return self._shell_commands def get_custom_signatures(self) -> List: return self._custom_signatures
class Dex(File, DexInterface): """ Parser implementation for Android classes.dex file. """ __FILE_NAME_CLASSES_DEX = "classes.dex" def __init__(self, filepath: str, string_processing: bool = True): super(Dex, self).__init__(filepath, "classes.dex") self._strings = [] # type: List[str] self._urls = [] # type: List[str] self._shell_commands = [] # type: List[str] self._custom_signatures = [] # type: List[str] self._extract_and_set_strings() if string_processing: self._extract_and_set_substring_from() def _extract_and_set_strings(self): """ Extract the strings from the classes.dex file and set the correspondent attributes. Empty strings will be removed. """ command = "strings " + self.get_file_path() process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=None, shell=True) self._strings = process.communicate()[0].decode("utf-8").splitlines() self._strings.sort() while "" in self._strings: self._strings.remove("") def _extract_and_set_substring_from(self): """ Extract the strings from the classes.dex file and set the correspondent attributes. Empty strings will be removed. """ for string in self._strings: if string == "": continue self._extract_and_set_urls_from(string) self._extract_and_set_shell_commands_from(string) #self._extract_and_set_signatures_from(string) self._urls.sort() self._shell_commands.sort() #self._custom_signatures.sort() def _extract_and_set_urls_from(self, string: str): """ Extract eventual URLs from a string and set the correspondent attribute. :param string: The string from which extracting the eventual URLs. """ if not hasattr(self, "_uri"): self._uri_signature = URISignature() if len(string) > 6: url = self._uri_signature.get_matches_in_string(string) if url != "" and url not in self._urls: self._urls.append(url) def _extract_and_set_shell_commands_from(self, string: str): """ Extract eventual shell commands from a string and set the correspondent attribute. :param string: The string from which extracting the eventual shell commands. """ if not hasattr(self, "_shell"): self._shell_signature = ShellCommandSignature() command = self._shell_signature.get_matches_in_string(string) if command != "" and command not in self._shell_commands: self._shell_commands.append(command) def _extract_and_set_signatures_from(self, string: str): """ Extract eventual signatures from a string and set the correspondent attribute. :param string: The string from which extracting the eventual signatures. """ if not hasattr(self, "_shell"): self._generic_signature = Signature() match = self._generic_signature.get_matches_in_string(string) if match != "" and match not in self._custom_signatures: self._custom_signatures.append(match) @staticmethod def looks_like_a_dex(filename: str) -> bool: return filename == Dex.__FILE_NAME_CLASSES_DEX def dump(self) -> Dict: dump = super(Dex, self).dump() dump["urls"] = self._urls dump["shell_commands"] = self._shell_commands #dump["custom_signatures"] = self._custom_signatures dump["strings"] = self._strings return dump def get_strings(self) -> List: return self._strings def get_urls(self) -> List: return self._urls def get_shell_commands(self) -> List: return self._shell_commands def get_custom_signatures(self) -> List: return self._custom_signatures
def setUpClass(cls): cls.signature = Signature()