Saturday 25 February 2012

(Monkey) Carvings of Unknown File Types with Scalpel / Foremost on SIFT


Thierry13 recently requested we look into file carving - specifically, how do we carve for a non-standard / unknown files. For the scalpel and foremost carving utilities (both on SIFT) it's monkey's play!

FYI There's another file carving utility on SIFT called photorec but this won't handle new unknown files only certain image/movie/document/archived files.

Anyhoo, in order to configure scalpel / foremost, you must first have an idea of:
- the file extension (if required eg .doc),
- the maximum size of a potential file,
- the file header signature (and optional file footer signature).

You enter these parameters into a "scalpel.conf" or "foremost.conf" file before running the respective executable.
As scalpel was derived from foremost, the .conf files look very similar. You can usually cut/paste the same rule for both.
By default the executable will look for its .conf file in the current working directory but you can also tell it which .conf file to use.

Here's how I prepared for testing all of the above:
I plugged in freshly formatted 512 Mb USB Memory stick (in Windows XP).
I downloaded (for free):
- WinHex 
- FTK Imager

We will now launch WinHex to create our mystery file (eg "cheeky-file.c4n6") and then copy it to the USB stick.

1. In WinHex, go to "File" ... "New" ... and select 1024 bytes in the resultant popup. Press "OK".

2. Press Ctrl-L to fill the file with data. At the subsequent popup, use the default "Simple pseudo-random numbers" radio button and hit "OK".

3. Add in our file header and footer data. You can simply click at the beginning of the file and type C4N6 like the screenshot below. Note: I clicked in the right most column of the corresponding hex digits to edit.
Editing the Mystery File Header Signature

4. Similarly, click 6 bytes/characters from the end of the file and type MONKEY.

Editing the Mystery File Footer Signature

5. Now go to "File" ... "Save" and call the file something like "cheeky-file.c4n6". Save it to BOTH the USB stick AND the local hard disk.

6. Using Windows File Explorer, we delete the mystery file from the USB stick (eg Shift-Delete).

7. Now we take a physical image of the USB stick using FTK Imager.
In FTK Imager go to "File" ... "Create Disk Image". Select "Physical Drive" then "Next". Select the USB drive in the drop-menu and click "Finish".
Click the "Add" button then choose the Raw(dd) Destination Image Type. Press "Next". Press "Next" again to skip entering the case details.
Enter in a save location/filename (eg "usb512") for the image and press "Finish". Now click "Start".
It was at this point I got a couple of failures. After I unplugged another USB device (curse you Madden Game Controller!), I was then able to save the whole image.
There should now be a file (eg "usb512.001") with a corresponding audit log ("usb512.001.txt") containing the MD5 hash(es).

8. Now launch the SANS SIFT VM (v2.12).

9. Using Windows File Explorer, copy the image (eg "usb512.001") to the SIFT VM "/cases/" directory.
Also copy the new mystery file (eg "cheeky-file.c4n6") to "/cases/" so we can MD5 hash compare it with any subsequently carved results.

10. In a new SIFT terminal window, we should check the MD5 hash of the USB image by typing:
 "md5sum /cases/usb512.001".
The terminal window should look something like:

sansforensics@SIFT-Workstation:~$ md5sum /cases/usb512.001
85cc5e5ef0b44c314da7dfc9954236f6  /cases/usb512.001
sansforensics@SIFT-Workstation:~$

We can then compare this to the "usb512.001.txt" audit file generated previously by FTK Imager (i.e.
"MD5 checksum:    85cc5e5ef0b44c314da7dfc9954236f6")

Cool bananas! Our image has not changed after being copied over.

11. Now we set up local editable copies of the "scalpel.conf" / "foremost.conf" files. Assuming the current directory is "/home/sansforensics/", type "cp /usr/local/etc/foremost.conf ." and "cp /usr/local/etc/scalpel.conf ."
Note: I found these existing .conf files on SIFT by typing "sudo find / -name scalpel.conf -print". There were two entries returned, so I picked the largest and most latest one. Ditto for the "foremost.conf" file. By default both .conf files have all their rules commented out. Which brings us to ...

12. Using gedit (eg "gedit foremost.conf &" to launch gedit in the background), add the following lines to the "foremost.conf" files (screenshot):

#Cheeky4n6Monkey Test file
    c4n6    y    2048    C4N6    MONKEY


This line is saying "If you find a header equal to C4N6 (case sensitive) and a footer equal to MONKEY (case sensitive) within 2048 bytes, retrieve the data and label it with the .c4n6 extension"
Note: The # sign means that line is a comment. Also note, there's a TAB between columns.


Editing "foremost.conf"

13. Similarly, edit the "scalpel.conf" file.

