230 lines
7.3 KiB
Python
230 lines
7.3 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 a parser for the Mac OS X MacKeeper cache database."""
|
|
|
|
import json
|
|
|
|
from plaso.lib import event
|
|
from plaso.lib import eventdata
|
|
from plaso.lib import timelib
|
|
from plaso.parsers import sqlite
|
|
from plaso.parsers.sqlite_plugins import interface
|
|
|
|
|
|
def DictToList(data_dict):
|
|
"""Take a dict object and return a list of strings back."""
|
|
ret_list = []
|
|
for key, value in data_dict.iteritems():
|
|
if key in ('body', 'datetime', 'type', 'room', 'rooms', 'id'):
|
|
continue
|
|
ret_list.append(u'{0:s} = {1!s}'.format(key, value))
|
|
|
|
return ret_list
|
|
|
|
|
|
def ExtractJQuery(jquery_raw):
|
|
"""Extract and return the data inside a JQuery as a dict object."""
|
|
data_part = u''
|
|
if not jquery_raw:
|
|
return {}
|
|
|
|
if '[' in jquery_raw:
|
|
_, _, first_part = jquery_raw.partition('[')
|
|
data_part, _, _ = first_part.partition(']')
|
|
elif jquery_raw.startswith('//'):
|
|
_, _, first_part = jquery_raw.partition('{')
|
|
data_part = u'{{{0:s}'.format(first_part)
|
|
elif '({' in jquery_raw:
|
|
_, _, first_part = jquery_raw.partition('(')
|
|
data_part, _, _ = first_part.rpartition(')')
|
|
|
|
if not data_part:
|
|
return {}
|
|
|
|
try:
|
|
data_dict = json.loads(data_part)
|
|
except ValueError:
|
|
return {}
|
|
|
|
return data_dict
|
|
|
|
|
|
def ParseChatData(data):
|
|
"""Parse a chat comment data dict and return a parsed one back.
|
|
|
|
Args:
|
|
data: A dict object that is parsed from the record.
|
|
|
|
Returns:
|
|
A dict object to store the results in.
|
|
"""
|
|
data_store = {}
|
|
|
|
if 'body' in data:
|
|
body = data.get('body', '').replace('\n', ' ')
|
|
if body.startswith('//') and '{' in body:
|
|
body_dict = ExtractJQuery(body)
|
|
title, _, _ = body.partition('{')
|
|
body = u'{0:s} <{1!s}>'.format(title[2:], DictToList(body_dict))
|
|
else:
|
|
body = 'No text.'
|
|
|
|
data_store['text'] = body
|
|
|
|
room = data.get('rooms', None)
|
|
if not room:
|
|
room = data.get('room', None)
|
|
if room:
|
|
data_store['room'] = room
|
|
|
|
data_store['id'] = data.get('id', None)
|
|
user = data.get('user', None)
|
|
if user:
|
|
try:
|
|
user_sid = int(user)
|
|
data_store['sid'] = user_sid
|
|
except (ValueError, TypeError):
|
|
data_store['user'] = user
|
|
|
|
return data_store
|
|
|
|
|
|
class MacKeeperCacheEvent(event.EventObject):
|
|
"""Convenience class for a MacKeeper Cache event."""
|
|
DATA_TYPE = 'mackeeper:cache'
|
|
|
|
def __init__(self, timestamp, description, identifier, url, data_dict):
|
|
"""Initializes the event object.
|
|
|
|
Args:
|
|
timestamp: A timestamp as a number of milliseconds since Epoch
|
|
or as a UTC string.
|
|
description: The description of the cache entry.
|
|
identifier: The row identifier.
|
|
url: The MacKeeper URL value that is stored in every event.
|
|
data_dict: A dict object with the descriptive information.
|
|
"""
|
|
super(MacKeeperCacheEvent, self).__init__()
|
|
|
|
# Two different types of timestamps stored in log files.
|
|
if type(timestamp) in (int, long):
|
|
self.timestamp = timelib.Timestamp.FromJavaTime(timestamp)
|
|
else:
|
|
self.timestamp = timelib.Timestamp.FromTimeString(timestamp)
|
|
|
|
self.timestamp_desc = eventdata.EventTimestamp.ADDED_TIME
|
|
self.description = description
|
|
self.offset = identifier
|
|
self.text = data_dict.get('text', None)
|
|
self.user_sid = data_dict.get('sid', None)
|
|
self.user_name = data_dict.get('user', None)
|
|
self.event_type = data_dict.get('event_type', None)
|
|
self.room = data_dict.get('room', None)
|
|
self.record_id = data_dict.get('id', None)
|
|
self.url = url
|
|
|
|
|
|
class MacKeeperCachePlugin(interface.SQLitePlugin):
|
|
"""Plugin for the MacKeeper Cache database file."""
|
|
|
|
NAME = 'mackeeper_cache'
|
|
DESCRIPTION = u'Parser for MacKeeper Cache SQLite database files.'
|
|
|
|
# Define the needed queries.
|
|
QUERIES = [((
|
|
'SELECT d.entry_ID AS id, d.receiver_data AS data, r.request_key, '
|
|
'r.time_stamp AS time_string FROM cfurl_cache_receiver_data d, '
|
|
'cfurl_cache_response r WHERE r.entry_ID = '
|
|
'd.entry_ID'), 'ParseReceiverData')]
|
|
|
|
# The required tables.
|
|
REQUIRED_TABLES = frozenset([
|
|
'cfurl_cache_blob_data', 'cfurl_cache_receiver_data',
|
|
'cfurl_cache_response'])
|
|
|
|
def ParseReceiverData(
|
|
self, parser_context, row, file_entry=None, parser_chain=None, query=None,
|
|
**unused_kwargs):
|
|
"""Parses a single row from the receiver and cache response table.
|
|
|
|
Args:
|
|
parser_context: A parser context object (instance of ParserContext).
|
|
row: The row resulting from the query.
|
|
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.
|
|
query: Optional query string. The default is None.
|
|
"""
|
|
data = {}
|
|
key_url = row['request_key']
|
|
|
|
data_dict = {}
|
|
description = 'MacKeeper Entry'
|
|
# Check the URL, since that contains vital information about the type of
|
|
# event we are dealing with.
|
|
if key_url.endswith('plist'):
|
|
description = 'Configuration Definition'
|
|
data['text'] = 'Plist content added to cache.'
|
|
elif key_url.startswith('http://event.zeobit.com'):
|
|
description = 'MacKeeper Event'
|
|
try:
|
|
_, _, part = key_url.partition('?')
|
|
data['text'] = part.replace('&', ' ')
|
|
except UnicodeDecodeError:
|
|
data['text'] = 'N/A'
|
|
elif key_url.startswith('http://account.zeobit.com'):
|
|
description = 'Account Activity'
|
|
_, _, activity = key_url.partition('#')
|
|
if activity:
|
|
data['text'] = u'Action started: {0:s}'.format(activity)
|
|
else:
|
|
data['text'] = u'Unknown activity.'
|
|
elif key_url.startswith('http://support.') and 'chat' in key_url:
|
|
description = 'Chat '
|
|
try:
|
|
jquery = unicode(row['data'])
|
|
except UnicodeDecodeError:
|
|
jquery = ''
|
|
|
|
data_dict = ExtractJQuery(jquery)
|
|
data = ParseChatData(data_dict)
|
|
|
|
data['entry_type'] = data_dict.get('type', '')
|
|
if data['entry_type'] == 'comment':
|
|
description += 'Comment'
|
|
elif data['entry_type'] == 'outgoing':
|
|
description += 'Outgoing Message'
|
|
elif data['entry_type'] == 'incoming':
|
|
description += 'Incoming Message'
|
|
else:
|
|
# Empty or not known entry type, generic status message.
|
|
description += 'Entry'
|
|
data['text'] = u';'.join(DictToList(data_dict))
|
|
if not data['text']:
|
|
data['text'] = 'No additional data.'
|
|
|
|
event_object = MacKeeperCacheEvent(
|
|
row['time_string'], description, row['id'], key_url, data)
|
|
parser_context.ProduceEvent(
|
|
event_object, query=query, parser_chain=parser_chain,
|
|
file_entry=file_entry)
|
|
|
|
|
|
sqlite.SQLiteParser.RegisterPlugin(MacKeeperCachePlugin)
|