mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-06 21:00:22 +00:00
465 lines
14 KiB
Perl
465 lines
14 KiB
Perl
package Parse::Win32Registry::WinNT::Key;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use base qw(Parse::Win32Registry::Key);
|
|
|
|
use Carp;
|
|
use Encode;
|
|
use Parse::Win32Registry::Base qw(:all);
|
|
use Parse::Win32Registry::WinNT::Value;
|
|
use Parse::Win32Registry::WinNT::Security;
|
|
|
|
use constant NK_HEADER_LENGTH => 0x50;
|
|
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $regfile = shift;
|
|
my $offset = shift; # offset to nk record relative to start of file
|
|
my $parent_key_path = shift; # parent key path (optional)
|
|
|
|
croak 'Missing registry file' if !defined $regfile;
|
|
croak 'Missing offset' if !defined $offset;
|
|
|
|
my $fh = $regfile->get_filehandle;
|
|
|
|
# 0x00 dword = key length (negative = allocated)
|
|
# 0x04 word = 'nk' signature
|
|
# 0x06 word = flags
|
|
# 0x08 qword = timestamp
|
|
# 0x10
|
|
# 0x14 dword = offset to parent
|
|
# 0x18 dword = number of subkeys
|
|
# 0x1c
|
|
# 0x20 dword = offset to subkey list (lf, lh, ri, li)
|
|
# 0x24
|
|
# 0x28 dword = number of values
|
|
# 0x2c dword = offset to value list
|
|
# 0x30 dword = offset to security
|
|
# 0x34 dword = offset to class name
|
|
# 0x38 dword = max subkey name length
|
|
# 0x3c dword = max class name length
|
|
# 0x40 dword = max value name length
|
|
# 0x44 dword = max value data length
|
|
# 0x48
|
|
# 0x4c word = key name length
|
|
# 0x4e word = class name length
|
|
# 0x50 = key name [for key name length bytes]
|
|
|
|
# Extracted offsets are always relative to first hbin
|
|
|
|
sysseek($fh, $offset, 0);
|
|
my $bytes_read = sysread($fh, my $nk_header, NK_HEADER_LENGTH);
|
|
if ($bytes_read != NK_HEADER_LENGTH) {
|
|
warnf('Could not read key at 0x%x', $offset);
|
|
return;
|
|
}
|
|
|
|
my ($length,
|
|
$sig,
|
|
$flags,
|
|
$timestamp,
|
|
# added 20190127
|
|
$access_bits,
|
|
$offset_to_parent,
|
|
$num_subkeys,
|
|
$offset_to_subkey_list,
|
|
$num_values,
|
|
$offset_to_value_list,
|
|
$offset_to_security,
|
|
$offset_to_class_name,
|
|
$largest_subkey_name_length,
|
|
$name_length,
|
|
$class_name_length,
|
|
# added 20190127
|
|
) = unpack('Va2va8VVVx4Vx4VVVVVx16vv', $nk_header);
|
|
# ) = unpack('Va2va8x4VVx4Vx4VVVVx20vv', $nk_header);
|
|
|
|
$offset_to_parent += OFFSET_TO_FIRST_HBIN
|
|
if $offset_to_parent != 0xffffffff;
|
|
$offset_to_subkey_list += OFFSET_TO_FIRST_HBIN
|
|
if $offset_to_subkey_list != 0xffffffff;
|
|
$offset_to_value_list += OFFSET_TO_FIRST_HBIN
|
|
if $offset_to_value_list != 0xffffffff;
|
|
$offset_to_security += OFFSET_TO_FIRST_HBIN
|
|
if $offset_to_security != 0xffffffff;
|
|
$offset_to_class_name += OFFSET_TO_FIRST_HBIN
|
|
if $offset_to_class_name != 0xffffffff;
|
|
|
|
my $allocated = 0;
|
|
if ($length > 0x7fffffff) {
|
|
$allocated = 1;
|
|
$length = (0xffffffff - $length) + 1;
|
|
}
|
|
# allocated should be true
|
|
|
|
if ($length < NK_HEADER_LENGTH) {
|
|
warnf('Invalid value entry length at 0x%x', $offset);
|
|
return;
|
|
}
|
|
|
|
if ($sig ne 'nk') {
|
|
warnf('Invalid signature for key at 0x%x', $offset);
|
|
return;
|
|
}
|
|
|
|
$bytes_read = sysread($fh, my $name, $name_length);
|
|
if ($bytes_read != $name_length) {
|
|
warnf('Could not read name for key at 0x%x', $offset);
|
|
return;
|
|
}
|
|
|
|
if ($flags & 0x20) {
|
|
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
|
|
}
|
|
else {
|
|
$name = decode('UCS-2LE', $name);
|
|
}
|
|
|
|
my $key_path = (defined $parent_key_path)
|
|
? "$parent_key_path\\$name"
|
|
: "$name";
|
|
|
|
my $class_name;
|
|
if ($offset_to_class_name != 0xffffffff) {
|
|
sysseek($fh, $offset_to_class_name + 4, 0);
|
|
$bytes_read = sysread($fh, $class_name, $class_name_length);
|
|
if ($bytes_read != $class_name_length) {
|
|
warnf('Could not read class name at 0x%x', $offset_to_class_name);
|
|
$class_name = undef;
|
|
}
|
|
else {
|
|
$class_name = decode('UCS-2LE', $class_name);
|
|
}
|
|
}
|
|
|
|
my $self = {};
|
|
$self->{_regfile} = $regfile;
|
|
$self->{_offset} = $offset;
|
|
$self->{_length} = $length;
|
|
$self->{_allocated} = $allocated;
|
|
$self->{_tag} = $sig;
|
|
$self->{_name} = $name;
|
|
$self->{_name_length} = $name_length;
|
|
$self->{_key_path} = $key_path;
|
|
$self->{_flags} = $flags;
|
|
$self->{_offset_to_parent} = $offset_to_parent;
|
|
$self->{_num_subkeys} = $num_subkeys;
|
|
$self->{_offset_to_subkey_list} = $offset_to_subkey_list;
|
|
$self->{_num_values} = $num_values;
|
|
$self->{_offset_to_value_list} = $offset_to_value_list;
|
|
$self->{_timestamp} = unpack_windows_time($timestamp);
|
|
# added 20190127
|
|
$self->{_access_bits} = $access_bits;
|
|
$self->{_largest_subkey_name_length} = $largest_subkey_name_length;
|
|
$self->{_offset_to_security} = $offset_to_security;
|
|
$self->{_offset_to_class_name} = $offset_to_class_name;
|
|
$self->{_class_name_length} = $class_name_length;
|
|
$self->{_class_name} = $class_name;
|
|
bless $self, $class;
|
|
|
|
return $self;
|
|
}
|
|
|
|
sub get_timestamp {
|
|
my $self = shift;
|
|
|
|
return $self->{_timestamp};
|
|
}
|
|
|
|
sub get_timestamp_as_string {
|
|
my $self = shift;
|
|
|
|
return iso8601($self->get_timestamp);
|
|
}
|
|
|
|
# added 20190127
|
|
sub get_access_bits {
|
|
my $self = shift;
|
|
return $self->{_access_bits};
|
|
}
|
|
|
|
sub get_largest_subkey_name_length {
|
|
my $self = shift;
|
|
return $self->{_largest_subkey_name_length};
|
|
}
|
|
|
|
|
|
sub get_class_name {
|
|
my $self = shift;
|
|
|
|
return $self->{_class_name};
|
|
}
|
|
|
|
sub is_root {
|
|
my $self = shift;
|
|
|
|
my $flags = $self->{_flags};
|
|
return $flags & 4 || $flags & 8;
|
|
}
|
|
|
|
sub get_parent {
|
|
my $self = shift;
|
|
|
|
my $regfile = $self->{_regfile};
|
|
my $offset_to_parent = $self->{_offset_to_parent};
|
|
my $key_path = $self->{_key_path};
|
|
|
|
return if $self->is_root;
|
|
|
|
my $grandparent_key_path;
|
|
my @keys = split /\\/, $key_path, -1;
|
|
if (@keys > 2) {
|
|
$grandparent_key_path = join('\\', @keys[0..$#keys-2]);
|
|
}
|
|
|
|
return Parse::Win32Registry::WinNT::Key->new($regfile,
|
|
$offset_to_parent,
|
|
$grandparent_key_path);
|
|
}
|
|
|
|
sub get_security {
|
|
my $self = shift;
|
|
|
|
my $regfile = $self->{_regfile};
|
|
my $offset_to_security = $self->{_offset_to_security};
|
|
my $key_path = $self->{_key_path};
|
|
|
|
if ($offset_to_security == 0xffffffff) {
|
|
return;
|
|
}
|
|
|
|
return Parse::Win32Registry::WinNT::Security->new($regfile,
|
|
$offset_to_security,
|
|
$key_path);
|
|
}
|
|
|
|
sub as_string {
|
|
my $self = shift;
|
|
|
|
my $string = $self->get_path . ' [' . $self->get_timestamp_as_string . ']';
|
|
return $string;
|
|
}
|
|
|
|
sub parse_info {
|
|
my $self = shift;
|
|
|
|
my $info = sprintf '0x%x nk len=0x%x alloc=%d "%s" par=0x%x keys=%d,0x%x vals=%d,0x%x sec=0x%x class=0x%x',
|
|
$self->{_offset},
|
|
$self->{_length},
|
|
$self->{_allocated},
|
|
$self->{_name},
|
|
$self->{_offset_to_parent},
|
|
$self->{_num_subkeys}, $self->{_offset_to_subkey_list},
|
|
$self->{_num_values}, $self->{_offset_to_value_list},
|
|
$self->{_offset_to_security},
|
|
$self->{_offset_to_class_name};
|
|
if (defined $self->{_class_name}) {
|
|
$info .= sprintf ',len=0x%x', $self->{_class_name_length};
|
|
}
|
|
return $info;
|
|
}
|
|
|
|
sub _get_offsets_to_subkeys {
|
|
my $self = shift;
|
|
|
|
# Offset is passed as a parameter for recursive lists such as 'ri'
|
|
my $offset_to_subkey_list = shift || $self->{_offset_to_subkey_list};
|
|
|
|
my $regfile = $self->{_regfile};
|
|
my $fh = $regfile->get_filehandle;
|
|
|
|
return if $offset_to_subkey_list == 0xffffffff
|
|
|| $self->{_num_subkeys} == 0;
|
|
|
|
sysseek($fh, $offset_to_subkey_list, 0);
|
|
my $bytes_read = sysread($fh, my $subkey_list_header, 8);
|
|
if ($bytes_read != 8) {
|
|
warnf('Could not read subkey list header at 0x%x',
|
|
$offset_to_subkey_list);
|
|
return;
|
|
}
|
|
|
|
# 0x00 dword = subkey list length (negative = allocated)
|
|
# 0x04 word = 'lf' signature
|
|
# 0x06 word = number of entries
|
|
# 0x08 dword = offset to 1st subkey
|
|
# 0x0c dword = first four characters of the key name
|
|
# 0x10 dword = offset to 2nd subkey
|
|
# 0x14 dword = first four characters of the key name
|
|
# ...
|
|
|
|
# 0x00 dword = subkey list length (negative = allocated)
|
|
# 0x04 word = 'lh' signature
|
|
# 0x06 word = number of entries
|
|
# 0x08 dword = offset to 1st subkey
|
|
# 0x0c dword = hash of the key name
|
|
# 0x10 dword = offset to 2nd subkey
|
|
# 0x14 dword = hash of the key name
|
|
# ...
|
|
|
|
# 0x00 dword = subkey list length (negative = allocated)
|
|
# 0x04 word = 'ri' signature
|
|
# 0x06 word = number of entries in ri list
|
|
# 0x08 dword = offset to 1st lf/lh/li list
|
|
# 0x0c dword = offset to 2nd lf/lh/li list
|
|
# 0x10 dword = offset to 3rd lf/lh/li list
|
|
# ...
|
|
|
|
# 0x00 dword = subkey list length (negative = allocated)
|
|
# 0x04 word = 'li' signature
|
|
# 0x06 word = number of entries in li list
|
|
# 0x08 dword = offset to 1st subkey
|
|
# 0x0c dword = offset to 2nd subkey
|
|
# ...
|
|
|
|
# Extracted offsets are always relative to first hbin
|
|
|
|
my @offsets_to_subkeys = ();
|
|
|
|
my ($length,
|
|
$sig,
|
|
$num_entries,
|
|
) = unpack('Va2v', $subkey_list_header);
|
|
|
|
my $subkey_list_length;
|
|
if ($sig eq 'lf' || $sig eq 'lh') {
|
|
$subkey_list_length = 2 * 4 * $num_entries;
|
|
}
|
|
elsif ($sig eq 'ri' || $sig eq 'li') {
|
|
$subkey_list_length = 4 * $num_entries;
|
|
}
|
|
else {
|
|
warnf('Invalid signature for subkey list at 0x%x',
|
|
$offset_to_subkey_list);
|
|
return;
|
|
}
|
|
|
|
$bytes_read = sysread($fh, my $subkey_list, $subkey_list_length);
|
|
if ($bytes_read != $subkey_list_length) {
|
|
warnf('Could not read subkey list at 0x%x',
|
|
$offset_to_subkey_list);
|
|
return;
|
|
}
|
|
|
|
if ($sig eq 'lf') {
|
|
foreach my $offset (unpack("(Vx4)$num_entries", $subkey_list)) {
|
|
push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset;
|
|
}
|
|
}
|
|
elsif ($sig eq 'lh') {
|
|
foreach my $offset (unpack("(Vx4)$num_entries", $subkey_list)) {
|
|
push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset;
|
|
}
|
|
}
|
|
elsif ($sig eq 'ri') {
|
|
foreach my $offset (unpack("V$num_entries", $subkey_list)) {
|
|
my $offsets_ref =
|
|
$self->_get_offsets_to_subkeys(OFFSET_TO_FIRST_HBIN + $offset);
|
|
if (defined $offsets_ref && ref $offsets_ref eq 'ARRAY') {
|
|
push @offsets_to_subkeys, @{ $offsets_ref };
|
|
}
|
|
}
|
|
}
|
|
elsif ($sig eq 'li') {
|
|
foreach my $offset (unpack("V$num_entries", $subkey_list)) {
|
|
push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset;
|
|
}
|
|
}
|
|
|
|
return \@offsets_to_subkeys;
|
|
}
|
|
|
|
sub get_subkey_iterator {
|
|
my $self = shift;
|
|
|
|
my $regfile = $self->{_regfile};
|
|
my $key_path = $self->{_key_path};
|
|
|
|
my @offsets_to_subkeys = ();
|
|
if ($self->{_num_subkeys} > 0) {
|
|
my $offsets_to_subkeys_ref = $self->_get_offsets_to_subkeys;
|
|
if (defined $offsets_to_subkeys_ref) {
|
|
@offsets_to_subkeys = @{$self->_get_offsets_to_subkeys};
|
|
}
|
|
}
|
|
|
|
return Parse::Win32Registry::Iterator->new(sub {
|
|
while (defined(my $offset_to_subkey = shift @offsets_to_subkeys)) {
|
|
my $subkey = Parse::Win32Registry::WinNT::Key->new($regfile,
|
|
$offset_to_subkey, $key_path);
|
|
if (defined $subkey) {
|
|
return $subkey;
|
|
}
|
|
}
|
|
return; # no more offsets to subkeys
|
|
});
|
|
}
|
|
|
|
sub _get_offsets_to_values {
|
|
my $self = shift;
|
|
|
|
my $regfile = $self->{_regfile};
|
|
my $fh = $regfile->get_filehandle;
|
|
my $offset_to_value_list = $self->{_offset_to_value_list};
|
|
|
|
my $num_values = $self->{_num_values};
|
|
return if $num_values == 0;
|
|
# Actually, this could probably just fall through
|
|
# as unpack("x4V0", ...) would return an empty array.
|
|
|
|
my @offsets_to_values = ();
|
|
|
|
# 0x00 dword = value list length (negative = allocated)
|
|
# 0x04 dword = 1st offset
|
|
# 0x08 dword = 2nd offset
|
|
# ...
|
|
|
|
# Extracted offsets are always relative to first hbin
|
|
|
|
sysseek($fh, $offset_to_value_list, 0);
|
|
my $value_list_length = 0x4 + $num_values * 4;
|
|
my $bytes_read = sysread($fh, my $value_list, $value_list_length);
|
|
if ($bytes_read != $value_list_length) {
|
|
warnf("Could not read value list at 0x%x",
|
|
$offset_to_value_list);
|
|
return;
|
|
}
|
|
|
|
foreach my $offset (unpack("x4V$num_values", $value_list)) {
|
|
push @offsets_to_values, OFFSET_TO_FIRST_HBIN + $offset;
|
|
}
|
|
|
|
return \@offsets_to_values;
|
|
}
|
|
|
|
sub get_value_iterator {
|
|
my $self = shift;
|
|
|
|
my $regfile = $self->{_regfile};
|
|
my $key_path = $self->{_key_path};
|
|
|
|
my @offsets_to_values = ();
|
|
if ($self->{_num_values} > 0) {
|
|
my $offsets_to_values_ref = $self->_get_offsets_to_values;
|
|
if (defined $offsets_to_values_ref) {
|
|
@offsets_to_values = @{$self->_get_offsets_to_values};
|
|
}
|
|
}
|
|
|
|
return Parse::Win32Registry::Iterator->new(sub {
|
|
while (defined(my $offset_to_value = shift @offsets_to_values)) {
|
|
my $value = Parse::Win32Registry::WinNT::Value->new($regfile,
|
|
$offset_to_value);
|
|
if (defined $value) {
|
|
return $value;
|
|
}
|
|
}
|
|
return; # no more offsets to values
|
|
});
|
|
}
|
|
|
|
1;
|