Editing "scalpel.conf"

14. Once we save the two .conf files, we can now run the carvers. For foremost the command line will look like:

sansforensics@SIFT-Workstation:~$ foremost -c foremost.conf -o usb512-foremost -i /cases/usb512.001
Processing: /cases/usb512.001
|*****|
sansforensics@SIFT-Workstation:~$

We are telling foremost to use the "foremost.conf" file in the current directory (this option is not strictly required) to carve the "/cases/usb512.001" file and store the results in the current directory under the "usb512-foremost" sub-directory .

Looking in the output directory yields:

sansforensics@SIFT-Workstation:~$ ls usb512-foremost/
audit.txt  c4n6
sansforensics@SIFT-Workstation:~$ ls usb512-foremost/c4n6/
00000640.c4n6

Hooray! It looks like foremost found our mystery file and stored it in its own file-type specific directory ("c4n6").
To verify this, lets compare MD5 hashes of the foremost recovered file and the file we copied over earlier ("/cases/cheeky-file.c4n6").

sansforensics@SIFT-Workstation:~$ md5sum usb512-foremost/c4n6/00000640.c4n6
94b4265826825763fbf8c661fa04ac1c  usb512-foremost/c4n6/00000640.c4n6
sansforensics@SIFT-Workstation:~$
sansforensics@SIFT-Workstation:~$ md5sum /cases/cheeky-file.c4n6
94b4265826825763fbf8c661fa04ac1c  /cases/cheeky-file.c4n6
sansforensics@SIFT-Workstation:~$

And we have a MATCH!

Similarly, for scalpel, the command line will look something like:

sansforensics@SIFT-Workstation:~$ scalpel -c scalpel.conf -o usb512-scalpel /cases/usb512.001
Scalpel version 2.0
Written by Golden G. Richard III and Lodovico Marziale.
Multi-core CPU threading model enabled.
Initializing thread group data structures.
Creating threads...
Thread creation completed.


Opening target "/cases/usb512.001"


Image file pass 1/2.
/cases/usb512.001: 100.0% |*****************************|  490.0 MB    00:00 ETAAllocating work queues...
Work queues allocation complete. Building work queues...
Work queues built.  Workload:
c4n6 with header "C4N6" and footer "MONKEY" --> 1 files
Carving files from image.
Image file pass 2/2.
/cases/usb512.001: 100.0% |*****************************|  490.0 MB    00:00 ETAProcessing of image file complete. Cleaning up...
Done.
Scalpel is done, files carved = 1, elapsed  = 7 secs.
sansforensics@SIFT-Workstation:~$

Note: The scalpel arguments take a slightly different format - there's no "-i" flag before the source file.

Now looking in the output directory yields:

sansforensics@SIFT-Workstation:~$ ls usb512-scalpel/
audit.txt  c4n6-0-0/ 
sansforensics@SIFT-Workstation:~$ ls usb512-scalpel/c4n6-0-0/
00000000.c4n6

And comparing MD5 hashes yields:

sansforensics@SIFT-Workstation:~$ md5sum usb512-scalpel/c4n6-0-0/00000000.c4n6
94b4265826825763fbf8c661fa04ac1c  usb512-scalpel/c4n6-0-0/00000000.c4n6
sansforensics@SIFT-Workstation:~$
sansforensics@SIFT-Workstation:~$ md5sum /cases/cheeky-file.c4n6
94b4265826825763fbf8c661fa04ac1c  /cases/cheeky-file.c4n6
sansforensics@SIFT-Workstation:~$

Another Match!

So that was pretty monkey proof eh? We have made up our own mystery file, deleted it and then recovered it using FTK Imager, foremost and scalpel. For more information on these file carvers you can type "man scalpel" and "man foremost" at the SIFT terminal window.

Thierry13 also asked about how do we identify file headers and my copout answer would be use WinHex to look at a sample file first and tailor the .conf accordingly. If anyone has any alternative ideas, please leave a comment!
Also FYI, Gary Kessler keeps a reference table of file header signatures here.

Wow, this is my 20th post - with all this techno-babble, I have forgotten the humour component. Post number 21 will hopefully be less technical, more humour.

Wednesday 22 February 2012

Making "exif2map.pl" recursively search


Recently Doppiamunnezza commented that it might be helpful if we could point the exif2map.pl script at a folder and have it automagically search all files below that for EXIF geotag data.

Being the code-monkey hack that I am, here's my quick/dirty solution ...

Code Listing

# START CODE


#!/usr/bin/perl -w

# Perl script to take the output of exiftool and conjure up a web link
# to google maps if the image has stored GPS lat/long info.

use strict;

use Image::ExifTool;
use Image::ExifTool::Location;
use Getopt::Long;
use HTML::QuickTable;
use File::Find;

