mirror of
https://github.com/overcuriousity/autopsy-flatpak.git
synced 2025-07-12 07:56:16 +00:00
541 lines
14 KiB
Perl
541 lines
14 KiB
Perl
package Parse::Win32Registry::Win95::File;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use base qw(Parse::Win32Registry::File);
|
|
|
|
use Carp;
|
|
use File::Basename;
|
|
use Parse::Win32Registry::Base qw(:all);
|
|
use Parse::Win32Registry::Win95::Key;
|
|
|
|
use constant CREG_HEADER_LENGTH => 0x20;
|
|
use constant OFFSET_TO_RGKN_BLOCK => 0x20;
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $filename = shift or croak 'No filename specified';
|
|
|
|
open my $fh, '<', $filename or croak "Unable to open '$filename': $!";
|
|
|
|
# CREG Header
|
|
# 0x00 dword = 'CREG' signature
|
|
# 0x04
|
|
# 0x08 dword = offset to first rgdb block
|
|
# 0x0c
|
|
# 0x10 word = number of rgdb blocks
|
|
|
|
my $bytes_read = sysread($fh, my $creg_header, CREG_HEADER_LENGTH);
|
|
if ($bytes_read != CREG_HEADER_LENGTH) {
|
|
warnf('Could not read registry file header');
|
|
return;
|
|
}
|
|
|
|
my ($creg_sig,
|
|
$offset_to_first_rgdb_block,
|
|
$num_rgdb_blocks) = unpack('a4x4Vx4v', $creg_header);
|
|
|
|
if ($creg_sig ne 'CREG') {
|
|
warnf('Invalid registry file signature');
|
|
return;
|
|
}
|
|
|
|
my $self = {};
|
|
$self->{_filehandle} = $fh;
|
|
$self->{_filename} = $filename;
|
|
$self->{_length} = (stat $fh)[7];
|
|
$self->{_offset_to_first_rgdb_block} = $offset_to_first_rgdb_block;
|
|
$self->{_num_rgdb_blocks} = $num_rgdb_blocks;
|
|
bless $self, $class;
|
|
|
|
# get_rgkn will cache the rgkn block for subsequent calls
|
|
my $rgkn_block = $self->get_rgkn;
|
|
return if !defined $rgkn_block; # warning will already have been made
|
|
|
|
# Index the rgdb entries by id for faster look up
|
|
$self->_index_rgdb_entries;
|
|
|
|
return $self;
|
|
}
|
|
|
|
sub get_timestamp {
|
|
return undef;
|
|
}
|
|
|
|
sub get_timestamp_as_string {
|
|
return iso8601(undef);
|
|
}
|
|
|
|
sub get_embedded_filename {
|
|
return undef;
|
|
}
|
|
|
|
sub get_root_key {
|
|
my $self = shift;
|
|
|
|
return $self->get_rgkn->get_root_key;
|
|
}
|
|
|
|
sub get_virtual_root_key {
|
|
my $self = shift;
|
|
my $fake_root = shift;
|
|
|
|
my $root_key = $self->get_root_key;
|
|
return if !defined $root_key;
|
|
|
|
if (!defined $fake_root) {
|
|
# guess virtual root from filename
|
|
my $filename = basename $self->{_filename};
|
|
|
|
if ($filename =~ /USER/i) {
|
|
$fake_root = 'HKEY_USERS';
|
|
}
|
|
elsif ($filename =~ /SYSTEM/i) {
|
|
$fake_root = 'HKEY_LOCAL_MACHINE';
|
|
}
|
|
else {
|
|
$fake_root = 'HKEY_UNKNOWN';
|
|
}
|
|
}
|
|
|
|
$root_key->{_name} = $fake_root;
|
|
$root_key->{_key_path} = $fake_root;
|
|
|
|
return $root_key;
|
|
}
|
|
|
|
sub _index_rgdb_entries {
|
|
my $self = shift;
|
|
|
|
my %index = ();
|
|
|
|
# Build index of rgdb key entries
|
|
# Entries are only included if $key_block_num matches $rgdb_block_num
|
|
my $rgdb_block_num = 0;
|
|
my $rgdb_iter = $self->get_rgdb_iterator;
|
|
while (my $rgdb = $rgdb_iter->()) {
|
|
my $rgdb_key_iter = $rgdb->get_key_iterator;
|
|
while (my $rgdb_key = $rgdb_key_iter->()) {
|
|
my $key_id = $rgdb_key->{_id};
|
|
my $key_block_num = $key_id >> 16;
|
|
if ($rgdb_block_num == $key_block_num) {
|
|
$index{$key_id} = $rgdb_key;
|
|
}
|
|
}
|
|
$rgdb_block_num++;
|
|
}
|
|
|
|
$self->{_rgdb_index} = \%index;
|
|
}
|
|
|
|
sub _dump_rgdb_index {
|
|
my $self = shift;
|
|
|
|
my $rgdb_index = $self->{_rgdb_index};
|
|
|
|
foreach my $key_id (sort { $a <=> $b } keys %$rgdb_index) {
|
|
my $rgdb_key = $rgdb_index->{$key_id};
|
|
printf qq{id=0x%x 0x%x,%d/%d "%s" vals=%d\n},
|
|
$key_id,
|
|
$rgdb_key->{_offset},
|
|
$rgdb_key->{_length_used},
|
|
$rgdb_key->{_length},
|
|
$rgdb_key->{_name},
|
|
$rgdb_key->{_num_values};
|
|
}
|
|
}
|
|
|
|
sub get_rgkn {
|
|
my $self = shift;
|
|
|
|
# Return cached rgkn block if present
|
|
if (defined $self->{_rgkn}) {
|
|
return $self->{_rgkn};
|
|
}
|
|
|
|
my $offset = OFFSET_TO_RGKN_BLOCK;
|
|
my $rgkn_block = Parse::Win32Registry::Win95::RGKN->new($self, $offset);
|
|
$self->{_rgkn} = $rgkn_block;
|
|
return $rgkn_block;
|
|
}
|
|
|
|
sub get_rgdb_iterator {
|
|
my $self = shift;
|
|
|
|
my $offset_to_next_rgdb_block = $self->{_offset_to_first_rgdb_block};
|
|
my $num_rgdb_blocks = $self->{_num_rgdb_blocks};
|
|
|
|
my $end_of_file = $self->{_length};
|
|
|
|
my $rgdb_block_num = 0;
|
|
|
|
return Parse::Win32Registry::Iterator->new(sub {
|
|
if ($offset_to_next_rgdb_block > $end_of_file) {
|
|
return; # no more rgdb blocks
|
|
}
|
|
if ($rgdb_block_num >= $num_rgdb_blocks) {
|
|
return; # no more rgdb blocks
|
|
}
|
|
$rgdb_block_num++;
|
|
if (my $rgdb_block = Parse::Win32Registry::Win95::RGDB->new($self,
|
|
$offset_to_next_rgdb_block))
|
|
{
|
|
return unless $rgdb_block->get_length > 0;
|
|
$offset_to_next_rgdb_block += $rgdb_block->get_length;
|
|
return $rgdb_block;
|
|
}
|
|
});
|
|
}
|
|
|
|
sub get_block_iterator {
|
|
my $self = shift;
|
|
|
|
my $rgdb_iter;
|
|
|
|
return Parse::Win32Registry::Iterator->new(sub {
|
|
if (!defined $rgdb_iter) {
|
|
$rgdb_iter = $self->get_rgdb_iterator;
|
|
return $self->get_rgkn;
|
|
}
|
|
return $rgdb_iter->();
|
|
});
|
|
}
|
|
|
|
*get_hbin_iterator = \&get_block_iterator;
|
|
|
|
|
|
package Parse::Win32Registry::Win95::RGKN;
|
|
|
|
use strict;
|
|
use warnings;
|
|
|
|
use base qw(Parse::Win32Registry::Entry);
|
|
|
|
use Carp;
|
|
use Parse::Win32Registry::Base qw(:all);
|
|
|
|
use constant RGKN_HEADER_LENGTH => 0x20;
|
|
use constant OFFSET_TO_RGKN_BLOCK => 0x20;
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $regfile = shift;
|
|
my $offset = shift || OFFSET_TO_RGKN_BLOCK;
|
|
|
|
croak 'Missing registry file' if !defined $regfile;
|
|
croak 'Missing offset' if !defined $offset;
|
|
|
|
my $fh = $regfile->get_filehandle;
|
|
|
|
# RGKN Block Header
|
|
# 0x0 dword = 'RGKN' signature
|
|
# 0x4 dword = length of rgkn block
|
|
# 0x8 dword = offset to root key entry (relative to start of rgkn block)
|
|
|
|
sysseek($fh, $offset, 0);
|
|
my $bytes_read = sysread($fh, my $rgkn_header, RGKN_HEADER_LENGTH);
|
|
if ($bytes_read != RGKN_HEADER_LENGTH) {
|
|
warnf('Could not read RGKN header at 0x%x', $offset);
|
|
return;
|
|
}
|
|
|
|
my ($sig,
|
|
$rgkn_block_length,
|
|
$offset_to_root_key) = unpack('a4VV', $rgkn_header);
|
|
|
|
if ($sig ne 'RGKN') {
|
|
warnf('Invalid RGKN block signature at 0x%x', $offset);
|
|
return;
|
|
}
|
|
|
|
$offset_to_root_key += $offset;
|
|
|
|
my $self = {};
|
|
$self->{_regfile} = $regfile;
|
|
$self->{_offset} = $offset;
|
|
$self->{_length} = $rgkn_block_length;
|
|
$self->{_header_length} = RGKN_HEADER_LENGTH;
|
|
$self->{_allocated} = 1;
|
|
$self->{_tag} = 'rgkn block';
|
|
$self->{_offset_to_root_key} = $offset_to_root_key;
|
|
bless $self, $class;
|
|
|
|
return $self;
|
|
}
|
|
|
|
sub get_root_key {
|
|
my $self = shift;
|
|
|
|
my $regfile = $self->{_regfile};
|
|
my $offset_to_root_key = $self->{_offset_to_root_key};
|
|
|
|
my $root_key = Parse::Win32Registry::Win95::Key->new($regfile,
|
|
$offset_to_root_key);
|
|
return $root_key;
|
|
}
|
|
|
|
sub get_entry_iterator {
|
|
my $self = shift;
|
|
|
|
my $root_key = $self->get_root_key;
|
|
|
|
# In the unlikely event there is no root key, return an empty iterator
|
|
if (defined $root_key) {
|
|
return $root_key->get_subtree_iterator;
|
|
}
|
|
else {
|
|
return Parse::Win32Registry::Iterator->new(sub {});
|
|
}
|
|
}
|
|
|
|
|
|
package Parse::Win32Registry::Win95::RGDB;
|
|
|
|
use base qw(Parse::Win32Registry::Entry);
|
|
|
|
use Carp;
|
|
use Parse::Win32Registry::Base qw(:all);
|
|
|
|
use constant RGDB_HEADER_LENGTH => 0x20;
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $regfile = shift;
|
|
my $offset = shift;
|
|
|
|
croak 'Missing registry file' if !defined $regfile;
|
|
croak 'Missing offset' if !defined $offset;
|
|
|
|
my $fh = $regfile->get_filehandle;
|
|
|
|
# RGDB Block Header
|
|
# 0x0 dword = 'RDGB' signature
|
|
# 0x4 dword = length of rgdb block
|
|
|
|
sysseek($fh, $offset, 0);
|
|
my $bytes_read = sysread($fh, my $rgdb_header, RGDB_HEADER_LENGTH);
|
|
if ($bytes_read != RGDB_HEADER_LENGTH) {
|
|
return;
|
|
}
|
|
|
|
my ($sig,
|
|
$rgdb_block_length) = unpack('a4V', $rgdb_header);
|
|
|
|
if ($sig ne 'RGDB') {
|
|
return;
|
|
}
|
|
|
|
my $self = {};
|
|
$self->{_regfile} = $regfile;
|
|
$self->{_offset} = $offset;
|
|
$self->{_length} = $rgdb_block_length;
|
|
$self->{_header_length} = RGDB_HEADER_LENGTH;
|
|
$self->{_allocated} = 1;
|
|
$self->{_tag} = 'rgdb block';
|
|
bless $self, $class;
|
|
|
|
return $self;
|
|
}
|
|
|
|
sub get_key_iterator {
|
|
my $self = shift;
|
|
|
|
my $regfile = $self->{_regfile};
|
|
my $offset = $self->{_offset};
|
|
my $length = $self->{_length};
|
|
|
|
my $offset_to_next_rgdb_key = $offset + RGDB_HEADER_LENGTH;
|
|
my $end_of_rgdb_block = $offset + $length;
|
|
|
|
return Parse::Win32Registry::Iterator->new(sub {
|
|
if ($offset_to_next_rgdb_key >= $end_of_rgdb_block) {
|
|
return;
|
|
}
|
|
if (my $rgdb_key = Parse::Win32Registry::Win95::RGDBKey->new($regfile,
|
|
$offset_to_next_rgdb_key))
|
|
{
|
|
return unless $rgdb_key->get_length > 0;
|
|
$offset_to_next_rgdb_key += $rgdb_key->get_length;
|
|
|
|
# Check rgdb key has not run past end of rgdb block
|
|
if ($offset_to_next_rgdb_key > $end_of_rgdb_block) {
|
|
return;
|
|
}
|
|
return $rgdb_key;
|
|
}
|
|
});
|
|
}
|
|
|
|
sub get_entry_iterator {
|
|
my $self = shift;
|
|
|
|
my $value_iter;
|
|
my $key_iter = $self->get_key_iterator;
|
|
|
|
return Parse::Win32Registry::Iterator->new(sub {
|
|
if (defined $value_iter) {
|
|
my $value = $value_iter->();
|
|
if (defined $value) {
|
|
return $value;
|
|
}
|
|
}
|
|
|
|
my $key = $key_iter->();
|
|
if (!defined $key) {
|
|
return; # key iterator finished
|
|
}
|
|
|
|
$value_iter = $key->get_value_iterator;
|
|
return $key;
|
|
});
|
|
}
|
|
|
|
|
|
package Parse::Win32Registry::Win95::RGDBKey;
|
|
|
|
use base qw(Parse::Win32Registry::Entry);
|
|
|
|
use Carp;
|
|
use Encode;
|
|
use Parse::Win32Registry::Base qw(:all);
|
|
|
|
use constant RGDB_ENTRY_HEADER_LENGTH => 0x14;
|
|
|
|
sub new {
|
|
my $class = shift;
|
|
my $regfile = shift;
|
|
my $offset = shift;
|
|
|
|
croak 'Missing registry file' if !defined $regfile;
|
|
croak 'Missing offset' if !defined $offset;
|
|
|
|
my $fh = $regfile->get_filehandle;
|
|
|
|
# RGDB Key Entry
|
|
# 0x00 dword = length of rgdb entry / offset to next rgdb entry
|
|
# (this length includes any following value entries)
|
|
# 0x04 dword = id (top word = block num, bottom word = id)
|
|
# 0x08 dword = bytes used (unpacked, but not used)
|
|
# 0x0c word = key name length
|
|
# 0x0e word = number of values
|
|
# 0x10 dword
|
|
# 0x14 = key name [for key name length bytes]
|
|
# followed immediately by any RGDB Value Entries belonging to this key
|
|
|
|
sysseek($fh, $offset, 0);
|
|
my $bytes_read = sysread($fh, my $rgdb_key_entry, RGDB_ENTRY_HEADER_LENGTH);
|
|
if ($bytes_read != RGDB_ENTRY_HEADER_LENGTH) {
|
|
return;
|
|
}
|
|
|
|
my ($length,
|
|
$key_id,
|
|
$length_used,
|
|
$name_length,
|
|
$num_values) = unpack('VVVvv', $rgdb_key_entry);
|
|
|
|
$bytes_read = sysread($fh, my $name, $name_length);
|
|
if ($bytes_read != $name_length) {
|
|
return;
|
|
}
|
|
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
|
|
|
|
# Calculate the length of the entry's key header
|
|
my $header_length = RGDB_ENTRY_HEADER_LENGTH + $name_length;
|
|
|
|
# Check for invalid/unused entries
|
|
if ($key_id == 0xffffffff || $length_used == 0xffffffff
|
|
|| $header_length > $length)
|
|
{
|
|
$name = '';
|
|
$header_length = RGDB_ENTRY_HEADER_LENGTH;
|
|
}
|
|
|
|
my $self = {};
|
|
$self->{_regfile} = $regfile;
|
|
$self->{_offset} = $offset;
|
|
$self->{_length} = $length;
|
|
$self->{_length_used} = $length_used;
|
|
$self->{_header_length} = $header_length;
|
|
$self->{_allocated} = 1;
|
|
$self->{_tag} = 'rgdb key';
|
|
$self->{_id} = $key_id;
|
|
$self->{_name} = $name;
|
|
$self->{_name_length} = $name_length;
|
|
$self->{_num_values} = $num_values;
|
|
bless $self, $class;
|
|
|
|
return $self;
|
|
}
|
|
|
|
sub get_name {
|
|
my $self = shift;
|
|
|
|
return $self->{_name};
|
|
}
|
|
|
|
sub parse_info {
|
|
my $self = shift;
|
|
|
|
my $info = sprintf '0x%x rgdb key len=0x%x/0x%x "%s" id=0x%x vals=%d',
|
|
$self->{_offset},
|
|
$self->{_length_used},
|
|
$self->{_length},
|
|
$self->{_name},
|
|
$self->{_id},
|
|
$self->{_num_values};
|
|
|
|
return $info;
|
|
}
|
|
|
|
sub get_value_iterator {
|
|
my $self = shift;
|
|
|
|
my $regfile = $self->{_regfile};
|
|
|
|
my $num_values_remaining = $self->{_num_values};
|
|
|
|
my $offset = $self->{_offset};
|
|
|
|
# offset_to_next_rgdb_value can only be set to a valid offset
|
|
# if num_values_remaining > 0
|
|
my $offset_to_next_rgdb_value = 0xffffffff;
|
|
if ($num_values_remaining > 0) {
|
|
$offset_to_next_rgdb_value = $offset
|
|
+ $self->{_header_length};
|
|
}
|
|
|
|
my $end_of_rgdb_key = $offset + $self->{_length};
|
|
|
|
# don't attempt to return values if id is invalid...
|
|
if ($self->{_id} == 0xffffffff) {
|
|
$num_values_remaining = 0;
|
|
}
|
|
|
|
return Parse::Win32Registry::Iterator->new(sub {
|
|
if ($num_values_remaining-- <= 0) {
|
|
return;
|
|
}
|
|
if ($offset_to_next_rgdb_value == 0xffffffff) {
|
|
return;
|
|
}
|
|
if ($offset_to_next_rgdb_value > $end_of_rgdb_key) {
|
|
return;
|
|
}
|
|
if (my $value = Parse::Win32Registry::Win95::Value->new($regfile,
|
|
$offset_to_next_rgdb_value))
|
|
{
|
|
return unless $value->get_length > 0;
|
|
$offset_to_next_rgdb_value += $value->get_length;
|
|
return $value;
|
|
}
|
|
else {
|
|
return; # no more values
|
|
}
|
|
});
|
|
}
|
|
|
|
1;
|
|
|