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;