# commented out for now - apparently File:Find can issue some weird warnings
#no warnings 'File::Find';

my $version = "exif2map.pl v2012.02.21";
my $help = ''; # help flag
my $htmloutput = ''; #html flag
my @filenames; # input files from -f flag
my @directories; # input directories from -dir flag (must use absolute paths)

my %file_listing; # stored results

GetOptions('help|h' => \$help,
    'html' => \$htmloutput,
    'f=s@' => \@filenames,
    'dir=s@' => \@directories);

if ($help||(@filenames == 0 && @directories == 0))
{
    print("\n$version\n");
    print("Perl script to take the output of exiftool and conjure up a web link\n");
    print("to google maps if the image has stored GPS lat/long info.\n");

    print("\nUsage: exif2map.pl [-h|help] [-f filename] [-html]\n");
    print("-h|help .......... Help (print this information). Does not run anything else.\n");
    print("-f filename ...... File(s) to extract lat/long from\n");
    print("-dir directory ... Absolute path to folder containing file(s) to extract lat/long from\n");
    print("-html ............ Also output results as a timestamped html file in current directory\n");

    print("\nExample: exif2map.pl -f /cases/galloping-gonzo.jpg");
    print("\nExample: exif2map.pl -f /cases/krazy-kermit.jpg -dir /cases/rockin-rowlf-pics/ -html\n\n");
    print("Note: Outputs results to command line and (if specified) to a timestamped html file\n");
    print("in the current directory (e.g. exif2map-output-TIMESTAMP.html)\n\n");
   
    exit;
}

# Main processing loop
print("\n$version\n");

# Process filenames specified using the -f flag first
if (@filenames)
{
    foreach my $name (@filenames)
    {
        ProcessFilename($name);
    }
}

# Process folders specified using the -dir flag
# Note: Will NOT follow symbolic links to files
if (@directories)
{
    find(\&ProcessDir, @directories);
}

# If html output required AND we have actually retrieved some data ...
if ( ($htmloutput) && (keys(%file_listing) > 0) )
{   
    #timestamped output filename
    my $htmloutputfile = "exif2map-output-".time.".html";

    open(my $html_output_file, ">".$htmloutputfile) || die("Unable to open $htmloutputfile for writing\n");

    my $htmltable = HTML::QuickTable->new(border => 1, labels => 1);

    # Added preceeding "/" to "Filename" so that the HTML::QuickTable sorting doesn't result in
    # the column headings being re-ordered after / below a filename beginning with a "\".
    $file_listing{"/Filename"} = "GoogleMaps Link";

    print $html_output_file "<HTML>";
    print $html_output_file $htmltable->render(\%file_listing);
    print $html_output_file "<\/HTML>";

    close($htmloutputfile);
    print("\nPlease refer to \"$htmloutputfile\" for a clickable link output table\n\n");
}

sub ProcessFilename
{
    my $filename = shift;

    if (-e $filename) #file must exist
    {
        my $exif = Image::ExifTool->new();
        # Extract all info from existing image
        if ($exif->ExtractInfo($filename))
        {
            # Ensure all 4 GPS params are present
            # ie GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef
            # The Ref values indicate North/South and East/West
            if ($exif->HasLocation())
            {
                my ($lat, $lon) = $exif->GetLocation();
                print("\n$filename contains Lat: $lat, Long: $lon\n");
                print("URL: http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en\n");
                if ($htmloutput) # save GoogleMaps URL to global hashmap indexed by filename
                {
                    $file_listing{$filename} = "<A HREF = \"http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en\"> http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en</A>";
                }
                return 1;
            }
            else
            {
                print("\n$filename : No Location Info available!\n");
                return 0;
            }
        }
        else
        {
            print("\n$filename : Cannot Extract Info!\n");
            return 0;
        }
    }
    else
    {
        print("\n$filename does not exist!\n");
        return 0;
    }
}

sub ProcessDir
{
    # $File::Find::dir is the current directory name,
    # $_ is the current filename within that directory
    # $File::Find::name is the complete pathname to the file.
    my $filename = $File::Find::name; # should contain absolute path eg /cases/pics/krazy-kermit.jpg

    if (-f $filename) # must be a file not a directory name ...
    {
        my $exif = Image::ExifTool->new();
        # Extract all info from existing image
        if ($exif->ExtractInfo($filename))
        {
            # Ensure all 4 GPS params are present
            # ie GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef
            # The Ref values indicate North/South and East/West
            if ($exif->HasLocation())
            {
                my ($lat, $lon) = $exif->GetLocation();
                print("\n$filename contains Lat: $lat, Long: $lon\n");
                print("URL: http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en\n");
                if ($htmloutput) # save GoogleMaps URL to global hashmap indexed by filename
                {
                    $file_listing{$filename} = "<A HREF = \"http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en\"> http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en</A>";
                }
                return 1;
            }
            else
            {
                print("\n$filename : No Location Info available!\n");
                return 0;
            }
        }
        else
        {
            print("\n$filename : Cannot Extract Info!\n");
            return 0;
        }
    }
}

