def _parse_sidebarplists(self): sidebar_plists = multiglob( self.options.inputdir, ['Users/*/Library/Preferences/com.apple.sidebarlists.plist']) for sblist in sidebar_plists: try: data = read_bplist(sblist)[0] except Exception: self.log.debug('Could not parse sidebarplist {0}: {1}'.format( sblist, [traceback.format_exc()])) data = None if data: for i in data['systemitems']['VolumesList']: record = OrderedDict((h, '') for h in self._headers) record['src_file'] = sblist record['src_name'] = "SidebarPlist" try: record['name'] = i['Name'].encode('utf-8') if 'Bookmark' in i: record['url'] = 'file:///' + str( i['Bookmark']).split('file:///')[1].split( '\x00')[0] record['source_key'] = 'VolumesList' except: self.log.debug( "Could not parse sidebarplist item: {0}".format(i)) self._output.write_entry(record.values())
def _parse_sandboxed_loginitems(self): sandboxed_loginitems = multiglob( self.options.inputdir, ['var/db/com.apple.xpc.launchd/disabled.*.plist']) for i in sandboxed_loginitems: record = OrderedDict((h, '') for h in self._headers) metadata = stats2(i, oMACB=True) record.update(metadata) record['src_file'] = i record['src_name'] = "sandboxed_loginitems" try: p = plistlib.readPlist(i) except: try: p = read_bplist(i) except: self.log.debug('Could not read plist {0}: {1}'.format( i, [traceback.format_exc()])) p = 'ERROR' if p != 'ERROR': for k, v in p.items(): if v is False: record['prog_name'] = k self._output.write_entry(record.values()) else: errors = { k: 'ERROR-CNR-PLIST' for k, v in record.items() if v == '' } record.update(errors)
def _parse_finderplists(self): finder_plists = multiglob( self.options.inputdir, ['Users/*/Library/Preferences/com.apple.finder.plist']) for fplist in finder_plists: try: data = read_bplist(fplist)[0] except Exception: self.log.debug('Could not parse finderplist {0}: {1}'.format( fplist, [traceback.format_exc()])) data = None if data: try: recentfolders = data['FXRecentFolders'] except KeyError: self.log.debug( "Could not find FXRecentFolders key in plist.") recentfolders = [] try: moveandcopy = data['RecentMoveAndCopyDestinations'] except KeyError: self.log.debug( "Could not find FXRecentFolders key in plist.") moveandcopy = [] for i in recentfolders: record = OrderedDict((h, '') for h in self._headers) record['src_file'] = fplist record['src_name'] = "FinderPlist" try: record['source_key'] = 'FXRecentFolders' record['name'] = i['name'].encode('utf-8') bkmk = i['file-bookmark'] record['url'] = 'file:///' + str(bkmk).split( ';')[-1].split('\x00')[0] except Exception: self.log.debug( "Could not parse finderplist item: {0}".format(i)) self._output.write_entry(record.values()) for i in moveandcopy: record = OrderedDict((h, '') for h in self._headers) record['src_file'] = fplist record['src_name'] = fplist try: record['url'] = i record['name'] = i.split('/')[-2].encode('utf-8') record['source_key'] = 'RecentMoveAndCopyDestinations' except Exception: self.log.debug( "Could not parse finderplist item: {0}: {1}". format(i, [traceback.format_exc()])) self._output.write_entry(record.values())
def _pull_download_history(self, downloads_plist, user, downloads_output, downloads_headers): self.log.debug("Trying to access Downloads.plist...") if not os.path.exists(downloads_plist): self.log.debug("File not found: {0}".format(downloads_plist)) return try: downloads = read_bplist(downloads_plist)[0]['DownloadHistory'] self.log.debug("Success. Found {0} lines of data.".format( len(downloads))) except IOError: self.log.error("File not found: {0}".format(downloads_plist)) downloads = [] self.log.debug("Parsing and writing downloads data...") for i in downloads: for k, v in i.items(): if k not in [ 'DownloadEntryPostBookmarkBlob', 'DownloadEntryBookmarkBlob' ]: record = OrderedDict((h, '') for h in downloads_headers) record['user'] = user record['download_url'] = i['DownloadEntryURL'] record['download_path'] = i['DownloadEntryPath'] record['download_started'] = str( i['DownloadEntryDateAddedKey']) record['download_finished'] = str( i['DownloadEntryDateFinishedKey']) record['download_totalbytes'] = int( i['DownloadEntryProgressTotalToLoad']) record['download_bytes_received'] = int( i['DownloadEntryProgressBytesSoFar']) downloads_output.write_entry(record.values()) self.log.debug("Done.")
def run(self): output = DataWriter(self.module_name(), self._headers, self.log, self.run_id, self.options) # Parse the com.apple.preferences.accounts.plist to identify deleted accounts. _deletedusers_plist = os.path.join(self.options.inputdir, 'Library/Preferences/com.apple.preferences.accounts.plist') if not os.path.exists(_deletedusers_plist): self.log.debug("File not found: {0}".format(_deletedusers_plist)) _deletedusers = [] else: try: _deletedusers = read_bplist(_deletedusers_plist)[0]['deletedUsers'] except Exception: self.log.debug("Could not parse: {0}".format(_deletedusers_plist)) _deletedusers = [] for i in range(len(_deletedusers)): record = OrderedDict((h, '') for h in self._headers) record['date_deleted'] = parser.parse(str(_deletedusers[i]['date'])).strftime('%Y-%m-%dT%H:%M:%SZ') record['uniq_id'] = _deletedusers[i]['dsAttrTypeStandard:UniqueID'] record['user'] = _deletedusers[i]['name'] record['real_name'] = _deletedusers[i]['dsAttrTypeStandard:RealName'] # Enumerate users still active on disk. _liveusers_plists = os.path.join(self.options.inputdir, 'private/var/db/dslocal/nodes/Default/users/') try: _liveplists = [i for i in os.listdir(_liveusers_plists) if not i.startswith("_") and i not in ['daemon.plist', 'nobody.plist']] except OSError: self.log.debug("Could not connect [{0}].".format([traceback.format_exc()])) _liveplists = [] _liveusers = glob.glob((os.path.join(self.options.inputdir, 'Users/*'))) _liveusers.append(os.path.join(self.options.inputdir, "var/root")) _admins = os.path.join(self.options.inputdir, 'private/var/db/dslocal/nodes/Default/groups/admin.plist') if not os.path.exists(_admins): self.log.debug("File not found: {0}".format(_admins)) self.log.error("Could not determine admin users.") admins = [] else: try: admins = list(read_bplist(_admins)[0]['users']) except Exception: try: # dscl . -read /Groups/admin GroupMembership admin_users, e = subprocess.Popen(["dscl", ".", "-read", "/Groups/admin", "GroupMembership"], stdout=subprocess.PIPE).communicate() admins = admin_users.split()[1:] except: admins = [] self.log.debug("Could not parse: {0}".format(_admins)) self.log.error("Could not determine admin users.") _loginwindow = os.path.join(self.options.inputdir, 'Library/Preferences/com.apple.loginwindow.plist') if not os.path.exists(_loginwindow): self.log.debug("File not found: {0}".format(_loginwindow)) else: try: lastuser = read_bplist(_loginwindow)[0]['lastUserName'] except Exception: lastuser = "" self.log.debug("Could not parse: {0}".format(_loginwindow)) self.log.error("Could not determine last logged in user.") for user_path in _liveusers: user_home = os.path.basename(user_path) if user_home not in ['.localized', 'Shared']: record = OrderedDict((h, '') for h in self._headers) oMACB = stats2(user_path, oMACB=True) record.update(oMACB) if user_home in admins: record['admin'] = 'Yes' if user_home == lastuser: record['lastloggedin_user'] = '******' _liveplists = [] record['user'] = user_home if len(_liveplists) > 0: i_plist = user_home + '.plist' if i_plist in _liveplists: i_plist_array = read_bplist(os.path.join(_liveusers_plists, i_plist))[0] record['uniq_id'] = i_plist_array['uid'][0] record['real_name'] = i_plist_array['realname'][0] elif 'Volumes' not in self.options.inputdir and self.options.forensic_mode != 'True': user_ids, e = subprocess.Popen(["dscl", ".", "-list", "Users", "UniqueID"], stdout=subprocess.PIPE).communicate() for i in user_ids.split('\n'): data = i.split(' ') if record['user'] == data[0]: record['uniq_id'] = data[-1] real_name, e = subprocess.Popen(["finger", record['user']], stdout=subprocess.PIPE, stderr=open(os.devnull, 'w')).communicate() names = [i for i in real_name.split('\n') if i.startswith("Login: ")] for i in names: if ' '+record['user'] in i: r_name =[i for i in i.split('\t') if i.startswith('Name: ')][0].split()[1:] record['real_name'] = ' '.join(r_name) output.write_entry(record.values())
def run(self): output = DataWriter(self.module_name(), self._headers, self.log, self.run_id, self.options) # -------------BEGIN MODULE-SPECIFIC LOGIC------------- # globalpreferences = read_bplist( os.path.join(self.options.inputdir, 'Library/Preferences/.GlobalPreferences.plist')) preferences = plistlib.readPlist( os.path.join( self.options.inputdir, 'Library/Preferences/SystemConfiguration/preferences.plist')) systemversion = plistlib.readPlist( os.path.join(self.options.inputdir, 'System/Library/CoreServices/SystemVersion.plist')) # KEEP THE LINE BELOW TO GENERATE AN ORDEREDDICT BASED ON THE HEADERS record = OrderedDict((h, '') for h in self._headers) record['local_hostname'] = finditem(preferences, 'LocalHostName') record['ipaddress'] = self.options.full_prefix.split(',')[2] computer_name = finditem(preferences, 'ComputerName') if computer_name is not None: record['computer_name'] = computer_name.encode('utf-8') record['hostname'] = finditem(preferences, 'HostName') record['model'] = finditem(preferences, 'Model') record['product_version'] = self.options.os_version record['product_build_version'] = finditem(systemversion, 'ProductBuildVersion') g = glob.glob( os.path.join( self.options.inputdir, 'private/var/folders/zz/zyxvpxvq6csfxvn_n00000sm00006d/C/*')) check_dbs = [ 'consolidated.db', 'cache_encryptedA.db', 'lockCache_encryptedA.db' ] serial_dbs = [ loc for loc in g if any(loc.endswith(db) for db in check_dbs) ] serial_query = 'SELECT SerialNumber FROM TableInfo;' for db in serial_dbs: try: cursor = sqlite3.connect(db).cursor() record['serial_no'] = cursor.execute( serial_query).fetchone()[0] break except Exception: record['serial_no'] = 'ERROR' record['volume_created'] = stats2(self.options.inputdir + "/", oMACB=True)['btime'] record['amtc_runtime'] = str(self.options.start_time).replace( ' ', 'T').replace('+00:00', 'Z') if 'Volumes' not in self.options.inputdir and self.options.forensic_mode is not True: tz, e = subprocess.Popen(["systemsetup", "-gettimezone"], stdout=subprocess.PIPE).communicate() record['system_tz'] = tz.rstrip().replace('Time Zone: ', '') _fdestatus, e = subprocess.Popen( ["fdesetup", "status"], stdout=subprocess.PIPE).communicate() if 'On' in _fdestatus: record['fvde_status'] = "On" else: record['fvde_status'] = "Off" else: try: record['system_tz'] = globalpreferences[0][ 'com.apple.TimeZonePref.Last_Selected_City'][3] except Exception: self.log.error("Could not get system timezone: {0}".format( [traceback.format_exc()])) record['system_tz'] = "ERROR" record['fvde_status'] = "NA" # PROVIDE OUTPUT LINE, AND WRITE TO OUTFILE line = record.values() output.write_entry(line)
def _parse_loginitems(self): user_loginitems_plist = multiglob( self.options.inputdir, ['Users/*/Library/Preferences/com.apple.loginitems.plist']) for i in user_loginitems_plist: record = OrderedDict((h, '') for h in self._headers) metadata = stats2(i, oMACB=True) record.update(metadata) record['src_file'] = i record['src_name'] = "login_items" try: p = plistlib.readPlist(i) except: try: p = read_bplist(i) except: self.log.debug('Could not read plist {0}: {1}'.format( i, [traceback.format_exc()])) p = 'ERROR' if p != 'ERROR': items = p[0]['SessionItems']['CustomListItems'] for i in items: record['prog_name'] = i['Name'] if 'Alias' in i: try: alias_bin = i['Alias'] except: alias_bin = 'ERROR' if alias_bin != 'ERROR': c = [i.encode('hex') for i in alias_bin] for i in range(len(c)): l = int(c[i], 16) if l < len(c) and l > 2: test = os.path.join( self.options.inputdir, (''.join( c[i + 1:i + l + 1])).decode('hex')) try: if not os.path.exists(test): continue else: record['program'] = test cs_check_path = os.path.join( self.options.inputdir, test.lstrip('/')) record['code_signatures'] = str( get_codesignatures( cs_check_path, self.options. dir_no_code_signatures)) except: continue record['program'] = 'ERROR' record['code_signatures'] = 'ERROR' elif 'Bookmark' in i: try: bookmark_bin = i['Bookmark'] except: bookmark_bin = 'ERROR' if bookmark_bin != 'ERROR': program = [i.encode('hex') for i in bookmark_bin] data = Bookmark.from_bytes( ''.join(program).decode('hex')) d = data.get(0xf081, default=None) d = ast.literal_eval(str(d).replace('Data', '')) if d is not None: prog = d.split(';')[-1].replace('\x00', '') record['program'] = prog cs_check_path = os.path.join( self.options.inputdir, prog.lstrip('/')) record['code_signatures'] = str( get_codesignatures( cs_check_path, self.options.dir_no_code_signatures)) self._output.write_entry(record.values()) else: errors = { k: 'ERROR-CNR-PLIST' for k, v in record.items() if v == '' } record.update(errors)
def _parse_LaunchAgentsDaemons(self): LaunchAgents = multiglob(self.options.inputdir, [ 'System/Library/LaunchAgents/*.plist', 'Library/LaunchAgents/*.plist', 'Users/*/Library/LaunchAgents/*.plist', 'System/Library/LaunchAgents/.*.plist', 'Library/LaunchAgents/.*.plist', 'Users/*/Library/LaunchAgents/.*.plist' ]) LaunchDaemons = multiglob(self.options.inputdir, [ 'System/Library/LaunchDaemons/*.plist', 'Library/LaunchDaemons/*.plist', 'System/Library/LaunchDaemons/.*.plist', 'Library/LaunchDaemons/.*.plist' ]) for i in LaunchDaemons + LaunchAgents: record = OrderedDict((h, '') for h in self._headers) metadata = stats2(i, oMACB=True) record.update(metadata) record['src_file'] = i record['src_name'] = "launch_items" try: p = plistlib.readPlist(i) except: try: p = read_bplist(i) except: self.log.debug('Could not read plist {0}: {1}'.format( i, [traceback.format_exc()])) p = 'ERROR' if p != 'ERROR': if type(p) is list and len(p) > 0: p = p[0] # Try to get Label from each plist. try: record['prog_name'] = p['Label'] except KeyError: self.log.debug( "Cannot extract 'Label' from plist: {0}".format(i)) record['prog_name'] = 'ERROR' # Try to get ProgramArguments if present, or Program, from each plist. try: prog_args = p['ProgramArguments'] program = p['ProgramArguments'][0] record['program'] = program if len(prog_args) > 1: record['args'] = ' '.join(p['ProgramArguments'][1:]) except (KeyError, IndexError): try: program = p['Program'] record['program'] = program except: self.log.debug( "Cannot extract 'Program' or 'ProgramArguments' from plist: {0}" .format(i)) program = None record['program'] = 'ERROR' record['args'] = 'ERROR' except Exception: self.log.debug('Could not parse plist {0}: {1}'.format( i, [traceback.format_exc()])) program = None # If program is ID'd, run additional checks. if program: cs_check_path = os.path.join(self.options.inputdir, program.lstrip('/')) record['code_signatures'] = str( get_codesignatures( cs_check_path, self.options.dir_no_code_signatures)) hashset = self._get_hashes(program) record['sha256'] = hashset['sha256'] record['md5'] = hashset['md5'] else: errors = { k: 'ERROR-CNR-PLIST' for k, v in record.items() if v == '' } record.update(errors) self._output.write_entry(record.values())
def _pull_visit_history(self, recently_closed_plist, history_db, user, history_output, history_headers): try: self.log.debug("Trying to access RecentlyClosedTabs.plist...") recently_closed = read_bplist( recently_closed_plist)[0]['ClosedTabOrWindowPersistentStates'] d = {} self.log.debug("Success. Found {0} lines of data.".format( len(recently_closed))) for i in recently_closed: for k, v in i['PersistentState'].items(): if k == 'TabURL': tab_title = i['PersistentState']['TabTitle'].encode( 'utf-8') date_closed = parser.parse( str(i['PersistentState']['DateClosed'])).replace( tzinfo=None).isoformat() + 'Z' try: last_visit_time = i['PersistentState'][ 'LastVisitTime'] last_visit_time = cocoa_time(last_visit_time) except KeyError: last_visit_time = '' d[i['PersistentState']['TabURL']] = [ tab_title, date_closed, last_visit_time ] except IOError: self.log.debug("File not found: {0}".format(recently_closed_plist)) d = {} pass desired_columns = ['visit_time', 'title', 'url', 'visit_count'] available_columns = self._get_column_headers( history_db, 'history_visits') + self._get_column_headers( history_db, 'history_items') query_columns_list = [ i for i in desired_columns if i in available_columns ] query_columns = ', '.join( [i for i in desired_columns if i in available_columns]) unavailable = ','.join( list(set(desired_columns) - set(query_columns_list))) if len(unavailable) > 0: self.log.debug( 'The following desired columns are not available in the database: {0}' .format(unavailable)) self.log.debug("Executing sqlite query for visit history...") try: history_data = sqlite3.connect(history_db).cursor().execute( 'SELECT {0} from history_visits \ left join history_items on history_items.id = history_visits.history_item' .format(query_columns)).fetchall() self.log.debug("Success. Found {0} lines of data.".format( len(history_data))) except sqlite3.OperationalError: error = [ x for x in traceback.format_exc().split('\n') if x.startswith("OperationalError") ] self.log.error('Failed to run query. [{0}]'.format(error[0])) return self.log.debug("Parsing and writing visits data...") nondict = dict.fromkeys(desired_columns) for item in history_data: record = OrderedDict((h, '') for h in history_headers) item_dict = dict(zip(query_columns_list, item)) nondict.update(item_dict) record['user'] = user record['visit_time'] = cocoa_time(nondict['visit_time']) if nondict['title'] is not None: record['title'] = nondict['title'].encode('utf-8') record['url'] = nondict['url'] record['visit_count'] = nondict['visit_count'] if nondict['url'] in d.keys(): record['recently_closed'] = 'Yes' record['tab_title'] = d[nondict['url']][0] record['date_closed'] = d[nondict['url']][1] record['last_visit_time'] = d[nondict['url']][2] history_output.write_entry(record.values())