mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
392 lines
12 KiB
Perl
392 lines
12 KiB
Perl
#-----------------------------------------------------------
|
|
# samparse.pl
|
|
# Parse the SAM hive file for user/group membership info
|
|
#
|
|
# Change history:
|
|
# 20160203 - updated to include add'l values (randomaccess/Phill Moore contribution)
|
|
# 20120722 - updated %config hash
|
|
# 20110303 - Fixed parsing of SID, added check for account type
|
|
# Acct type determined based on Dustin Hulburt's "Forensic
|
|
# Determination of a User's Logon Status in Windows"
|
|
# from 10 Aug 2009 (link below)
|
|
# 20100712 - Added References entry
|
|
# 20091020 - Added extracting UserPasswordHint value
|
|
# 20090413 - Added account creation date
|
|
# 20080415 - created
|
|
#
|
|
# References
|
|
# Source available here: http://pogostick.net/~pnh/ntpasswd/
|
|
# http://accessdata.com/downloads/media/Forensic_Determination_Users_Logon_Status.pdf
|
|
#
|
|
# copyright 2016 Quantum Analytics Research, LLC
|
|
# Author: H. Carvey, keydet89@yahoo.com
|
|
#-----------------------------------------------------------
|
|
package samparse;
|
|
use strict;
|
|
use Encode::Unicode;
|
|
use JSON::PP;
|
|
|
|
my %config = (hive => "SAM",
|
|
hivemask => 2,
|
|
output => "report",
|
|
category => "",
|
|
osmask => 63, #XP - Win8
|
|
hasShortDescr => 1,
|
|
hasDescr => 0,
|
|
hasRefs => 1,
|
|
version => 20160203);
|
|
|
|
sub getConfig{return %config}
|
|
|
|
sub getShortDescr {
|
|
return "Parse SAM file for user & group mbrshp info";
|
|
}
|
|
sub getDescr{}
|
|
sub getRefs {
|
|
my %refs = ("Well-known SIDs" => "http://support.microsoft.com/kb/243330");
|
|
return %refs;
|
|
}
|
|
sub getHive {return $config{hive};}
|
|
sub getVersion {return $config{version};}
|
|
|
|
my $VERSION = getVersion();
|
|
|
|
my %acb_flags = (0x0001 => "Account Disabled",
|
|
0x0002 => "Home directory required",
|
|
0x0004 => "Password not required",
|
|
0x0008 => "Temporary duplicate account",
|
|
0x0010 => "Normal user account",
|
|
0x0020 => "MNS logon user account",
|
|
0x0040 => "Interdomain trust account",
|
|
0x0080 => "Workstation trust account",
|
|
0x0100 => "Server trust account",
|
|
0x0200 => "Password does not expire",
|
|
0x0400 => "Account auto locked");
|
|
|
|
my %types = (0xbc => "Default Admin User",
|
|
0xd4 => "Custom Limited Acct",
|
|
0xb0 => "Default Guest Acct");
|
|
|
|
sub pluginmain {
|
|
my $class = shift;
|
|
my $hive = shift;
|
|
::logMsg("Launching samparse v.".$VERSION);
|
|
::rptMsg("samparse v.".$VERSION); # banner
|
|
::rptMsg("(".getHive().") ".getShortDescr()."\n"); # banner
|
|
my $reg = Parse::Win32Registry->new($hive);
|
|
my $root_key = $reg->get_root_key;
|
|
::rptMsg("");
|
|
# Get user information
|
|
::rptMsg("User Information");
|
|
::rptMsg("-" x 25);
|
|
my $key_path = 'SAM\\Domains\\Account\\Users';
|
|
my $key;
|
|
my $local_sid = "";
|
|
my $account_key = $root_key->get_subkey("SAM\\Domains\\Account");
|
|
if (defined $account_key) {
|
|
my $account_value = $account_key->get_value("V");
|
|
if (defined $account_value) {
|
|
my $account_data = $account_value->get_data();
|
|
if (defined $account_data) {
|
|
my $data_len = length($account_data);
|
|
if ($data_len >= 12) {
|
|
my @vArray = unpack("VVV",substr($account_data, $data_len-12, 12));
|
|
my $vArray_len = @vArray;
|
|
if ($vArray_len == 3) {
|
|
$local_sid = "S-1-5-21-".$vArray[0]."-".$vArray[1]."-".$vArray[2];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ($key = $root_key->get_subkey($key_path)) {
|
|
my @user_list = $key->get_list_of_subkeys();
|
|
if (scalar(@user_list) > 0) {
|
|
foreach my $u (@user_list) {
|
|
my $rid = $u->get_name();
|
|
my $ts = $u->get_timestamp();
|
|
my $tag = "0000";
|
|
if ($rid =~ m/^$tag/) {
|
|
my $v_value = $u->get_value("V");
|
|
my $v = $v_value->get_data();
|
|
my %v_val = parseV($v);
|
|
$rid =~ s/^0000//;
|
|
$rid = hex($rid);
|
|
|
|
my $c_date;
|
|
eval {
|
|
my $create_path = $key_path."\\Names\\".$v_val{name};
|
|
if (my $create = $root_key->get_subkey($create_path)) {
|
|
$c_date = $create->get_timestamp();
|
|
}
|
|
};
|
|
|
|
::rptMsg("Username : ".$v_val{name}." [".$rid."]");
|
|
::rptMsg("SID : ".$local_sid."-".$rid);
|
|
::rptMsg("Full Name : ".$v_val{fullname});
|
|
::rptMsg("User Comment : ".$v_val{comment});
|
|
::rptMsg("Account Type : ".$v_val{type});
|
|
::rptMsg("Account Created : ".gmtime($c_date)." Z") if ($c_date > 0);
|
|
|
|
my $f_value = $u->get_value("F");
|
|
my $f = $f_value->get_data();
|
|
my %f_val = parseF($f);
|
|
|
|
eval {
|
|
my $reset_data_value = $u->get_value("ResetData");
|
|
my $reset_data = $reset_data_value->get_data();
|
|
my $reset_data_hash = decode_json($reset_data);
|
|
my $reset_data_question_1 = $reset_data_hash->{'questions'}[0];
|
|
my $reset_data_question_2 = $reset_data_hash->{'questions'}[1];
|
|
my $reset_data_question_3 = $reset_data_hash->{'questions'}[2];
|
|
my $question_1 = $reset_data_question_1->{'question'};
|
|
::rptMsg("Security Questions:");
|
|
::rptMsg(" Question 1 : ".$question_1);
|
|
::rptMsg(" Answer 1 : ".$reset_data_question_1->{'answer'});
|
|
::rptMsg(" Question 2 : ".$reset_data_question_2->{'question'});
|
|
::rptMsg(" Answer 2 : ".$reset_data_question_2->{'answer'});
|
|
::rptMsg(" Question 3 : ".$reset_data_question_3->{'question'});
|
|
::rptMsg(" Answer 3 : ".$reset_data_question_3->{'answer'});
|
|
};
|
|
|
|
my $lastlogin;
|
|
my $pwdreset;
|
|
my $pwdfail;
|
|
($f_val{last_login_date} == 0) ? ($lastlogin = "Never") : ($lastlogin = gmtime($f_val{last_login_date})." Z");
|
|
($f_val{pwd_reset_date} == 0) ? ($pwdreset = "Never") : ($pwdreset = gmtime($f_val{pwd_reset_date})." Z");
|
|
($f_val{pwd_fail_date} == 0) ? ($pwdfail = "Never") : ($pwdfail = gmtime($f_val{pwd_fail_date})." Z");
|
|
|
|
my $given;
|
|
my $surname;
|
|
eval {
|
|
$given = $u->get_value("GivenName")->get_data();
|
|
$given =~ s/\x00//g;
|
|
};
|
|
|
|
eval {
|
|
$surname = $u->get_value("SurName")->get_data();
|
|
$surname =~ s/\x00//g;
|
|
};
|
|
|
|
::rptMsg("Name : ".$given." ".$surname);
|
|
|
|
my $internet;
|
|
eval {
|
|
$internet = $u->get_value("InternetUserName")->get_data();
|
|
$internet =~ s/\x00//g;
|
|
::rptMsg("InternetName : ".$internet);
|
|
};
|
|
|
|
|
|
|
|
my $pw_hint;
|
|
eval {
|
|
$pw_hint = $u->get_value("UserPasswordHint")->get_data();
|
|
$pw_hint =~ s/\x00//g;
|
|
};
|
|
::rptMsg("Password Hint : ".$pw_hint) unless ($@);
|
|
::rptMsg("Last Login Date : ".$lastlogin);
|
|
::rptMsg("Pwd Reset Date : ".$pwdreset);
|
|
::rptMsg("Pwd Fail Date : ".$pwdfail);
|
|
::rptMsg("Login Count : ".$f_val{login_count});
|
|
foreach my $flag (keys %acb_flags) {
|
|
::rptMsg(" --> ".$acb_flags{$flag}) if ($f_val{acb_flags} & $flag);
|
|
}
|
|
::rptMsg("");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
::rptMsg($key_path." not found.");
|
|
::logMsg($key_path." not found.");
|
|
}
|
|
::rptMsg("-" x 25);
|
|
::rptMsg("Group Membership Information");
|
|
::rptMsg("-" x 25);
|
|
# Get Group membership information
|
|
$key_path = 'SAM\\Domains\\Builtin\\Aliases';
|
|
if ($key = $root_key->get_subkey($key_path)) {
|
|
my %grps;
|
|
my @groups = $key->get_list_of_subkeys();
|
|
if (scalar(@groups) > 0) {
|
|
foreach my $k (@groups) {
|
|
my $name = $k->get_name();
|
|
if ($name =~ m/^0000/) {
|
|
$grps{$name}{LastWrite} = $k->get_timestamp();
|
|
$grps{$name}{C_value} = $k->get_value("C")->get_data();
|
|
}
|
|
}
|
|
|
|
foreach my $k (keys %grps) {
|
|
my $name = $k;
|
|
$name =~ s/^0000//;
|
|
my %c_val = parseC($grps{$k}{C_value});
|
|
::rptMsg("Group Name : ".$c_val{group_name}." [".$c_val{num_users}."]");
|
|
::rptMsg("LastWrite : ".gmtime($grps{$k}{LastWrite})." Z");
|
|
::rptMsg("Group Comment : ".$c_val{comment});
|
|
if ($c_val{num_users} == 0) {
|
|
::rptMsg("Users : None");
|
|
}else {
|
|
my %users = parseCUsers($grps{$k}{C_value});
|
|
if (scalar(keys %users) != $c_val{num_users}) {
|
|
::logMsg("parseC function reports ".$c_val{num_users}."; parseCUsers function returned ".(scalar(keys %users)));
|
|
}
|
|
::rptMsg("Users :");
|
|
foreach my $u (keys %users) {
|
|
::rptMsg(" ".$u);
|
|
}
|
|
|
|
}
|
|
::rptMsg("");
|
|
}
|
|
::rptMsg("Analysis Tips:");
|
|
::rptMsg(" - For well-known SIDs, see http://support.microsoft.com/kb/243330");
|
|
::rptMsg(" - S-1-5-4 = Interactive");
|
|
::rptMsg(" - S-1-5-11 = Authenticated Users");
|
|
::rptMsg(" - Correlate the user SIDs to the output of the ProfileList plugin");
|
|
::rptMsg("");
|
|
}
|
|
else {
|
|
::rptMsg($key_path." has no subkeys.");
|
|
}
|
|
}
|
|
else {
|
|
::rptMsg($key_path." not found.");
|
|
}
|
|
}
|
|
|
|
sub parseF {
|
|
my $f = shift;
|
|
my %f_value = ();
|
|
my @tv;
|
|
# last login date
|
|
@tv = unpack("VV",substr($f,8,8));
|
|
$f_value{last_login_date} = ::getTime($tv[0],$tv[1]);
|
|
# password reset/acct creation
|
|
@tv = unpack("VV",substr($f,24,8));
|
|
$f_value{pwd_reset_date} = ::getTime($tv[0],$tv[1]);
|
|
# Account expires
|
|
@tv = unpack("VV",substr($f,32,8));
|
|
$f_value{acct_exp_date} = ::getTime($tv[0],$tv[1]);
|
|
# Incorrect password
|
|
@tv = unpack("VV",substr($f,40,8));
|
|
$f_value{pwd_fail_date} = ::getTime($tv[0],$tv[1]);
|
|
$f_value{rid} = unpack("V",substr($f,48,4));
|
|
$f_value{acb_flags} = unpack("v",substr($f,56,2));
|
|
$f_value{failed_count} = unpack("v",substr($f,64,2));
|
|
$f_value{login_count} = unpack("v",substr($f,66,2));
|
|
return %f_value;
|
|
}
|
|
|
|
sub parseV {
|
|
my $v = shift;
|
|
my %v_val = ();
|
|
my $header = substr($v,0,44);
|
|
my @vals = unpack("V*",$header);
|
|
$v_val{type} = $types{$vals[1]};
|
|
$v_val{name} = _uniToAscii(substr($v,($vals[3] + 0xCC),$vals[4]));
|
|
$v_val{fullname} = _uniToAscii(substr($v,($vals[6] + 0xCC),$vals[7])) if ($vals[7] > 0);
|
|
$v_val{comment} = _uniToAscii(substr($v,($vals[9] + 0xCC),$vals[10])) if ($vals[10] > 0);
|
|
return %v_val;
|
|
}
|
|
|
|
sub parseC {
|
|
my $cv = $_[0];
|
|
my %c_val = ();
|
|
my $header = substr($cv,0,0x34);
|
|
my @vals = unpack("V*",$header);
|
|
|
|
$c_val{group_name} = _uniToAscii(substr($cv,(0x34 + $vals[4]),$vals[5]));
|
|
$c_val{comment} = _uniToAscii(substr($cv,(0x34 + $vals[7]),$vals[8]));
|
|
$c_val{num_users} = $vals[12];
|
|
|
|
return %c_val;
|
|
}
|
|
|
|
sub parseCUsers {
|
|
my $cv = $_[0];
|
|
my %members = ();
|
|
my $header = substr($cv,0,0x34);
|
|
my @vals = unpack("V*",$header);
|
|
|
|
my $num = $vals[12];
|
|
|
|
my @users = ();
|
|
my $ofs;
|
|
if ($num > 0) {
|
|
my $count = 0;
|
|
foreach my $c (1..$num) {
|
|
my $ofs = $vals[10] + 52 + $count;
|
|
my $tmp = unpack("V",substr($cv,$ofs,4));
|
|
|
|
if ($tmp == 0x101) {
|
|
$ofs++ if (unpack("C",substr($cv,$ofs,1)) == 0);
|
|
$members{_translateSID(substr($cv,$ofs,12))} = 1;
|
|
$count += 12;
|
|
}
|
|
elsif ($tmp == 0x501) {
|
|
$members{_translateSID(substr($cv,$ofs,28))} = 1;
|
|
$count += 28;
|
|
}
|
|
else {
|
|
|
|
}
|
|
}
|
|
}
|
|
return %members;
|
|
}
|
|
|
|
#---------------------------------------------------------------------
|
|
# _translateSID()
|
|
# Translate binary data into a SID
|
|
# References:
|
|
# http://blogs.msdn.com/oldnewthing/archive/2004/03/15/89753.aspx
|
|
# http://support.microsoft.com/kb/286182/
|
|
# http://support.microsoft.com/kb/243330
|
|
#---------------------------------------------------------------------
|
|
sub _translateSID {
|
|
my $sid = $_[0];
|
|
my $len = length($sid);
|
|
my $revision;
|
|
my $dashes;
|
|
my $idauth;
|
|
if ($len < 12) {
|
|
# Is a SID ever less than 12 bytes?
|
|
return "SID less than 12 bytes";
|
|
}
|
|
elsif ($len == 12) {
|
|
$revision = unpack("C",substr($sid,0,1));
|
|
$dashes = unpack("C",substr($sid,1,1));
|
|
$idauth = unpack("H*",substr($sid,2,6));
|
|
$idauth =~ s/^0+//g;
|
|
my $sub = unpack("V",substr($sid,8,4));
|
|
return "S-".$revision."-".$idauth."-".$sub;
|
|
}
|
|
elsif ($len > 12) {
|
|
$revision = unpack("C",substr($sid,0,1));
|
|
$dashes = unpack("C",substr($sid,1,1));
|
|
$idauth = unpack("H*",substr($sid,2,6));
|
|
$idauth =~ s/^0+//g;
|
|
my @sub = unpack("V4",substr($sid,8,16));
|
|
my $rid = unpack("V",substr($sid,24,4));
|
|
my $s = join('-',@sub);
|
|
return "S-".$revision."-".$idauth."-".$s."-".$rid;
|
|
}
|
|
else {
|
|
# Nothing to do
|
|
}
|
|
}
|
|
|
|
#---------------------------------------------------------------------
|
|
# _uniToAscii()
|
|
#---------------------------------------------------------------------
|
|
sub _uniToAscii {
|
|
my $str = $_[0];
|
|
Encode::from_to($str,'UTF-16LE','utf8');
|
|
$str = Encode::decode_utf8($str);
|
|
return $str;
|
|
}
|
|
|
|
1;
|