# END CODE


Code Summary

The code mostly works as before - I've just added some extra code to handle any user specified folders.
It could probably be re-written so only one function was required but I reckon the code would become a bit harder to explain/understand. Plus, I can be a pretty lazy monkey ;)
Anyhoo, I've added a function called "ProcessDir" which gets called for each file/directory under the user specified folder(s). It is essentially the same code as "ProcessFilename" except it derives the filenames from the File::Find module. The File::Find's "find" function (ie "find(\&ProcessDir, @directories);") will search the given directories array and then call "ProcessDir" for each directory/file found. Consequently, "ProcessDir" should only call our EXIF checks if it's looking at a file (ie that's what the "if (-f $filename)" condition is for). The good news is the "File::Find" module is already loaded on the SIFT VM so we don't need to explicitly install it.
Apart from those changes, the command line output now also tells the user the output HTML filename (if the -html flag is set).
One caveat I noticed during testing was that folder names MUST use ABSOLUTE paths (eg "/home/sansforensics/testpics"). Otherwise, the file test mentioned above fails.


Testing:

Here's the file/folder structure I set up for testing:

/home/sansforensics/wheres-Cheeky4n6Monkey.jpg
/home/sansforensics/testpics/case1/Vodafone710.jpg
/home/sansforensics/testpics/case1/wheres-Cheeky4n6Monkey.jpg
/home/sansforensics/testpics/case1/subpics/GPS_location_stamped_with_GPStamper.jpg
/home/sansforensics/testpics/case2/wheres-Cheeky4n6Monkey.jpg

The various "wheres-Cheeky4n6Monkey.jpg" copies have GPS Lat/Long info as does "GPS_location_stamped_with_GPStamper.jpg". I also added in "Vodafone710.jpg" which has no GPS Lat/Long data. Both of our tests will be run from "/home/sansforensics/".

For the first test, we shall specify a single file from the current directory ("wheres-Cheeky4n6Monkey.jpg") and also the "case1" folder which contains some images ("Vodafone710.jpg" and another copy of "wheres-Cheeky4n6Monkey.jpg").  The "case1" folder also contains a sub folder containing another image ("case1/subpics/GPS_location_stamped_with_GPStamper.jpg").

Here's the command line input/output ...

sansforensics@SIFT-Workstation:~$ exif2map.pl -dir /home/sansforensics/testpics/case1/ -f wheres-Cheeky4n6Monkey.jpg -html


exif2map.pl v2012.02.21


wheres-Cheeky4n6Monkey.jpg contains Lat: 36.1147630001389, Long: -115.172811
URL: http://maps.google.com/maps?q=36.1147630001389,+-115.172811(wheres-Cheeky4n6Monkey.jpg)&iwloc=A&hl=en


/home/sansforensics/testpics/case1/Vodafone710.jpg : No Location Info available!


/home/sansforensics/testpics/case1/wheres-Cheeky4n6Monkey.jpg contains Lat: 36.1147630001389, Long: -115.172811
URL: http://maps.google.com/maps?q=36.1147630001389,+-115.172811(/home/sansforensics/testpics/case1/wheres-Cheeky4n6Monkey.jpg)&iwloc=A&hl=en


/home/sansforensics/testpics/case1/subpics/GPS_location_stamped_with_GPStamper.jpg contains Lat: 41.888948, Long: -87.624494
URL: http://maps.google.com/maps?q=41.888948,+-87.624494(/home/sansforensics/testpics/case1/subpics/GPS_location_stamped_with_GPStamper.jpg)&iwloc=A&hl=en


Please refer to "exif2map-output-1329903003.html" for a clickable link output table


sansforensics@SIFT-Workstation:~$


The output file "exif2map-output-1329903003.html" looks like:

Results for "case1" Folder + 1 x Local File

For the second test, we now specify the parent "testpics" directory (plus the local "wheres-Cheeky4n6Monkey.jpg" file). The script should pick up both "case1" and "case2" directories.

sansforensics@SIFT-Workstation:~$ exif2map.pl -dir /home/sansforensics/testpics/ -f wheres-Cheeky4n6Monkey.jpg -html


exif2map.pl v2012.02.21


