def show_attachments(parsed_eml: Message): print_headline_banner('Attachments') attachments = list() for child in parsed_eml.walk(): if child.get_filename() is not None: attachment_filename = _get_printable_attachment_filename( attachment=child) attachments.append( (attachment_filename, str(child.get_content_type()), str(child.get_content_disposition()))) if len(attachments) == 0: info('E-Mail contains no attachments') else: max_width_filename = max([ len(filename) for (filename, content_type, disposition) in attachments ]) + 7 max_width_content_type = max([ len(content_type) for (filename, content_type, disposition) in attachments ]) + 7 for index, (filename, content_type, disposition) in enumerate(attachments): index_str = '[' + colorize_string(text=str(index + 1), color=Color.CYAN) + ']' print(index_str, filename.ljust(max_width_filename), content_type.ljust(max_width_content_type), disposition) print()
def extract_attachment(parsed_eml: Message, attachment_number: int, output_path: str or None): print_headline_banner('Attachment Extracting') attachment = None counter = 1 for child in parsed_eml.walk(): if child.get_filename() is not None: if counter == attachment_number: attachment = child break counter += 1 # Check if attachment was found if attachment is None: error('Attachment {} could not be found'.format(attachment_number)) return attachment_filename = _get_printable_attachment_filename( attachment=attachment) info('Found attachment [{}] "{}"'.format(attachment_number, attachment_filename)) if output_path is None: output_path = attachment.get_filename() elif os.path.isdir(output_path): output_path = os.path.join(output_path, attachment_filename) payload = attachment.get_payload(decode=True) output_file = open(output_path, mode='wb') output_file.write(payload) info('Attachment extracted to {}'.format(output_path))
def extract_all_attachments(parsed_eml: Message, path: str or None): print_headline_banner('Attachment Extracting') # if no output directory is given then a default directory with the name 'eml_attachments' is used if path is None: path = 'eml_attachments' if not os.path.exists(path): os.makedirs(path) counter = 0 for child in parsed_eml.walk(): if child.get_filename() is None: continue counter += 1 attachment_filename = _get_printable_attachment_filename( attachment=child) output_path = os.path.join(path, attachment_filename) # write attachment to disk payload = child.get_payload(decode=True) output_file = open(output_path, mode='wb') output_file.write(payload) info('Attachment [{}] "{}" extracted to {}'.format( counter, attachment_filename, output_path))
def show_html(parsed_eml: Message): print_headline_banner(headline='HTML') html = __get_decoded_payload(parsed_eml=parsed_eml, content_type='text/html') if html is None: info('Email contains no HTML') else: print(html) print()
def show_text(parsed_eml: Message): print_headline_banner(headline='Plaintext') text = __get_decoded_payload(parsed_eml=parsed_eml, content_type='text/plain') if text is None: info('Email contains no plaintext') else: print(text) print()
def get_all_emails_of_a_user(username: str, repo_name: str) -> Dict[str, Set[str]]: emails_to_name = defaultdict(set) seen_commits = set() page_counter = 1 commit_counter = 1 while True: continue_loop = True url = '{}/repos/{}/{}/commits?per_page=100&page={}'.format( API_URL, username, repo_name, page_counter) result_dict = __api_call(url=url) if 'message' in result_dict: if result_dict['message'] == 'Git Repository is empty.': info('Git repository is empty', verbosity_level=5) continue if 'API rate limit exceeded for ' in result_dict['message']: warning( 'API rate limit exceeded - not all repos where fetched') return emails_to_name if result_dict['message'] == 'Not Found': warning('There is no repository with the name "{}"'.format( repo_name)) return emails_to_name for commit_dict in result_dict: sha = commit_dict['sha'] if sha in seen_commits: continue_loop = False break seen_commits.add(sha) info('scan commit {}'.format(commit_counter), verbosity_level=5) commit_counter += 1 if commit_dict['author'] is None: continue user = commit_dict['author']['login'] if user.lower() == username.lower(): commit = commit_dict['commit'] author_name = commit['author']['name'] author_email = commit['author']['email'] committer_name = commit['committer']['name'] committer_email = commit['committer']['email'] emails_to_name[author_email].add(author_name) emails_to_name[committer_email].add(committer_name) if continue_loop and len(result_dict) == 100: page_counter += 1 else: break return emails_to_name
def show_urls(parsed_eml: Message): print_headline_banner(headline='URLs in HTML part') all_links = set() html_str = __get_decoded_payload(parsed_eml=parsed_eml, content_type='text/html') if html_str is None: warning('Email contains no HTML') else: for pattern in [r'href="(.+?)"', r"href='(.+?)'"]: for match in re.finditer(pattern, html_str): all_links.add(match.group(1)) if len(all_links) == 0: info(message='No URLs found in the html') for x in all_links: print(' - ' + colorize_string(text=x, color=Color.MAGENTA)) print()
def check_tracking(parsed_eml: Message): print_headline_banner(headline='Reloaded Content (aka. Tracking Pixels)') sources = set() html_str = __get_decoded_payload(parsed_eml=parsed_eml, content_type='text/html') if html_str is None: warning('Email contains no HTML') else: for pattern in [r'src="(.+?)"', r"src='(.+?)'", r'background="(.+?)"', r"background='(.+?)'"]: for match in re.finditer(pattern, html_str): if not match.group(1).startswith('cid:'): sources.add(match.group(1)) if len(sources) == 0: info(message='No content found which will be reloaded from external resources') for x in sources: print(' - ' + colorize_string(text=x, color=Color.MAGENTA)) print()
def __final_mount_procedure(source_path: str, target_path: str, partition_number: int): if not os.path.exists(target_path): os.makedirs(target_path) mount_options = list() while True: user_input = input('Mount in readonly mode (y/n) [y]: ') if len(user_input) == 0 or user_input == 'y': mount_options.append('ro') break elif user_input == 'n': break else: error(message='input not valid.') print() mount_options.append('show_sys_files') while True: user_input = input('Mount as NTFS filesystem (y/n) [y]: ') if len(user_input) == 0 or user_input == 'y': mount_options.append('streams_interface=windows') break elif user_input == 'n': break else: error(message='input not valid.') print() # generate mount command mount_options_string = ','.join(mount_options) mount_command = 'mount -o {} "{}" "{}" >/dev/null 2>&1'.format( mount_options_string, __escape_path(source_path), __escape_path(target_path)) if int(os.system(mount_command)) == 0: info(message='Partition {} was mounted under "{}"'.format( partition_number, __escape_path(target_path))) else: error('An Error occurred. Partition could not be mounted')
def __run(self): while True: try: filename, file_extension, content = self.queue.get(block=True, timeout=0.5) except Empty: if not self.is_running: break else: # store the carved file on the drive output_directory_for_file_type = os.path.join( self.output_directory, file_extension) if not os.path.exists(output_directory_for_file_type): os.makedirs(output_directory_for_file_type) path_to_file = os.path.join( output_directory_for_file_type, '{}.{}'.format(filename, file_extension)) with open(path_to_file, mode='wb') as carved_file: carved_file.write(content) info('Carved new file: {}'.format(path_to_file))
def __scan_data(self): info('start file carving...') sector_before = bytes() self.scanned_sectors = 0 self.worker_list = list() while True: try: next_sector = self.buffer.get(block=True, timeout=0.01) # update current worker workers_to_remove = list() for worker in self.worker_list: maintain_worker = worker.update(next_sector) if not maintain_worker: workers_to_remove.append(worker) # remove workers which which should no longer be maintained for worker in workers_to_remove: self.worker_list.remove(worker) # check for new start triggers search_bytes = sector_before + next_sector index_offset = max( (self.scanned_sectors - 1), 0) * self.size_of_a_sector for plugin in self.plugin_list: new_workers = plugin.check_for_trigger_sequence( index_offset=index_offset, search_data=search_bytes) self.worker_list.extend(new_workers) sector_before = next_sector self.scanned_sectors += 1 except Empty: if self.scanned_sectors >= self.number_of_sectors != 0: for worker in self.worker_list: worker.end_of_data_signal() info('Complete Input was processed') break
def crack_zip_archive(password_list: list, zip_archive: zipfile.ZipFile) -> str or None: """ returns password of the zip file or None if the password was not in the password list """ if not __archive_is_encrypted(zip_archive=zip_archive): warning('Zip file is not encrypted') info('Try {} passwords'.format(len(password_list))) for x in password_list: try: zip_archive.extractall(pwd=x.encode('utf-8')) info('Password found: "{}"'.format(x)) info('Files extracted') return x except KeyboardInterrupt: warning('Keyboard Interruption. Existing.') except: pass info('Password not found') return None
def __read_data(self, device): self.number_of_sectors = device.length self.size_of_a_sector = device.sectorSize info('Model of input device: {}'.format(device.model)) info('Number of sectors: {}'.format(self.number_of_sectors)) info('Bytes per sector: {}'.format(self.size_of_a_sector)) try: with open(self.path_to_input_device, mode='rb') as input_file: input_file.seek(0) for sector_i in range(self.number_of_sectors + 1): data_of_new_sector = input_file.read(self.size_of_a_sector) self.buffer.put(data_of_new_sector, block=True) except IsADirectoryError: error('The Input has to be a drive not an directory') self.is_running = False exit()
def main(): parser = argparse.ArgumentParser(usage='run.py -i INPUT -o OUTPUT') parser.add_argument( '-i', '--input', type=str, help='Path to the device or file which should be scanned') parser.add_argument('-o', '--output', type=str, help='Path to the output directory') parser.add_argument( '-c', '--no-corruption-checks', dest='no_corruption_checks', help='No corruption checks will be made, which faster the scan', action='store_true', default=False) parser.add_argument( '-f', dest='flush_if_maximum_file_size_is_reached', help= 'Flush files when the maximum file size is reached even if its not completely carved', action='store_true', default=False) parser.add_argument( '-p', '--plugin', type=str, nargs='+', dest='plugin_list', help= 'List of plugins which will be used [keys, cert, pictures, binary, pdf]', default=['keys', 'cert', 'pictures', 'binary', 'pdf']) arguments = parser.parse_args() if arguments.input is None: parser.print_help() exit() arguments.input = os.path.abspath(arguments.input) if arguments.output is None: parser.print_help() exit() arguments.output = os.path.abspath(arguments.output) file_carver = FileCarver( path_to_input_device=arguments.input, output_directory=arguments.output, make_corruption_checks=not arguments.no_corruption_checks, flush_if_maximum_file_size_is_reached=arguments. flush_if_maximum_file_size_is_reached) # Private Keys if 'keys' in arguments.plugin_list: file_carver.register_plugin(EncryptedPrivateKey) file_carver.register_plugin(PrivateKey) file_carver.register_plugin(EncryptedPrivateKey) file_carver.register_plugin(PrivateDSAKey) file_carver.register_plugin(PrivateECKey) file_carver.register_plugin(PrivateRsaKey) # Certificates and Certificate Requests if 'cert' in arguments.plugin_list: file_carver.register_plugin(CertificateRequest) file_carver.register_plugin(Certificate) file_carver.register_plugin(TrustedCertificate) # Pictures if 'pictures' in arguments.plugin_list: file_carver.register_plugin(JPG) file_carver.register_plugin(PNG) # Binaries if 'binary' in arguments.plugin_list: file_carver.register_plugin(PeFile) # PDF if 'pdf' in arguments.plugin_list: file_carver.register_plugin(PDF) file_carver.start_scanning() try: time_started = time.time() info('Starting file carving process...') while file_carver.is_running: scanned_sectors = file_carver.scanned_sectors if scanned_sectors > 0: number_of_sectors = file_carver.number_of_sectors progress_in_percent = 100 * (scanned_sectors / number_of_sectors) # predict time it still takes duration_up_to_now = time.time() - time_started prediction_in_sec = (duration_up_to_now / scanned_sectors) * ( number_of_sectors - scanned_sectors) d_hours, d_minutes, d_seconds = __convert_seconds( seconds_total=duration_up_to_now) p_hours, p_minutes, p_seconds = __convert_seconds( seconds_total=prediction_in_sec) info('{:2.2f}% duration: {}:{}:{} - remaining: {}:{}:{}'. format(progress_in_percent, d_hours, d_minutes, d_seconds, p_hours, p_minutes, p_seconds)) time.sleep(1) d_hours, d_minutes, d_seconds = __convert_seconds(time.time() - time_started) info('File carving process is complete. Needed: {}:{}:{}'.format( d_hours, d_minutes, d_seconds)) except KeyboardInterrupt: warning('Keyboard Interrupt! Existing.') exit()
def program(input_path: str, mounting_path: str): # create directory which contains all mounting endpoints if not os.path.exists(mounting_path): try: os.makedirs(mounting_path) except PermissionError: error('Permission denied for creating directory "{}"'.format( mounting_path)) exit() # mount the ewf file mounting_path_ewf_dir = os.path.join(mounting_path, '.ewf') if not os.path.exists(mounting_path_ewf_dir): try: os.makedirs(mounting_path_ewf_dir, exist_ok=True) except PermissionError: error('Permission denied for creating directory "{}"'.format( mounting_path_ewf_dir)) exit() if int( os.system('ewfmount "{}" "{}" >/dev/null 2>&1'.format( __escape_path(input_path), __escape_path(mounting_path_ewf_dir)))) != 0: error(message= 'An error occurred while mounting ewf file to "{}". Exiting.'. format(mounting_path_ewf_dir)) exit() else: info(message='ewf file mounted to "{}"'.format(mounting_path_ewf_dir)) # get the path to the ewf file path_to_the_mounted_ewf_file = None for file_path in os.listdir(mounting_path_ewf_dir): path_to_the_mounted_ewf_file = os.path.join(mounting_path_ewf_dir, file_path) if path_to_the_mounted_ewf_file is None: info(message='Could not find mounted ewf file. Exiting.') exit() # Mount ewf tile to unused loop device path_to_loop_device = __get_first_unused_loop_device() if int( os.system('losetup -Pv "{}" "{}" >/dev/null 2>&1'.format( __escape_path(path_to_loop_device), __escape_path(path_to_the_mounted_ewf_file)))) != 0: info( message= 'An error occurred while mounting ewf file to loop back device. Exiting.' ) exit() while True: info(message='Select Partition to mount:') os.system('fdisk -l "{}"'.format(__escape_path(path_to_loop_device))) print() selected_partition_number = input( 'select number of partition (0 for complete disk) [1] > ') if len(selected_partition_number) == 0: # Default value selected_partition_number = 1 else: # check if partition number is an integer try: selected_partition_number = int(selected_partition_number) except ValueError: error('The partition number must be an integer') print() continue if selected_partition_number == 0: selected_partition_path = path_to_loop_device info(message='selected the complete disk "{}"'.format( __escape_path(selected_partition_path))) else: selected_partition_path = '{}p{}'.format( path_to_loop_device, selected_partition_number) info(message='selected partition "{}"'.format( __escape_path(selected_partition_path))) bitlocker_key = input( 'Bitlocker Recovery Key (if encrypted otherwise empty) > ') if len(bitlocker_key) > 0: # check if provided key is valid if re.fullmatch(r'((\d){6}-){7}(\d{6})', bitlocker_key) is None: error( 'The format of the recovery key you typed in is invalid.') info( 'The key must be in the format: DDDDDD-DDDDDD-DDDDDD-DDDDDD-DDDDDD-DDDDDD-DDDDDD' ) print() continue mounting_path_dislocker = os.path.join( mounting_path, '.partition_{}_encrypted'.format(selected_partition_number)) if not os.path.exists(mounting_path_dislocker): os.makedirs(mounting_path_dislocker) if int( os.system( 'dislocker -v -V "{}" -p{} "{}" >/dev/null 2>&1'. format(__escape_path(selected_partition_path), bitlocker_key, __escape_path(mounting_path_dislocker)))) != 0: info(message= 'An Error occurred. Partition could not be decrypted') else: mounting_path_dislocker_file = os.path.join( mounting_path_dislocker, 'dislocker-file') mounting_path_decrypted = os.path.join( mounting_path, 'partition_{}_decrypted'.format(selected_partition_number)) __final_mount_procedure( source_path=mounting_path_dislocker_file, target_path=mounting_path_decrypted, partition_number=selected_partition_number) else: mounting_path_partition = os.path.join( mounting_path, 'partition_{}'.format(selected_partition_number)) __final_mount_procedure(source_path=selected_partition_path, target_path=mounting_path_partition, partition_number=selected_partition_number) print() input('Press ENTER to mount another partition')
def main(): argument_parser = argparse.ArgumentParser( usage='emlAnalyzer [OPTION]... -i FILE', description= 'A cli script to analyze an E-Mail in the eml format for viewing the header, extracting attachments etc.' ) argument_parser.add_argument('-i', '--input', help="path to the eml-file (is required)", type=str) argument_parser.add_argument('--header', action='store_true', default=False, help="Shows the headers") argument_parser.add_argument( '-x', '--tracking', action='store_true', default=False, help= "Shows content which is reloaded from external resources in the HTML part" ) argument_parser.add_argument('-a', '--attachments', action='store_true', default=False, help="Lists attachments") argument_parser.add_argument('--text', action='store_true', default=False, help="Shows plaintext") argument_parser.add_argument('--html', action='store_true', default=False, help="Shows HTML") argument_parser.add_argument('-s', '--structure', action='store_true', default=False, help="Shows structure of the E-Mail") argument_parser.add_argument( '-u', '--url', action='store_true', default=False, help="Shows embedded links and urls in the html part") argument_parser.add_argument('-ea', '--extract', type=int, default=None, help="Extracts the x-th attachment") argument_parser.add_argument('--extract-all', action='store_true', default=None, help="Extracts all attachments") argument_parser.add_argument( '-o', '--output', type=str, default=None, help= "Path for the extracted attachment (default is filename in working directory)" ) arguments = argument_parser.parse_args() if arguments.input is None or len(arguments.input) == 0: warning('No Input specified') argument_parser.print_help() exit() # get the absolute path to the input file path_to_input = os.path.abspath(arguments.input) # read the eml file try: with open(path_to_input, mode='r') as input_file: eml_content = input_file.read() except Exception as e: error('Error: {}'.format(e)) error('File could not be loaded') info('Existing') exit() # parse the eml file try: parsed_eml = message_from_string(eml_content) except Exception as e: error('Error: {}'.format(e)) error('File could not be parsed. Sure it is a eml-file?') info('Existing') exit() # use default functionality if no options are specified is_default_functionality = not (arguments.header or arguments.tracking or arguments.attachments or arguments.text or arguments.html or arguments.structure or arguments.url or arguments.extract is not None) if is_default_functionality: arguments.structure = True arguments.url = True arguments.tracking = True arguments.attachments = True if arguments.header: show_header(parsed_eml=parsed_eml) if arguments.structure: show_structure(parsed_eml=parsed_eml) if arguments.url: show_urls(parsed_eml=parsed_eml) if arguments.tracking: check_tracking(parsed_eml=parsed_eml) if arguments.attachments: show_attachments(parsed_eml=parsed_eml) if arguments.text: show_text(parsed_eml=parsed_eml) if arguments.html: show_html(parsed_eml=parsed_eml) if arguments.extract is not None: extract_attachment(parsed_eml=parsed_eml, attachment_number=arguments.extract, output_path=arguments.output) if arguments.extract_all is not None: extract_all_attachments(parsed_eml=parsed_eml, path=arguments.output)
def main(): parser = argparse.ArgumentParser( usage='showExposedGitHubEmails [OPTION]... -u USERNAME', description= 'Lists information about the FILEs (the current directory by default) including Alternate Data Streams.' ) parser.add_argument( '-u', '--user', dest="user", help="Username of the user which public repositories should be scanned", type=str) parser.add_argument('-r', '--repository', dest='repository', help="check only one specific repository", type=str) parser.add_argument( '-t', '--token', dest='token', help="Paste a GitHub token her to increase the API quota", type=str) parser.add_argument('-v', '--verbose', dest="verbose", help="verbose mode", action='store_true', default=False) parser.add_argument('-d', '--delay', dest="delay", help="The delay between to requests in seconds", type=int, default=None) parser.add_argument( '--api-url', dest="api_url", help='Specify the URL to the GitHub Api (default is "{}")'.format( API_URL), type=str, default=None) parser.add_argument('--no-forks', dest="no_forks", help='Ignore forked repositories', action='store_true', default=False) parsed_arguments = parser.parse_args() if parsed_arguments.user is None: warning('No username specified') parser.print_help() exit() if parsed_arguments.token is not None: update_header( {'Authorization': 'token {}'.format(parsed_arguments.token)}) if parsed_arguments.delay is not None: set_delay(delay=parsed_arguments.delay) if parsed_arguments.api_url is not None: set_api_url(api_url=parsed_arguments.api_url) if parsed_arguments.verbose: set_verbosity_level(level=5) if parsed_arguments.repository is not None: repos_to_scan = [parsed_arguments.repository] else: info('Scan for public repositories of user {}'.format( parsed_arguments.user)) repos_to_scan_sorted = sorted( get_all_repositories_of_a_user(username=parsed_arguments.user), key=lambda x: x.is_fork) repos_to_scan = [ x.name for x in repos_to_scan_sorted if (parsed_arguments.no_forks and not x.is_fork) or not parsed_arguments.no_forks ] info('Found {} public repositories'.format(len(repos_to_scan))) emails_to_name = defaultdict(set) try: for repo in repos_to_scan: info('Scan repository {}'.format(repo)) emails_to_name_new = get_all_emails_of_a_user( username=parsed_arguments.user, repo_name=repo) for email, names in emails_to_name_new.items(): emails_to_name[email].update(names) except KeyboardInterrupt: warning('Keyboard interrupt. Stopped scanning.') if len(emails_to_name) > 0: max_width_email = max([len(x) for x in emails_to_name.keys()]) info('Exposed emails and names:') for email, names in emails_to_name.items(): names_string = '' for i, n in enumerate(names): names_string += colorize_string(n, Color.BLUE) if i < len(names) - 1: names_string += '; ' print( '\t', colorize_string(email.ljust(max_width_email), Color.RED) + ' - ' + names_string) else: info('No emails found')