root/honeyclient/branches/bug/42/lib/HoneyClient/Agent/Integrity/Registry.pm

Revision 128, 63.9 kB (checked in by kindlund, 2 years ago)

Added additional registry key to exclude.

  • Property svn:keywords set to Id "$file"
Line 
1 #######################################################################
2 # Created on:  Dec 03, 2006
3 # Package:     HoneyClient::Agent::Integrity::Registry
4 # File:        Registry.pm
5 # Description: Performs static checks of the Windows OS registry.
6 #
7 # CVS: $Id$
8 #
9 # @author kindlund, xkovah
10 #
11 # Copyright (C) 1998 Memorial University of Newfoundland.
12 # Copyright (C) 2006 The MITRE Corporation.  All rights reserved.
13 #
14 # This program is free software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation, using version 2
17 # of the License.
18 #
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22 # GNU General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301, USA.
28 #
29 #######################################################################
30
31 =pod
32
33 =head1 NAME
34
35 HoneyClient::Agent::Integrity::Registry - Perl extension to perform
36 static checks of the Windows OS registry.
37
38 =head1 VERSION
39
40 This documentation refers to HoneyClient::Agent::Integrity::Registry version 1.0.
41
42 =head1 SYNOPSIS
43
44   use HoneyClient::Agent::Integrity::Registry;
45   use Data::Dumper;
46
47   # Create the registry object.  Upon creation, the object
48   # will be initialized, by collecting a baseline of the registry.
49   my $registry = HoneyClient::Agent::Integrity::Registry->new();
50
51   # ... Some time elapses ...
52
53   # Check the registry, for any changes.
54   my $changes = $registry->check();
55
56   if (!defined($changes)) {
57       print "No registry changes have occurred.\n";
58   } else {
59       print "Registry has changed:\n";
60       print Dumper($changes);
61   }
62
63   # $changes refers to an array of hashtable references, where
64   # each hashtable has the following format:
65   #
66   # $changes = [ {
67   #     # The registry directory name.
68   #     'key' => 'HKEY_LOCAL_MACHINE\Software...',
69   #
70   #     # Indicates if the registry directory was deleted,
71   #     # added, or changed.
72   #     'status' => 'deleted' | 'added' | 'changed',
73   #
74   #     # An array containing the list of entries within the
75   #     # registry directory that have been deleted, added, or
76   #     # changed.  If this array is empty, then the corresponding
77   #     # registry directory in the original and new hives contained
78   #     # no entries.
79   #     'entries'  => [ {
80   #         'name' => "\"string\"",  # A (potentially) quoted string;
81   #                                  # "@" for default
82   #         'new_value' => "string", # New string; maybe undef, if deleted
83   #         'old_value' => "string", # Old string; maybe undef, if added
84   #     }, ],
85   # }, ]
86
87 =head1 DESCRIPTION
88
89 This library allows the Agent module to easily baseline and check
90 the Windows OS registry hives for any changes that may occur, while
91 instrumenting a target application.
92
93 This library uses modified code from the 'regutils' library by
94 John Rochester and Michael Rendell.
95 See L<http://www.cs.mun.ca/~michael/regutils/> for more information.
96
97 =cut
98
99 package HoneyClient::Agent::Integrity::Registry;
100
101 use strict;
102 use warnings;
103 use Carp ();
104
105 # Traps signals, allowing END: blocks to perform cleanup.
106 #use sigtrap qw(die untrapped normal-signals error-signals);
107
108 # Include Global Configuration Processing Library
109 use HoneyClient::Util::Config qw(getVar);
110
111 # Include Registry Parsing Library
112 use HoneyClient::Agent::Integrity::Registry::Parser;
113
114 # Use Dumper Library
115 use Data::Dumper;
116
117 # Use Storable Library
118 use Storable qw(dclone);
119
120 # Include Logging Library
121 use Log::Log4perl qw(:easy);
122
123 # Include File IO Libraries.
124 use IO::Handle;
125 use IO::File;
126 use Fcntl qw(:seek);
127
128 # Include Temporary File Libraries.
129 use File::Temp qw(tmpnam unlink0);
130
131 # Include Cygwin Path Conversion Library.
132 use Filesys::CygwinPaths qw(:all);
133
134 # Use Binary Search Library.
135 use Search::Binary;
136
137 #######################################################################
138 # Module Initialization                                               #
139 #######################################################################
140
141 BEGIN {
142     # Defines which functions can be called externally.
143     require Exporter;
144     our (@ISA, @EXPORT, @EXPORT_OK, %EXPORT_TAGS, $VERSION);
145
146     # Set our package version.
147     $VERSION = 0.9;
148
149     @ISA = qw(Exporter);
150
151     # Symbols to export on request
152     @EXPORT = qw( );
153
154     # Items to export into callers namespace by default. Note: do not export
155     # names by default without a very good reason. Use EXPORT_OK instead.
156     # Do not simply export all your public functions/methods/constants.
157
158     # This allows declaration use HoneyClient::Agent::Integrity::Registry ':all';
159     # If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
160     # will save memory.
161
162     %EXPORT_TAGS = (
163         'all' => [ qw( ) ],
164     );
165
166     # Symbols to autoexport (:DEFAULT tag)
167     @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
168
169     $SIG{PIPE} = 'IGNORE'; # Do not exit on broken pipes.
170 }
171 our (@EXPORT_OK, $VERSION);
172
173 =pod
174
175 =begin testing
176
177 # Make sure Log::Log4perl loads
178 BEGIN { use_ok('Log::Log4perl', qw(:nowarn))
179         or diag("Can't load Log::Log4perl package. Check to make sure the package library is correctly listed within the path.");
180        
181         # Suppress all logging messages, since we need clean output for unit testing.
182         Log::Log4perl->init({
183             "log4perl.rootLogger"                               => "DEBUG, Buffer",
184             "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
185             "log4perl.appender.Buffer.min_level"                => "fatal",
186             "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
187             "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
188         });
189 }
190 require_ok('Log::Log4perl');
191 use Log::Log4perl qw(:easy);
192
193 # Make sure the module loads properly, with the exportable
194 # functions shared.
195 BEGIN { use_ok('HoneyClient::Util::Config', qw(getVar setVar))
196         or diag("Can't load HoneyClient::Util::Config package.  Check to make sure the package library is correctly listed within the path."); }
197 require_ok('HoneyClient::Util::Config');
198 can_ok('HoneyClient::Util::Config', 'getVar');
199 can_ok('HoneyClient::Util::Config', 'setVar');
200 use HoneyClient::Util::Config qw(getVar setVar);
201
202 # Suppress all logging messages, since we need clean output for unit testing.
203 Log::Log4perl->init({
204     "log4perl.rootLogger"                               => "DEBUG, Buffer",
205     "log4perl.appender.Buffer"                          => "Log::Log4perl::Appender::TestBuffer",
206     "log4perl.appender.Buffer.min_level"                => "fatal",
207     "log4perl.appender.Buffer.layout"                   => "Log::Log4perl::Layout::PatternLayout",
208     "log4perl.appender.Buffer.layout.ConversionPattern" => "%d{yyyy-MM-dd HH:mm:ss} %5p [%M] (%F:%L) - %m%n",
209 });
210
211 # Make sure Data::Dumper loads
212 BEGIN { use_ok('Data::Dumper')
213         or diag("Can't load Data::Dumper package. Check to make sure the package library is correctly listed within the path."); }
214 require_ok('Data::Dumper');
215 use Data::Dumper;
216
217 # Make sure Storable loads
218 BEGIN { use_ok('Storable', qw(dclone))
219         or diag("Can't load Storable package. Check to make sure the package library is correctly listed within the path."); }
220 require_ok('Storable');
221 can_ok('Storable', 'dclone');
222 use Storable qw(dclone);
223
224 # Make sure IO::Handle loads
225 BEGIN { use_ok('IO::Handle')
226         or diag("Can't load IO::Handle package. Check to make sure the package library is correctly listed within the path."); }
227 require_ok('IO::Handle');
228 use IO::Handle;
229
230 # Make sure IO::File loads
231 BEGIN { use_ok('IO::File')
232         or diag("Can't load IO::File package. Check to make sure the package library is correctly listed within the path."); }
233 require_ok('IO::File');
234 use IO::File;
235
236 # Make sure Fcntl loads
237 BEGIN { use_ok('Fcntl')
238         or diag("Can't load Fcntl package. Check to make sure the package library is correctly listed within the path."); }
239 require_ok('Fcntl');
240 use Fcntl qw(:seek);
241
242 # Make sure File::Temp loads
243 BEGIN { use_ok('File::Temp')
244         or diag("Can't load File::Temp package. Check to make sure the package library is correctly listed within the path."); }
245 require_ok('File::Temp');
246 can_ok('File::Temp', 'tmpnam');
247 can_ok('File::Temp', 'unlink0');
248 use File::Temp qw(tmpnam unlink0);
249
250 # Make sure Filesys::CygwinPaths loads
251 BEGIN { use_ok('Filesys::CygwinPaths')
252         or diag("Can't load Filesys::CygwinPaths package. Check to make sure the package library is correctly listed within the path."); }
253 require_ok('Filesys::CygwinPaths');
254 use Filesys::CygwinPaths qw(:all);
255
256 # Make sure Search::Binary loads
257 BEGIN { use_ok('Search::Binary')
258         or diag("Can't load Search::Binary package. Check to make sure the package library is correctly listed within the path."); }
259 require_ok('Search::Binary');
260 can_ok('Search::Binary', 'binary_search');
261 use Search::Binary;
262
263 # Make sure HoneyClient::Agent::Integrity::Registry::Parser loads
264 BEGIN { use_ok('HoneyClient::Agent::Integrity::Registry::Parser')
265         or diag("Can't load HoneyClient::Agent::Integrity::Registry::Parser package. Check to make sure the package library is correctly listed within the path."); }
266 require_ok('HoneyClient::Agent::Integrity::Registry::Parser');
267 use HoneyClient::Agent::Integrity::Registry::Parser;
268
269 # Make sure HoneyClient::Agent::Integrity::Registry loads
270 BEGIN { use_ok('HoneyClient::Agent::Integrity::Registry')
271         or diag("Can't load HoneyClient::Agent::Integrity::Registry package. Check to make sure the package library is correctly listed within the path."); }
272 require_ok('HoneyClient::Agent::Integrity::Registry');
273 use HoneyClient::Agent::Integrity::Registry;
274
275 # Make sure File::Basename loads.
276 BEGIN { use_ok('File::Basename', qw(dirname basename fileparse)) or diag("Can't load File::Basename package.  Check to make sure the package library is correctly listed within the path."); }
277 require_ok('File::Basename');
278 can_ok('File::Basename', 'dirname');
279 can_ok('File::Basename', 'basename');
280 can_ok('File::Basename', 'fileparse');
281 use File::Basename qw(dirname basename fileparse);
282
283 =end testing
284
285 =cut
286
287 #######################################################################
288 # Global Configuration Variables
289 #######################################################################
290
291 # The global logging object.
292 our $LOG = get_logger();
293
294 # Make Dumper format more terse.
295 $Data::Dumper::Terse = 1;
296 $Data::Dumper::Indent = 0;
297
298 =pod
299
300 =head1 DEFAULT PARAMETER LIST
301
302 When a Registry B<$object> is instantiated using the B<new()> function,
303 the following parameters are supplied default values.  Each value
304 can be overridden by specifying the new (key => value) pair into the
305 B<new()> function, as arguments.
306
307 =head2 hives_to_check
308
309 =over 4
310
311 This parameter indicates the default array of registry hive names
312 to monitor for changes.
313
314 =back
315
316 =head2 key_dirnames_to_ignore
317
318 =over 4
319
320 This parameter indicates the default array of regular expressions
321 that each registry directory will be checked against.  Any matching
322 key directory names will be ignored and any subsequent additions,
323 deletions, or changes to all content in these matches will also
324 be ignored.
325
326 Each $entry will be used via the syntax /$entry/.  Thus,
327 it is recommended to specify the ^ prefix and $ suffix, when
328 possible.
329
330 A single backslash (\) must be represented using triple
331 backslashes (\\\) and each $entry must not end with any
332 backslash character.
333
334 =back
335
336 =head2 bypass_baseline
337
338 =over 4
339
340 When set to 1, the object will forgo any type of initial baselining
341 process, upon initialization.  Otherwise, baselining will occur
342 as normal, upon initialization.
343
344 =back
345
346 =cut
347
348 my %PARAMS = (
349
350     # An array, specifying which registry hives to
351     # analyze.
352     hives_to_check => [
353                         'HKEY_LOCAL_MACHINE',
354                         'HKEY_CLASSES_ROOT',
355                         'HKEY_CURRENT_USER',
356                         'HKEY_USERS',
357                         'HKEY_CURRENT_CONFIG',
358                       ],
359
360     # An array of regular expressions that each registry directory
361     # will be checked against.  Any matching key directory names will
362     # be ignored and any subsequent additions, deletions, or changes
363     # to all content in these matches will also be ignored.
364     #
365     # Each $entry will be used via the syntax /$entry/.  Thus,
366     # it is recommended to specify the ^ prefix and $ suffix, when
367     # possible.
368     #
369     # A single backslash (\) must be represented using triple
370     # backslashes (\\\) and each $entry must not end with any
371     # backslash character.
372     key_dirnames_to_ignore => [
373         '^HKEY_CURRENT_USER\\\SessionInformation.*$',
374         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Internet Explorer\\\Main$',
375         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Internet Explorer\\\Security\\\AntiPhishing.*$',
376         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Internet Explorer\\\TypedURLs$',
377         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Explorer\\\MenuOrder\\\Favorites\\\Links.*$',
378         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Explorer\\\MenuOrder\\\Start Menu2\\\Programs.*$',
379         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Explorer\\\MountPoints2\\\CPC\\\Volume.*$',
380         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Explorer\\\UserAssist\\\.+\\\Count.*$',
381         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Ext\\\Stats\\\.+\\\iexplore.*$',
382         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Internet Settings\\\Connections.*$',
383         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Internet Settings\\\5.0\\\Cache.*$',
384         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Windows\\\ShellNoRoam\\\DUIBags\\\ShellFolders\\\.*$',
385         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Windows\\\ShellNoRoam\\\BagMRU.*$',
386         '^HKEY_CURRENT_USER\\\Software\\\Microsoft\\\Windows\\\ShellNoRoam\\\MUICache.*$',
387         '^HKEY_CURRENT_USER\\\Volatile Environment$',
388         '^HKEY_LOCAL_MACHINE\\\SOFTWARE\\\Microsoft\\\Cryptography\\\RNG$',
389         '^HKEY_LOCAL_MACHINE\\\SOFTWARE\\\Microsoft\\\Windows\\\CurrentVersion\\\BITS$',
390         '^HKEY_LOCAL_MACHINE\\\SOFTWARE\\\Microsoft\\\Windows\\\CurrentVersion\\\Group Policy\\\State\\\Machine\\\Extension-List\\\.*$',
391         '^HKEY_LOCAL_MACHINE\\\SOFTWARE\\\Microsoft\\\Windows\\\CurrentVersion\\\WindowsUpdate\\\.*$',
392         '^HKEY_LOCAL_MACHINE\\\SOFTWARE\\\Microsoft\\\Windows\\\CurrentVersion\\\WindowsUpdate\\\Auto Update.*$',
393         '^HKEY_LOCAL_MACHINE\\\SOFTWARE\\\Microsoft\\\Windows NT\\\CurrentVersion\\\Winlogon\\\Notify\\\WgaLogon\\\Settings$',
394         '^HKEY_LOCAL_MACHINE\\\SYSTEM\\\ControlSet.+\\\Services\\\.+\\\Parameters\\\Tcpip.*$',
395         '^HKEY_LOCAL_MACHINE\\\SYSTEM\\\ControlSet.+\\\Services\\\Dhcp\\\Parameters.*$',
396         '^HKEY_LOCAL_MACHINE\\\SYSTEM\\\ControlSet.+\\\Services\\\Eventlog\\\Application\\\ESENT.*$',
397         '^HKEY_LOCAL_MACHINE\\\SYSTEM\\\ControlSet.+\\\Services\\\SharedAccess\\\Epoch.*$',
398         '^HKEY_LOCAL_MACHINE\\\SYSTEM\\\ControlSet.+\\\Services\\\Tcpip\\\Parameters\\\Interfaces\\\.*$',
399         '^HKEY_LOCAL_MACHINE\\\SYSTEM\\\CurrentControlSet\\\Services\\\Dhcp\\\Parameters.*$',
400         '^HKEY_LOCAL_MACHINE\\\SYSTEM\\\CurrentControlSet\\\Services\\\Eventlog\\\Application\\\ESENT.*$',
401         '^HKEY_LOCAL_MACHINE\\\SYSTEM\\\CurrentControlSet\\\Services\\\SharedAccess\\\Epoch$',
402         '^HKEY_LOCAL_MACHINE\\\SYSTEM\\\CurrentControlSet\\\Services\\\Tcpip\\\Parameters\\\Interfaces\\\.*$',
403         '^HKEY_LOCAL_MACHINE\\\SYSTEM\\\CurrentControlSet\\\Services\\\.+\\\Parameters\\\Tcpip.*$',
404         '^HKEY_USERS\\\.+\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Explorer\\\UserAssist\\\.+\\\Count.*$',
405         '^HKEY_USERS\\\.+\\\Software\\\Microsoft\\\Windows\\\ShellNoRoam\\\BagMRU.*$',
406         '^HKEY_USERS\\\.+\\\UNICODE Program Groups.*$',
407         '^HKEY_USERS\\\S.+\\\SessionInformation$',
408         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Internet Explorer\\\Main$',
409         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Internet Explorer\\\Security\\\AntiPhishing.*$',
410         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Internet Explorer\\\TypedURLs$',
411         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Explorer\\\MenuOrder\\\Favorites\\\Links.*$',
412         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Explorer\\\MenuOrder\\\Start Menu2\\\Programs.*$',
413         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Explorer\\\MountPoints2\\\CPC\\\Volume.*$',
414         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Ext\\\Stats\\\.+\\\iexplore.*$',
415         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Internet Settings\\\Connections.*$',
416         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Windows\\\CurrentVersion\\\Internet Settings\\\5.0\\\Cache.*$',
417         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Windows\\\ShellNoRoam\\\DUIBags\\\ShellFolders\\\.*$',
418         '^HKEY_USERS\\\S.+\\\Software\\\Microsoft\\\Windows\\\ShellNoRoam\\\MUICache.*$',
419     ],
420
421     # When set to 1, the object will forgo any type of initial baselining
422     # process, upon initialization.  Otherwise, baselining will occur
423     # as normal, upon initialization.
424     bypass_baseline => 0,
425
426     # Baseline File Collection
427     # A hashtable of file parsers, one parser per hive name.
428     # (For internal use only.)
429     _baseline_parsers => { },
430
431     # Checkpoint File Collection
432     # A hashtable of file parsers, one parser per hive name.
433     # (For internal use only.)
434     _checkpoint_parsers => { },
435
436     # A hashtable of file names, where the hash key is the file parser
437     # and the hash value is the file name.
438     # (For internal use only.)
439     _filenames => { },
440
441     # A hashtable of current key info objects, where the hash key is the
442     # file parser and the hash value is the info object.
443     # (For internal use only.)
444     _currentKeys => { },
445
446     # A hashtable of counters, where each counter keeps track of
447     # which entry was last read in from the current key.  The hash key
448     # is the file parser, and the hash value is the entry counter.
449     _currentEntryIndex => { },
450
451     # A helper variable, used to keep track of the last search index,
452     # used by the _search() function.
453     _last_search_index => undef,
454
455     # A helper variable, used to set the array of known line numbers,
456     # where each array entry is a line number, which separates a different
457     # group block.   
458     _group_index_linenums => [ ],
459 );
460
461 #######################################################################
462 # Private Methods Implemented                                         #
463 #######################################################################
464
465 # Base destructor function.
466 # Since none of our state data ever contains circular references,
467 # we can simply leave the garbage collection up to Perl's internal
468 # mechanism.
469 sub DESTROY {
470     my $self = shift;
471
472     # Delete any temporary files created.
473     my $parser = undef;
474     my $fname = undef;
475     foreach my $hive (@{$self->{hives_to_check}}) {
476         $parser = $self->{_baseline_parsers}->{$hive};
477         if (defined($parser)) {
478             $fname = $self->{_filenames}->{$parser};
479             $LOG->debug("Deleting baseline of hive '" . $hive . "' in '" .
480                         $fname . "'.");
481             if (!unlink($fname)) {
482                 $LOG->fatal("Error: Unable to unlink '" . $hive . "' hive data in '" . $fname ."'.");
483                 Carp::croak("Error: Unable to unlink '" . $hive . "' hive data in '" . $fname ."'.");
484             }
485             delete($self->{_filenames}->{$parser});
486             delete($self->{_baseline_parsers}->{$hive});
487         }
488         $parser = $self->{_checkpoint_parsers}->{$hive};
489         if (defined($parser)) {
490             $fname = $self->{_filenames}->{$parser};
491             $LOG->debug("Deleting checkpoint of hive '" . $hive . "' in '" .
492                         $fname . "'.");
493             if (!unlink($fname)) {
494                 $LOG->fatal("Error: Unable to unlink '" . $hive . "' hive data in '" . $fname ."'.");
495                 Carp::croak("Error: Unable to unlink '" . $hive . "' hive data in '" . $fname ."'.");
496             }
497             delete($self->{_filenames}->{$parser});
498             delete($self->{_checkpoint_parsers}->{$hive});
499         }
500     }
501 }
502
503 # Helper function, designed to update the Registry object by
504 # taking snapshots of all specified hives and saving these
505 # to temporary files.
506 #
507 # Inputs: HoneyClient::Agent::Integrity::Registry object,
508 #         the hashtable collection of files to snapshot
509 #
510 # Outputs: None.
511 sub _snapshot {
512     # Extract arguments.
513     my ($self, $parser_collection) = @_;
514     my $parser = undef;
515     my $fname = undef;
516     my $fname_tmp = undef;
517     foreach my $hive (@{$self->{hives_to_check}}) {
518         $fname = tmpnam();
519         $fname_tmp = tmpnam();
520         $LOG->debug("Storing snapshot of hive '" . $hive . "' into '" . $fname . "'.");
521         $LOG->debug("Creating temporary file '" . $fname_tmp . "' to perform data conversion.");
522
523         # Dump registry.  Strip all '\r' characters.
524         if (system("regedit.exe /a \"" . fullwin32path($fname_tmp) . "\" \"$hive\" &&
525                    cat " . $fname_tmp . " | sed -e 's/\r//g' > " . $fname) != 0) {
526             $LOG->fatal("Error: Unable to write '" . $hive . "' hive data to '" . $fname ."'.");
527             Carp::croak("Error: Unable to write '" . $hive . "' hive data to '" . $fname ."'.");
528         }
529
530         # Delete the unconverted temporary file.
531         _cleanup($fname_tmp);
532
533         $parser = HoneyClient::Agent::Integrity::Registry::Parser->init(input_file    => $fname,
534                                                                         index_groups  => 1,
535                                                                         show_progress => 0);
536
537         $parser_collection->{$hive} = $parser;
538         $self->{_filenames}->{$parser} = $fname;
539     }
540 }
541
542 # Helper function, designed to compare two registry key directory
543 # names.  This comparison is case insensitive and handles any strings
544 # that may contain '\\' correctly.
545 #
546 # Inputs: HoneyClient::Agent::Integrity::Registry object,
547 #         x directory name, y directory name
548 #         
549 # Outputs: -1 if x is alphabetically less than y,
550 #           0 if x and y are equal,
551 #           1 if x is alphabetically greater than y
552 sub _cmpGroup {
553     my ($self, $x, $y) = @_;
554     $x =~ tr/A-Z/a-z/;
555     $x =~ s/\\/\001/g;    # \001 instead of \0 due to perl 5.003 bug
556     $y =~ tr/A-Z/a-z/;
557     $y =~ s/\\/\001/g;
558     return $x cmp $y;
559 }
560
561 # Helper function, designed to compare two registry key entry
562 # names.  This comparison is case insensitive, handles default entry
563 # names correctly (@), and strips any quotes.
564 #
565 # Inputs: HoneyClient::Agent::Integrity::Registry object,
566 #         x entry name, y entry name
567 #
568 # Outputs: -1 if x is alphabetically less than y,
569 #           0 if x and y are equal,
570 #           1 if x is alphabetically greater than y
571 sub _cmpEntryName {
572     my ($self, $x, $y) = @_;
573
574     if ($x eq '@') {
575         $x = '';
576     } else {
577         $x =~ s/^"//;
578         $x =~ s/"$//;
579         $x =~ tr/A-Z/a-z/;
580     }
581
582     if ($y eq '@') {
583         $y = '';
584     } else {
585         $y =~ s/^"//;
586         $y =~ s/"$//;
587         $y =~ tr/A-Z/a-z/;
588     }
589
590     return $x cmp $y;
591 }
592
593 # Helper function, designed to get the next registry key directory
594 # name out of the specified file.
595 #
596 # Inputs: HoneyClient::Agent::Integrity::Registry object, file parser to read
597 #
598 # Outputs: next registry key directory name; undef if no key directory names
599 #          are left to read
600 sub _nextGroup {
601     my ($self, $parser) = @_;
602
603     # Read the next key from the specified file.
604     $self->{_currentKeys}->{$parser} = $parser->nextGroup();
605
606     # Check to make sure read was successful.
607     if (!defined($self->{_currentKeys}->{$parser})) {
608         $LOG->fatal("Error: Unable to read registry keys from '" .
609                     $self->{_filenames}->{$parser} . "'.");
610         Carp::croak("Error: Unable to read registry keys from '" .
611                     $self->{_filenames}->{$parser} . "'.");
612     }
613
614     # Encountered empty hash ref, thus we are at
615     # the end of the file.
616     if (!%{$self->{_currentKeys}->{$parser}}) {
617         return undef;
618     }
619
620     # Key read was successful, reset the entry index
621     # for this file.
622     $self->{_currentEntryIndex}->{$parser} = 0;
623
624     # Return the corresponding key directory name.
625     return $self->{_currentKeys}->{$parser}->{'key'};
626 }
627
628 # Helper function, designed to get the next entry (name, value) pair within
629 # the last key directory that was fetched by _nextGroup().
630 #
631 # Inputs: HoneyClient::Agent::Integrity::Registry object, file parser to read
632 #
633 # Outputs: next entry (name, value) pair; undef if no entries were found that
634 #          correspond to the last key directory fetched by _nextGroup()
635 sub _nextVal {
636     my ($self, $parser) = @_;
637
638     # Read the last key object read from the specified file.
639     my ($k) = $self->{_currentKeys}->{$parser};
640
641     # Get the latest entry index associated with the latest key.
642     my ($i) = $self->{_currentEntryIndex}->{$parser};
643
644     # If the index is past our array of known entries, then return undef.
645     if ($i >= @{$k->{'entries'}}) {
646         return undef;
647     }
648
649     # There is an entry to be read, so increment the entry index.
650     $self->{_currentEntryIndex}->{$parser} = $i + 1;
651
652     # Return the corresponding entry (name, value) pair.
653     return (${$k->{'entries'}}[$i]->{'name'},
654             ${$k->{'entries'}}[$i]->{'value'});
655 }
656
657 # Helper function, designed to perform a binary diff on two files,
658 # providing two arrays of line numbers, corresponding to where changes
659 # have occurred in either source or target registry hive dumps, along
660 # with an array of corresponding characters ('a', 'c', or 'd'),
661 # signifying what type of change was made.
662 #
663 # Inputs: HoneyClient::Agent::Integrity::Registry object,
664 #         source parser, target parser
665 #
666 # Outputs: source line number array ref, target line number array ref,
667 #          diff type char array ref
668 sub _diff {
669     # Extract arguments.
670     my ($self, $src_parser, $tgt_parser) = @_;
671
672     my $src_linenums = [];
673     my $tgt_linenums = [];
674     my $diff_types = [];
675
676     # Get the corresponding file names.
677     my $src_filename = $self->{_filenames}->{$src_parser};
678     my $tgt_filename = $self->{_filenames}->{$tgt_parser};
679
680     my $fname_tmp = tmpnam();
681     $LOG->debug("Creating temporary file '" . $fname_tmp . "' to perform differential analysis.");
682
683     # Perform diff operation.
684     # Because we're chaining together multiple system operations, we have to check the file output
685     # directly, to see if any failures occurred.
686     system("((diff --speed-large-files \"" . $src_filename . "\" \"" . $tgt_filename . "\" && " .
687              "echo \"0NOCHANGES\") | " .
688             "grep -e '^[0-9].*' || echo \"FAILURE\") > " . $fname_tmp . " 2>/dev/null");
689
690     my $fh = new IO::File($fname_tmp, "r");
691     if (!defined($fh)) {
692         $LOG->fatal("Error: Unable to read file '" . $fname_tmp . "'!");
693         Carp::croak("Error: Unable to read file '" . $fname_tmp . "'!");
694     }
695
696     # Read in the first line.
697     $/ = "\n";
698     $_ = <$fh>;
699
700     if (defined($_)) {
701         if ($_ eq "0NOCHANGES\n") {
702             $LOG->info("No changes detected in specified data.");
703             _cleanup($fname_tmp);
704             return ($src_linenums, $tgt_linenums);
705         }
706         if ($_ eq "FAILURE\n") {
707             # Check if diff operation failed.
708             _cleanup($fname_tmp);
709             $LOG->fatal("Error: Unable to diff '" . $src_filename . "' against '" . $tgt_filename ."'.");
710             Carp::croak("Error: Unable to diff '" . $src_filename . "' against '" . $tgt_filename ."'.");
711         }
712     }
713
714     do {
715         if (/([0-9]+)(?:|,[0-9]+)([a-z])([0-9]+)/) {
716             push (@{$src_linenums}, $1);
717             push (@{$diff_types}, $2);
718             push (@{$tgt_linenums}, $3);
719         }
720     } while (<$fh>);
721
722     _cleanup($fname_tmp);
723     return ($src_linenums, $tgt_linenums, $diff_types);
724 }
725
726 # Helper function, to delete a specified temporary file.
727 #
728 # Inputs: tmpfile
729 # Outputs: None
730 sub _cleanup {
731     my $tmpfile = shift;
732     undef $/;
733     if (!unlink($tmpfile)) {
734         $LOG->fatal("Error: Unable to delete temporary file '" . $tmpfile ."'.");
735         Carp::croak("Error: Unable to delete temporary file '" . $tmpfile ."'.");
736     }
737 }
738
739 # Helper function, designed to filter a given array reference of
740 # registry changes and return a new list that does not contain
741 # any of the excluded directory names.
742 #
743 # Inputs: self, arrayref of changes
744 # Outpus: arrayref of filtered changes
745 sub _filter {
746     # Extract arguments
747     my ($self, $changes) = @_;
748
749     # Array reference of filtered changes.
750     my $filteredChanges = [ ];
751
752     # Indicates if the change should be filtered out.
753     my $changeFiltered = 0;
754
755     foreach my $change (@{$changes}) {
756         $changeFiltered = 0;
757         foreach my $criteria (@{$self->{'key_dirnames_to_ignore'}}) {
758             if ($change->{'key'} =~ /$criteria/) {
759                 $changeFiltered = 1;
760                 last;
761             }
762         }
763         if (!$changeFiltered) {
764             push (@{$filteredChanges}, $change);
765         }
766     }
767
768     return $filteredChanges;
769 }
770
771 # Helper function, designed to be called from within the
772 # Search::Binary::binary_search() function, in order to allow
773 # the binary_search to properly read in group line number data from
774 # the default Registry object reference.
775 #
776 # For more information about how this function operates, please
777 # see the Search::Binary POD documentation.
778 #
779 # Inputs: self, value_to_compare, current_array_index
780 # Outputs: comparison, last_valid_array_index
781 sub _search {
782     # Extract arguments.
783     my ($self, $value_to_compare, $current_array_index) = @_;
784
785     # Increment the search index, if the current one is undef.
786     if (defined($current_array_index)) {
787         $self->{'_last_search_index'} = $current_array_index;
788     } else {
789         $self->{'_last_search_index'}++;
790     }
791
792     # Perform a comparison, if the array entry is defined.
793     if (defined(@{$self->{'_group_index_linenums'}}[$self->{'_last_search_index'}])) {
794         return($value_to_compare <=> @{$self->{'_group_index_linenums'}}[$self->{'_last_search_index'}],
795                $self->{'_last_search_index'});
796     }
797
798     # Array entry not found, return undef with this position.
799     return (undef, $self->{'_last_search_index'});
800 }
801
802 # Helper function, designed to compare the contents of two registry dumps,
803 # where each dump is represented by a Parser object.
804 #
805 # Inputs: before_parser => bparser, after_parser => aparser
806 # Outputs: an arrayref of registry changes
807 sub _compare {
808
809     # Extract arguments.
810     my ($self, %args) = @_;
811
812     my $before_parser = $args{'before_parser'};
813     my $after_parser  = $args{'after_parser'};
814
815     # A hashtable reference, containing the latest change found.
816     my $currentChange = { };
817
818     # Indicates if the $currentChange hashtable is not empty.
819     my $currentChangeExists = 0;
820
821     # Array reference, containing hashtables, where each
822     # hashtable represents a change between the before and
823     # after parsers.
824     my $changes = [];
825
826     # State variable:
827     # - Positive value: Keep comparing groups, linearly.
828     # -