wheres-Cheeky4n6Monkey.jpg contains Lat: 36.1147630001389, Long: -115.172811
URL: http://maps.google.com/maps?q=36.1147630001389,+-115.172811(wheres-Cheeky4n6Monkey.jpg)&iwloc=A&hl=en


/home/sansforensics/testpics/case1/Vodafone710.jpg : No Location Info available!


/home/sansforensics/testpics/case1/wheres-Cheeky4n6Monkey.jpg contains Lat: 36.1147630001389, Long: -115.172811
URL: http://maps.google.com/maps?q=36.1147630001389,+-115.172811(/home/sansforensics/testpics/case1/wheres-Cheeky4n6Monkey.jpg)&iwloc=A&hl=en


/home/sansforensics/testpics/case1/subpics/GPS_location_stamped_with_GPStamper.jpg contains Lat: 41.888948, Long: -87.624494
URL: http://maps.google.com/maps?q=41.888948,+-87.624494(/home/sansforensics/testpics/case1/subpics/GPS_location_stamped_with_GPStamper.jpg)&iwloc=A&hl=en


/home/sansforensics/testpics/case2/wheres-Cheeky4n6Monkey.jpg contains Lat: 36.1147630001389, Long: -115.172811
URL: http://maps.google.com/maps?q=36.1147630001389,+-115.172811(/home/sansforensics/testpics/case2/wheres-Cheeky4n6Monkey.jpg)&iwloc=A&hl=en


Please refer to "exif2map-output-1329903236.html" for a clickable link output table


sansforensics@SIFT-Workstation:~$

This produces the following output HTML file:

Results for "testpics" Parent Folder + 1 x Local File

We can see that our newly modified "exif2map.pl" script can now also recursively search given folders for files with EXIF GPS Lat/Long info. Hooray!
The good thing about Perl is that someone has probably already thought of what you might need and has already written a module for it (eg File::Find). I highly recommend searching CPAN  before starting any new project.

Friday 17 February 2012

Diving in to Perl with GeoTags and GoogleMaps


Girl, Unallocated recently posted a guide to plotting geotag data using exiftool and Google Earth here.

GoogleMaps also has some info about how to plot lat / long coordinates along with an info box on a map here.
The example URL given looks like:
http://maps.google.com/maps?q=37.771008,+-122.41175+%28You+can+insert+your+text+here%29&iwloc=A&hl=en
You can see that the latitude parameter comes first in floating point (eg 37.771008) followed by the longitude and then a text field ("You can insert your text here") to be printed in the information popup. The "iwloc" parameter shows the GoogleMaps point label ("A"). The "hl" parameter is the language to use (en = English).

So ... I'd thought I'd build on (*cough RIPOFF *cough) those posts  and write a basic Perl script to automatically extract the EXIF location data from a photo and then formulate an URL the user can then paste into their Internet Browser. The SIFT VM already has the exiftool Perl module and Perl 5 installed so it should be simple eh?

Method

At first, I thought I would filter/convert the output text of "exiftool" but after hunting around at www.cpan.org (the Perl package repository), I discovered the Image::ExifTool::Location module which provides some easy to use functions to retrieve lat / long coordinates from EXIF data.

In order to use this though, we first have to install it on SIFT. Here's the guide.
Its pretty simple though, you just type:
"sudo cpan Module::Name"
to get the cpan installer to automagically retrieve and install the module you're after. I added in the "sudo" so the installations aren't hindered by file permission issues.

Looking at the documentation for the Image::ExifTool::Location module shows that it requires/depends on the Image::ExifTool and the Geo::Coordinates::DecimalDegrees modules. The Image::ExifTool module is already installed on SIFT - so we will install the Geo::Coordinates::DecimalDegrees module before installing the Image::ExifTool::Location module just in case.

At a terminal window type:
"sudo cpan Geo::Coordinates::DecimalDegrees"
followed by
"sudo cpan Image::ExifTool::Location"

At this point, I had a bit of of an epiphany / scope creep when I thought "Wouldn't it be cool if you could also output the URLs for multiple files as links in a single HTML file?". The user could then open the HTML file and click on the various links to see the corresponding Google Map. No cutting and pasting of URLs and if there's a lot of pics with geotag information, it could make things a little easier for our fearless forensicators.

Fortunately, there's already a Perl module to create an HTML table - its called HTML::QuickTable. We can install it by typing "sudo cpan HTML::QuickTable" at the command line.

After we've completed the initial setup, we can now start writing our script (finally!). I called it "exif2map.pl" and put it in "/usr/local/bin/". Note: Don't forget to to make the file executable by typing "chmod a+x /usr/local/bin/exif2map.pl".


The Code

Here's the code I mangled came up with:

#START CODE
#!/usr/bin/perl -w

# Perl script to take the output of exiftool and conjure up a web link
# to google maps if the image has stored GPS lat/long info.

