def fn_extract_from_ext4(self, ext4_filepath=None): """Extracts Android application files from EXT4 image. This function calls multiple other functions to accomplish the following: - First analyse superblock to get info regarding block size/count and inode size/count. - Then analyse group descriptor table to get inode table locations. - Then go through each inode table, get directory listings, and subsequently filenames. - Find the inodes corresponding to APK files and extract. Based on https://ext4.wiki.kernel.org/index.php/Ext4_Disk_Layout Files are extracted to the same folder as the ext4 image. Nothing is returned. :param ext4_filepath: string specifying path to ext4 file. If not specified, the function will look for an ext4 file within the app folder :raises JandroidException: an exception is thrown if more than one ext4 file is found in the app folder, or if no files are found """ # If no ext4 file is specified, then see if app folder contains any. if ext4_filepath == None: ext4_to_pull_from = [] for root, dirs, filenames in os.walk(self.path_app_folder): for filename in fnmatch.filter(filenames, '*.ext4'): ext4_to_pull_from.append(os.path.join(root, filename)) # If no files are identified, then we have nothing to analyse. if len(ext4_to_pull_from) < 1: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': Ext4Error', 'reason': 'No ext4 files found in app folder.' }) # We don't support more than one ext4 analysis at a time. elif len(ext4_to_pull_from) > 1: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': Ext4Error', 'reason': 'More than one ext4 files found ' + 'in app folder.' }) self.ext4_filepath = ext4_to_pull_from[0] else: self.ext4_filepath = ext4_filepath logging.info('Extracting files from ext4: ' + self.ext4_filepath) ### Start analysis ### # Analyse superblock in block group 0. self.fn_analyse_super_block() # Analyse group descriptor in block group 0. self.fn_get_group_descriptor_table() # Analyse the inode tables to get file/dir info. self.fn_analyse_inode_tables()
def fn_perform_initial_checks(self, bool_pull_apps, app_pull_src): """Checks for existence of required files and folders. :param bool_pull_apps: boolean indicating whether apps are to be pulled from device/image :param app_pull_src: string specifying pull location. Can be one of "device", "img" or "ext4" :returns: string specifying path to ADB platform tools :raises JandroidException: an exception is raised if the app folder is not present, or if it is empty (only when bool_pull_apps is False) """ logging.info('Performing basic checks. Please wait.') self.bool_pull_apps = bool_pull_apps self.pull_source = app_pull_src # Check app folder. # First check if the folder exists at all. if not os.path.isdir(self.path_app_folder): raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': DirectoryNotPresent', 'reason': 'App directory "' + self.path_app_folder + '" does not exist.' }) # Check if the folder has APK or DEX files inside. # Only do this if we are not expected to pull apps. if self.bool_pull_apps == False: if [ f for f in os.listdir(self.path_app_folder) if ((f.endswith('.apk') or f.endswith('.dex')) and not f.startswith('.')) ] == []: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': EmptyAppDirectory', 'reason': 'App directory "' + self.path_app_folder + '" does not contain APK/DEX files.' }) # If we *are* expected to pull apps, then if we are pulling # from device, make sure we have Android platform-tools. else: if self.pull_source == 'device': self.fn_check_for_adb_executable() logging.info('Basic checks complete.') return self.path_platform_tools
def fn_pull_apk(self, pkg, path_to_pkg): """Pulls an individual APK file. :param pkg: package name string :param path_to_pkg: string specifying path to package (on Android) :raises JandroidException: an exception is raised if any ADB command should fail. """ logging.debug('Pulling package ' + pkg + ' from ' + path_to_pkg) # Remove the "package:" string from path. path_to_apk = path_to_pkg.replace('package:', '').strip() # Pull APK (or raise error on failure). try: pull_result = self.fn_execute_adb( ['pull', path_to_apk, self.path_app_folder]) except JandroidException as e: if (e.args[0]['type'] == 'ADBError'): # This is an error thrown when attempting to # execute ADB. Probably fatal. raise JandroidException({ 'type': e.args[0]['type'], 'reason': 'Error pulling APK via ADB. ' + e.args[0]['reason'] }) elif (e.args[0]['type'] == 'ADBExecuteError'): # This might be a 'Permission Denied'. # We ignore this type of error for now. pass
def fn_get_attributes_labels(self, object): """Retrieves attributes and labels from object. This function takes as input a dictionary object containing (at least) two keys: "attributes" and "labels". It retrieves the attributes and labels and returns them as lists within a list. :param object: dictionary object containing keys: "attributes" and "labels" :returns: list containing two lists: one of attributes, one of labels """ if 'attributes' in object: node_attributes = object['attributes'] else: node_attributes = [] if 'labels' in object: node_labels = object['labels'] else: node_labels = [] if ((node_attributes == []) and (node_labels == [])): raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': EmptyAttributeAndLabelList', 'reason': 'A node must have at least ' + 'one attribute or label.' }) return [node_attributes, node_labels]
def fn_pull_from_image(self): """Enumerate img files and initiate extraction.""" logging.debug('Extracting files from image.') # Enumerate .img files within app folder. images_to_pull_from = [] for root, dirs, filenames in os.walk(self.path_app_folder): for filename in fnmatch.filter(filenames, '*.img'): images_to_pull_from.append(os.path.join(root, filename)) # For each .img file, extract different images (only sparse image # supported at present). for image_to_pull_from in images_to_pull_from: try: self.fn_identify_image_type_from_header(image_to_pull_from) except JandroidException: raise except Exception as e: raise JandroidException( { 'type': str(os.path.basename(__file__)) + ': ImageExtractError', 'reason': str(e) } )
def fn_check_for_adb_executable(self): """Checks whether the ADB executable exists. This function identifies the execution platform, i.e., Windows, Linux, or Mac (Darwin). It then creates the expected filepath to the ADB executable and tests for whether the file is present in the expected location (it expects the executable to be included with the code, rather than being available somewhere on the system). :raises JandroidException: an exception is raised if the ADB executable is not found at the expected location """ # Get execution platform. ADB differs based on platform. run_platform = platform.system().lower().strip() logging.debug('Platform identified as "' + run_platform + '".') # Path to ADB. if run_platform == 'windows': executable = 'adb.exe' else: executable = 'adb' self.path_platform_tools = os.path.join( self.path_base_dir, 'libs', 'platform-tools', 'platform-tools_' + run_platform, executable) if os.path.isfile(self.path_platform_tools): logging.debug('Using adb tool at ' + self.path_platform_tools + '.') else: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': ADBNotPresent', 'reason': 'Could not find adb tool at ' + self.path_platform_tools + '.' })
def fn_pull_from_device(self): """Pulls APKs from an attached Android device via ADB. :raises JandroidException: an exception is raised if any ADB command should fail. """ logging.debug('Pulling APKs from device.') # First execute 'adb devices', # to make sure there is a single device attached. try: self.fn_test_connected_devices() except JandroidException as e: raise # Get list of packages. try: result = self.fn_execute_adb( ['shell', 'pm', 'list', 'packages'] ) except JandroidException as e: raise JandroidException( { 'type': e.args[0]['type'], 'reason': 'Error getting package list via ADB. ' + e.args[0]['reason'] } ) for pkg in result.split('\n'): # Get path-to-APK. pkg = pkg.replace('package:', '') try: path_to_pkg = self.fn_execute_adb( ['shell', 'pm', 'path', pkg] ) except JandroidException as e: raise JandroidException( { 'type': e.args[0]['type'], 'reason': 'Error getting package path ' + 'via ADB. ' + e.args[0]['reason'] } ) # Pull the APK. self.fn_pull_apk(pkg, path_to_pkg)
def fn_test_connected_devices(self): """Tests to make sure a single device is connected. This function runs 'adb devices' to make sure a device is attached. Only one Android device (or VM) should be returned. :raises JandroidException: an exception is raised if more or less than one device is returned by 'adb devices' """ logging.debug('Testing for connected devices.') result = self.fn_execute_adb(['devices']) result_text_as_list = [ f for f in result.split('\n') if 'daemon' not in f ] device_list = list(filter(None, result_text_as_list[1:])) logging.debug( 'Device list: \n\t ' + result.replace('\n','\n\t ') ) if len(device_list) < 1: raise JandroidException( { 'type': str(os.path.basename(__file__)) + ': DeviceError', 'reason': 'No Android devices detected.' } ) if len(device_list) > 1: raise JandroidException( { 'type': str(os.path.basename(__file__)) + ': DeviceError', 'reason': 'More than one Android device ' + 'attached.' } )
def fn_execute_graph_query(self, cypher_query): """Executes the provided Cypher query against a neo4j graph. :param cypher_query: the Cypher query to execute against the neo4j graph :raises JandroidException: an exception is raised if query fails to execute """ try: res = self.db.query(cypher_query) except Exception as e: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': DBQueryError', 'reason': str(e) }) logging.debug('Executed query "' + cypher_query + '" with result stats: ' + str(res.stats) + ' and values: ' + str(res.rows)) return res
def __init__(self, base_dir, app_dir, adb_path, pull_location='device'): """Sets paths and app extraction location. :param base_dir: string specifying path to the base/root directory (contains src/, templates/, etc) :param app_dir: string specifying path to the directory containing the applications under analysis :param adb_path: string specifying path to the Android adb executable :param pull_location: string specifying location to pull apps from - can be one of "device", "ext4", "img" :raises JandroidException: an exception is raised if app folder cannot be created """ # Set paths. self.path_base_dir = base_dir self.path_platform_tools = adb_path # Path to app folder # (i.e., where apps should be stored after being pulled). # Folder is created if it doesn't exist. self.path_app_folder = app_dir # Test if app folder exists, and if not, create it. if not os.path.isdir(self.path_app_folder): try: os.makedirs(self.path_app_folder) except Exception as e: raise JandroidException( { 'type': str(os.path.basename(__file__)) + ': FolderCreateError', 'reason': str(e) } ) # Set pull location. self.pull_location = pull_location
def fn_connect_to_graph(self): """Connects to Neo4j database. Creates a connection to the neo4j database, using the parameters specified in the config file (or default values). :raises JandroidException: an exception is raised if connection to neo4j database fails. """ logging.info('Trying to connect to Neo4j graph DB.') try: self.db = GraphDatabase(self.neo4j_url, username=self.neo4j_username, password=self.neo4j_password) logging.info('Connected to graph DB.') except Exception as e: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': GraphConnectError', 'reason': 'Unable to connect to Neo4j ' + 'graph database. ' + 'Are you sure it\'s running? ' + 'Returned error is: ' + str(e) })
def fn_process_exported(self, lookfor_tags, lookfor_value, current_xml_tree, is_match=True): """Processes the "exported" tag. :param lookfor_tags: a list of tags (namespace variants) to look for :param lookfor_value: string value (either "true" or "false") :param current_xml_tree: lxml Element :param is_match: boolean indicating whether the requirement is to check for match or no-match """ # Make sure we are at the correct level in the XML tree. # That is, the exported tag is only used with activities, services, # receivers and providers. current_tag = current_xml_tree.tag exported_tag_options = [ 'activity', 'activity-alias', 'receiver', 'service', 'provider' ] if current_tag not in exported_tag_options: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': InvalidTag', 'reason': 'Exported tag must belong to one of [' + '"activity", "activity-alias", "receiver", ' + '"service", "provider"' + '].' }) # First check if exported is explicitly defined. # If it is, then we needn't do much more processing. tag_present = False tag_value_in_manifest = None for tag in lookfor_tags: if tag in current_xml_tree.attrib: tag_present = True tag_value_in_manifest = current_xml_tree.attrib[tag] # If exported isn't explicitly defined, then consider default values. if tag_present == False: # For activities, receivers and services, the presence of an # intent-filter means exported defaults to True. # Else it defaults to False. if current_tag in [ 'activity', 'activity-alias', 'receiver', 'service' ]: intent_filters = current_xml_tree.findall('intent-filter') if intent_filters == []: tag_value_in_manifest = 'false' else: tag_value_in_manifest = 'true' # For providers, if sdkversion >= 17, defaults to False. # Else, defaults to True. elif current_tag in ['provider']: target_sdk_version = None uses_sdk = self.apk_manifest_root.findall('uses-sdk') if uses_sdk != []: possible_targetsdktags = \ self.fn_generate_namespace_variants( '<NAMESPACE>:targetSdkVersion' ) for uses_sdk_element in uses_sdk: for targetsdktag in possible_targetsdktags: if targetsdktag in uses_sdk_element.attrib: target_sdk_version = \ int(uses_sdk_element.attrib[targetsdktag]) if target_sdk_version != None: if target_sdk_version >= 17: tag_value_in_manifest = 'false' else: tag_value_in_manifest = 'true' # This is a non-ideal way to handle the situation where there # is a provider with no explicit export, and no # uses-sdk/targetSdkVersion element. if tag_value_in_manifest == None: return False # If the values match, then if the goal was # to have the values match, return True. Else, return False. if tag_value_in_manifest == lookfor_value: if is_match == True: return True else: return False # If the values match, and the goal was that they should *not* match, # return False. Else, return True. else: if is_match == True: return False else: return True
def fn_analyse_lookfor(self, lookfor_object, current_xml_tree): """Analyses LOOKFOR elements. :param lookfor_object: dictionary object specifying the parameters to look for :param current_xml_tree: lxml Element :returns: boolean indicating whether the LOOKFOR was satisfied """ # Initialise variables to keep track of how many things we are # supposed to bechecking and how many have been satisfied. expected_lookfors = 0 satisfied_lookfors = 0 # There are different LOOKFOR types, each with a corresponding function. fn_to_execute = None for lookfor_key in lookfor_object: expected_lookfors += 1 if lookfor_key == 'TAGEXISTS': fn_to_execute = self.fn_analyse_tag_exists elif lookfor_key == 'TAGNOTEXISTS': fn_to_execute = self.fn_analyse_tag_not_exists elif lookfor_key == 'TAGVALUEMATCH': fn_to_execute = self.fn_analyse_tag_value_match elif lookfor_key == 'TAGVALUENOMATCH': fn_to_execute = self.fn_analyse_tag_value_no_match else: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': IncorrectLookforKey', 'reason': 'Unrecognised LOOKFOR key.' }) # A single LOOKFOR object may have a number of elements to # satisfy (specified as a list). all_lookfors = self.fn_process_lookfor_lists( lookfor_object[lookfor_key]) # We have to keep track of these individual elements as well. expected_per_tag_lookfors = len(all_lookfors) satisfied_per_tag_lookfors = 0 # Check each individual element. for single_lookfor in all_lookfors: lookfor_output = fn_to_execute(single_lookfor, current_xml_tree) if lookfor_output == True: satisfied_per_tag_lookfors += 1 # If even one fails, the whole thing fails. else: break # Check if this one LOOKFOR check was fully satisfied. if expected_per_tag_lookfors == satisfied_per_tag_lookfors: satisfied_lookfors += 1 # If even one fails, the whole thing fails. else: break # Finally, check if all expected lookfor elements were satisfied. if expected_lookfors == satisfied_lookfors: return True else: return False
def fn_analyse_super_block(self): """Analyses the superblock in block group 0. The superblock contains a lot of information that we will require later on. These are set as class attributes. We do also read a lot of data that we don't use. Does not return anything. :raises JandroidException: an exception is raised if unsupported modes are identified. """ # Open the file in binary read mode. ext4_file = open(self.ext4_filepath, "rb") # First 1024 bytes in BG0 are padding. ext4_file.read(1024) ### Read superblock ### # A superblock has 1024 bytes of data. ext4_super_block = ext4_file.read(1024) s_inodes_count = \ struct.unpack('<I', ext4_super_block[0:4])[0] # Total inode count. s_blocks_count_lo = \ struct.unpack('<I', ext4_super_block[4:8])[0] # Total block count. s_r_blocks_count_lo = \ struct.unpack('<I', ext4_super_block[8:12])[0] s_free_blocks_count_lo = \ struct.unpack('<I', ext4_super_block[12:16])[0] s_free_inodes_count = \ struct.unpack('<I', ext4_super_block[16:20])[0] s_first_data_block = \ struct.unpack('<I', ext4_super_block[20:24])[0] s_log_block_size = \ struct.unpack('<I', ext4_super_block[24:28])[0] self.block_size = 2**(10 + s_log_block_size) if self.block_size == 1024: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': Ext4Error', 'reason': 'Unsupported block size.' }) s_log_cluster_size = \ struct.unpack('<I', ext4_super_block[28:32])[0] s_blocks_per_group = \ struct.unpack('<I', ext4_super_block[32:36])[0] self.block_group_size = \ self.block_size * s_blocks_per_group self.num_block_groups = \ int(os.path.getsize(self.ext4_filepath)/self.block_group_size) s_clusters_per_group = \ struct.unpack('<I', ext4_super_block[36:40])[0] s_inodes_per_group = \ struct.unpack('<I', ext4_super_block[40:44])[0] self.inodes_per_group = \ s_inodes_per_group s_mtime = \ struct.unpack('<I', ext4_super_block[44:48])[0] s_wtime = \ struct.unpack('<I', ext4_super_block[48:52])[0] s_mnt_count = \ struct.unpack('<H', ext4_super_block[52:54])[0] s_max_mnt_count = \ struct.unpack('<H', ext4_super_block[54:56])[0] s_magic = \ struct.unpack('<H', ext4_super_block[56:58])[0] if s_magic != 0xEF53: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': Ext4Error', 'reason': 'Imvalid magic number in superblock.' }) s_state = \ struct.unpack('<H', ext4_super_block[58:60])[0] s_errors = \ struct.unpack('<H', ext4_super_block[60:62])[0] s_minor_rev_level = \ struct.unpack('<H', ext4_super_block[62:64])[0] s_lastcheck = \ struct.unpack('<I', ext4_super_block[64:68])[0] s_checkinterval = \ struct.unpack('<I', ext4_super_block[68:72])[0] s_creator_os = \ struct.unpack('<I', ext4_super_block[72:76])[0] s_rev_level = \ struct.unpack('<I', ext4_super_block[76:80])[0] s_def_resuid = \ struct.unpack('<H', ext4_super_block[80:82])[0] s_def_resgid = \ struct.unpack('<H', ext4_super_block[82:84])[0] s_first_ino = \ struct.unpack('<I', ext4_super_block[84:88])[0] s_inode_size = \ struct.unpack('<H', ext4_super_block[88:90])[0] self.inode_size = \ s_inode_size s_block_group_nr = \ struct.unpack('<H', ext4_super_block[90:92])[0] s_feature_compat = \ struct.unpack('<I', ext4_super_block[92:96])[0] if ((s_feature_compat & 0x10) == 0x10): self.has_reserved_gdt = 1 self.num_reserved_gdt_entries = \ struct.unpack('<H', ext4_super_block[206:208])[0] else: self.has_reserved_gdt = 0 # Next section (compatibility). s_feature_incompat = \ struct.unpack('<I', ext4_super_block[96:100])[0] # Support for 64-bit. self.INCOMPAT_64BIT = 0 if ((s_feature_incompat & 0x80) == 0x80): self.INCOMPAT_64BIT = 1 if self.INCOMPAT_64BIT != 0: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': Ext4Error', 'reason': 'No support for 64-bit.' }) # Directories store file type info. self.INCOMPAT_FILETYPE = 0 if ((s_feature_incompat & 0x2) == 0x2): self.INCOMPAT_FILETYPE = 1 # Data in inode. self.INCOMPAT_INLINE_DATA = 0 if ((s_feature_incompat & 0x8000) == 0x8000): self.INCOMPAT_INLINE_DATA = 1 # Readonly-compatible feature set. s_feature_ro_compat = \ struct.unpack('<I', ext4_super_block[100:104])[0] # Check for large files. if ((s_feature_ro_compat & 0x8) == 0x8): self.RO_COMPAT_HUGE_FILE = 1 else: self.RO_COMPAT_HUGE_FILE = 0 logging.debug('Superblock details:\n\t ' + 'Total inode count ' + str(s_inodes_count) + '\n\t ' + 'Total block count ' + str(s_blocks_count_lo) + '\n\t ' + 'Log block size ' + str(s_log_block_size) + '\n\t ' + 'Block size ' + str(self.block_size) + '\n\t ' + 'Blocks per group ' + str(s_blocks_per_group) + '\n\t ' + 'Inode size ' + str(s_inode_size) + '\n\t ' + 'Inodes per group ' + str(s_inodes_per_group) + '\n\t ' + 'Block group size ' + str(self.block_group_size) + '\n\t ' + 'Number of block groups ' + str(self.num_block_groups) + '\n\t ' + 'Size of inode structure (bytes) ' + str(s_inode_size) + '\n\t ' + 'Current block group number ' + str(s_block_group_nr) + '\n\t ') ext4_file.close()
def fn_convert_sparse_to_ext4(self, path_to_img_file): """Extracts ext4 files from Android sparse image. Extraction process is based on https://android.googlesource.com/platform/system/core/+/master/ \ libsparse/sparse_format.h Ext4 file will be written to same directory as the sparse image. :param path_to_img_file: string specifying path to sparse image file. :raises JandroidException: an exception is raised if the magic header is incorrect, or if any subsequent byte values are not as expected. """ logging.debug('Extracting files from Android sparse image.') # Open an output file in Binary Write mode. out_file_path = os.path.join(self.path_app_folder, 'temp.ext4') out_file = open(out_file_path, 'wb') # Open the sparse image file in Binary Read mode. try: image_file = open(path_to_img_file, "rb") except Exception as e: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': ImageOpenError', 'reason': 'Unable to open file ' + path_to_img_file + ' in binary read mode: ' + str(e) }) # Unpack bytes and analyse. This will result in an Ext4 file. try: magic_header = struct.unpack('<I', image_file.read(4))[0] if magic_header != 0xed26ff3a: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': InvalidMagicHeader', 'reason': 'Error reading header bytes ' + 'or invalid magic header.' }) major_version = struct.unpack('<H', image_file.read(2))[0] if major_version != 0x01: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': InvalidVersionHeader', 'reason': 'Error reading header bytes ' + 'or unsupported major version.' }) minor_version = struct.unpack('<H', image_file.read(2))[0] if minor_version != 0x00: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': InvalidVersionHeader', 'reason': 'Error reading header bytes ' + 'or unsupported minor version.' }) file_hdr_sz = struct.unpack('<H', image_file.read(2))[0] if file_hdr_sz != 28: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': InvalidNumFileHeaderSize', 'reason': 'Invalid file header size.' }) chunk_hdr_sz = struct.unpack('<H', image_file.read(2))[0] if chunk_hdr_sz != 12: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': InvalidNumChunkHeaderSize', 'reason': 'Invalid chunk header size.' }) blk_sz = struct.unpack('<I', image_file.read(4))[0] if blk_sz % 4 > 0: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': InvalidBlkSize', 'reason': 'Invalid block size (not ' + 'multiple of 4).' }) total_blks = struct.unpack('<I', image_file.read(4))[0] total_chunks = struct.unpack('<I', image_file.read(4))[0] image_checksum = struct.unpack('<I', image_file.read(4))[0] logging.debug('Read header info from sparse image file:\n\t ' + 'Major version: ' + str(major_version) + '\n\t ' + 'Minor version: ' + str(minor_version) + '\n\t ' + 'File header size: ' + str(file_hdr_sz) + '\n\t ' + 'Chunk header size: ' + str(chunk_hdr_sz) + '\n\t ' + 'Block size in bytes: ' + str(blk_sz) + '\n\t ' + 'Total blocks: ' + str(total_blks) + '\n\t ' + 'Total chunks: ' + str(total_chunks) + '\n\t ' + 'Image checksum: ' + str(image_checksum) + '\n\t ') chunk_offset = 0 for i in range(1, total_chunks + 1): # Chunk header. chunk_type = struct.unpack('<H', image_file.read(2))[0] reserved1 = struct.unpack('<H', image_file.read(2))[0] chunk_sz = struct.unpack('<I', image_file.read(4))[0] total_sz = struct.unpack('<I', image_file.read(4))[0] logging.debug('Header information from chunk:\n\t ' + 'Chunk_type: ' + str(hex(chunk_type)) + '\n\t ' + 'Chunk size: ' + str(chunk_sz) + '\n\t ' + 'Total size: ' + str(total_sz) + '\n\t ' + 'Data size: ' + str(total_sz - 12)) if chunk_type == 0xCAC1: #raw data_size = chunk_sz * blk_sz data_bytes = image_file.read(data_size) out_file.seek(chunk_offset * blk_sz) out_file.write(data_bytes) elif chunk_type == 0xCAC2: #fill # TODO: Check for correctness. data_size = 4 data_bytes = image_file.read(data_size) out_file.seek(chunk_offset * blk_sz) out_file.write(data_bytes) elif chunk_type == 0xCAC3: #don't care if total_sz - 12 != 0: logging.error('Don\'t care chunk has non-zero bytes!') break elif chunk_type == 0xCAC4: #crc32 data_size = 4 data_bytes = image_file.read(data_size) # Increment offset. chunk_offset += chunk_sz except Exception as e: raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': ByteReadError', 'reason': 'Error analysing sparse image ' + path_to_img_file + ': ' + str(e) }) out_file.close() # Now extract files from ext4. inst_ext4_extractor = Ext4Extractor(self.path_app_folder) inst_ext4_extractor.fn_extract_from_ext4(out_file_path)
def fn_get_class_method_desc_from_string(self, input_string): """Gets class/method/descriptor parts from a string. A method call in smali is of the format classname->methodname descriptor (without the space) For example: Landroid/util/ArrayMap;->get(Ljava/lang/Object;)Ljava/lang/Object; In the above example: The class part is Landroid/util/ArrayMap; The method part is get The descriptor part is (Ljava/lang/Object;)Ljava/lang/Object; The method part is separated from the class by "->" and from the descriptor by an opening parenthesis. This function takes as input a string in the smali format and splits it into the class, method and descriptor parts. If "->" is not present in the string, then the entire string is assumed to be the class name and the method and descriptor are assigned values of ".*" (which is considered in Androguard as "any" or "don't care". If an opening parenthesis is not present, then it is assumed that there is no descriptor part, and only the descriptor is assigned ".*". :param input_string: a string representation of a class/method :returns: a list containing (in order) the class, method and descriptor parts obtained from the string """ # Assign default values of "don't care" to method and descriptor. method_part = '.*' desc_part = '.*' # In a smali method specification, the class and method must be # separated using '->'. if '->' in input_string: # There must be some string fragment after the '->'. split_string = input_string.split('->') if ((len(split_string) != 2) or (split_string[1] == '')): raise JandroidException({ 'type': str(os.path.basename(__file__)) + ': IncorrectMethodCall', 'reason': 'Call to method specified incorrectly in ' + 'string: "' + input_string + '". Ensure that correct smali format is used.' }) # The class part is easy: it's the part preceding the '->'. class_part = split_string[0] # The part following the '->' may comprise the method *and* descriptor. method_desc_part = split_string[1] # However, it's possible that the descriptor part is not specified. # If the descriptor *is* included, it will begin with an # opening parenthesis. if '(' in method_desc_part: method_part = method_desc_part.split('(')[0] desc_part = '(' + method_desc_part.split('(')[1] # If no opening parenthesis exists, we assume the descriptor hasn't # been provided, i.e., the entire string is the method name. else: method_part = method_desc_part desc_part = '.' # If there is no "->" then assume that the entire string is the # class name. else: class_part = input_string return [class_part, method_part, desc_part]