Sunday 5 February 2012

Writing a CCleaner RegRipper Plugin Part 2


Welcome Back Viewers!
We now continue with our scheduled programming ... heh-heh...

About RegRipper (on SIFT V2.12)

RegRipper is written in Perl and is included with the SIFT VM. There are 3 main components to it:
  • the rip.pl Perl script located in /usr/local/bin
  • the various plugin scripts located in /usr/local/src/regripper/plugins
  • the Parse::Win32Registry library which is already installed/run (from source code at /usr/local/share/perl/5.10.0/Parse/Win32Registry)
Harlan Carvey has written a "how to write a plugin" section in Windows Registry Forensics (pp.73-78).

For more information on the methods/functions available for the Parse::Win32Registry, you can type:
"man parse::win32registry" at a SIFT VM terminal window.

RegRipper can be called at the SIFT command line, using something like:
"rip.pl -r /cases/NTUSER.DAT -p ccleaner"

*assuming "ccleaner.pl" exists in the plugin directory

Typing "rip -h" gives the full syntax/explanation.

Most plugins follow a similar pattern:
  1. Create a new Parse::Win32Registry object (passing the hive name as a parameter eg "NTUSER.DAT")
  2. Call the Parse::Win32Registry's "get_root_key" method
  3. Use the root key to call the "get_subkey" method (passing the key name as a parameter eg "Software\Piriform\CCleaner")
  4. Use "get_list_of_values" method to obtain the values under the subkey.
Some Perls of Wisdom?

A few Perls of note before we get stuck into coding:
  • "#" denotes comments. Anything after a # sign on the line is ignored by the Perl interpreter.
  • Each statement line should be terminated with a ";". A statement can be a declaration / assignment / function call.
  • "{ }" can be used to logically group multiple lines.
  • "sub" denotes subroutines (aka methods/functions).
  • A method / function can be passed a parameter using "( )" eg "function(x)" or it can be called without and use a default input argument eg in subroutines, "shift" defaults to use "@_".
  • "my" denotes local variables (they do not exist outside the containing "{ }" braces).
  • "$" denote scalar values (can be strings, numbers) and its the most basic / common variable Perl type.
  • "@" denotes an array / list of scalars.
  • "( )" can also be used to denote arrays/lists (see examples below).
  • "[i]" can be used to index items in arrays/lists (see examples below).
  • "%" denotes an associated array / hashmap (where each "value" in a list has a corresponding "key id").
  • "::rptMsg" is an existing RegRipper function to print out messages to the command line.
  • "." can be used to join strings and variables together.

Some array examples:
@x = (10, 20, 30); # sets up an array of numbers and stores it in @x
$y = $x[1]; # sets scalar y to equal the second scalar item in x (array index starts at 0). $x[1] is used instead of @x because we are assigning / using a scalar value contained in x. ie y = 20

%fruits("apple" => 9,
        "banana" => 23,
        "cherry" =>11); # sets up an associated array (%) with "apple" being the "key id" and 9 being the "key value". Similarly, "banana" has a value of 23 etc.

$fruits{"orange"} = 1; # will add the variable "orange" and its value of "1" to the "fruits" associated array

delete($fruits{"orange"}); # will remove "orange" and its value from the "fruits" array

@fruit_keys = keys(%fruits); # copies the key ids to the "fruit_keys" array using the perl "keys" function. "keys" returns an array list so that's why we use "@fruit_keys" instead of $fruit_keys or %fruitkeys.

@fruit_values = values(%fruits); # copies the key values to the "fruit_values" array using the perl "values" function

Some helpful Perl references are:
  • the official documentation here
  • good old wikipedia
  • and Chapter 16, Unix Unleashed 3ed. by Robin Burk (SAMS 1998) - which has a good summary of Perl. That's where I got the array examples from.
Writing the RegRipper Plugin

So after all that, I can tell you're just rearing to go ... and for those of you that stay - here's the code I came up with. Its based on the "warcraft3" plugin already included with SIFT V2.12. Note: the formatting might be a little off because of the blog column width causing long lines (typically comments) to wrap.

# Start of Code

 #-----------------------------------------------------------
# ccleaner.pl
#   Gets CCleaner User Settings
#
# Change history
#   20120128 Initial Version based on warcraft3.pl plugin
#
# References
#
#
#-----------------------------------------------------------
package ccleaner;
use strict;

my %config = (hive          => "NTUSER\.DAT",
              hasShortDescr => 1,
              hasDescr      => 0,
              hasRefs       => 0,
              osmask        => 22,
              version       => 20120128);

