Exemple #1
0
    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())
Exemple #2
0
    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)
Exemple #3
0
    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())
Exemple #6
0
    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)
Exemple #7
0
    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)
Exemple #8
0
    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())