use strict;

use Image::ExifTool;
use Image::ExifTool::Location;
use Getopt::Long;
use HTML::QuickTable;

my $help = '';
my $htmloutput = '';
my @filenames;
my %file_listing;

GetOptions('help|h' => \$help,
    'html' => \$htmloutput,
    'f=s@' => \@filenames);

if ($help||@filenames == 0)
{
    print("\nexif2map.pl v2012.02.16\n");
    print("Perl script to take the output of exiftool and conjure up a web link\n");
    print("to google maps if the image has stored GPS lat/long info.\n");

    print("\nUsage: exif2map.pl [-h|help] [-f filename] [-html]\n");
    print("-h|help ....... Help (print this information). Does not run anything else.\n");
    print("-f filename ... File(s) to extract lat/long from\n");
    print("-html ......... Also output results as a timestamped html file in current directory\n");

    print("\nExample: exif2map.pl -f /cases/galloping-gonzo.jpg");
    print("\nExample: exif2map.pl -f /cases/krazy-kermit.jpg -f/cases/flying-piggy.jpg -html\n\n");
    print("Note: Outputs results to command line and (if specified) to a timestamped html file\n");
    print("in the current directory (e.g. exif2map-output-TIMESTAMP.html)\n\n");
   
    exit;
}


# Main processing loop
print("\nexif2map.pl v2012.02.16\n");
foreach my $name (@filenames)
{
    ProcessFilename($name);
}

# If html output required AND we have actually retrieved some data ...
if ( ($htmloutput) && (keys(%file_listing) > 0) )
{   
    #timestamped output filename
    my $htmloutputfile = "exif2map-output-".time.".html";

    open(my $html_output_file, ">".$htmloutputfile) || die("Unable to open $htmloutputfile for writing\n");

    my $htmltable = HTML::QuickTable->new(border => 1, labels => 1);

    # Added preceding "/" to "Filename" so that the HTML::QuickTable sorting doesn't result in
    # the column headings being re-ordered after / below a filename beginning with a "\".
    $file_listing{"/Filename"} = "GoogleMaps Link";

    print $html_output_file "<HTML>";
    print $html_output_file $htmltable->render(\%file_listing);
    print $html_output_file "<\/HTML>";

    close($htmloutputfile);
}

sub ProcessFilename
{
    my $filename = shift;

    if (-e $filename) #file must exist
    {
        my $exif = Image::ExifTool->new();
        # Extract all info from existing image
        if ($exif->ExtractInfo($filename))
        {
            # Ensure all 4 GPS params are present
            # ie GPSLatitude, GPSLatitudeRef, GPSLongitude, GPSLongitudeRef
            # The Ref values indicate North/South and East/West
            if ($exif->HasLocation())
            {
                my ($lat, $lon) = $exif->GetLocation();
                print("\n$filename contains Lat: $lat, Long: $lon\n");
                print("URL: http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en\n");
                if ($htmloutput) # save GoogleMaps URL to global hashmap indexed by filename
                {
                    $file_listing{$filename} = "<A HREF = \"http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en\"> http://maps.google.com/maps?q=$lat,+$lon($filename)&iwloc=A&hl=en</A>";
                }
                return 1;
            }
            else
            {
                print("\n$filename : No Location Info available!\n");
                return 0;
            }
        }
        else
        {
            print("\n$filename : Cannot Extract Info!\n");
            return 0;
        }
    }
    else
    {
        print("\n$filename does not exist!\n");
        return 0;
    }
}

#END CODE

A brief explanation? The first section is getting the command line args (GetOptions) and printing help if the user stuffs up. The next section is the main processing loop which takes the list of filenames and calls our ProcessFilename subroutine for each. The results will be printed AND stored in the %file_listing hash (contains the filename and the URL).
The next section handles writing the HTML file (if required) and uses the HTML::QuickTable module to come up with the HTML for the table. We then print a preceding <HTML> tag,  the table HTML and the trailing </HTML> tag all to a timestamped file in the users current directory called "exif2map-output-TIME.html". Where TIME is the number of seconds since 1970.


Testing

Here's a sample geotagged image from wikipedia:
I've saved it into "/cases/" along with a .jpg file with no EXIF geotag information (Cheeky4n6Monkey.jpg) and a mystery geotagged file (wheres-Cheeky4n6Monkey.jpg). I prepared this mystery file on the sly earlier (using a separate but similar Perl script). Note: Blogger seems to be stripping out the GPS data (maybe I missed setting some other parameters?) of the pic below but I did actually manage to set the GPS info on a local copy. Honest!


Mystery file - "wheres-Cheeky4n6Monkey.jpg"
 
Lets see what happens when we run our script ...

