#!/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. """Parser for the Sleuthkit (TSK) bodyfile or mactime format. The format specifications can be read here: http://wiki.sleuthkit.org/index.php?title=Body_file """ import re from plaso.events import time_events from plaso.lib import eventdata from plaso.parsers import manager from plaso.parsers import text_parser class MactimeEvent(time_events.PosixTimeEvent): """Convenience class for a mactime-based event.""" DATA_TYPE = 'fs:mactime:line' def __init__(self, posix_time, usage, row_offset, data): """Initializes a mactime-based event object. Args: posix_time: The POSIX time value. usage: The description of the usage of the time value. row_offset: The offset of the row. data: A dict object containing extracted data from the body file. """ super(MactimeEvent, self).__init__(posix_time, usage) self.offset = row_offset self.user_sid = unicode(data.get('uid', u'')) self.user_gid = data.get('gid', None) self.md5 = data.get('md5', None) self.filename = data.get('name', 'N/A') # Check if the filename field is not a string, eg in the instances where a # filename only conists of numbers. In that case the self.filename field # becomes an integer value instead of a string value. That causes issues # later in the process, where we expect the filename value to be a string. if not isinstance(self.filename, basestring): self.filename = unicode(self.filename) self.mode_as_string = data.get('mode_as_string', None) self.size = data.get('size', None) inode_number = data.get('inode', 0) if isinstance(inode_number, basestring): if '-' in inode_number: inode_number, _, _ = inode_number.partition('-') try: inode_number = int(inode_number, 10) except ValueError: inode_number = 0 self.inode = inode_number class MactimeParser(text_parser.TextCSVParser): """Parses SleuthKit's mactime bodyfiles.""" NAME = 'mactime' DESCRIPTION = u'Parser for SleuthKit\'s mactime bodyfiles.' COLUMNS = [ 'md5', 'name', 'inode', 'mode_as_string', 'uid', 'gid', 'size', 'atime', 'mtime', 'ctime', 'crtime'] VALUE_SEPARATOR = '|' MD5_RE = re.compile('^[0-9a-fA-F]+$') _TIMESTAMP_DESC_MAP = { 'atime': eventdata.EventTimestamp.ACCESS_TIME, 'crtime': eventdata.EventTimestamp.CREATION_TIME, 'ctime': eventdata.EventTimestamp.CHANGE_TIME, 'mtime': eventdata.EventTimestamp.MODIFICATION_TIME, } def VerifyRow(self, unused_parser_context, row): """Verify we are dealing with a mactime bodyfile. Args: parser_context: A parser context object (instance of ParserContext). row: A single row from the CSV file. Returns: True if this is the correct parser, False otherwise. """ if not self.MD5_RE.match(row['md5']): return False try: # Verify that the "size" field is an integer, thus cast it to int # and then back to string so it can be compared, if the value is # not a string representation of an integer, eg: '12a' then this # conversion will fail and we return a False value. if str(int(row.get('size', '0'), 10)) != row.get('size', None): return False except ValueError: return False # TODO: Add additional verification. return True def ParseRow( self, parser_context, row_offset, row, file_entry=None, parser_chain=None): """Parses a row and extract event objects. Args: parser_context: A parser context object (instance of ParserContext). row_offset: The offset of the row. row: A dictionary containing all the fields as denoted in the COLUMNS class list. 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. """ for key, value in row.iteritems(): if isinstance(row[key], basestring): try: row[key] = int(value, 10) except ValueError: pass for key, timestamp_description in self._TIMESTAMP_DESC_MAP.iteritems(): value = row.get(key, None) if not value: continue event_object = MactimeEvent( value, timestamp_description, row_offset, row) parser_context.ProduceEvent( event_object, parser_chain=parser_chain, file_entry=file_entry) manager.ParsersManager.RegisterParser(MactimeParser)