Import from old repository

This commit is contained in:
Stefan
2020-04-06 18:48:34 +02:00
commit 0da6783a45
762 changed files with 103065 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
#!/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 an import statement for each plist related plugin."""
from plaso.parsers.plist_plugins import airport
from plaso.parsers.plist_plugins import appleaccount
from plaso.parsers.plist_plugins import bluetooth
from plaso.parsers.plist_plugins import ipod
from plaso.parsers.plist_plugins import install_history
from plaso.parsers.plist_plugins import macuser
from plaso.parsers.plist_plugins import safari
from plaso.parsers.plist_plugins import softwareupdate
from plaso.parsers.plist_plugins import spotlight
from plaso.parsers.plist_plugins import spotlight_volume
from plaso.parsers.plist_plugins import timemachine
from plaso.parsers.plist_plugins import default
+63
View File
@@ -0,0 +1,63 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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 the airport plist plugin in Plaso."""
from plaso.events import plist_event
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
__author__ = 'Joaquin Moreno Garijo (Joaquin.MorenoGarijo.2013@live.rhul.ac.uk)'
class AirportPlugin(interface.PlistPlugin):
"""Plist plugin that extracts WiFi information."""
NAME = 'plist_airport'
DESCRIPTION = u'Parser for Airport plist files.'
PLIST_PATH = 'com.apple.airport.preferences.plist'
PLIST_KEYS = frozenset(['RememberedNetworks'])
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, match=None,
**unused_kwargs):
"""Extracts relevant Airport entries.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
match: Optional dictionary containing keys extracted from PLIST_KEYS.
The default is None.
"""
for wifi in match['RememberedNetworks']:
description = (
u'[WiFi] Connected to network: <{0:s}> using security {1:s}').format(
wifi.get('SSIDString', u'no SSID string'),
wifi.get('SecurityType', u'N/A'))
last_connected = wifi.get('LastConnected')
event_object = plist_event.PlistEvent(
u'/RememberedNetworks', u'item', last_connected, description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(AirportPlugin)
@@ -0,0 +1,69 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Tests for the airport plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.parsers import plist
from plaso.parsers.plist_plugins import airport
from plaso.parsers.plist_plugins import test_lib
class AirportPluginTest(test_lib.PlistPluginTestCase):
"""Tests for the airport plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = airport.AirportPlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
test_file = self._GetTestFilePath(['com.apple.airport.preferences.plist'])
plist_name = 'com.apple.airport.preferences.plist'
event_queue_consumer = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 4)
timestamps = []
for event_object in event_objects:
timestamps.append(event_object.timestamp)
expected_timestamps = frozenset([
1375144166000000, 1386874984000000, 1386949546000000,
1386950747000000])
self.assertTrue(set(timestamps) == expected_timestamps)
event_object = event_objects[0]
self.assertEqual(event_object.key, u'item')
self.assertEqual(event_object.root, u'/RememberedNetworks')
expected_desc = (
u'[WiFi] Connected to network: <europa> using security '
u'WPA/WPA2 Personal')
self.assertEqual(event_object.desc, expected_desc)
expected_string = u'/RememberedNetworks/item {0:s}'.format(expected_desc)
expected_short = expected_string[:77] + u'...'
self._TestGetMessageStrings(
event_object, expected_string, expected_short)
if __name__ == '__main__':
unittest.main()
+111
View File
@@ -0,0 +1,111 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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 a Apple Account plist plugin in Plaso."""
from plaso.events import plist_event
from plaso.lib import errors
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
__author__ = 'Joaquin Moreno Garijo (Joaquin.MorenoGarijo.2013@live.rhul.ac.uk)'
class AppleAccountPlugin(interface.PlistPlugin):
"""Basic plugin to extract the apple account information."""
NAME = 'plist_appleaccount'
DESCRIPTION = u'Parser for Apple account information plist files.'
PLIST_PATH = u'com.apple.coreservices.appleidauthenticationinfo'
PLIST_KEYS = frozenset(['AuthCertificates', 'AccessorVersions', 'Accounts'])
def Process(
self, parser_context, file_entry=None, parser_chain=None, plist_name=None,
top_level=None, **kwargs):
"""Check if it is a valid Apple account plist file name.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
plist_name: name of the plist file.
top_level: dictionary with the plist file parsed.
"""
if not plist_name.startswith(self.PLIST_PATH):
raise errors.WrongPlistPlugin(self.NAME, plist_name)
super(AppleAccountPlugin, self).Process(
parser_context, file_entry=file_entry, parser_chain=parser_chain,
plist_name=self.PLIST_PATH, top_level=top_level, **kwargs)
# Generated events:
# Accounts: account name.
# FirstName: first name associated with the account.
# LastName: family name associate with the account.
# CreationDate: timestamp when the account was configured in the system.
# LastSuccessfulConnect: last time when the account was connected.
# ValidationDate: last time when the account was validated.
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, match=None,
**unused_kwargs):
"""Extracts relevant Apple Account entries.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
match: Optional dictionary containing keys extracted from PLIST_KEYS.
The default is None.
"""
root = '/Accounts'
for name_account, account in match['Accounts'].iteritems():
general_description = u'{0:s} ({1:s} {2:s})'.format(
name_account, account.get('FirstName', '<FirstName>'),
account.get('LastName', '<LastName>'))
key = name_account
description = u'Configured Apple account {0:s}'.format(
general_description)
event_object = plist_event.PlistEvent(
root, key, account['CreationDate'], description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if 'LastSuccessfulConnect' in account:
description = u'Connected Apple account {0:s}'.format(
general_description)
event_object = plist_event.PlistEvent(
root, key, account['LastSuccessfulConnect'], description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if 'ValidationDate' in account:
description = u'Last validation Apple account {0:s}'.format(
general_description)
event_object = plist_event.PlistEvent(
root, key, account['ValidationDate'], description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(AppleAccountPlugin)
@@ -0,0 +1,79 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Tests for the Apple account plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.parsers import plist
from plaso.parsers.plist_plugins import appleaccount
from plaso.parsers.plist_plugins import test_lib
class AppleAccountPluginTest(test_lib.PlistPluginTestCase):
"""Tests for the Apple account plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = appleaccount.AppleAccountPlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
plist_file = (u'com.apple.coreservices.appleidauthenticationinfo.'
u'ABC0ABC1-ABC0-ABC0-ABC0-ABC0ABC1ABC2.plist')
test_file = self._GetTestFilePath([plist_file])
plist_name = plist_file
event_queue_consumer = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 3)
timestamps = []
for event_object in event_objects:
timestamps.append(event_object.timestamp)
expected_timestamps = frozenset([
1372106802000000, 1387980032000000, 1387980032000000])
self.assertTrue(set(timestamps) == expected_timestamps)
event_object = event_objects[0]
self.assertEqual(event_object.root, u'/Accounts')
self.assertEqual(event_object.key, u'email@domain.com')
expected_desc = (
u'Configured Apple account email@domain.com (Joaquin Moreno Garijo)')
self.assertEqual(event_object.desc, expected_desc)
expected_string = u'/Accounts/email@domain.com {0:s}'.format(expected_desc)
expected_short = expected_string[:77] + u'...'
self._TestGetMessageStrings(
event_object, expected_string, expected_short)
event_object = event_objects[1]
expected_desc = (u'Connected Apple account '
u'email@domain.com (Joaquin Moreno Garijo)')
self.assertEqual(event_object.desc, expected_desc)
event_object = event_objects[2]
expected_desc = (u'Last validation Apple account '
u'email@domain.com (Joaquin Moreno Garijo)')
self.assertEqual(event_object.desc, expected_desc)
if __name__ == '__main__':
unittest.main()
+99
View File
@@ -0,0 +1,99 @@
#!/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 a default plist plugin in Plaso."""
from plaso.events import plist_event
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
class BluetoothPlugin(interface.PlistPlugin):
"""Basic plugin to extract interesting Bluetooth related keys."""
NAME = 'plist_bluetooth'
DESCRIPTION = u'Parser for Bluetooth plist files.'
PLIST_PATH = 'com.apple.bluetooth.plist'
PLIST_KEYS = frozenset(['DeviceCache', 'PairedDevices'])
# LastInquiryUpdate = Device connected via Bluetooth Discovery. Updated
# when a device is detected in discovery mode. E.g. BT headphone power
# on. Pairing is not required for a device to be discovered and cached.
#
# LastNameUpdate = When the human name was last set. Usually done only once
# during initial setup.
#
# LastServicesUpdate = Time set when device was polled to determine what it
# is. Usually done at setup or manually requested via advanced menu.
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, match=None,
**unused_kwargs):
"""Extracts relevant BT entries.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
match: Optional dictionary containing extracted keys from PLIST_KEYS.
The default is None.
"""
root = '/DeviceCache'
for device, value in match['DeviceCache'].items():
name = value.get('Name', '')
if name:
name = u''.join(('Name:', name))
if device in match['PairedDevices']:
desc = 'Paired:True {0:s}'.format(name)
key = device
if 'LastInquiryUpdate' in value:
event_object = plist_event.PlistEvent(
root, key, value['LastInquiryUpdate'], desc)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if value.get('LastInquiryUpdate'):
desc = u' '.join(filter(None, ('Bluetooth Discovery', name)))
key = u''.join((device, '/LastInquiryUpdate'))
event_object = plist_event.PlistEvent(
root, key, value['LastInquiryUpdate'], desc)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if value.get('LastNameUpdate'):
desc = u' '.join(filter(None, ('Device Name Set', name)))
key = u''.join((device, '/LastNameUpdate'))
event_object = plist_event.PlistEvent(
root, key, value['LastNameUpdate'], desc)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if value.get('LastServicesUpdate'):
desc = desc = u' '.join(filter(None, ('Services Updated', name)))
key = ''.join((device, '/LastServicesUpdate'))
event_object = plist_event.PlistEvent(
root, key, value['LastServicesUpdate'], desc)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(BluetoothPlugin)
@@ -0,0 +1,85 @@
#!/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.
"""Tests for the Bluetooth plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.parsers import plist
from plaso.parsers.plist_plugins import bluetooth
from plaso.parsers.plist_plugins import test_lib
class TestBtPlugin(test_lib.PlistPluginTestCase):
"""Tests for the Bluetooth plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = bluetooth.BluetoothPlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
test_file = self._GetTestFilePath(['plist_binary'])
plist_name = 'com.apple.bluetooth.plist'
event_queue_consumer = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 14)
paired_event_objects = []
timestamps = []
for event_object in event_objects:
timestamps.append(event_object.timestamp)
if event_object.desc.startswith(u'Paired'):
paired_event_objects.append(event_object)
# Ensure all 14 events and times from the plist are parsed correctly.
self.assertEquals(len(timestamps), 14)
expected_timestamps = frozenset([
1341957896010535, 1341957896010535, 1350666385239661, 1350666391557044,
1341957900020116, 1302199013524275, 1301012201414766, 1351818797324095,
1351818797324095, 1351819298997672, 1351818803000000, 1351827808261762,
1345251268370453, 1345251192528750])
self.assertTrue(set(timestamps) == expected_timestamps)
# Ensure two paired devices are matched.
self.assertEquals(len(paired_event_objects), 2)
# One of the paired event object descriptions should contain the string:
# Paired:True Name:Apple Magic Trackpad 2.
paired_descriptions = [
event_object.desc for event_object in paired_event_objects]
self.assertTrue(
'Paired:True Name:Apple Magic Trackpad 2' in paired_descriptions)
expected_string = (
u'/DeviceCache/44-00-00-00-00-04 '
u'Paired:True '
u'Name:Apple Magic Trackpad 2')
self._TestGetMessageStrings(event_object, expected_string, expected_string)
if __name__ == '__main__':
unittest.main()
+86
View File
@@ -0,0 +1,86 @@
#!/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 a default plist plugin in Plaso."""
import datetime
import logging
from plaso.events import plist_event
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
class DefaultPlugin(interface.PlistPlugin):
"""Basic plugin to extract keys with timestamps as values from plists."""
NAME = 'plist_default'
DESCRIPTION = u'Parser for plist files.'
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, top_level=None,
**unused_kwargs):
"""Simple method to exact date values from a Plist.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
top_level: Plist in dictionary form.
"""
for root, key, value in interface.RecurseKey(top_level):
if isinstance(value, datetime.datetime):
event_object = plist_event.PlistEvent(root, key, value)
parser_context.ProduceEvent(
event_object, file_entry=file_entry, parser_chain=parser_chain)
# TODO: Binplist keeps a list of offsets but not mapped to a key.
# adjust code when there is a way to map keys to offsets.
# TODO: move this into the parser as with the olecf plugins.
def Process(
self, parser_context, file_entry=None, parser_chain=None, plist_name=None,
top_level=None, **kwargs):
"""Overwrite the default Process function so it always triggers.
Process() checks if the current plist being processed is a match for a
plugin by comparing the PATH and KEY requirements defined by a plugin. If
both match processing continues; else raise WrongPlistPlugin.
The purpose of the default plugin is to always trigger on any given plist
file, thus it needs to overwrite the default behavior of comparing PATH
and KEY.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
plist_name: Name of the plist file.
top_level: Plist in dictionary form.
"""
logging.debug(u'Plist {0:s} plugin used for: {1:s}'.format(
self.NAME, plist_name))
self.GetEntries(
parser_context, file_entry=file_entry, parser_chain=parser_chain,
top_level=top_level, **kwargs)
plist.PlistParser.RegisterPlugin(DefaultPlugin)
+110
View File
@@ -0,0 +1,110 @@
#!/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.
"""Tests for the default plist plugin."""
import datetime
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.lib import timelib_test
from plaso.parsers.plist_plugins import default
from plaso.parsers.plist_plugins import test_lib
import pytz
class TestDefaultPlist(test_lib.PlistPluginTestCase):
"""Tests for the default plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = default.DefaultPlugin()
def testProcessSingle(self):
"""Tests Process on a plist containing a root, value and timestamp."""
top_level_dict_single = {
'DE-00-AD-00-BE-EF': {
'Name': 'DBF Industries Slideshow Lazer', 'LastUsed':
datetime.datetime(
2012, 11, 2, 1, 21, 38, 997672, tzinfo=pytz.utc)}}
event_object_generator = self._ParsePlistWithPlugin(
self._plugin, 'single', top_level_dict_single)
event_objects = self._GetEventObjectsFromQueue(event_object_generator)
self.assertEquals(len(event_objects), 1)
event_object = event_objects[0]
expected_timestamp = timelib_test.CopyStringToTimestamp(
'2012-11-02 01:21:38.997672')
self.assertEquals(event_object.timestamp, expected_timestamp)
self.assertEquals(event_object.root, u'/DE-00-AD-00-BE-EF')
self.assertEquals(event_object.key, u'LastUsed')
expected_string = (
u'/DE-00-AD-00-BE-EF/LastUsed')
self._TestGetMessageStrings(event_object, expected_string, expected_string)
def testProcessMulti(self):
"""Tests Process on a plist containing five keys with date values."""
top_level_dict_many_keys = {
'DeviceCache': {
'44-00-00-00-00-04': {
'Name': 'Apple Magic Trackpad 2', 'LMPSubversion': 796,
'LMPVersion': 3, 'PageScanMode': 0, 'ClassOfDevice': 9620,
'SupportedFeatures': '\x00\x00\x00\x00', 'Manufacturer': 76,
'PageScanPeriod': 0, 'ClockOffset': 17981, 'LastNameUpdate':
datetime.datetime(
2012, 11, 2, 1, 21, 38, 997672, tzinfo=pytz.utc),
'InquiryRSSI': 198, 'PageScanRepetitionMode': 1,
'LastServicesUpdate':
datetime.datetime(2012, 11, 2, 1, 13, 23, tzinfo=pytz.utc),
'displayName': 'Apple Magic Trackpad 2', 'LastInquiryUpdate':
datetime.datetime(
2012, 11, 2, 1, 13, 17, 324095, tzinfo=pytz.utc),
'Services': '', 'BatteryPercent': 0.61},
'44-00-00-00-00-02': {
'Name': 'test-macpro', 'ClockOffset': 28180, 'ClassOfDevice':
3670276, 'PageScanMode': 0, 'LastNameUpdate':
datetime.datetime(
2011, 4, 7, 17, 56, 53, 524275, tzinfo=pytz.utc),
'PageScanPeriod': 2, 'PageScanRepetitionMode': 1,
'LastInquiryUpdate':
datetime.datetime(
2012, 7, 10, 22, 5, 0, 20116, tzinfo=pytz.utc)}}}
event_queue_consumer = self._ParsePlistWithPlugin(
self._plugin, 'nested', top_level_dict_many_keys)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 5)
event_object = event_objects[0]
expected_timestamp = timelib_test.CopyStringToTimestamp(
'2011-04-07 17:56:53.524275')
self.assertEquals(event_object.timestamp, expected_timestamp)
self.assertEquals(event_object.root, u'/DeviceCache/44-00-00-00-00-02')
self.assertEquals(event_object.key, u'LastNameUpdate')
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,67 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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 the install history plist plugin in Plaso."""
from plaso.events import plist_event
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
__author__ = 'Joaquin Moreno Garijo (Joaquin.MorenoGarijo.2013@live.rhul.ac.uk)'
class InstallHistoryPlugin(interface.PlistPlugin):
"""Plist plugin that extracts the installation history."""
NAME = 'plist_install_history'
DESCRIPTION = u'Parser for installation history plist files.'
PLIST_PATH = 'InstallHistory.plist'
PLIST_KEYS = frozenset([
'date', 'displayName', 'displayVersion',
'processName', 'packageIdentifiers'])
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, top_level=None,
**unused_kwargs):
"""Extracts relevant install history entries.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
top_level: Optional plist in dictionary form. The default is None.
"""
for entry in top_level:
packages = []
for package in entry.get('packageIdentifiers'):
packages.append(package)
description = (
u'Installation of [{0:s} {1:s}] using [{2:s}]. '
u'Packages: {3:s}.').format(
entry.get('displayName'), entry.get('displayVersion'),
entry.get('processName'), u', '.join(packages))
event_object = plist_event.PlistEvent(
u'/item', u'', entry.get('date'), description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(InstallHistoryPlugin)
@@ -0,0 +1,77 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Tests for the install history plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.parsers import plist
from plaso.parsers.plist_plugins import install_history
from plaso.parsers.plist_plugins import test_lib
class InstallHistoryPluginTest(test_lib.PlistPluginTestCase):
"""Tests for the install history plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = install_history.InstallHistoryPlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
test_file = self._GetTestFilePath(['InstallHistory.plist'])
plist_name = 'InstallHistory.plist'
event_queue_consumer = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 7)
timestamps = []
for event_object in event_objects:
timestamps.append(event_object.timestamp)
expected_timestamps = frozenset([
1384225175000000, 1388205491000000, 1388232883000000, 1388232883000000,
1388232883000000, 1388232883000000, 1390941528000000])
self.assertTrue(set(timestamps) == expected_timestamps)
event_object = event_objects[0]
self.assertEqual(event_object.key, u'')
self.assertEqual(event_object.root, u'/item')
expected_desc = (
u'Installation of [OS X 10.9 (13A603)] using [OS X Installer]. '
u'Packages: com.apple.pkg.BaseSystemBinaries, '
u'com.apple.pkg.BaseSystemResources, '
u'com.apple.pkg.Essentials, com.apple.pkg.BSD, '
u'com.apple.pkg.JavaTools, com.apple.pkg.AdditionalEssentials, '
u'com.apple.pkg.AdditionalSpeechVoices, '
u'com.apple.pkg.AsianLanguagesSupport, com.apple.pkg.MediaFiles, '
u'com.apple.pkg.JavaEssentials, com.apple.pkg.OxfordDictionaries, '
u'com.apple.pkg.X11redirect, com.apple.pkg.OSInstall, '
u'com.apple.pkg.update.compatibility.2013.001.')
self.assertEqual(event_object.desc, expected_desc)
expected_string = u'/item/ {}'.format(expected_desc)
expected_short = expected_string[:77] + u'...'
self._TestGetMessageStrings(
event_object, expected_string, expected_short)
if __name__ == '__main__':
unittest.main()
+323
View File
@@ -0,0 +1,323 @@
#!/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.
"""Plist_interface contains basic interface for plist plugins within Plaso.
Plist files are only one example of a type of object that the Plaso tool is
expected to encounter and process. There can be and are many other parsers
which are designed to process specific data types.
PlistPlugin defines the attributes necessary for registration, discovery
and operation of plugins for plist files which will be used by PlistParser.
"""
import abc
import logging
from plaso.lib import errors
from plaso.parsers import plugins
class PlistPlugin(plugins.BasePlugin):
"""This is an abstract class from which plugins should be based.
The following are the attributes and methods expected to be overridden by a
plugin.
Attributes:
PLIST_PATH - string of the filename the plugin is designed to process.
PLIST_KEY - list of keys holding values that are necessary for processing.
Please note, PLIST_KEY is cAse sensitive and for a plugin to match a
plist file needs to contain at minimum the number of keys needed for
processing or WrongPlistPlugin is raised.
For example if a Plist file contains the following keys,
{'foo': 1, 'bar': 2, 'opt': 3} with 'foo' and 'bar' being keys critical to
processing define PLIST_KEY as ['foo', 'bar']. If 'opt' is only optionally
defined it can still be accessed by manually processing self.top_level from
the plugin.
Methods:
GetEntries() - extract and format info from keys and yields event.PlistEvent.
"""
NAME = 'plist_plugin'
# PLIST_PATH is a string for the filename this parser is designed to process.
# This is expected to be overriden by the processing plugin.
# Ex. 'com.apple.bluetooth.plist'
PLIST_PATH = 'any'
# PLIST_KEYS is a list of keys required by a plugin.
# This is expected to be overriden by the processing plugin.
# Ex. frozenset(['DeviceCache', 'PairedDevices'])
PLIST_KEYS = frozenset(['any'])
# This is expected to be overriden by the processing plugin.
# URLS should contain a list of URLs with additional information about
# this key or value.
# Ex. ['http://www.forensicswiki.org/wiki/Property_list_(plist)']
URLS = []
@abc.abstractmethod
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, top_level=None,
match=None, **unused_kwargs):
"""Extracts event objects from the values of entries within a plist.
This is the main method that a plist plugin needs to implement.
The contents of the plist keys defined in PLIST_KEYS will be made available
to the plugin as self.matched{'KEY': 'value'}. The plugin should implement
logic to parse this into a useful event for incorporation into the Plaso
timeline.
For example if you want to note the timestamps of when devices were
LastInquiryUpdated you would need to examine the bluetooth config file
called 'com.apple.bluetooth' and need to look at devices under the key
'DeviceCache'. To do this the plugin needs to define
PLIST_PATH = 'com.apple.bluetooth' and PLIST_KEYS =
frozenset(['DeviceCache']). IMPORTANT: this interface requires exact names
and is case sensitive. A unit test based on a real world file is expected
for each plist plugin.
When a file with this key is encountered during processing self.matched is
populated and the plugin's GetEntries() is called. The plugin would have
self.matched = {'DeviceCache': [{'DE:AD:BE:EF:01': {'LastInquiryUpdate':
DateTime_Object}, 'DE:AD:BE:EF:01': {'LastInquiryUpdate':
DateTime_Object}'...}]} and needs to implement logic here to extract
values, format, and produce the data as a event.PlistEvent.
The attributes for a PlistEvent should include the following:
root = Root key this event was extracted from. E.g. DeviceCache/
key = Key the value resided in. E.g. 'DE:AD:BE:EF:01'
time = Date this artifact was created in microseconds(usec) from epoch.
desc = Short description. E.g. 'Device LastInquiryUpdated'
See plist/bluetooth.py for the implemented example plugin.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
top_level: Optional plist in dictionary form. The default is None.
match: Optional dictionary containing extracted keys from PLIST_KEYS.
The default is None.
"""
def Process(
self, parser_context, file_entry=None, parser_chain=None, plist_name=None,
top_level=None, **kwargs):
"""Determine if this is the correct plugin; if so proceed with processing.
Process() checks if the current plist being processed is a match for a
plugin by comparing the PATH and KEY requirements defined by a plugin. If
both match processing continues; else raise WrongPlistPlugin.
This function also extracts the required keys as defined in self.PLIST_KEYS
from the plist and stores the result in self.match[key] and calls
self.GetEntries() which holds the processing logic implemented by the
plugin.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
plist_name: Name of the plist file.
top_level: Plist in dictionary form.
Raises:
WrongPlistPlugin: If this plugin is not able to process the given file.
ValueError: If top_level or plist_name are not set.
"""
if plist_name is None or top_level is None:
raise ValueError(u'Top level or plist name are not set.')
if plist_name.lower() != self.PLIST_PATH.lower():
raise errors.WrongPlistPlugin(self.NAME, plist_name)
if isinstance(top_level, dict):
if not set(top_level.keys()).issuperset(self.PLIST_KEYS):
raise errors.WrongPlistPlugin(self.NAME, plist_name)
else:
# Make sure we are getting back an object that has an iterator.
if not hasattr(top_level, '__iter__'):
raise errors.WrongPlistPlugin(self.NAME, plist_name)
# This is a list and we need to just look at the first level
# of keys there.
keys = []
for top_level_entry in top_level:
if isinstance(top_level_entry, dict):
keys.extend(top_level_entry.keys())
# Compare this is a set, which removes possible duplicate entries
# in the list.
if not set(keys).issuperset(self.PLIST_KEYS):
raise errors.WrongPlistPlugin(self.NAME, plist_name)
# This will raise if unhandled keyword arguments are passed.
super(PlistPlugin, self).Process(parser_context, **kwargs)
logging.debug(u'Plist Plugin Used: {0:s} for: {1:s}'.format(
self.NAME, plist_name))
match = GetKeys(top_level, self.PLIST_KEYS)
# Add ourselves to the parser chain, which will be used in all subsequent
# event creation in this parser.
parser_chain = self._BuildParserChain(parser_chain)
self.GetEntries(
parser_context, file_entry=file_entry, parser_chain=parser_chain,
top_level=top_level, match=match)
def RecurseKey(recur_item, root='', depth=15):
"""Flattens nested dictionaries and lists by yielding it's values.
The hierarchy of a plist file is a series of nested dictionaries and lists.
This is a helper function helps plugins navigate the structure without
having to reimplement their own recursive methods.
This method implements an overridable depth limit to prevent processing
extremely deeply nested plists. If the limit is reached a debug message is
logged indicating which key processing stopped on.
Example Input Plist:
recur_item = { DeviceRoot: { DeviceMAC1: [Value1, Value2, Value3],
DeviceMAC2: [Value1, Value2, Value3]}}
Example Output:
('', DeviceRoot, {DeviceMACs...})
(DeviceRoot, DeviceMAC1, [Value1, Value2, Value3])
(DeviceRoot, DeviceMAC2, [Value1, Value2, Value3])
Args:
recur_item: An object to be checked for additional nested items.
root: The pathname of the current working key.
depth: A counter to ensure we stop at the maximum recursion depth.
Yields:
A tuple of the root, key, and value from a plist.
"""
if depth < 1:
logging.debug(u'Recursion limit hit for key: {0:s}'.format(root))
return
if type(recur_item) in (list, tuple):
for recur in recur_item:
for key in RecurseKey(recur, root, depth):
yield key
return
if not hasattr(recur_item, 'iteritems'):
return
for key, value in recur_item.iteritems():
yield root, key, value
if isinstance(value, dict):
value = [value]
if isinstance(value, list):
for item in value:
if isinstance(item, dict):
for keyval in RecurseKey(
item, root=root + u'/' + key, depth=depth - 1):
yield keyval
def GetKeys(top_level, keys, depth=1):
"""Helper function to return keys nested in a plist dict.
By default this function will return the values for the named keys requested
by a plugin in match dictonary objecte. The default setting is to look
a single layer down from the root (same as the check for plugin
applicability). This level is suitable for most cases.
For cases where there is varability in the name at the first level
(e.g. it is the MAC addresses of a device, or a UUID) it is possible to
override the depth limit and use GetKeys to fetch from a deeper level.
E.g.
Top_Level (root): # depth = 0
|-- Key_Name_is_UUID_Generated_At_Install 1234-5678-8 # depth = 1
| |-- Interesting_SubKey_with_value_to_Process: [Values, ...] # depth = 2
Args:
top_level: Plist in dictionary form.
keys: A list of keys that should be returned.
depth: Defines how many levels deep to check for a match.
Returns:
A dictionary with just the keys requested or an empty dict if the plist
is flat, eg. top_level is a list instead of a dict object.
"""
match = {}
if not isinstance(top_level, dict):
# Return an empty dict here if top_level is a list object, which happens
# if the plist file is flat.
return match
keys = set(keys)
if depth == 1:
for key in keys:
match[key] = top_level.get(key, None)
else:
for _, parsed_key, parsed_value in RecurseKey(top_level, depth=depth):
if parsed_key in keys:
match[parsed_key] = parsed_value
if set(match.keys()) == keys:
return match
return match
def GetKeysDefaultEmpty(top_level, keys, depth=1):
"""Return keys nested in a plist dict, defaulting to an empty value.
The method GetKeys fails if the supplied key does not exist within the
plist object. This alternate method behaves the same way as GetKeys
except that instead of raising an error if the key doesn't exist it will
assign a default empty value ('') to the field.
Args:
top_level: Plist in dictionary form.
keys: A list of keys that should be returned.
depth: Defines how many levels deep to check for a match.
Returns:
A dictionary with just the keys requested.
"""
keys = set(keys)
match = {}
if depth == 1:
for key in keys:
value = top_level.get(key, None)
if value is not None:
match[key] = value
else:
for _, parsed_key, parsed_value in RecurseKey(top_level, depth=depth):
if parsed_key in keys:
match[parsed_key] = parsed_value
if set(match.keys()) == keys:
return match
return match
@@ -0,0 +1,135 @@
#!/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.
"""Tests for the plist plugin interface."""
import unittest
from plaso.events import plist_event
from plaso.lib import errors
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
from plaso.parsers.plist_plugins import test_lib
class MockPlugin(interface.PlistPlugin):
"""Mock plugin."""
NAME = 'mock_plist_plugin'
DESCRIPTION = u'Parser for testing parsing plist files.'
PLIST_PATH = 'plist_binary'
PLIST_KEYS = frozenset(['DeviceCache', 'PairedDevices'])
def GetEntries(self, parser_context, **unused_kwargs):
event_object = plist_event.PlistEvent(
u'/DeviceCache/44-00-00-00-00-00', u'LastInquiryUpdate',
1351827808261762)
parser_context.ProduceEvent(event_object, parser_chain=self.NAME)
class TestPlistPlugin(test_lib.PlistPluginTestCase):
"""Tests for the plist plugin interface."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._top_level_dict = {
'DeviceCache': {
'44-00-00-00-00-04': {
'Name': 'Apple Magic Trackpad 2', 'LMPSubversion': 796,
'Services': '', 'BatteryPercent': 0.61},
'44-00-00-00-00-02': {
'Name': 'test-macpro', 'ClockOffset': 28180,
'PageScanPeriod': 2, 'PageScanRepetitionMode': 1}}}
def testGetPluginNames(self):
"""Tests the GetPluginNames function."""
plugin_names = plist.PlistParser.GetPluginNames()
self.assertNotEquals(plugin_names, [])
self.assertTrue('plist_default' in plugin_names)
def testProcess(self):
"""Tests the Process function."""
# Ensure the plugin only processes if both filename and keys exist.
plugin_object = MockPlugin()
# Test correct filename and keys.
top_level = {'DeviceCache': 1, 'PairedDevices': 1}
event_object_generator = self._ParsePlistWithPlugin(
plugin_object, 'plist_binary', top_level)
event_objects = self._GetEventObjectsFromQueue(event_object_generator)
self.assertEquals(len(event_objects), 1)
# Correct filename with odd filename cAsinG. Adding an extra useless key.
top_level = {'DeviceCache': 1, 'PairedDevices': 1, 'R@ndomExtraKey': 1}
event_object_generator = self._ParsePlistWithPlugin(
plugin_object, 'pLiSt_BinAry', top_level)
event_objects = self._GetEventObjectsFromQueue(event_object_generator)
self.assertEquals(len(event_objects), 1)
# Test wrong filename.
top_level = {'DeviceCache': 1, 'PairedDevices': 1}
with self.assertRaises(errors.WrongPlistPlugin):
_ = self._ParsePlistWithPlugin(
plugin_object, 'wrong_file.plist', top_level)
# Test not enough required keys.
top_level = {'Useless_Key': 0, 'PairedDevices': 1}
with self.assertRaises(errors.WrongPlistPlugin):
_ = self._ParsePlistWithPlugin(
plugin_object, 'plist_binary.plist', top_level)
def testRecurseKey(self):
"""Tests the RecurseKey function."""
# Ensure with a depth of 1 we only return the root key.
result = list(interface.RecurseKey(self._top_level_dict, depth=1))
self.assertEquals(len(result), 1)
# Trying again with depth limit of 2 this time.
result = list(interface.RecurseKey(self._top_level_dict, depth=2))
self.assertEquals(len(result), 3)
# A depth of two should gives us root plus the two devices. Let's check.
my_keys = []
for unused_root, key, unused_value in result:
my_keys.append(key)
expected = set(['DeviceCache', '44-00-00-00-00-04', '44-00-00-00-00-02'])
self.assertTrue(expected == set(my_keys))
def testGetKeys(self):
"""Tests the GetKeys function."""
# Match DeviceCache from the root level.
key = ['DeviceCache']
result = interface.GetKeys(self._top_level_dict, key)
self.assertEquals(len(result), 1)
# Look for a key nested a layer beneath DeviceCache from root level.
# Note: overriding the default depth to look deeper.
key = ['44-00-00-00-00-02']
result = interface.GetKeys(self._top_level_dict, key, depth=2)
self.assertEquals(len(result), 1)
# Check the value of the result was extracted as expected.
self.assertTrue('test-macpro' == result[key[0]]['Name'])
if __name__ == '__main__':
unittest.main()
+91
View File
@@ -0,0 +1,91 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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 a plist plugin for the iPod/iPhone storage plist."""
from plaso.events import time_events
from plaso.lib import eventdata
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
class IPodPlistEvent(time_events.PythonDatetimeEvent):
"""An event object for an entry in the iPod plist file."""
DATA_TYPE = 'ipod:device:entry'
def __init__(self, datetime_timestamp, device_id, device_info):
"""Initialize the event.
Args:
datetime_timestamp: The timestamp for the event as a datetime object.
device_id: The device ID.
device_info: A dict that contains extracted information from the plist.
"""
super(IPodPlistEvent, self).__init__(
datetime_timestamp, eventdata.EventTimestamp.LAST_CONNECTED)
self.device_id = device_id
# Save the other attributes.
for key, value in device_info.iteritems():
if key == 'Connected':
continue
attribute_name = key.lower().replace(u' ', u'_')
setattr(self, attribute_name, value)
class IPodPlugin(interface.PlistPlugin):
"""Plugin to extract iPod/iPad/iPhone device information."""
NAME = 'ipod_device'
DESCRIPTION = u'Parser for iPod, iPad and iPhone plist files.'
PLIST_PATH = 'com.apple.iPod.plist'
PLIST_KEYS = frozenset(['Devices'])
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, match=None,
**unused_kwargs):
"""Extract device information from the iPod plist.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
match: Optional dictionary containing keys extracted from PLIST_KEYS.
The default is None.
"""
if not 'Devices' in match:
return
devices = match['Devices']
if not devices:
return
for device, device_info in devices.iteritems():
if 'Connected' not in device_info:
continue
event_object = IPodPlistEvent(
device_info.get('Connected'), device, device_info)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(IPodPlugin)
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Tests for the iPod plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import ipod as ipod_formatter
from plaso.lib import eventdata
from plaso.lib import timelib_test
from plaso.parsers import plist
from plaso.parsers.plist_plugins import ipod
from plaso.parsers.plist_plugins import test_lib
class TestIPodPlugin(test_lib.PlistPluginTestCase):
"""Tests for the iPod plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = ipod.IPodPlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
plist_name = 'com.apple.iPod.plist'
test_file = self._GetTestFilePath([plist_name])
event_queue_consumer = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 4)
event_object = event_objects[1]
timestamp = timelib_test.CopyStringToTimestamp('2013-10-09 19:27:54')
self.assertEquals(event_object.timestamp, timestamp)
expected_string = (
u'Device ID: 4C6F6F6E65000000 Type: iPhone [10016] Connected 1 times '
u'Serial nr: 526F676572 IMEI [012345678901234]')
self._TestGetMessageStrings(
event_object, expected_string, expected_string[0:77] + '...')
self.assertEquals(
event_object.timestamp_desc, eventdata.EventTimestamp.LAST_CONNECTED)
self.assertEquals(event_object.device_class, u'iPhone')
self.assertEquals(event_object.device_id, u'4C6F6F6E65000000')
self.assertEquals(event_object.firmware_version, 256)
self.assertEquals(event_object.imei, u'012345678901234')
self.assertEquals(event_object.use_count, 1)
event_object = event_objects[3]
timestamp = timelib_test.CopyStringToTimestamp('1995-11-22 18:25:07')
self.assertEquals(event_object.timestamp, timestamp)
self.assertEquals(event_object.device_id, u'0000A11300000000')
if __name__ == '__main__':
unittest.main()
+172
View File
@@ -0,0 +1,172 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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 the Mac OS X user plist plugin."""
# TODO: Only plists from Mac OS X 10.8 and 10.9 were tested. Look at other
# versions as well.
import binascii
from binplist import binplist
from dfvfs.file_io import fake_file_io
from dfvfs.path import fake_path_spec
from dfvfs.resolver import context
from xml.etree import ElementTree
from plaso.events import plist_event
from plaso.lib import timelib
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
__author__ = 'Joaquin Moreno Garijo (Joaquin.MorenoGarijo.2013@live.rhul.ac.uk)'
class MacUserPlugin(interface.PlistPlugin):
"""Basic plugin to extract timestamp Mac user information."""
NAME = 'plist_macuser'
DESCRIPTION = u'Parser for Mac OS X user plist files.'
# The PLIST_PATH is dynamic, "user".plist is the name of the
# Mac OS X user.
PLIST_KEYS = frozenset([
'name', 'uid', 'home',
'passwordpolicyoptions', 'ShadowHashData'])
_ROOT = u'/'
def Process(
self, parser_context, file_entry=None, parser_chain=None, plist_name=None,
top_level=None, **kwargs):
"""Check if it is a valid Mac OS X system account plist file name.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
plist_name: name of the plist file.
top_level: dictionary with the plist file parsed.
"""
super(MacUserPlugin, self).Process(
parser_context, file_entry=file_entry, parser_chain=parser_chain,
plist_name=self.PLIST_PATH, top_level=top_level, **kwargs)
# Generated events:
# name: string with the system user.
# uid: user ID.
# passwordpolicyoptions: XML Plist structures with the timestamp.
# passwordLastSetTime: last time the password was changed.
# lastLoginTimestamp: last time the user was authenticated (*).
# failedLoginTimestamp: last time the user passwd was incorrectly(*).
# failedLoginCount: times of incorrect passwords.
# (*): depending on the situation, these timestamps are reset (0 value).
# It is translated by the library as a 2001-01-01 00:00:00 (COCAO
# zero time representation). If this happens, the event is not yield.
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, match=None,
**unused_kwargs):
"""Extracts relevant user timestamp entries.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
match: Optional dictionary containing keys extracted from PLIST_KEYS.
The default is None.
"""
account = match['name'][0]
uid = match['uid'][0]
cocoa_zero = (
timelib.Timestamp.COCOA_TIME_TO_POSIX_BASE *
timelib.Timestamp.MICRO_SECONDS_PER_SECOND)
# INFO: binplist return a string with the Plist XML.
for policy in match['passwordpolicyoptions']:
xml_policy = ElementTree.fromstring(policy)
for dict_elements in xml_policy.iterfind('dict'):
key_values = [value.text for value in dict_elements.getchildren()]
policy_dict = dict(zip(key_values[0::2], key_values[1::2]))
if policy_dict.get('passwordLastSetTime', 0):
timestamp = timelib.Timestamp.FromTimeString(
policy_dict.get('passwordLastSetTime', '0'))
if timestamp > cocoa_zero:
# Extract the hash password information.
# It is store in the attribure ShadowHasData which is
# a binary plist data; However binplist only extract one
# level of binary plist, then it returns this information
# as a string.
# TODO: change this into a DataRange instead. For this we
# need the file offset and size of the ShadowHashData value data.
resolver_context = context.Context()
fake_file = fake_file_io.FakeFile(
resolver_context, match['ShadowHashData'][0])
fake_file.open(path_spec=fake_path_spec.FakePathSpec(
location=u'ShadowHashData'))
try:
plist_file = binplist.BinaryPlist(file_obj=fake_file)
top_level = plist_file.Parse()
except binplist.FormatError:
top_level = dict()
salted_hash = top_level.get('SALTED-SHA512-PBKDF2', None)
if salted_hash:
password_hash = u'$ml${0:d}${1:s}${2:s}'.format(
salted_hash['iterations'],
binascii.hexlify(salted_hash['salt']),
binascii.hexlify(salted_hash['entropy']))
else:
password_hash = u'N/A'
description = (
u'Last time {0:s} ({1!s}) changed the password: {2!s}').format(
account, uid, password_hash)
event_object = plist_event.PlistTimeEvent(
self._ROOT, u'passwordLastSetTime', timestamp, description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if policy_dict.get('lastLoginTimestamp', 0):
timestamp = timelib.Timestamp.FromTimeString(
policy_dict.get('lastLoginTimestamp', '0'))
description = u'Last login from {0:s} ({1!s})'.format(account, uid)
if timestamp > cocoa_zero:
event_object = plist_event.PlistTimeEvent(
self._ROOT, u'lastLoginTimestamp', timestamp, description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if policy_dict.get('failedLoginTimestamp', 0):
timestamp = timelib.Timestamp.FromTimeString(
policy_dict.get('failedLoginTimestamp', '0'))
description = (
u'Last failed login from {0:s} ({1!s}) ({2!s} times)').format(
account, uid, policy_dict['failedLoginCount'])
if timestamp > cocoa_zero:
event_object = plist_event.PlistTimeEvent(
self._ROOT, u'failedLoginTimestamp', timestamp, description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(MacUserPlugin)
@@ -0,0 +1,73 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Tests for the Mac OS X local users plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.lib import timelib_test
from plaso.parsers import plist
from plaso.parsers.plist_plugins import macuser
from plaso.parsers.plist_plugins import test_lib
class MacUserPluginTest(test_lib.PlistPluginTestCase):
"""Tests for the Mac OS X local user plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = macuser.MacUserPlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
plist_name = u'user.plist'
test_file = self._GetTestFilePath([plist_name])
event_queue_consumer = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 1)
event_object = event_objects[0]
expected_timestamp = timelib_test.CopyStringToTimestamp(
'2013-12-28 04:35:47')
self.assertEqual(event_object.timestamp, expected_timestamp)
self.assertEqual(event_object.key, u'passwordLastSetTime')
self.assertEqual(event_object.root, u'/')
expected_desc = (
u'Last time user (501) changed the password: '
u'$ml$37313$fa6cac1869263baa85cffc5e77a3d4ee164b7'
u'5536cae26ce8547108f60e3f554$a731dbb0e386b169af8'
u'9fbb33c255ceafc083c6bc5194853f72f11c550c42e4625'
u'ef113b66f3f8b51fc3cd39106bad5067db3f7f1491758ff'
u'e0d819a1b0aba20646fd61345d98c0c9a411bfd1144dd4b'
u'3c40ec0f148b66d5b9ab014449f9b2e103928ef21db6e25'
u'b536a60ff17a84e985be3aa7ba3a4c16b34e0d1d2066ae178')
self.assertEqual(event_object.desc, expected_desc)
expected_string = u'//passwordLastSetTime {}'.format(expected_desc)
expected_short = u'{}...'.format(expected_string[:77])
self._TestGetMessageStrings(
event_object, expected_string, expected_short)
if __name__ == '__main__':
unittest.main()
+97
View File
@@ -0,0 +1,97 @@
#!/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 a default plist plugin in Plaso."""
import logging
from plaso.events import time_events
from plaso.lib import eventdata
from plaso.lib import timelib
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
class SafariHistoryEvent(time_events.TimestampEvent):
"""An EventObject for Safari history entries."""
def __init__(self, timestamp, history_entry):
"""Initialize the event.
Args:
timestamp: The timestamp of the Event, in microseconds since Unix Epoch.
history_entry: A dict object read from the Safari history plist.
"""
super(SafariHistoryEvent, self).__init__(
timestamp, eventdata.EventTimestamp.LAST_VISITED_TIME)
self.data_type = 'safari:history:visit'
self.url = history_entry.get('', None)
self.title = history_entry.get('title', None)
display_title = history_entry.get('displayTitle', None)
if display_title != self.title:
self.display_title = display_title
self.visit_count = history_entry.get('visitCount', None)
self.was_http_non_get = history_entry.get('lastVisitWasHTTPNonGet', None)
class SafariHistoryPlugin(interface.PlistPlugin):
"""Plugin to extract Safari history timestamps."""
NAME = 'safari_history'
DESCRIPTION = u'Parser for Safari history plist files.'
PLIST_PATH = 'History.plist'
PLIST_KEYS = frozenset(['WebHistoryDates', 'WebHistoryFileVersion'])
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, match=None,
**unused_kwargs):
"""Extracts Safari history items.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
match: Optional dictionary containing keys extracted from PLIST_KEYS.
The default is None.
"""
if match.get('WebHistoryFileVersion', 0) != 1:
logging.warning(u'Unable to parse Safari version: {0:s}'.format(
match.get('WebHistoryFileVersion', 0)))
return
for history_entry in match.get('WebHistoryDates', {}):
try:
time = timelib.Timestamp.FromCocoaTime(float(
history_entry.get('lastVisitedDate', 0)))
except ValueError:
logging.warning(u'Unable to translate timestamp: {0:s}'.format(
history_entry.get('lastVisitedDate', 0)))
continue
if not time:
logging.debug('No timestamp set, skipping record.')
continue
event_object = SafariHistoryEvent(time, history_entry)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(SafariHistoryPlugin)
@@ -0,0 +1,65 @@
#!/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.
"""Tests for the Safari history plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.lib import timelib_test
from plaso.parsers import plist
from plaso.parsers.plist_plugins import safari
from plaso.parsers.plist_plugins import test_lib
class SafariPluginTest(test_lib.PlistPluginTestCase):
"""Tests for the Safari history plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = safari.SafariHistoryPlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
test_file = self._GetTestFilePath(['History.plist'])
plist_name = 'History.plist'
event_queue_consumer = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
# 18 entries in timeline.
self.assertEquals(len(event_objects), 18)
event_object = event_objects[8]
expected_timestamp = timelib_test.CopyStringToTimestamp(
'2013-07-08 17:31:00')
self.assertEquals(event_objects[10].timestamp, expected_timestamp)
expected_url = u'http://netverslun.sci-mx.is/aminosyrur'
self.assertEquals(event_object.url, expected_url)
expected_string = (
u'Visited: {0:s} (Am\xedn\xf3s\xfdrur ) Visit Count: 1').format(
expected_url)
self._TestGetMessageStrings(event_object, expected_string, expected_string)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,83 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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 a default plist plugin in Plaso."""
from plaso.events import plist_event
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
__author__ = 'Joaquin Moreno Garijo (Joaquin.MorenoGarijo.2013@live.rhul.ac.uk)'
class SoftwareUpdatePlugin(interface.PlistPlugin):
"""Basic plugin to extract the Mac OS X update status."""
NAME = 'plist_softwareupdate'
DESCRIPTION = u'Parser for Mac OS X software update plist files.'
PLIST_PATH = 'com.apple.SoftwareUpdate.plist'
PLIST_KEYS = frozenset([
'LastFullSuccessfulDate', 'LastSuccessfulDate',
'LastAttemptSystemVersion', 'LastUpdatesAvailable',
'LastRecommendedUpdatesAvailable', 'RecommendedUpdates'])
# Generated events:
# LastFullSuccessfulDate: timestamp when Mac OS X was full update.
# LastSuccessfulDate: timestamp when Mac OS X was partially update.
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, match=None,
**unused_kwargs):
"""Extracts relevant Mac OS X update entries.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
match: Optional dictionary containing keys extracted from PLIST_KEYS.
The default is None.
"""
root = '/'
key = ''
version = match.get('LastAttemptSystemVersion', u'N/A')
pending = match['LastUpdatesAvailable']
description = u'Last Mac OS X {0:s} full update.'.format(version)
event_object = plist_event.PlistEvent(
root, key, match['LastFullSuccessfulDate'], description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
if pending:
software = []
for update in match['RecommendedUpdates']:
software.append(u'{0:s}({1:s})'.format(
update['Identifier'], update['Product Key']))
description = (
u'Last Mac OS {0!s} partially update, pending {1!s}: {2:s}.').format(
version, pending, u','.join(software))
event_object = plist_event.PlistEvent(
root, key, match['LastSuccessfulDate'], description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(SoftwareUpdatePlugin)
@@ -0,0 +1,65 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Tests for the Software Update plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.parsers import plist
from plaso.parsers.plist_plugins import softwareupdate
from plaso.parsers.plist_plugins import test_lib
class SoftwareUpdatePluginTest(test_lib.PlistPluginTestCase):
"""Tests for the SoftwareUpdate plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = softwareupdate.SoftwareUpdatePlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
plist_name = u'com.apple.SoftwareUpdate.plist'
test_file = self._GetTestFilePath([plist_name])
event_queue_consumer = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 2)
event_object = event_objects[0]
self.assertEqual(event_object.key, u'')
self.assertEqual(event_object.root, u'/')
expected_desc = u'Last Mac OS X 10.9.1 (13B42) full update.'
self.assertEqual(event_object.desc, expected_desc)
expected_string = u'// {}'.format(expected_desc)
self._TestGetMessageStrings(
event_object, expected_string, expected_string)
event_object = event_objects[1]
self.assertEqual(event_object.key, u'')
self.assertEqual(event_object.root, u'/')
expected_desc = (
u'Last Mac OS 10.9.1 (13B42) partially '
u'update, pending 1: RAWCameraUpdate5.03(031-2664).')
self.assertEqual(event_object.desc, expected_desc)
if __name__ == '__main__':
unittest.main()
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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 the Spotlight searched terms plugin in Plaso."""
from plaso.events import plist_event
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
__author__ = 'Joaquin Moreno Garijo (Joaquin.MorenoGarijo.2013@live.rhul.ac.uk)'
class SpotlightPlugin(interface.PlistPlugin):
"""Basic plugin to extract Spotlight."""
NAME = 'plist_spotlight'
DESCRIPTION = u'Parser for Spotlight plist files.'
PLIST_PATH = 'com.apple.spotlight.plist'
PLIST_KEYS = frozenset(['UserShortcuts'])
# Generated events:
# name of the item: searched term.
# PATH: path of the program associated to the term.
# LAST_USED: last time when it was executed.
# DISPLAY_NAME: the display name of the program associated.
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, match=None,
**unused_kwargs):
"""Extracts relevant Spotlight entries.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
match: Optional dictionary containing keys extracted from PLIST_KEYS.
The default is None.
"""
for search_text, data in match['UserShortcuts'].iteritems():
description = (
u'Spotlight term searched "{0:s}" associate to {1:s} '
u'({2:s})').format(search_text, data['DISPLAY_NAME'], data['PATH'])
event_object = plist_event.PlistEvent(
u'/UserShortcuts', search_text, data['LAST_USED'], description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(SpotlightPlugin)
@@ -0,0 +1,69 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Tests for the spotlight plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.parsers import plist
from plaso.parsers.plist_plugins import spotlight
from plaso.parsers.plist_plugins import test_lib
class SpotlightPluginTest(test_lib.PlistPluginTestCase):
"""Tests for the spotlight plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = spotlight.SpotlightPlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
test_file = self._GetTestFilePath(['com.apple.spotlight.plist'])
plist_name = 'com.apple.spotlight.plist'
event_queue_consumer = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 9)
timestamps = []
for event_object in event_objects:
timestamps.append(event_object.timestamp)
expected_timestamps = frozenset([
1379937262090906, 1387822901900937, 1375236414408299, 1388331212005129,
1376696381196456, 1386951868185477, 1380942616952359, 1389056477460443,
1386111811136093])
self.assertTrue(set(timestamps) == expected_timestamps)
event_object = event_objects[1]
self.assertEqual(event_object.key, u'gr')
self.assertEqual(event_object.root, u'/UserShortcuts')
expected_desc = (u'Spotlight term searched "gr" associate to '
u'Grab (/Applications/Utilities/Grab.app)')
self.assertEqual(event_object.desc, expected_desc)
expected_string = u'/UserShortcuts/gr {}'.format(expected_desc)
expected_short = expected_string[:77] + u'...'
self._TestGetMessageStrings(
event_object, expected_string, expected_short)
if __name__ == '__main__':
unittest.main()
@@ -0,0 +1,60 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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 the Spotlight Volume Configuration plist in Plaso."""
from plaso.events import plist_event
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
__author__ = 'Joaquin Moreno Garijo (Joaquin.MorenoGarijo.2013@live.rhul.ac.uk)'
class SpotlightVolumePlugin(interface.PlistPlugin):
"""Basic plugin to extract the Spotlight Volume Configuration."""
NAME = 'plist_spotlight_volume'
DESCRIPTION = u'Parser for Spotlight volume configuration plist files.'
PLIST_PATH = 'VolumeConfiguration.plist'
PLIST_KEYS = frozenset(['Stores'])
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, match=None,
**unused_kwargs):
"""Extracts relevant VolumeConfiguration Spotlight entries.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
match: Optional dictionary containing keys extracted from PLIST_KEYS.
The default is None.
"""
for volume_name, volume in match['Stores'].iteritems():
description = u'Spotlight Volume {0:s} ({1:s}) activated.'.format(
volume_name, volume['PartialPath'])
event_object = plist_event.PlistEvent(
u'/Stores', '', volume['CreationDate'], description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(SpotlightVolumePlugin)
@@ -0,0 +1,67 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Tests for the Spotlight Volume configuration plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.parsers import plist
from plaso.parsers.plist_plugins import spotlight_volume
from plaso.parsers.plist_plugins import test_lib
class SpotlightVolumePluginTest(test_lib.PlistPluginTestCase):
"""Tests for the Spotlight Volume configuration plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = spotlight_volume.SpotlightVolumePlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
test_file = self._GetTestFilePath(['VolumeConfiguration.plist'])
plist_name = 'VolumeConfiguration.plist'
event_queue_consumer = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_queue_consumer)
self.assertEquals(len(event_objects), 2)
timestamps = []
for event_object in event_objects:
timestamps.append(event_object.timestamp)
expected_timestamps = frozenset([
1372139683000000, 1369657656000000])
self.assertTrue(set(timestamps) == expected_timestamps)
event_object = event_objects[0]
self.assertEqual(event_object.key, u'')
self.assertEqual(event_object.root, u'/Stores')
expected_desc = (u'Spotlight Volume 4D4BFEB5-7FE6-4033-AAAA-'
u'AAAABBBBCCCCDDDD (/.MobileBackups) activated.')
self.assertEqual(event_object.desc, expected_desc)
expected_string = u'/Stores/ {}'.format(expected_desc)
expected_short = expected_string[:77] + u'...'
self._TestGetMessageStrings(
event_object, expected_string, expected_short)
if __name__ == '__main__':
unittest.main()
+87
View File
@@ -0,0 +1,87 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Plist plugin related functions and classes for testing."""
from dfvfs.lib import definitions
from dfvfs.path import factory as path_spec_factory
from dfvfs.resolver import resolver as path_spec_resolver
from plaso.engine import single_process
from plaso.parsers import test_lib
class PlistPluginTestCase(test_lib.ParserTestCase):
"""The unit test case for a plist plugin."""
def _ParsePlistFileWithPlugin(
self, parser_object, plugin_object, path, plist_name,
knowledge_base_values=None):
"""Parses a file using the parser and plugin object.
Args:
parser_object: the parser object.
plugin_object: the plugin object.
path: the path of the file to parse.
plist_name: the name of the plist to parse.
knowledge_base_values: optional dict containing the knowledge base
values. The default is None.
Returns:
An event object queue consumer object (instance of
TestEventObjectQueueConsumer).
"""
path_spec = path_spec_factory.Factory.NewPathSpec(
definitions.TYPE_INDICATOR_OS, location=path)
file_entry = path_spec_resolver.Resolver.OpenFileEntry(path_spec)
file_object = file_entry.GetFileObject()
top_level_object = parser_object.GetTopLevel(file_object)
self.assertNotEquals(top_level_object, None)
return self._ParsePlistWithPlugin(
plugin_object, plist_name, top_level_object,
knowledge_base_values=knowledge_base_values)
def _ParsePlistWithPlugin(
self, plugin_object, plist_name, top_level_object,
knowledge_base_values=None):
"""Parses a plist using the plugin object.
Args:
plugin_object: the plugin object.
plist_name: the name of the plist to parse.
top_level_object: the top-level plist object.
knowledge_base_values: optional dict containing the knowledge base
values. The default is None.
Returns:
An event object queue consumer object (instance of
TestEventObjectQueueConsumer).
"""
event_queue = single_process.SingleProcessQueue()
event_queue_consumer = test_lib.TestEventObjectQueueConsumer(event_queue)
parse_error_queue = single_process.SingleProcessQueue()
parser_context = self._GetParserContext(
event_queue, parse_error_queue,
knowledge_base_values=knowledge_base_values)
plugin_object.Process(
parser_context, plist_name=plist_name, top_level=top_level_object)
return event_queue_consumer
@@ -0,0 +1,86 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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 a TimeMachine plist plugin in Plaso."""
import construct
from plaso.events import plist_event
from plaso.parsers import plist
from plaso.parsers.plist_plugins import interface
__author__ = 'Joaquin Moreno Garijo (Joaquin.MorenoGarijo.2013@live.rhul.ac.uk)'
class TimeMachinePlugin(interface.PlistPlugin):
"""Basic plugin to extract time machine hardisk and the backups."""
NAME = 'plist_timemachine'
DESCRIPTION = u'Parser for TimeMachine plist files.'
PLIST_PATH = 'com.apple.TimeMachine.plist'
PLIST_KEYS = frozenset(['Destinations', 'RootVolumeUUID'])
# Generated events:
# DestinationID: remote UUID hard disk where the backup is done.
# BackupAlias: structure that contains the extra information from the
# destinationID.
# SnapshotDates: list of the backup dates.
TM_BACKUP_ALIAS = construct.Struct(
'tm_backup_alias',
construct.Padding(10),
construct.PascalString('value', length_field=construct.UBInt8('length')))
def GetEntries(
self, parser_context, file_entry=None, parser_chain=None, match=None,
**unused_kwargs):
"""Extracts relevant TimeMachine entries.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: Optional file entry object (instance of dfvfs.FileEntry).
The default is None.
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
match: Optional dictionary containing keys extracted from PLIST_KEYS.
The default is None.
"""
root = '/Destinations'
key = 'item/SnapshotDates'
# For each TimeMachine devices.
for destination in match['Destinations']:
hd_uuid = destination['DestinationID']
if not hd_uuid:
hd_uuid = u'Unknown device'
alias = destination['BackupAlias']
try:
alias = self.TM_BACKUP_ALIAS.parse(alias).value
except construct.FieldError:
alias = u'Unknown alias'
# For each Backup.
for timestamp in destination['SnapshotDates']:
description = u'TimeMachine Backup in {0:s} ({1:s})'.format(
alias, hd_uuid)
event_object = plist_event.PlistEvent(root, key, timestamp, description)
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
plist.PlistParser.RegisterPlugin(TimeMachinePlugin)
@@ -0,0 +1,72 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright 2014 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.
"""Tests for the timemachine plist plugin."""
import unittest
# pylint: disable=unused-import
from plaso.formatters import plist as plist_formatter
from plaso.parsers import plist
from plaso.parsers.plist_plugins import timemachine
from plaso.parsers.plist_plugins import test_lib
class TimeMachinePluginTest(test_lib.PlistPluginTestCase):
"""Tests for the timemachine plist plugin."""
def setUp(self):
"""Sets up the needed objects used throughout the test."""
self._plugin = timemachine.TimeMachinePlugin()
self._parser = plist.PlistParser()
def testProcess(self):
"""Tests the Process function."""
test_file = self._GetTestFilePath(['com.apple.TimeMachine.plist'])
plist_name = 'com.apple.timemachine.plist'
event_object_generator = self._ParsePlistFileWithPlugin(
self._parser, self._plugin, test_file, plist_name)
event_objects = self._GetEventObjectsFromQueue(event_object_generator)
self.assertEquals(len(event_objects), 13)
timestamps = []
for event_object in event_objects:
timestamps.append(event_object.timestamp)
expected_timestamps = frozenset([
1379165051000000, 1380098455000000, 1380810276000000, 1381883538000000,
1382647890000000, 1383351739000000, 1384090020000000, 1385130914000000,
1386265911000000, 1386689852000000, 1387723091000000, 1388840950000000,
1388842718000000])
self.assertTrue(set(timestamps) == expected_timestamps)
event_object = event_objects[0]
self.assertEqual(event_object.root, u'/Destinations')
self.assertEqual(event_object.key, u'item/SnapshotDates')
expected_desc = (
u'TimeMachine Backup in BackUpFast '
u'(5B33C22B-A4A1-4024-A2F5-C9979C4AAAAA)')
self.assertEqual(event_object.desc, expected_desc)
expected_string = u'/Destinations/item/SnapshotDates {}'.format(
expected_desc)
expected_short = expected_string[:77] + u'...'
self._TestGetMessageStrings(
event_object, expected_string, expected_short)
if __name__ == '__main__':
unittest.main()