174 lines
5.6 KiB
Python
174 lines
5.6 KiB
Python
#!/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 specifiSc language governing permissions and
|
|
# limitations under the License.
|
|
"""Parser for PL-SQL Developer Recall files."""
|
|
|
|
import construct
|
|
import os
|
|
|
|
from plaso.lib import errors
|
|
from plaso.lib import event
|
|
from plaso.lib import timelib
|
|
from plaso.lib import utils
|
|
from plaso.parsers import interface
|
|
from plaso.parsers import manager
|
|
|
|
|
|
class PlsRecallEvent(event.EventObject):
|
|
"""Convenience class for a PL-SQL Recall file container."""
|
|
|
|
DATA_TYPE = 'PLSRecall:event'
|
|
|
|
def __init__(self, timestamp, sequence, user, database, query):
|
|
"""Initializes the event object.
|
|
|
|
Args:
|
|
timestamp: The timestamp when the entry was created.
|
|
sequence: Sequence indicates the order of execution.
|
|
username: The username that made the query.
|
|
database_name: String containing the databe name.
|
|
query: String containing the PL-SQL query.
|
|
"""
|
|
super(PlsRecallEvent, self).__init__()
|
|
self.timestamp = timestamp
|
|
self.sequence = sequence
|
|
self.username = user
|
|
self.database_name = database
|
|
self.query = query
|
|
|
|
|
|
class PlsRecallParser(interface.BaseParser):
|
|
"""Parse PL-SQL Recall files.
|
|
|
|
Parser is based on a:
|
|
TRecallRecord = packed record
|
|
Sequence: Integer;
|
|
TimeStamp: TDateTime;
|
|
Username: array[0..30] of Char;
|
|
Database: array[0..80] of Char;
|
|
Text: array[0..4000] of Char;
|
|
end;
|
|
|
|
Delphi TDateTime is a little endian 64-bit
|
|
floating point without any time zone information
|
|
"""
|
|
|
|
NAME = 'pls_recall'
|
|
DESCRIPTION = u'Parser for PL-SQL Recall files.'
|
|
|
|
PLS_STRUCT = construct.Struct(
|
|
'PL-SQL_Recall',
|
|
construct.ULInt32('Sequence'),
|
|
construct.LFloat64('TimeStamp'),
|
|
construct.String('Username', 31, None, '\x00'),
|
|
construct.String('Database', 81, None, '\x00'),
|
|
construct.String('Query', 4001, None, '\x00'))
|
|
|
|
def Parse(self, parser_context, file_entry, parser_chain=None):
|
|
"""Extract entries from a PLSRecall.dat 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()
|
|
|
|
try:
|
|
is_pls = self.VerifyFile(file_object)
|
|
except (IOError, construct.FieldError) as exception:
|
|
file_object.close()
|
|
raise errors.UnableToParseFile((
|
|
u'Not a PLSrecall File, unable to parse.'
|
|
u'with error: {0:s}').format(exception))
|
|
|
|
if not is_pls:
|
|
file_object.close()
|
|
raise errors.UnableToParseFile(
|
|
u'Not a PLSRecall File, unable to parse.')
|
|
|
|
file_object.seek(0, os.SEEK_SET)
|
|
pls_record = self.PLS_STRUCT.parse_stream(file_object)
|
|
|
|
# Add ourselves to the parser chain, which will be used in all subsequent
|
|
# event creation in this parser.
|
|
parser_chain = self._BuildParserChain(parser_chain)
|
|
|
|
while pls_record:
|
|
event_object = PlsRecallEvent(
|
|
timelib.Timestamp.FromDelphiTime(pls_record.TimeStamp),
|
|
pls_record.Sequence, pls_record.Username,
|
|
pls_record.Database, pls_record.Query)
|
|
parser_context.ProduceEvent(
|
|
event_object, parser_chain=parser_chain, file_entry=file_entry)
|
|
|
|
try:
|
|
pls_record = self.PLS_STRUCT.parse_stream(file_object)
|
|
except construct.FieldError as exception:
|
|
# The code has reached the end of file (EOF).
|
|
break
|
|
|
|
file_object.close()
|
|
|
|
def VerifyFile(self, file_object):
|
|
"""Check if the file is a PLSRecall.dat file.
|
|
|
|
Args:
|
|
file_object: file that we want to check.
|
|
|
|
Returns:
|
|
True if this is a valid PLSRecall.dat file, otherwise False.
|
|
"""
|
|
file_object.seek(0, os.SEEK_SET)
|
|
|
|
# The file consists of PL-SQL structures that are equal
|
|
# size (4125 bytes) TRecallRecord records. It should be
|
|
# noted that the query value is free form.
|
|
try:
|
|
structure = self.PLS_STRUCT.parse_stream(file_object)
|
|
except (IOError, construct.FieldError):
|
|
return False
|
|
|
|
# Verify few entries inside the structure.
|
|
try:
|
|
timestamp = timelib.Timestamp.FromDelphiTime(structure.TimeStamp)
|
|
except ValueError:
|
|
return False
|
|
|
|
if timestamp <= 0:
|
|
return False
|
|
|
|
# TODO: Add other verification checks here. For instance make sure
|
|
# that the query actually looks like a SQL query. This structure produces a
|
|
# lot of false positives and thus we need to add additional verification to
|
|
# make sure we are not parsing non-PLSRecall files.
|
|
# Another check might be to make sure the username looks legitimate, or the
|
|
# sequence number, or the database name.
|
|
# For now we just check if all three fields pass our "is this a text" test.
|
|
if not utils.IsText(structure.Username):
|
|
return False
|
|
if not utils.IsText(structure.Query):
|
|
return False
|
|
if not utils.IsText(structure.Database):
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
manager.ParsersManager.RegisterParser(PlsRecallParser)
|