sansforensics@SIFT-Workstation:~$ exif2map.pl -f /cases/wheres-Cheeky4n6Monkey.jpg -f /cases/GPS_location_stamped_with_GPStamper.jpg  -f /cases/Cheeky4n6Monkey.jpg -html

exif2map.pl v2012.02.16

/cases/wheres-Cheeky4n6Monkey.jpg contains Lat: 36.1147630001389, Long: -115.172811
URL: http://maps.google.com/maps?q=36.1147630001389,+-115.172811(/cases/wheres-Cheeky4n6Monkey.jpg)&iwloc=A&hl=en

/cases/GPS_location_stamped_with_GPStamper.jpg contains Lat: 41.888948, Long: -87.624494
URL: http://maps.google.com/maps?q=41.888948,+-87.624494(/cases/GPS_location_stamped_with_GPStamper.jpg)&iwloc=A&hl=en

/cases/Cheeky4n6Monkey.jpg : No Location Info available!
sansforensics@SIFT-Workstation:~$


You can see that the script found/printed GPS positions and URLs for our mystery file wheres-Cheeky4n6Monkey.jpg and GPS_location_stamped_with_GPStamper.jpg but nothing for Cheeky4n6Monkey.jpg.

In Firefox, The outputted HTML file looks like:

Output HTML Table

Notice how we don't have a table entry for the file with no GPS info (Cheeky4n6Monkey.jpg)? I thought it wouldn't make sense as the whole purpose was to plot EXIF GPS data.
Anyhow, when we follow the table link for "GPS_location_stamped_with_GPStamper.jpg" we get:

"GPS_Location_stamped_withGPStamper.jpg" plotted on GoogleMaps

Aha! Chicago eh? No surprise when you look at the picture. Go Cubbies !


 "wheres-Cheeky4n6Monkey.jpg" gives:

 "wheres-Cheeky4n6Monkey.jpg" plotted on GoogleMaps


 36.114763,-115.172811 = Vegas Baby!  Uh-oh, what have you been up to Cheek4n6Monkey?!

Like any good forensicator we should validate our results. We can check our script's output against the "Exif Viewer" Firefox plugin (already installed on SIFT):

"Exif Viewer" of "GPS_Location_stamped_withGPStamper.jpg"


"Exif Viewer" of "wheres-Cheeky4n6Monkey.jpg"

Hooray! Our script output matches Exif Viewer's results for Latitude and Longitude. In fact, I think "Exif Viewer" has rounded off a few digits of precision.

So that's about it folks - all of this was not in vain. There might be some further formatting to be done to make it look prettier but the basic functionality has been achieved. Cool bananas eh? Let me know what your think - comments/suggestions/curses?


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 ...



Writing a CCleaner RegRipper Plugin Part 1

Introduction

Hello again!

I thought I would do another multi-part post - this time we will use SysInternals ProcMon (v 2.96) monitoring software to investigate the CCleaner (v 3.14.1616) Windows Cleaner program. As this program gets mentioned quite often on the LifeHacker website, I thought I'd try it out. It cleans Temporary (Internet and Windows) files, Browser History (downloads and forms), Cookies, Recycle Bin, the Recent Documents list, old log files and old/unused Registry entries. Since testing completed, an update has been released (3.14.1643).
Once we have finished our (very) basic analysis, we shall then write a RegRipper plugin (in Perl) and test it using the SIFT v2.12 VM. Hopefully, the CCleaner installer will leave some artifacts in the registry which we can then parse using our RegRipper plugin to prove that CCleaner was installed on the PC/see what user settings were set.

You can get your mits on the ProcMon executable here.
The CCleaner installer can be found here.

About CCleaner

CCleaner "Cleaner" Menu
CCleaner "Registry" Menu

After installing (for all users) and launching, there are 4 main menu buttons on the left hand side of the app window:
 - Cleaner (for selecting which Windows/Application log files to clean),
 - Registry (for selecting the Registry cleaning settings),
 - Tools (running Uninstallers, selecting Windows Startup programs, selecting Internet Explorer startup plugins, removing System Restore Points, setting the (free/whole drive) Drive Wiper settings) and
 - Options (Secure Delete and Wipe Free Space settings, Cookies to delete/keep, Folders to delete/exclude,  some further Advanced Settings).

As the objective is to write a RegRipper plugin to detect CCleaner installation/settings, we won't be trying to recover any deleted files at this time. Nor will we be investigating the Registry clean of old/unused entries functionality.
Just FYI - there is a "Secure Delete" overwrite setting (under Option-Settings) which can be used to overwrite deleted files from 1 to 35 times. This is not enabled by default but if it works as advertised, I suspect file recovery will be close to impossible.
There is also another option to "Wipe MFT Free Space" but presumably the user must either call the inbuilt "Drive Wiper" tool explicitly OR check the "Wipe Free Space" Cleaner option (not checked by default). A warning does pop up when the check box is selected recommending it stay unchecked for normal use due to the extra time it takes.

