plaso-rubanetra/plaso/preprocessors/macosx.py
2020-04-06 18:48:34 +02:00

391 lines
12 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2013 The Plaso Project Authors.
# Please see the AUTHORS file for details on individual authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""This file contains preprocessors for Mac OS X."""
import logging
from binplist import binplist
from dfvfs.helpers import file_system_searcher
from xml.etree import ElementTree
from plaso.lib import errors
from plaso.lib import utils
from plaso.parsers.plist_plugins import interface as plist_interface
from plaso.preprocessors import interface
from plaso.preprocessors import manager
class PlistPreprocessPlugin(interface.PreprocessPlugin):
"""Class that defines the plist preprocess plugin object."""
SUPPORTED_OS = ['MacOSX']
WEIGHT = 2
# Path to the plist file to be parsed, can depend on paths discovered
# in previous preprocessors.
PLIST_PATH = ''
# The key that's value should be returned back. It is an ordered list
# of preference. If the first value is found it will be returned and no
# others will be searched.
PLIST_KEYS = ['']
def GetValue(self, searcher, unused_knowledge_base):
"""Returns a value retrieved from keys within a plist file.
Where the name of the keys are defined in PLIST_KEYS.
Args:
searcher: The file system searcher object (instance of
dfvfs.FileSystemSearcher).
knowledge_base: A knowledge base object (instance of KnowledgeBase),
which contains information from the source data needed
for parsing.
Returns:
The value of the first key that is found.
Raises:
errors.PreProcessFail: if the preprocessing fails.
"""
file_entry = self._FindFileEntry(searcher, self.PLIST_PATH)
if not file_entry:
raise errors.PreProcessFail(
u'Unable to open file: {0:s}'.format(self.PLIST_PATH))
file_object = file_entry.GetFileObject()
value = self.ParseFile(file_entry, file_object)
file_object.close()
return value
def ParseFile(self, file_entry, file_object):
"""Parses the plist file and returns the parsed key.
Args:
file_entry: The file entry (instance of dfvfs.FileEntry).
file_object: The file-like object.
Returns:
The value of the first key defined by PLIST_KEYS that is found.
Raises:
errors.PreProcessFail: if the preprocessing fails.
"""
try:
plist_file = binplist.BinaryPlist(file_object)
top_level_object = plist_file.Parse()
except binplist.FormatError as exception:
raise errors.PreProcessFail(
u'File is not a plist: {0:s} with error: {1:s}'.format(
file_entry.path_spec.comparable, exception))
except OverflowError as exception:
raise errors.PreProcessFail(
u'Unable to process plist: {0:s} with error: {1:s}'.format(
file_entry.path_spec.comparable, exception))
if not plist_file:
raise errors.PreProcessFail(
u'File is not a plist: {0:s}'.format(file_entry.path_spec.comparable))
match = None
key_name = ''
for plist_key in self.PLIST_KEYS:
try:
match = plist_interface.GetKeys(
top_level_object, frozenset([plist_key]))
except KeyError:
continue
if match:
key_name = plist_key
break
if not match:
raise errors.PreProcessFail(
u'Keys not found inside plist file: {0:s}.'.format(
u','.join(self.PLIST_KEYS)))
return self.ParseKey(match, key_name)
def ParseKey(self, key, key_name):
"""Retrieves a specific value from the key.
Args:
key: The key object (instance of dict).
key_name: The name of the key.
Returns:
The value of the key defined by key_name.
Raises:
errors.PreProcessFail: if the preprocessing fails.
"""
value = key.get(key_name, None)
if not value:
raise errors.PreProcessFail(
u'Value of key: {0:s} not found.'.format(key_name))
return value
class XMLPlistPreprocessPlugin(PlistPreprocessPlugin):
"""Class that defines the Mac OS X XML plist preprocess plugin object."""
def _GetKeys(self, xml_root, key_name):
"""Return a dict with the requested keys."""
match = {}
generator = xml_root.iter()
for key in generator:
if 'key' in key.tag and key_name in key.text:
value_key = generator.next()
value = ''
for subkey in value_key.iter():
if 'string' in subkey.tag:
value = subkey.text
match[key.text] = value
# Now we need to go over the match dict and retrieve values.
return match
def ParseFile(self, file_entry, file_object):
"""Parse the file and return parsed key.
Args:
file_entry: The file entry (instance of dfvfs.FileEntry).
file_object: The file-like object.
Returns:
The value of the first key defined by PLIST_KEYS that is found.
Raises:
errors.PreProcessFail: if the preprocessing fails.
"""
# TODO: Move to defusedxml for safer XML parsing.
try:
xml = ElementTree.parse(file_object)
except ElementTree.ParseError:
raise errors.PreProcessFail(u'File is not a XML file.')
except IOError:
raise errors.PreProcessFail(u'File is not a XML file.')
xml_root = xml.getroot()
key_name = ''
match = None
for key in self.PLIST_KEYS:
match = self._GetKeys(xml_root, key)
if match:
key_name = key
break
if not match:
raise errors.PreProcessFail(
u'Keys not found inside plist file: {0:s}.'.format(
u','.join(self.PLIST_KEYS)))
return self.ParseKey(match, key_name)
class MacOSXBuild(XMLPlistPreprocessPlugin):
"""Fetches build information about a Mac OS X system."""
ATTRIBUTE = 'build'
PLIST_PATH = '/System/Library/CoreServices/SystemVersion.plist'
PLIST_KEYS = ['ProductUserVisibleVersion']
class MacOSXHostname(XMLPlistPreprocessPlugin):
"""Fetches hostname information about a Mac OS X system."""
ATTRIBUTE = 'hostname'
PLIST_PATH = '/Library/Preferences/SystemConfiguration/preferences.plist'
PLIST_KEYS = ['ComputerName', 'LocalHostName']
class MacOSXKeyboard(PlistPreprocessPlugin):
"""Fetches keyboard information from a Mac OS X system."""
ATTRIBUTE = 'keyboard_layout'
PLIST_PATH = '/Library/Preferences/com.apple.HIToolbox.plist'
PLIST_KEYS = ['AppleCurrentKeyboardLayoutInputSourceID']
def ParseKey(self, key, key_name):
"""Determines the keyboard layout."""
value = super(MacOSXKeyboard, self).ParseKey(key, key_name)
if type(value) in (list, tuple):
value = value[0]
_, _, keyboard_layout = value.rpartition('.')
return keyboard_layout
class MacOSXTimeZone(interface.PreprocessPlugin):
"""Gather timezone information from a Mac OS X system."""
ATTRIBUTE = 'time_zone_str'
SUPPORTED_OS = ['MacOSX']
WEIGHT = 1
ZONE_FILE_PATH = u'/private/etc/localtime'
def GetValue(self, searcher, unused_knowledge_base):
"""Determines the local time zone settings.
Args:
searcher: The file system searcher object (instance of
dfvfs.FileSystemSearcher).
knowledge_base: A knowledge base object (instance of KnowledgeBase),
which contains information from the source data needed
for parsing.
Returns:
The local timezone settings.
Raises:
errors.PreProcessFail: if the preprocessing fails.
"""
path = self.ZONE_FILE_PATH
file_entry = self._FindFileEntry(searcher, path)
if not file_entry:
raise errors.PreProcessFail(
u'Unable to find file: {0:s}'.format(path))
if not file_entry.link:
raise errors.PreProcessFail(
u'Unable to retrieve timezone information from: {0:s}.'.format(path))
_, _, zone = file_entry.link.partition(u'zoneinfo/')
return zone
class MacOSXUsers(interface.PreprocessPlugin):
"""Get information about user accounts on a Mac OS X system."""
SUPPORTED_OS = ['MacOSX']
ATTRIBUTE = 'users'
WEIGHT = 1
# Define the path to the user account information.
USER_PATH = '/private/var/db/dslocal/nodes/Default/users/[^_].+.plist'
_KEYS = frozenset(['name', 'uid', 'home', 'realname'])
def _OpenPlistFile(self, searcher, path_spec):
"""Open a Plist file given a path and returns a plist top level object.
Args:
searcher: The file system searcher object (instance of
dfvfs.FileSystemSearcher).
path_spec: The path specification (instance of dfvfs.PathSpec)
of the plist file.
Raises:
errors.PreProcessFail: if the preprocessing fails.
"""
plist_file_location = getattr(path_spec, 'location', u'')
file_entry = searcher.GetFileEntryByPathSpec(path_spec)
file_object = file_entry.GetFileObject()
try:
plist_file = binplist.BinaryPlist(file_object)
top_level_object = plist_file.Parse()
except binplist.FormatError as exception:
exception = utils.GetUnicodeString(exception)
raise errors.PreProcessFail(
u'File is not a plist: {0:s}'.format(exception))
except OverflowError as exception:
raise errors.PreProcessFail(
u'Error processing: {0:s} with error: {1:s}'.format(
plist_file_location, exception))
if not plist_file:
raise errors.PreProcessFail(
u'File is not a plist: {0:s}'.format(plist_file_location))
return top_level_object
def GetValue(self, searcher, unused_knowledge_base):
"""Determines the user accounts.
Args:
searcher: The file system searcher object (instance of
dfvfs.FileSystemSearcher).
knowledge_base: A knowledge base object (instance of KnowledgeBase),
which contains information from the source data needed
for parsing.
Returns:
A list containing username information dicts.
Raises:
errors.PreProcessFail: if the preprocessing fails.
"""
find_spec = file_system_searcher.FindSpec(
location_regex=self.USER_PATH, case_sensitive=False)
path_specs = list(searcher.Find(find_specs=[find_spec]))
if not path_specs:
raise errors.PreProcessFail(u'Unable to find user plist files.')
users = []
for path_spec in path_specs:
plist_file_location = getattr(path_spec, 'location', u'')
if not plist_file_location:
raise errors.PreProcessFail(u'Missing user plist file location.')
try:
top_level_object = self._OpenPlistFile(searcher, path_spec)
except IOError:
logging.warning(u'Unable to parse user plist file: {0:s}'.format(
plist_file_location))
continue
try:
match = plist_interface.GetKeysDefaultEmpty(
top_level_object, self._KEYS)
except KeyError as exception:
logging.warning(
u'Unable to read user plist file: {0:s} with error: {1:s}'.format(
plist_file_location, exception))
continue
# TODO: as part of artifacts, create a proper object for this.
user = {
'uid': match.get('uid', [-1])[0],
'path': match.get('home', [u'<not set>'])[0],
'name': match.get('name', [u'<not set>'])[0],
'realname': match.get('realname', [u'N/A'])[0]}
users.append(user)
if not users:
raise errors.PreProcessFail(u'Unable to find any users on the system.')
return users
manager.PreprocessPluginsManager.RegisterPlugins([
MacOSXBuild, MacOSXHostname, MacOSXKeyboard, MacOSXTimeZone, MacOSXUsers])