sub getConfig{return %config}
sub getShortDescr {
    return "Gets User's CCleaner Settings";  
}
sub getDescr{}
sub getRefs {}
sub getHive {return $config{hive};}
sub getVersion {return $config{version};}

my $VERSION = getVersion();

sub pluginmain {
    my $class = shift; # pops the first element off @_ ie the parameter array passed in to pluginmain
    my $hive = shift; # 1st element in @_ is class/package name (ccleaner), 2nd is the hive name passed in from rip.pl
    ::logMsg("Launching ccleaner v.".$VERSION);
    ::rptMsg("ccleaner v.".$VERSION);
    ::rptMsg("(".getHive().") ".getShortDescr()."\n");
    my $reg = Parse::Win32Registry->new($hive); # creates a Win32Registry object
    my $root_key = $reg->get_root_key;
    my $key;
    my $key_path = "Software\\Piriform\\CCleaner";
    # If CCleaner key_path exists ... ie get_subkey returns a non-empty value
    if ($key = $root_key->get_subkey($key_path)) {
        # Print registry key name and last modified date
        ::rptMsg($key_path);
        ::rptMsg("LastWrite Time ".gmtime($key->get_timestamp())." (UTC)");
        ::rptMsg("");
        my %cckeys; # temporary associative array for storing name / value pairs eg ("UpdateCheck", 1)
        # Extract ccleaner key values into ccvals array
        # Note: ccvals becomes an array of "Parse::Win32Registry::WinNT::Value"
        # As this is implemented in an Object oriented manner, we cannot access the values directly -
        # we have to use the "get_name" and "get_value" subroutines
        my @ccvals = $key->get_list_of_values();
        # If ccvals has any "Values" in it, call "Value::get_name" and "Value::get_data" for each
        # and store the results in the %cckeys associative array using data returned by Value::get_name as the id/index
        # and Value::get_data for the actual key value
        if (scalar(@ccvals) > 0) {
            foreach my $val (@ccvals) {
                $cckeys{$val->get_name()} = $val->get_data();
            }
            # Sorts keynames into a temp list and then prints each key name + value in list order
            # the values are retrieved from cckeys assoc. array which was populated in the previous foreach loop
            foreach my $keyval (sort keys %cckeys) {
                ::rptMsg($keyval." -> ".$cckeys{$keyval});
            }
        }
        else {
            ::rptMsg($key_path." has no values.");
        }
    }
    else {
        ::rptMsg($key_path." does not exist.");
    }
    # Return obligatory new-line
    ::rptMsg("");
}

1;


# End of Code

I have also included some screenshots of the code with line numbers so we can walk through it. Fun, fun, fun eh?
Code Screenshot #1

I used the SIFT's Ubuntu gedit file editor (by typing "gedit" at the command line) which has automatic Perl syntax highlighting, line numbers and multiple tabs. You can see the file path "/usr/local/src/regripper/plugins/" of the current file being edited ("ccleaner.pl") in the title bar.
Comments are in blue, variable names are green, Perl keywords are brownish-orange, Perl functions are aqua. You can probably customize it further.
As mentioned previously, I followed the same structure as the warcraft3.pl plugin so the first few lines are kinda standard / common to all plugins.

Lines 1 - 11 are just comments
Line 12 declares the name of this package/module.
Line 13 is a directive to ensure we follow the Perl syntax rules
Lines 15-20 declares a "config" associative array e.g. "hive" key having a value of "NTUSER\.DAT" etc.
The "\" is required so Perl doesn't interpret the "." as something else.
These are standard RegRipper variables that can later be accessed using the subroutines declared on lines 22 - 31.


Code Screenshot #2