Further unchecked by default "Cleaner" Options include:
- Autocomplete Form History
- Saved Passwords
- Network Passwords
- DNS cache
- Old Prefetch data (timeframe unclear)
- tray notifications Cache
- Window Size/Location cache
- User Assist History

Additionally, under Option-Advanced settings, the "Only delete files in Windows Temp folder older than 24 hours" option is checked by default.

As you can see, there's a shedload of CCleaner functionality to investigate. Any takers?


Using ProcMon to analyse CCleaner's Cleaner Function

After launching the ProcMon.exe (does not require installation), the user is first shown a popup filter window. For now, we can "Cancel" it.
In the remaining main window, the capture button (third button from the left) should have a magnifying glass with a red line through it meaning ProcMon is not currently capturing any events. If it doesn't have the red line, toggle the button to stop monitoring.

Now we press the ProcMon capture button and launch the CCleaner program. In the lower part of the Cleaner option window, we press the "Analyze" button and when the analysis completes, we then press the "Run Cleaner" button.
We then exit the CCleaner program and toggle off the capture button in ProcMon.
In  ProcMon's File menu, we can save the capture for later review. I chose to save "All Events" in the native PML format.

If you now look at what you've captured (or open the .PML logfile), you can filter out everything except CCleaner events by going to the Filter...filter sub menu  (or the Filter pop up window) and then set the drop boxes to read "Process Name" "is" "CCleaner.exe" (probably have to type process name directly) then "Include". Now hit the "Add" button and then the "Apply" and "OK" buttons. The Filter Window generally looks something like this:

ProcMon Filter Window

 In my capture file, I still had ~200 000 events - clearly, more filtering is necessary.
However, in a rush of excitement / not-so youthful exuberance I started wading into this event list anyway to try to figure out what CCleaner actually does. Here's a brief (and probably incomplete) overview:
- Opens/Reads the CCleaner.exe prefetch file
- Reads the C:\, C:\Documents and Settings, C:\Program Files, C:\Windows directories
- Opens/Reads HKCU\Software\Piriform\CCleaner registry key
- mostly opens/queries but also sets HKCU\Software\Microsoft\Windows\CurrentVersion\Explorer\MountPoints2 subkeys
- tries to open various browsers profiles.ini files (presumably so it can find their cache/history files)
- searches thru HKCU\Software looking for applications to clean
- reads C:\RECYCLER
- reads the following directories looking for log (and .dmp)  files :
 C:\WINDOWS\Temp, C:\Documents and Settings, C:\WINDOWS\Minidump, C:\WINDOWS\system32\wbem\Logs, C:\Documents and Settings\All Users\Application Data\Microsoft\Dr Watson, C:\WINDOWS\Debug,C:\WINDOWS\security\logs, C:\WINDOWS\Logs, C:\WINDOWS\Microsoft.NET
- queries C:\WINDOWS\Prefetch directory
- queries browser profile directories
- calls WriteFile on C:\RECYCLER, C:\Documents and Settings\USERNAME\Recent\*.lnk files, index.dat files, Desktop.ini files, *.log files and (in this case) the Firefox Profiles directory.

Thats kinda where I finished up / lost the will to keep going ...

Getting back to our capture file, we shall try to limit the filter to Registry events only.
Setup/Add/Apply another filter for "Operation" is "RegQueryValue" then "include".
I just chose "RegQuery" value because it sounds like what CCleaner might use to read a CCleaner setting from the registry. There are actually loads more function names to choose from - see the MSDN documentation for registry function calls and for file system function calls.

Anyhow ... you should now see about 1500 events from keys such as HKLM and HKCU.
Scrolling down reveals ... events for the HKCU\Software\Piriform\CCleaner subkey and the HKLM\SOFTWARE\Piriform\CCleaner subkey.
Aha! If CCleaner saves its configuration info to the Registry, we can now create a RegRipper plugin to look for those registry values.
To better view these registry values, we can disable the previous filter and setup another filter - "Path contains Piriform" (also see ProcMon Filter Window pic above). The results indicate that the HKCU subkey contains our CCleaner configuration settings (eg WINDOW_WIDTH, WipeFreeSpaceDrives).

"Path contains Piriform" Filtered Results

So now we can see that if we create a RegRipper plugin to look for a Software\Piriform\CCleaner subkey and then parse all the values, we can determine what the User's settings were. Stay tuned viewers ... we'll do this in the next exciting installment.