| HoneyClient-Agent documentation | Contained in the HoneyClient-Agent distribution. |
HoneyClient::Agent::Integrity::Filesystem - Perl extension to perform static checks of the Windows OS filesystem.
This documentation refers to HoneyClient::Agent::Integrity::Filesystem version 0.98.
use HoneyClient::Agent::Integrity::Filesystem;
use Data::Dumper;
# Create the filesystem object. Upon creation, the object will
# be initialized, by performing a baseline of the filesystem.
my $filesystem = HoneyClient::Agent::Integrity::Filesystem->new();
# ... Some time elapses ...
# Check the filesystem, for any violations.
my $changes = $filesystem->check();
if (!defined($changes)) {
print "No filesystem changes have occurred.\n";
} else {
print "Filesystem has changed:\n";
print Dumper($changes);
}
# $changes refers to an array of hashtable references, where
# each hashtable has the following format:
#
# $changes = [ {
# # Indicates if the filesystem entry was deleted,
# # added, or changed.
# 'status' => $STATUS_DELETED | $STATUS_ADDED | $STATUS_MODIFIED,
# 'name' => 'C:\WINDOWS\SYSTEM32...',
# 'mtime' => 'YYYY-MM-DD HH:MM:SS', # new mtime for added/modified files;
# # old mtime for deleted files
#
# # content will only exist for added/modified files
# 'content' => {
# 'size' => 1263, # size of new content
# 'type' => 'application/octect-stream', # type of new content
# 'md5' => 'b1946ac92492d2347c6235b4d2611184', # md5 of new content
# 'sha1' => 'f572d396fae9206628714fb2ce00f72e94f2258f', # sha1 of new content
# },
# }, ]
This library allows the Integrity module to easily baseline and check the Windows OS filesystem for any changes that may occur, while instrumenting a target application.
When a Filesystem $object is instantiated using the new() function, the following parameters are supplied default values. Each value can be overridden by specifying the new (key => value) pair into the new() function, as arguments.
When set to 1, the object will forgo any type of initial baselining process, upon initialization. Otherwise, baselining will occur as normal, upon initialization.
An array of hashtables used to hold the file analysis information, for the baseline filesystem operation.
The base list of drives, directories, and/or files to monitor.
The list of regular expressions that match drives, directories, and/or files to exclude from analysis.
The following functions have been implemented by any Filesystem object.
Creates a new Filesystem object, which contains a hashtable containing any of the supplied "param => value" arguments. Upon creation, the Filesystem object performs a baseline of the Windows filesystem.
Inputs:$param is an optional parameter variable.$value is $param's corresponding value.
Note: If any $param(s) are supplied, then an equal number of corresponding $value(s) must also be specified.
Output: The instantiated Filesystem $object, fully initialized.
Checks the filesystem for various changes, based upon the filesystem baseline, when the new() method was invoked.
Inputs:$no_prepare is an optional parameter, specifying the output format of the changes found.
Output:$changes, which is an array of hashtable references, where each hashtable has the following format:
If $no_prepare == 1, then the format will be:
$changes = [ {
# Indicates if the filesystem entry was deleted,
# added, or changed.
'status' => $STATUS_DELETED | $STATUS_ADDED | $STATUS_MODIFIED,
# If the entry has been added/changed, then this
# hashtable contains the file/directory's new information.
'new' => {
'name' => 'C:\WINDOWS\SYSTEM32...',
'size' => 1263, # in bytes
'mtime' => 1178135092, # modification time, seconds since epoch
},
# If the entry has been deleted/changed, then this
# hashtable contains the file/directory's old information.
'old' => {
'name' => 'C:\WINDOWS\SYSTEM32...',
'size' => 802, # in bytes
'mtime' => 1178135028, # modification time, seconds since epoch
},
}, ]
Otherwise, the format will be:
$changes = [ {
# Indicates if the filesystem entry was deleted,
# added, or changed.
'status' => $STATUS_DELETED | $STATUS_ADDED | $STATUS_MODIFIED,
'name' => 'C:\WINDOWS\SYSTEM32...',
'mtime' => 'YYYY-MM-DD HH:MM:SS', # new mtime for added/modified files;
# old mtime for deleted files
# content will only exist for added/modified files
'content' => {
'size' => 1263, # size of new content
'type' => 'application/octet-stream', # type of new content
'md5' => 'b1946ac92492d2347c6235b4d2611184', # md5 of new content
'sha1' => 'f572d396fae9206628714fb2ce00f72e94f2258f', # sha1 of new content
},
}, ]
Notes: If $no_prepare != 1 or $no_prepare == undef, then the outputted changes will NEVER refer to any directories. All the changes will correspond to individual files.
This library performs STATIC checks of the Windows filesystem. If malware modifies the filesystem between the time the $object->new() and $object->check() methods are called, then this library may FAIL to detect those changes if:
This library also only monitors FILE changes. Thus, if malware manipulates EMPTY DIRECTORIES or SYMLINKS on the system, then this library will NOT report those changes.
Mark-Jason Dominus <mjd-perl-diff@plover.com>, Ned Konz <perl@bike-nomad.com>, and Tye McQueen, for using their Algorithm::Diff code.
Xeno Kovah, <xkovah@mitre.org>
Darien Kindlund, <kindlund@mitre.org>
Brad Stephenson, <stephenson@mitre.org>
Copyright (C) 2007 The MITRE Corporation. All rights reserved.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, using version 2 of the License.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
| HoneyClient-Agent documentation | Contained in the HoneyClient-Agent distribution. |
################################################################################ # Created on: April 12, 2007 # Package: HoneyClient::Agent::Integrity::Filesystem # File: Filesystem.pm # Description: Performs static checks of the Windows OS filesystem. # # CVS: $Id: Filesystem.pm 773 2007-07-26 19:04:55Z kindlund $ # # @author xkovah, kindlund, stephenson # # Copyright (C) 2007 The MITRE Corporation. All rights reserved. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation, using version 2 # of the License. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. # ################################################################################
package HoneyClient::Agent::Integrity::Filesystem; use strict; use warnings; use Carp (); # Include Global Configuration Processing Library use HoneyClient::Util::Config qw(getVar); # Include the File/Directory Search Library use File::Find qw(find); # Include the Diff algorithm for comparing files use Algorithm::Diff; # Include Cygwin Path Conversion Library. use Filesys::CygwinPaths qw(:all); # Use Storable Library use Storable qw(nfreeze thaw dclone); $Storable::Deparse = 1; $Storable::Eval = 1; # Use Dumper Library use Data::Dumper; # Use Basename Library use File::Basename qw(dirname); # Include Logging Library use Log::Log4perl qw(:easy); # Use DateTime Library use DateTime; # Use MD5 Library use Digest::MD5; # Use SHA Library use Digest::SHA; # Use File::Type Library use File::Type; # Use IO::File Library use IO::File; ####################################################################### # Module Initialization # ####################################################################### BEGIN { # Defines which functions can be called externally. require Exporter; our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION); # Set our package version. $VERSION = 0.98; @ISA = qw(Exporter); # Symbols to export automatically @EXPORT = qw( ); # Items to export into callers namespace by default. Note: do not export # names by default without a very good reason. Use EXPORT_OK instead. # Do not simply export all your public functions/methods/constants. # This allows declaration use HoneyClient::Agent::Integrity::Filesystem ':all'; # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK # will save memory. %EXPORT_TAGS = ( 'all' => [ qw( ) ], ); # Symbols to autoexport (when qw(:all) tag is used) @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); $SIG{PIPE} = 'IGNORE'; # Do not exit on broken pipes. } our (@EXPORT_OK, $VERSION);
####################################################################### # Global Configuration Variables # ####################################################################### # TODO: Need to link these constants with DB code. # Filesystem Status Identifiers our $STATUS_DELETED = 0; our $STATUS_ADDED = 1; our $STATUS_MODIFIED = 2; # TODO: Need to link these constants with DB code. # Set hash value to this constant, if unable to compute. our $HASH_UNKNOWN = 'UNKNOWN'; # Set type value to this constant, if unable to compute. our $TYPE_UNKNOWN = 'UNKNOWN'; # The global logging object. our $LOG = get_logger(); # Temporary global array reference, used to hold the file analysis information, # for the baseline and check operations. my $file_analysis = [ ]; # The global delimeter used for storing file analysis information inside # a single string. our $DELIMETER = ":"; # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0;
my %PARAMS = ( # When set to 1, the object will forgo any type of initial baselining # process, upon initialization. Otherwise, baselining will occur # as normal, upon initialization. bypass_baseline => 0, # An array of hashtables used to hold the file analysis information, # for the baseline filesystem operation. baseline_analysis => [ ], # The base list of drives/directories/files to monitor. monitored_directories => getVar(name => 'directories_to_check')->{name}, # The list of drives/directories/files to ignore. ignored_entries => getVar(name => 'exclude_list')->{regex}, ); ####################################################################### # Private Methods Implemented # ####################################################################### # A helper function, designed to baseline the filesystem. # # Input: self # Output: none sub _baseline { # Extract arguments. my ($self) = @_; # Convert monitored directories to a Cygwin-style format. my @search_dirs; foreach (@{$self->{monitored_directories}}) { push (@search_dirs, posixpath($_)); } # Save converted results back into our object. $self->{monitored_directories} = \@search_dirs; # Convert ignored entires to a Cygwin-style format. my @ignored_entries; foreach (@{$self->{ignored_entries}}) { push (@ignored_entries, posixpath($_)); } # Save converted results back into our object. $self->{ignored_entries} = \@ignored_entries; # Analyze filesystem. $self->_analyze(); # Save analyzed results to object's baseline array. $self->{baseline_analysis} = $file_analysis; } # A helper function, designed to analyze the filesystem # and create entries of filesystem objects. # # Input: self # Output: none sub _analyze { # Extract arguments. my ($self) = @_; # Clear previous analysis array. $file_analysis = [ ]; # Search the filesystem. # Trap and ignore all warnings from the find operation. { no warnings; find(\&_processFile, @{$self->{monitored_directories}}); }; } # A helper callback function, designed to populate the $file_analysis # global array reference with hashtable entries about filesystem objects. # # Input: none # Output: none sub _processFile { # Get file stats. my @attr = stat($File::Find::name); # Create a new entry. my $entry = { name => defined($File::Find::name) ? $File::Find::name : 'UNKNOWN', size => defined($attr[7]) ? $attr[7] : 0, mtime => defined($attr[9]) ? $attr[9] : 0, }; # Push entry onto analysis array. push (@{$file_analysis}, $entry); } # A helper callback function, designed to stringify each filesystem # entry object. Used by Algorithm::Diff operations. # # Input: filesystem entry # Output: unique string sub _toString { # Extract arguments. my ($entry) = @_; # Check to make sure that each entry is defined. my $name = defined($entry->{name}) ? $entry->{name} : ""; my $size = defined($entry->{size}) ? $entry->{size} : ""; my $mtime = defined($entry->{mtime}) ? $entry->{mtime} : ""; my $string = $name . $DELIMETER . $size . $DELIMETER . $mtime . $DELIMETER; return $string; } # A helper function, designed to take the output of an Algorithm::Diff object # and return a list of changes found in the filesystem. # # Input: Algorithm::Diff object # Output: Array reference of hashtables # Notes: This function returns hashtables in the following # format: # # $changes = [ { # # Indicates if the filesystem entry was deleted, # # added, or changed. # 'status' => $STATUS_DELETED | $STATUS_ADDED | $STATUS_MODIFIED, # # # If the entry has been added/changed, then this # # hashtable contains the file/directory's new information. # 'new' => { # 'name' => 'C:\WINDOWS\SYSTEM32...', # 'size' => 1263, # in bytes # 'mtime' => 1178135092, # modification time, seconds since epoch # }, # # # If the entry has been deleted/changed, then this # # hashtable contains the file/directory's old information. # 'old' => { # 'name' => 'C:\WINDOWS\SYSTEM32...', # 'size' => 802, # in bytes # 'mtime' => 1178135028, # modification time, seconds since epoch # }, # }, ] sub _diff { # Extract arguments. my ($self, $diff) = @_; # List of changes found. my $ret = [ ]; # Temporary variables. my $index; my $old_entry; my $new_entry; while ($diff->Next()) { # Ignore all matches. next if $diff->Same(); # Check if entries were deleted. if(!$diff->Items(2)) { for ($diff->Items(1)) { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Deleted - " . Dumper($_)); push (@{$ret}, { 'status' => $STATUS_DELETED, 'old' => $_, }); } # Check if entries were added. } elsif(!$diff->Items(1)) { for ($diff->Items(2)) { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Added - " . Dumper($_)); push (@{$ret}, { 'status' => $STATUS_ADDED, 'new' => $_, }); } # Check if entries are different. } else { # This is the complicated case where there may be a single change or # multiple changes which are contiguous my $size_of_1 = scalar($diff->Items(1)); my $size_of_2 = scalar($diff->Items(2)); # There are multiples, but the same number from each scan if ($size_of_1 == $size_of_2) { $index = 0; for ($diff->Items(1)) { $old_entry = $_; $new_entry = ($diff->Items(2))[$index]; # If the entry names are the same, then we know the contents # of the entry have changed. if ($old_entry->{name} eq $new_entry->{name}) { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Changed - Old - " . Dumper($old_entry) . " - New - " . Dumper($new_entry)); push (@{$ret}, { 'status' => $STATUS_MODIFIED, 'old' => $old_entry, 'new' => $new_entry, }); # Otherwise, the old entry got deleted and the new entry got # added. } else { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Deleted - " . Dumper($old_entry)); $LOG->debug("File Added - " . Dumper($new_entry)); push (@{$ret}, { 'status' => $STATUS_DELETED, 'old' => $old_entry, }); push (@{$ret}, { 'status' => $STATUS_ADDED, 'new' => $new_entry, }); } $index++; } # There are more contiguous entries in the baseline. } elsif ($size_of_1 > $size_of_2) { $index = 0; for ($diff->Items(1)) { $old_entry = $_; $new_entry = ($diff->Items(2))[$index]; # If the entry names are the same, then we know the contents # of the entry have changed. if (defined($new_entry) && ($old_entry->{name} eq $new_entry->{name})) { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Changed - Old - " . Dumper($old_entry) . " - New - " . Dumper($new_entry)); push (@{$ret}, { 'status' => $STATUS_MODIFIED, 'old' => $old_entry, 'new' => $new_entry, }); $index++; # Otherwise, the old entry got deleted and the new entry got # added (possibly). } else { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Deleted - " . Dumper($old_entry)); push (@{$ret}, { 'status' => $STATUS_DELETED, 'old' => $old_entry, }); # Mark the new entry as added, if and only if it's defined # and NOT the final new entry in this chunk. if (defined($new_entry) && ($index < ($size_of_2 - 1))) { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Added - " . Dumper($new_entry)); push (@{$ret}, { 'status' => $STATUS_ADDED, 'new' => $new_entry, }); $index++; } } } # If we still have a final new entry to process, and we're finished # with all old entries, then we know the new entry was a filesystem # addition. if ($index < $size_of_2) { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Added - " . Dumper($new_entry)); push (@{$ret}, { 'status' => $STATUS_ADDED, 'new' => $new_entry, }); } # There are more contiguous entries in the second scan. } else { $index = 0; for ($diff->Items(2)) { $old_entry = ($diff->Items(1))[$index]; $new_entry = $_; # If the entry names are the same, then we know the contents # of the entry have changed. if (defined($old_entry) && ($old_entry->{name} eq $new_entry->{name})) { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Changed - Old - " . Dumper($old_entry) . " - New - " . Dumper($new_entry)); push (@{$ret}, { 'status' => $STATUS_MODIFIED, 'old' => $old_entry, 'new' => $new_entry, }); $index++; # Otherwise, the old entry got (possibly) deleted and the new entry got # added. } else { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Added - " . Dumper($new_entry)); push (@{$ret}, { 'status' => $STATUS_ADDED, 'new' => $new_entry, }); # Mark the old entry as deleted, if and only if it's defined # and NOT the final old entry in this chunk. if (defined($old_entry) && ($index < ($size_of_1 - 1))) { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Deleted - " . Dumper($old_entry)); push (@{$ret}, { 'status' => $STATUS_DELETED, 'old' => $old_entry, }); $index++; } } } # If we still have a final old entry to process, and we're finished # with all the new entries, then we know the old entry was a filesystem # deletion. if ($index < $size_of_1) { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; $LOG->debug("File Deleted - " . Dumper($old_entry)); push (@{$ret}, { 'status' => $STATUS_DELETED, 'old' => $old_entry, }); } } } } return $ret; } # A helper function, designed to filter out changes that should be # ignored and correlate matching add/delete entries as changes, # instead of separate add/delete entries. # # Input: Array reference of hashtables # Output: Array reference of hashtables (filtered) # Notes: This function expects and returns hashtables in the following # format: # # $changes = [ { # # Indicates if the filesystem entry was deleted, # # added, or changed. # 'status' => $STATUS_DELETED | $STATUS_ADDED | $STATUS_MODIFIED, # # # If the entry has been added/changed, then this # # hashtable contains the file/directory's new information. # 'new' => { # 'name' => 'C:\WINDOWS\SYSTEM32...', # 'size' => 1263, # in bytes # 'mtime' => 1178135092, # modification time, seconds since epoch # }, # # # If the entry has been deleted/changed, then this # # hashtable contains the file/directory's old information. # 'old' => { # 'name' => 'C:\WINDOWS\SYSTEM32...', # 'size' => 802, # in bytes # 'mtime' => 1178135028, # modification time, seconds since epoch # }, # }, ] sub _filter { my ($self, $changes) = @_; my $ret = []; my $entries_by_name = { }; foreach (@{$changes}) { # Extract the file name from each entry. my $name = undef; if (($_->{status} == $STATUS_ADDED) or ($_->{status} == $STATUS_MODIFIED)) { $name = $_->{'new'}->{name}; } else { $name = $_->{'old'}->{name}; } # Check to make sure a name exists, skip if not found. if (!defined($name)) { next; } # Set an exclude flag. my $exclude_flag = 0; foreach (@{$self->{ignored_entries}}) { if ($name =~ /^$_$/i) { $exclude_flag = 1; last; # We only need to set the flag once. } } # Skip if excluded. if ($exclude_flag) { $LOG->debug("Excluding '" . win32path($name) . "' from integrity checks."); next; } # Check to see if an entry with the same name has already # been pushed onto our return array. if (exists($entries_by_name->{$name}) && defined($entries_by_name->{$name})) { $LOG->debug("Correlating multiple filesystem changes for '" . $name . "'."); my $prev_entry = $entries_by_name->{$name}; my $curr_entry = $_; # Sanity check. if ((($prev_entry->{status} == $STATUS_MODIFIED) || ($curr_entry->{status} == $STATUS_MODIFIED)) || (($prev_entry->{status} == $STATUS_ADDED) && ($curr_entry->{status} == $STATUS_ADDED)) || (($prev_entry->{status} == $STATUS_DELETED) && ($curr_entry->{status} == $STATUS_DELETED))) { $LOG->error("Duplicate filesystem change entries were found. " . "Previous Entry - " . Dumper($prev_entry) . " - ". "Current Entry - " . Dumper($curr_entry)); push (@{$ret}, $_); next; } # If the previous entry was added and the current # was deleted. if (($prev_entry->{status} == $STATUS_ADDED) && ($curr_entry->{status} == $STATUS_DELETED)) { $prev_entry->{status} = $STATUS_MODIFIED; $prev_entry->{old} = $curr_entry->{old}; # Otherwise, if the previous entry was deleted and the # current was added. } else { $prev_entry->{status} = $STATUS_MODIFIED; $prev_entry->{'new'} = $curr_entry->{'new'}; } } else { # The entry is completely new, so record it. $entries_by_name->{$name} = $_; # And push it onto our return array. push (@{$ret}, $_); } } return $ret; } # A helper function, designed to manipulate the array of changes into # a format that is expected by the check() function -- collecting # more forensic data about each change along the way. # # Input: Array reference of hashtables # Output: Array reference of hashtables (manipulated) # Notes: This function expects hashtables in the following # format: # # $inputChanges = [ { # # Indicates if the filesystem entry was deleted, # # added, or changed. # 'status' => $STATUS_DELETED | $STATUS_ADDED | $STATUS_MODIFIED, # # # If the entry has been added/changed, then this # # hashtable contains the file/directory's new information. # 'new' => { # 'name' => 'C:\WINDOWS\SYSTEM32...', # 'size' => 1263, # in bytes # 'mtime' => 1178135092, # modification time, seconds since epoch # }, # # # If the entry has been deleted/changed, then this # # hashtable contains the file/directory's old information. # 'old' => { # 'name' => 'C:\WINDOWS\SYSTEM32...', # 'size' => 802, # in bytes # 'mtime' => 1178135028, # modification time, seconds since epoch # }, # }, ] # # And outputs hashtables in the following format: # # $outputChanges = [ { # # Indicates if the filesystem entry was deleted, # # added, or changed. # 'status' => $STATUS_DELETED | $STATUS_ADDED | $STATUS_MODIFIED, # 'name' => 'C:\WINDOWS\SYSTEM32...', # 'mtime' => 'YYYY-MM-DD HH:MM:SS', # new mtime for added/modified files; # # old mtime for deleted files # # # content will only exist for added/modified files # 'content' => { # 'size' => 1263, # size of new content # 'type' => 'application/octet-stream', # type of new content # 'md5' => 'b1946ac92492d2347c6235b4d2611184', # md5 of new content # 'sha1' => 'f572d396fae9206628714fb2ce00f72e94f2258f', # sha1 of new content # }, # }, ] # sub _prepare { my ($self, $changes) = @_; my $ret = []; $LOG->debug("Preparing changes."); my $md5_ctx = Digest::MD5->new(); my $sha1_ctx = Digest::SHA->new("1"); my $type_ctx = File::Type->new(); foreach my $entry (@{$changes}) { # Construct a new entry in the new format. my $newEntry = { 'status' => $entry->{'status'}, }; # Figure out which type of entry it is. if ($entry->{'status'} == $STATUS_DELETED) { # Convert Filename $newEntry->{'name'} = _convertFilename($entry->{'old'}->{'name'}); $newEntry->{'mtime'} = _convertTime($entry->{'old'}->{'mtime'}); $LOG->debug("Filename: " . $newEntry->{'name'}); } else { $newEntry->{'name'} = $entry->{'new'}->{'name'}; $newEntry->{'mtime'} = _convertTime($entry->{'new'}->{'mtime'}); $LOG->debug("Filename: " . $newEntry->{'name'}); # Create a new file handle. my $fh = IO::File->new($newEntry->{'name'}, "r"); my $md5 = $HASH_UNKNOWN; my $sha1 = $HASH_UNKNOWN; my $type = $TYPE_UNKNOWN; # Check to make sure the new/changed file exists. if (defined($fh)) { # If the entry is a directory. if (-d $fh) { $type = "directory"; undef $fh; # XXX: We currently skip all entries that # only correspond to directories. # This is a known limitation. next; # If the entry is a symlink. } elsif (-l $newEntry->{'name'}) { $type = "symlink"; undef $fh; # XXX: We currently skip all entries that # only correspond to symlinks. # This is a known limitation. next; # If the entry is a file. } else { # Compute MD5 Checksum. $md5_ctx->addfile($fh); $md5 = $md5_ctx->hexdigest(); # Rewind file handle. seek($fh, 0, 0); # Compute SHA1 Checksum. $sha1_ctx->addfile($fh); $sha1 = $sha1_ctx->hexdigest(); # Close the file handle. undef $fh; # Compute File Type. $type = $type_ctx->mime_type($newEntry->{'name'}); } } # Populate the content, accordingly. $newEntry->{'content'} = { 'size' => $entry->{'new'}->{'size'}, 'type' => $type, 'md5' => $md5, 'sha1' => $sha1, }; # Convert Filename $newEntry->{'name'} = _convertFilename($newEntry->{'name'}); } # Finally, push it onto our return array. push (@{$ret}, $newEntry); } return $ret; } # Helper function, designed to convert seconds since epoch to # an ISO 8601 date time format. # # Input: epoch # Output: iso8601 date/time sub _convertTime { my $dt = DateTime->from_epoch(epoch => shift); return $dt->ymd('-') . " " . $dt->hms(':'); } # Helper function, designed to convert Cygwin filename paths to # a Windows format, where the output is always lowercase. # # Input: cygwin filename path # Output: absolute windows filename path sub _convertFilename { my $path = shift; # Unfortunately Filesys::CygwinPaths seems to like # to follow symbolic links, when resolving win32 paths. # This is bad. To counter this, we make sure the filename # we give it isn't a valid symlink so that it can properly # perform the conversion. if (-l $path) { $path .= "*"; $path = lc(fullwin32path($path)); chop($path); return $path; } else { return lc(fullwin32path($path)); } } ####################################################################### # Public Methods Implemented # #######################################################################
sub new { # - This function takes in an optional hashtable, # that contains various key => 'value' configuration # parameters. # # - For each parameter given, it overwrites any corresponding # parameters specified within the default hashtable, %PARAMS, # with custom entries that were given as parameters. # # - Finally, it returns a blessed instance of the # merged hashtable, as an 'object'. # Get the class name. my $self = shift; # Get the rest of the arguments, as a hashtable. # Hash-based arguments are used, since HoneyClient::Util::SOAP is unable to handle # hash references directly. Thus, flat hashtables are used throughout the code # for consistency. my %args = @_; # Check to see if the class name is inherited or defined. my $class = ref($self) || $self; # Initialize default parameters. $self = { }; my %params = %{dclone(\%PARAMS)}; @{$self}{keys %params} = values %params; # Now, overwrite any default parameters that were redefined # in the supplied arguments. @{$self}{keys %args} = values %args; # Now, assign our object the appropriate namespace. bless $self, $class; # Perform baselining, if not bypassed. if (!$self->{'bypass_baseline'}) { $LOG->info("Baselining filesystem."); $self->_baseline(); } # Finally, return the blessed object. return $self; }
sub check { # Extract arguments. my ($self, %args) = @_; # Sanity check: Make sure we've been fed an object. unless (ref($self)) { $LOG->error("Error: Function must be called in reference to a " . __PACKAGE__ . "->new() object!"); Carp::croak "Error: Function must be called in reference to a " . __PACKAGE__ . "->new() object!\n"; } # Log resolved arguments. $LOG->debug(sub { # Make Dumper format more terse. $Data::Dumper::Terse = 1; $Data::Dumper::Indent = 0; Dumper(\%args); }); # Sanity checks; check if any args were specified. my $argsExist = scalar(%args); # Analyze the filesystem. $LOG->info("Analyzing filesystem."); $self->_analyze(); # Compare analysis with baseline. $LOG->info("Checking for filesystem changes."); my $changes = $self->_diff(Algorithm::Diff->new($self->{baseline_analysis}, $file_analysis, { keyGen => \&_toString })); # Filter results. $changes = $self->_filter($changes); if (scalar(@{$changes})) { $LOG->warn("Filesystem changes found."); } else { $LOG->info("No filesystem changes found."); } # Prepare results, if not directed otherwise. if (!$argsExist || !exists($args{'no_prepare'}) || !defined($args{'no_prepare'}) || !$args{'no_prepare'}) { $changes = $self->_prepare($changes); } # Return formatted results. return $changes; } 1;