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

208 lines
6.4 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.
"""Parser for utmpx files."""
# TODO: Add support for other implementations than Mac OS X.
# The parser should be checked against IOS UTMPX file.
import construct
import logging
from plaso.lib import errors
from plaso.lib import event
from plaso.lib import eventdata
from plaso.lib import timelib
from plaso.parsers import interface
from plaso.parsers import manager
__author__ = 'Joaquin Moreno Garijo (Joaquin.MorenoGarijo.2013@live.rhul.ac.uk)'
class UtmpxMacOsXEvent(event.EventObject):
"""Convenience class for an event utmpx."""
DATA_TYPE = 'mac:utmpx:event'
def __init__(self, timestamp, user, terminal, status, computer_name):
"""Initializes the event object.
Args:
timestamp: when the terminal was started
user: active user name
terminal: name of the terminal
status: terminal status
computer_name: name of the host or IP.
"""
super(UtmpxMacOsXEvent, self).__init__()
self.timestamp = timestamp
self.timestamp_desc = eventdata.EventTimestamp.START_TIME
self.user = user
self.terminal = terminal
self.status = status
self.computer_name = computer_name
class UtmpxParser(interface.BaseParser):
"""Parser for UTMPX files."""
NAME = 'utmpx'
DESCRIPTION = u'Parser for UTMPX files.'
# INFO: Type is suppose to be a short (2 bytes),
# however if we analyze the file it is always
# byte follow by 3 bytes with \x00 value.
MAC_UTMPX_ENTRY = construct.Struct(
'utmpx_mac',
construct.String('user', 256),
construct.ULInt32('id'),
construct.String('tty_name', 32),
construct.ULInt32('pid'),
construct.ULInt16('status_type'),
construct.ULInt16('unknown'),
construct.ULInt32('timestamp'),
construct.ULInt32('microsecond'),
construct.String('hostname', 256),
construct.Padding(64))
MAC_UTMPX_ENTRY_SIZE = MAC_UTMPX_ENTRY.sizeof()
# 9, 10 and 11 are only for Darwin and IOS.
MAC_STATUS_TYPE = {
0: 'EMPTY',
1: 'RUN_LVL',
2: 'BOOT_TIME',
3: 'OLD_TIME',
4: 'NEW_TIME',
5: 'INIT_PROCESS',
6: 'LOGIN_PROCESS',
7: 'USER_PROCESS',
8: 'DEAD_PROCESS',
9: 'ACCOUNTING',
10: 'SIGNATURE',
11: 'SHUTDOWN_TIME'}
def _ReadEntry(self, file_object):
"""Reads an UTMPX entry.
Args:
file_object: a file-like object that points to an UTMPX file.
Returns:
An event object constructed from the UTMPX entry.
"""
data = file_object.read(self.MAC_UTMPX_ENTRY_SIZE)
if len(data) != self.MAC_UTMPX_ENTRY_SIZE:
return
try:
entry = self.MAC_UTMPX_ENTRY.parse(data)
except (IOError, construct.FieldError) as exception:
logging.warning(
u'Unable to parse Mac OS X UTMPX entry with error: {0:s}'.format(
exception))
return
user, _, _ = entry.user.partition('\x00')
if not user:
user = u'N/A'
terminal, _, _ = entry.tty_name.partition('\x00')
if not terminal:
terminal = u'N/A'
computer_name, _, _ = entry.hostname.partition('\x00')
if not computer_name:
computer_name = u'localhost'
value_status = self.MAC_STATUS_TYPE.get(entry.status_type, u'N/A')
status = u'{0}'.format(value_status)
timestamp = timelib.Timestamp.FromPosixTimeWithMicrosecond(
entry.timestamp, entry.microsecond)
return UtmpxMacOsXEvent(timestamp, user, terminal, status, computer_name)
def _VerifyStructure(self, file_object):
"""Verify that we are dealing with an UTMPX entry.
Args:
file_object: a file-like object that points to an UTMPX file.
Returns:
True if it is a UTMPX entry or False otherwise.
"""
# First entry is a SIGNAL entry of the file ("header").
try:
header = self.MAC_UTMPX_ENTRY.parse_stream(file_object)
except (IOError, construct.FieldError):
return False
user, _, _ = header.user.partition('\x00')
# The UTMPX_ENTRY structure will often successfully compile on various
# structures, such as binary plist files, and thus we need to do some
# additional validation. The first one is to check if the user name
# can be converted into a unicode string, otherwise we can assume
# we are dealing with non UTMPX data.
try:
_ = unicode(user)
except UnicodeDecodeError:
return False
if user != u'utmpx-1.00':
return False
if self.MAC_STATUS_TYPE[header.status_type] != 'SIGNATURE':
return False
if header.timestamp != 0 or header.microsecond != 0 or header.pid != 0:
return False
tty_name, _, _ = header.tty_name.partition('\x00')
hostname, _, _ = header.hostname.partition('\x00')
if tty_name or hostname:
return False
return True
def Parse(self, parser_context, file_entry, parser_chain=None):
"""Extract data from a UTMPX file.
Args:
parser_context: A parser context object (instance of ParserContext).
file_entry: A file entry object (instance of dfvfs.FileEntry).
parser_chain: Optional string containing the parsing chain up to this
point. The default is None.
"""
file_object = file_entry.GetFileObject()
if not self._VerifyStructure(file_object):
file_object.close()
raise errors.UnableToParseFile(
u'The file is not an UTMPX file.')
# Add ourselves to the parser chain, which will be used in all subsequent
# event creation in this parser.
parser_chain = self._BuildParserChain(parser_chain)
event_object = self._ReadEntry(file_object)
while event_object:
event_object.offset = file_object.tell()
parser_context.ProduceEvent(
event_object, parser_chain=parser_chain, file_entry=file_entry)
event_object = self._ReadEntry(file_object)
file_object.close()
manager.ParsersManager.RegisterParser(UtmpxParser)