Every RegRipper plugin must declare a "pluginmain" subroutine. This particular one will be called whenever you specify the "-p ccleaner" option.
Lines 34-35: We see two local (ie "my") variables declared. These variables don't exist outside of our "pluginmain". The "pluginmain" is called in / by "rip.pl" which also passes in the hive name parameter (eg NTUSER.DAT).
The parameter is available via the inbuilt Perl array "@_". To get to the hive name we need to pop/shift the first item off the "@_" array (ie the "ccleaner" package name). I'm not really sure why the package name was added to the "@_" array when "rip.pl" only passes in the hive name. Anyway, we call "shift" again to get our hive value (NTUSER.DAT).
Lines 36-38: Prints out "Launching ccleaner" and the version info. This seems to be the standard thing to do.
Lines 39-40: Creates a new "Parse::Win32Registry" object using the hive we specified at the command line (via "-r /cases/NTUSER.DAT"). Then we get a root key object (storing it in "root_key") so we can then use it later to get to our CCleaner key.
Lines 41-42: We declare a local variable "key" (to be used later) and also declare a "key_path" scalar containing the actual Software registry key we are interested in (ie Software\\Piriform\\CCleaner).
Line 44: If our "root_key" get_subkey method returns a result (stored in "key") we can then parse through / print the values otherwise execution skips to line 73 and we print out "Software\Piriform\CCleaner does not exist"
Lines 46-48: Prints out the "key_path" ("Software\Piriform\CCleaner") and the last write time.
Lines 49 and 54: We create a new local associative array called "cckeys" and a new local array called "ccvals". We then use the returned "key" variable to call "get_list_of_values" and store the result in "ccvals". "ccvals" should now contain multiple variables of the "Parse:Win32Registry::WinNT::Value" type. We can't access the registry key names / values directly - we have to use the library supplied "get_name" and "get_data" subroutines.
Lines 58-70: We check to see if we have any "Value" items in the "ccvals" array and if we don't, execution skips to line 69 and we print out "Software\Piriform\CCleaner has no values". If we do have items in the "ccvals" array, we iterate through each item and store the name and value (eg "UpdateCheck" and the value "1") in the "cckeys" associative array using the name as the index / id. Then on lines 64-65, we get a list of these key ids (eg "UpdateCheck") and sort the list using the inbuilt Perl "sort" function. We iterate through this sorted list for each key id and we print out the key id (eg "UpdateCheck") and then a " -> " followed by the key value (eg "1"). Execution then goes to line 76 where we print out an empty line and the package returns a value of "1" by convention.

We can now copy / save this script into the plugin directory, type "chmod a+x /usr/local/src/regripper/plugins/ccleaner.pl" to make it executable and then launch RegRipper with our new ccleaner plugin using:
"rip.pl -p ccleaner -r /cases/NTUSER.DAT". *Assuming the relevant NTUSER.DAT is in /cases/.

Here is the output of "ccleaner.pl" when run against a NTUSER.DAT containing CCleaner keys:

Launching ccleaner v.20120128
ccleaner v.20120128
(NTUSER.DAT) Gets User's CCleaner Settings


Software\Piriform\CCleaner
LastWrite Time Fri Jan  6 05:52:48 2012 (UTC)


(App)Autocomplete Form History -> True
(App)Custom Folders -> False
(App)DNS Cache -> True
(App)Desktop Shortcuts -> False
(App)FTP Accounts -> True
(App)Google Chrome - Saved Form Information -> True
(App)Google Chrome - Saved Passwords -> True
(App)Hotfix Uninstallers -> False
(App)IIS Log Files -> False
(App)Last Download Location -> True
(App)MS Photo Editor -> True
(App)Mozilla - Saved Form Information -> True
(App)Mozilla - Saved Passwords -> True
(App)Mozilla - Site Preferences -> True
(App)Notepad++ -> False
(App)Old Prefetch data -> True
(App)Saved Passwords -> True
(App)Start Menu Shortcuts -> False
(App)Sun Java -> True
(App)Tray Notifications Cache -> False
(App)Wipe Free Space -> False
BackupPrompt -> 0
DefaultDetailedView -> 1
DelayTemp -> 1
FFDetailed -> 1
IEDetailed -> 1
Language -> 1033
MSG_CONFIRMCLEAN -> False
MSG_WARNMOZCACHE -> False
SecureDeleteMethod -> 0
SecureDeleteType -> 1
UpdateCheck -> 1
UpdateKey -> 01/06/2012 04:52:48 PM
WINDOW_HEIGHT -> 679
WINDOW_LEFT -> 329
WINDOW_MAX -> 0
WINDOW_TOP -> 23
WINDOW_WIDTH -> 1202
WipeFreeSpaceDrives -> C:\|F:\

Here's the output when we run the ccleaner plugin against an NTUSER.DAT which doesn't have any CCleaner artifacts (eg "rip.pl -p ccleaner -r /mnt/m57jean/Documents\ and\ Settings/Jean/NTUSER.DAT"):

Launching ccleaner v.20120128
ccleaner v.20120128
(NTUSER.DAT) Gets User's CCleaner Settings


Software\Piriform\CCleaner does not exist.

And so that concludes our little exercise ...  it wasn't too complicated in the end eh? My main goal was to write a simple RegRipper plugin and I think I did that OK. However, I think further ProcMon analysis of CCleaner could prove interesting. Anyway that's all we have time for today folks. As always, let me know what you think in the Comments section. I won't hold my breath lol ...