mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 16:06:15 +00:00
384 lines
10 KiB
Perl
384 lines
10 KiB
Perl
#-----------------------------------------------------------
|
|
# shellbags.pl
|
|
# RR plugin to parse (Vista, Win7/Win2008R2) shell bags
|
|
#
|
|
# History:
|
|
# 20130514 - added checking of ShellNoRoam\Bags subkeys for WinXP
|
|
# 20120814 - created
|
|
#
|
|
# References
|
|
# http://c0nn3ct0r.blogspot.com/2011/11/windows-shellbag-forensics.html
|
|
# Andrew's Python code for Registry Decoder
|
|
# http://code.google.com/p/registrydecoder/source/browse/trunk/templates/template_files/ShellBag.py
|
|
# Joachim Metz's shell item format specification
|
|
# http://download.polytechnic.edu.na/pub4/download.sourceforge.net/pub/
|
|
# sourceforge/l/project/li/liblnk/Documentation/Windows%20Shell%20Item%20format/
|
|
# Windows%20Shell%20Item%20format.pdf
|
|
# Converting DOS Date format
|
|
# http://msdn.microsoft.com/en-us/library/windows/desktop/ms724274(v=VS.85).aspx
|
|
#
|
|
# Thanks to Willi Ballenthin and Joachim Metz for the documentation they
|
|
# provided, Andrew Case for posting the Registry Decoder code, and Kevin
|
|
# Moore for writing the shell bag parser for Registry Decoder, as well as
|
|
# assistance with some parsing.
|
|
#
|
|
# License: GPL v3
|
|
# copyright 2013 Quantum Analytics Research, LLC
|
|
# Author: H. Carvey, keydet89@yahoo.com
|
|
#-----------------------------------------------------------
|
|
package itempos;
|
|
use strict;
|
|
use Time::Local;
|
|
|
|
my %config = (hive => "NTUSER\.DAT",
|
|
hivemask => 16,
|
|
output => "report",
|
|
category => "User Activity",
|
|
osmask => 16, #Win7/Win2008R2
|
|
hasShortDescr => 1,
|
|
hasDescr => 0,
|
|
hasRefs => 0,
|
|
version => 20130514);
|
|
|
|
sub getConfig{return %config}
|
|
|
|
sub getShortDescr {
|
|
return "Shell/Bags/1/Desktop ItemPos* value parsing; Win7 NTUSER.DAT hives";
|
|
}
|
|
sub getDescr{}
|
|
sub getRefs {}
|
|
sub getHive {return $config{hive};}
|
|
sub getVersion {return $config{version};}
|
|
|
|
my $VERSION = getVersion();
|
|
|
|
sub pluginmain {
|
|
my $class = shift;
|
|
my $hive = shift;
|
|
::logMsg("Launching itempos v.".$VERSION);
|
|
::rptMsg("itempos v.".$VERSION); # banner
|
|
::rptMsg("(".$config{hive}.") ".getShortDescr()."\n"); # banner
|
|
my %itempos = ();
|
|
|
|
my $reg = Parse::Win32Registry->new($hive);
|
|
my $root_key = $reg->get_root_key;
|
|
|
|
my $key_path = "Software\\Microsoft\\Windows\\Shell\\Bags\\1\\Desktop";
|
|
my $key;
|
|
|
|
if ($key = $root_key->get_subkey($key_path)) {
|
|
::rptMsg($key_path);
|
|
my $lw = $key->get_timestamp();
|
|
::rptMsg("LastWrite: ".gmtime($lw));
|
|
::rptMsg("");
|
|
|
|
my @vals = $key->get_list_of_values();
|
|
foreach my $v (@vals) {
|
|
my $name = $v->get_name();
|
|
if ($name =~ m/^ItemPos/) {
|
|
$itempos{$name} = $v->get_data();
|
|
}
|
|
}
|
|
|
|
if (scalar keys %itempos > 0) {
|
|
foreach my $i (keys %itempos) {
|
|
::rptMsg("Value: ".$i);
|
|
::rptMsg(sprintf "%-10s|%-20s|%-20s|%-20s|Name","Size","Modified","Accessed","Created");
|
|
::rptMsg(sprintf "%-10s|%-20s|%-20s|%-20s|"."-" x 10,"-" x 10,"-" x 20,"-" x 20,"-" x 20);
|
|
parseBagEntry($itempos{$i});
|
|
::rptMsg("");
|
|
}
|
|
}
|
|
else {
|
|
::rptMsg("No ItemPos* values found.");
|
|
}
|
|
}
|
|
else {
|
|
::rptMsg($key_path." not found.");
|
|
}
|
|
# ::rptMsg("");
|
|
# The following was added on 20130514 to address Windows XP systems
|
|
$key_path = "Software\\Microsoft\\Windows\\ShellNoRoam\\Bags";
|
|
if ($key = $root_key->get_subkey($key_path)) {
|
|
my @sk = $key->get_list_of_subkeys();
|
|
if (scalar(@sk) > 0) {
|
|
foreach my $s (@sk) {
|
|
my %itempos = ();
|
|
my @vals = $s->get_subkey("Shell")->get_list_of_values();
|
|
|
|
if (scalar(@vals) > 0) {
|
|
foreach my $v (@vals) {
|
|
my $name = $v->get_name();
|
|
if ($name =~ m/^ItemPos/) {
|
|
$itempos{$name} = $v->get_data();
|
|
}
|
|
}
|
|
|
|
if (scalar keys %itempos > 0) {
|
|
::rptMsg($key_path."\\".$s->get_name()."\\Shell");
|
|
foreach my $i (keys %itempos) {
|
|
::rptMsg("Value: ".$i);
|
|
::rptMsg(sprintf "%-10s|%-20s|%-20s|%-20s|Name","Size","Modified","Accessed","Created");
|
|
::rptMsg(sprintf "%-10s|%-20s|%-20s|%-20s|"."-" x 10,"-" x 10,"-" x 20,"-" x 20,"-" x 20);
|
|
parseBagEntry($itempos{$i});
|
|
::rptMsg("");
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
# No subkeys
|
|
}
|
|
}
|
|
else {
|
|
::rptMsg($key_path." not found\.");
|
|
}
|
|
}
|
|
|
|
#-----------------------------------------------------------
|
|
#
|
|
#-----------------------------------------------------------
|
|
|
|
|
|
#-----------------------------------------------------------
|
|
# parseBagEntry()
|
|
#-----------------------------------------------------------
|
|
sub parseBagEntry {
|
|
my $data = shift;
|
|
my $ofs = 24;
|
|
my $len = length($data);
|
|
while ($ofs < $len) {
|
|
my %item = ();
|
|
my $sz = unpack("v",substr($data,$ofs,2));
|
|
|
|
my $data = substr($data,$ofs,$sz);
|
|
|
|
my $type = unpack("C",substr($data,2,1));
|
|
|
|
if ($type == 0x1f) {
|
|
%item = parseSystemBagItem($data);
|
|
::rptMsg(sprintf "%-10s|%-20s|%-20s|%-20s|".$item{name},"","","","");
|
|
}
|
|
elsif ($type == 0x31 || $type == 0x32 || $type == 0x3a) {
|
|
%item = parseFolderItem($data);
|
|
|
|
my ($m,$a,$c);
|
|
(exists $item{mtime_str} && $item{mtime_str} ne "0") ? ($m = $item{mtime_str}) : ($m = "");
|
|
(exists $item{atime_str} && $item{atime_str} ne "0") ? ($a = $item{atime_str}) : ($a = "");
|
|
(exists $item{ctime_str} && $item{ctime_str} ne "0") ? ($c = $item{ctime_str}) : ($c = "");
|
|
my $str = sprintf "%-10s|%-20s|%-20s|%-20s|",$item{size},$m,$a,$c;
|
|
::rptMsg($str.$item{name});
|
|
|
|
}
|
|
else {
|
|
|
|
}
|
|
$ofs += $sz + 8;
|
|
}
|
|
}
|
|
#-----------------------------------------------------------
|
|
# parseSystemBagItem()
|
|
#-----------------------------------------------------------
|
|
sub parseSystemBagItem {
|
|
my $data = shift;
|
|
my %item = ();
|
|
my %vals = (0x00 => "Explorer",
|
|
0x42 => "Libraries",
|
|
0x44 => "Users",
|
|
0x4c => "Public",
|
|
0x48 => "My Documents",
|
|
0x50 => "My Computer",
|
|
0x58 => "My Network Places",
|
|
0x60 => "Recycle Bin",
|
|
0x68 => "Explorer",
|
|
0x70 => "Control Panel",
|
|
0x78 => "Recycle Bin",
|
|
0x80 => "My Games");
|
|
|
|
$item{type} = unpack("C",substr($data,2,1));
|
|
$item{id} = unpack("C",substr($data,3,1));
|
|
if (exists $vals{$item{id}}) {
|
|
$item{name} = $vals{$item{id}};
|
|
}
|
|
else {
|
|
$item{name} = parseGUID(substr($data,4,16));
|
|
}
|
|
return %item;
|
|
}
|
|
|
|
#-----------------------------------------------------------
|
|
# parseFolderItem()
|
|
#-----------------------------------------------------------
|
|
sub parseFolderItem {
|
|
my $data = shift;
|
|
my %item = ();
|
|
my $ofs_mdate = 0x08;
|
|
$item{type} = unpack("C",substr($data,2,1));
|
|
|
|
$item{size} = unpack("V",substr($data,4,4));
|
|
|
|
my @m = unpack("vv",substr($data,$ofs_mdate,4));
|
|
($item{mtime_str},$item{mtime}) = convertDOSDate($m[0],$m[1]);
|
|
|
|
my $ofs_shortname = $ofs_mdate + 6;
|
|
my $tag = 1;
|
|
my $cnt = 0;
|
|
my $str = "";
|
|
while($tag) {
|
|
my $s = substr($data,$ofs_shortname + $cnt,1);
|
|
return %item unless (defined $s);
|
|
if ($s =~ m/\x00/ && ((($cnt + 1) % 2) == 0)) {
|
|
$tag = 0;
|
|
}
|
|
else {
|
|
$str .= $s;
|
|
$cnt++;
|
|
}
|
|
}
|
|
# $str =~ s/\x00//g;
|
|
my $shortname = $str;
|
|
my $ofs = $ofs_shortname + $cnt + 1;
|
|
# Read progressively, 1 byte at a time, looking for 0xbeef
|
|
$tag = 1;
|
|
$cnt = 0;
|
|
while ($tag) {
|
|
my $s = substr($data,$ofs + $cnt,2);
|
|
return %item unless (defined $s);
|
|
if (unpack("v",$s) == 0xbeef) {
|
|
$tag = 0;
|
|
}
|
|
else {
|
|
$cnt++;
|
|
}
|
|
}
|
|
$item{extver} = unpack("v",substr($data,$ofs + $cnt - 4,2));
|
|
$ofs = $ofs + $cnt + 2;
|
|
|
|
@m = unpack("vv",substr($data,$ofs,4));
|
|
($item{ctime_str},$item{ctime}) = convertDOSDate($m[0],$m[1]);
|
|
$ofs += 4;
|
|
@m = unpack("vv",substr($data,$ofs,4));
|
|
($item{atime_str},$item{atime}) = convertDOSDate($m[0],$m[1]);
|
|
|
|
my $jmp;
|
|
if ($item{extver} == 0x03) {
|
|
$jmp = 8;
|
|
}
|
|
elsif ($item{extver} == 0x07) {
|
|
$jmp = 26;
|
|
}
|
|
elsif ($item{extver} == 0x08) {
|
|
$jmp = 30;
|
|
}
|
|
else {}
|
|
|
|
$ofs += $jmp;
|
|
|
|
$str = substr($data,$ofs,length($data) - 30);
|
|
my $longname = (split(/\x00\x00/,$str,2))[0];
|
|
$longname =~ s/\x00//g;
|
|
|
|
if ($longname ne "") {
|
|
$item{name} = $longname;
|
|
}
|
|
else {
|
|
$item{name} = $shortname;
|
|
}
|
|
return %item;
|
|
|
|
|
|
}
|
|
|
|
#-----------------------------------------------------------
|
|
# convertDOSDate()
|
|
# subroutine to convert 4 bytes of binary data into a human-
|
|
# readable format. Returns both a string and a Unix-epoch
|
|
# time.
|
|
#-----------------------------------------------------------
|
|
sub convertDOSDate {
|
|
my $date = shift;
|
|
my $time = shift;
|
|
|
|
if ($date == 0x00 || $time == 0x00){
|
|
return (0,0);
|
|
}
|
|
else {
|
|
my $sec = ($time & 0x1f) * 2;
|
|
$sec = "0".$sec if (length($sec) == 1);
|
|
if ($sec == 60) {$sec = 59};
|
|
my $min = ($time & 0x7e0) >> 5;
|
|
$min = "0".$min if (length($min) == 1);
|
|
my $hr = ($time & 0xF800) >> 11;
|
|
$hr = "0".$hr if (length($hr) == 1);
|
|
my $day = ($date & 0x1f);
|
|
$day = "0".$day if (length($day) == 1);
|
|
my $mon = ($date & 0x1e0) >> 5;
|
|
$mon = "0".$mon if (length($mon) == 1);
|
|
my $yr = (($date & 0xfe00) >> 9) + 1980;
|
|
my $gmtime = timegm($sec,$min,$hr,$day,($mon - 1),$yr);
|
|
return ("$yr-$mon-$day $hr:$min:$sec",$gmtime);
|
|
# return gmtime(timegm($sec,$min,$hr,$day,($mon - 1),$yr));
|
|
}
|
|
}
|
|
|
|
#-----------------------------------------------------------
|
|
# parseGUID()
|
|
# Takes 16 bytes of binary data, returns a string formatted
|
|
# as an MS GUID.
|
|
#-----------------------------------------------------------
|
|
sub parseGUID {
|
|
my $data = shift;
|
|
my $d1 = unpack("V",substr($data,0,4));
|
|
my $d2 = unpack("v",substr($data,4,2));
|
|
my $d3 = unpack("v",substr($data,6,2));
|
|
my $d4 = unpack("H*",substr($data,8,2));
|
|
my $d5 = unpack("H*",substr($data,10,6));
|
|
return sprintf "{%08x-%x-%x-$d4-$d5}",$d1,$d2,$d3;
|
|
}
|
|
|
|
#-----------------------------------------------------------
|
|
# printData()
|
|
# subroutine used primarily for debugging; takes an arbitrary
|
|
# length of binary data, prints it out in hex editor-style
|
|
# format for easy debugging
|
|
#-----------------------------------------------------------
|
|
sub printData {
|
|
my $data = shift;
|
|
my $len = length($data);
|
|
my $tag = 1;
|
|
my $cnt = 0;
|
|
|
|
my $loop = $len/16;
|
|
$loop++ if ($len%16);
|
|
|
|
foreach my $cnt (0..($loop - 1)) {
|
|
# while ($tag) {
|
|
my $left = $len - ($cnt * 16);
|
|
|
|
my $n;
|
|
($left < 16) ? ($n = $left) : ($n = 16);
|
|
|
|
my $seg = substr($data,$cnt * 16,$n);
|
|
my @str1 = split(//,unpack("H*",$seg));
|
|
|
|
my @s3;
|
|
my $str = "";
|
|
|
|
foreach my $i (0..($n - 1)) {
|
|
$s3[$i] = $str1[$i * 2].$str1[($i * 2) + 1];
|
|
|
|
if (hex($s3[$i]) > 0x1f && hex($s3[$i]) < 0x7f) {
|
|
$str .= chr(hex($s3[$i]));
|
|
}
|
|
else {
|
|
$str .= "\.";
|
|
}
|
|
}
|
|
my $h = join(' ',@s3);
|
|
::rptMsg(sprintf "0x%08x: %-47s ".$str,($cnt * 16),$h);
|
|
}
|
|
}
|
|
1;
|