modified extractregistry.java in recentactivity to make regripper work

This commit is contained in:
rishwanth 2018-02-07 09:04:30 -05:00
parent 3455aa61e0
commit e60d5967a4
33 changed files with 11546 additions and 11 deletions

View File

@ -76,7 +76,7 @@ class ExtractRegistry extends Extract {
final private static UsbDeviceIdMapper USB_MAPPER = new UsbDeviceIdMapper();
final private static String RIP_EXE = "rip.exe";
final private static String RIP_PL = "rip.pl";
final private static String PERL = "perl ";
private static String PERL = "perl ";
ExtractRegistry() throws IngestModuleException {
moduleName = NbBundle.getMessage(ExtractIE.class, "ExtractRegistry.moduleName.text");
@ -107,8 +107,7 @@ class ExtractRegistry extends Extract {
}
if (!PlatformUtil.isWindowsOS()) {
RR_PATH = PERL + RR_PATH;
RR_FULL_PATH = PERL + RR_FULL_PATH;
PERL = "/usr/bin/perl";
}
}
@ -281,6 +280,7 @@ class ExtractRegistry extends Extract {
private void executeRegRipper(String regRipperPath, Path regRipperHomeDir, String hiveFilePath, String hiveFileType, String outputFile, String errFile) {
try {
List<String> commandLine = new ArrayList<>();
commandLine.add(PERL);
commandLine.add(regRipperPath);
commandLine.add("-r"); //NON-NLS
commandLine.add(hiveFilePath);
@ -288,6 +288,7 @@ class ExtractRegistry extends Extract {
commandLine.add(hiveFileType);
ProcessBuilder processBuilder = new ProcessBuilder(commandLine);
System.out.println(processBuilder.command());
processBuilder.directory(regRipperHomeDir.toFile()); // RegRipper 2.8 has to be run from its own directory
processBuilder.redirectOutput(new File(outputFile));
processBuilder.redirectError(new File(errFile));

1834
thirdparty/rr-full/Parse/Win32Registry.pm vendored Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,151 @@
package Parse::Win32Registry::Entry;
use strict;
use warnings;
use Carp;
use Parse::Win32Registry::Base qw(:all);
sub get_regfile {
my $self = shift;
return $self->{_regfile};
}
sub get_offset {
my $self = shift;
return $self->{_offset};
}
sub get_length {
my $self = shift;
return $self->{_length};
}
sub is_allocated {
my $self = shift;
return $self->{_allocated};
}
sub get_tag {
my $self = shift;
return $self->{_tag};
}
sub as_string {
my $self = shift;
my $tag = $self->{_tag};
$tag = 'unidentified entry' if !defined $tag;
return "($tag)";
}
sub parse_info {
my $self = shift;
my $info = sprintf '0x%x %s len=0x%x',
$self->{_offset},
$self->{_tag},
$self->{_length};
return $info;
}
sub unparsed {
my $self = shift;
return hexdump($self->get_raw_bytes, $self->get_offset);
}
sub get_raw_bytes {
my $self = shift;
my $regfile = $self->{_regfile};
my $fh = $regfile->get_filehandle;
my $offset = $self->{_offset};
my $length = $self->{_length};
if (defined $self->{_header_length}) {
$length = $self->{_header_length};
}
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $buffer, $length);
if ($bytes_read == $length) {
return $buffer;
}
else {
return '';
}
}
sub looks_like_key {
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::Key");
}
sub looks_like_value {
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::Value");
}
sub looks_like_security {
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::WinNT::Security");
}
sub _dumpvar {
my $self = shift;
my $depth = shift || 1;
my $dumpvar = '';
foreach (sort keys %$self) {
$dumpvar .= ' ' x ($depth*2);
$dumpvar .= "$_ => ";
my $var = $self->{$_};
if (!defined $var) {
$dumpvar .= "undef\n";
}
elsif (/offset/ || /_id$/ || /^_unk/) {
$dumpvar .= sprintf "0x%x\n", $var;
}
elsif (/_flags$/) {
$dumpvar .= sprintf "0x%x (0b%b)\n", $var, $var;
}
elsif (/length/ || /bytes_used/) {
$dumpvar .= sprintf "0x%x (%d)\n", $var, $var;
}
elsif (/_data$/) {
if (length($var) == 0) {
$dumpvar .= '(no data)';
}
else {
$dumpvar .= join(' ', unpack('(H2)20', $var));
if (length($var) > 20) {
$dumpvar .= '...';
}
}
$dumpvar .= "\n";
}
elsif (/timestamp$/) {
$dumpvar .= $var . " (" . iso8601($var) . ")\n";
}
elsif ($var =~ /^\d+$/) {
$dumpvar .= sprintf "%d\n", $var;
}
elsif (ref($var)) {
$dumpvar .= "$var\n"; # stringify object ref
}
else {
$dumpvar .= qq{"$var"};
$dumpvar .= ' ';
$dumpvar .= Encode::is_utf8($var) ? "(UTF8)" : "(BYTES)";
$dumpvar .= "\n";
}
}
return $dumpvar;
}
1;

View File

@ -0,0 +1,66 @@
package Parse::Win32Registry::File;
use strict;
use warnings;
sub get_filehandle {
my $self = shift;
return $self->{_filehandle};
}
sub get_filename {
my $self = shift;
return $self->{_filename};
}
sub get_length {
my $self = shift;
return $self->{_length};
}
sub get_entry_iterator {
my $self = shift;
my $entry_iter;
my $block_iter = $self->get_block_iterator;
return Parse::Win32Registry::Iterator->new(sub {
while (1) {
if (defined $entry_iter) {
my $entry = $entry_iter->();
if (defined $entry) {
return $entry;
}
}
# entry iterator is undefined or finished
my $block = $block_iter->();
if (!defined $block) {
return; # block iterator finished
}
$entry_iter = $block->get_entry_iterator;
}
});
}
# method provided for backwards compatibility
sub move_to_first_entry {
my $self = shift;
$self->{_entry_iter} = undef;
}
# method provided for backwards compatibility
sub get_next_entry {
my $self = shift;
my $entry_iter = $self->{_entry_iter};
if (!defined $entry_iter) {
$self->{_entry_iter} = $entry_iter = $self->get_entry_iterator;
}
return $entry_iter->();
}
1;

View File

@ -0,0 +1,245 @@
package Parse::Win32Registry::Key;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Entry);
use Carp;
sub get_name {
my $self = shift;
# the root key of a windows 95 registry has no defined name
# but this should be set to '' when created
return $self->{_name};
}
sub get_path {
my $self = shift;
return $self->{_key_path};
}
sub _look_up_subkey {
my $self = shift;
my $subkey_name = shift;
croak 'Missing subkey name' if !defined $subkey_name;
foreach my $subkey ($self->get_list_of_subkeys) {
if (uc $subkey_name eq uc $subkey->{_name}) {
return $subkey;
}
}
return;
}
sub get_subkey {
my $self = shift;
my $subkey_path = shift;
# check for definedness in case key name is '' or '0'
croak "Usage: get_subkey('key name')" if !defined $subkey_path;
my $key = $self;
# Current path component separator is '\' to match that used in Windows.
# split returns nothing if it is given an empty string,
# and without a limit of -1 drops trailing empty fields.
# The following returns a list with a single zero-length string ("")
# for an empty string, as split(/\\/, $subkey_path, -1) returns (),
# an empty list.
my @path_components = index($subkey_path, "\\") == -1
? ($subkey_path)
: split(/\\/, $subkey_path, -1);
my %offsets_seen = ();
$offsets_seen{$key->get_offset} = undef;
foreach my $subkey_name (@path_components) {
if (my $subkey = $key->_look_up_subkey($subkey_name)) {
if (exists $offsets_seen{$subkey->get_offset}) {
return; # found loop
}
$key = $subkey;
$offsets_seen{$key->get_offset} = undef;
}
else { # subkey name not found, abort look up
return;
}
}
return $key;
}
sub get_value {
my $self = shift;
my $value_name = shift;
# check for definedness in case value name is '' or '0'
croak "Usage: get_value('value name')" if !defined $value_name;
foreach my $value ($self->get_list_of_values) {
if (uc $value_name eq uc $value->{_name}) {
return $value;
}
}
return undef;
}
sub print_summary {
my $self = shift;
print $self->as_string, "\n";
}
sub as_regedit_export {
my $self = shift;
return "[" . $self->{_key_path} . "]\n";
}
sub regenerate_path {
my $self = shift;
# ascend to the root
my $key = $self;
my @key_names = ($key->get_name);
my %offsets_seen = ();
while (!$key->is_root) {
$offsets_seen{$key->get_offset}++;
$key = $key->get_parent;
if (!defined $key) { # found an undefined parent key
unshift @key_names, '(Invalid Parent Key)';
last;
}
if (exists $offsets_seen{$key->get_offset}) { # found loop
unshift @key_names, '(Invalid Parent Key)';
last;
}
unshift @key_names, $key->get_name;
}
my $key_path = join('\\', @key_names);
$self->{_key_path} = $key_path;
return $key_path;
}
sub get_value_data {
my $self = shift;
my $value_name = shift;
croak "Usage: get_value_data('value name')" if !defined $value_name;
if (my $value = $self->get_value($value_name)) {
return $value->get_data;
}
return;
}
sub get_mru_list_of_values {
my $self = shift;
my @values = ();
if (my $mrulist = $self->get_value('MRUList')) {
foreach my $ch (split(//, $mrulist->get_data)) {
if (my $value = $self->get_value($ch)) {
push @values, $value;
}
}
}
elsif (my $mrulistex = $self->get_value('MRUListEx')) {
foreach my $item (unpack('V*', $mrulistex->get_data)) {
last if $item == 0xffffffff;
if (my $value = $self->get_value($item)) {
push @values, $value;
}
}
}
return @values;
}
sub get_list_of_subkeys {
my $self = shift;
my $subkey_iter = $self->get_subkey_iterator;
my @subkeys;
while (my $subkey = $subkey_iter->()) {
push @subkeys, $subkey;
}
return @subkeys;
}
sub get_list_of_values {
my $self = shift;
my $value_iter = $self->get_value_iterator;
my @values;
while (my $value = $value_iter->()) {
push @values, $value;
}
return @values;
}
sub get_subtree_iterator {
my $self = shift;
my @start_keys = ($self);
push my (@subkey_iters), Parse::Win32Registry::Iterator->new(sub {
return shift @start_keys;
});
my $value_iter;
my $key; # used to remember key while iterating values
return Parse::Win32Registry::Iterator->new(sub {
if (defined $value_iter && wantarray) {
my $value = $value_iter->();
if (defined $value) {
return ($key, $value);
}
# $value_iter finished, so fetch a new one
# from the (current) $subkey_iter[-1]
}
while (@subkey_iters > 0) {
$key = $subkey_iters[-1]->(); # depth-first
if (defined $key) {
push @subkey_iters, $key->get_subkey_iterator;
$value_iter = $key->get_value_iterator;
return $key;
}
pop @subkey_iters; # $subkey_iter finished, so remove it
}
return;
});
}
sub walk {
my $self = shift;
my $key_enter_func = shift;
my $value_func = shift;
my $key_leave_func = shift;
if (!defined $key_enter_func &&
!defined $value_func &&
!defined $key_leave_func) {
$key_enter_func = sub { print "+ ", $_[0]->get_path, "\n"; };
$value_func = sub { print " '", $_[0]->get_name, "'\n"; };
$key_leave_func = sub { print "- ", $_[0]->get_path, "\n"; };
}
$key_enter_func->($self) if ref $key_enter_func eq 'CODE';
foreach my $value ($self->get_list_of_values) {
$value_func->($value) if ref $value_func eq 'CODE';
}
foreach my $subkey ($self->get_list_of_subkeys) {
$subkey->walk($key_enter_func, $value_func, $key_leave_func);
}
$key_leave_func->($self) if ref $key_leave_func eq 'CODE';
}
1;

View File

@ -0,0 +1,101 @@
package Parse::Win32Registry::Value;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Entry);
use Carp;
use Parse::Win32Registry::Base qw(:all);
sub get_name {
my $self = shift;
return $self->{_name};
}
sub get_type {
my $self = shift;
return $self->{_type};
}
our @Types = qw(
REG_NONE
REG_SZ
REG_EXPAND_SZ
REG_BINARY
REG_DWORD
REG_DWORD_BIG_ENDIAN
REG_LINK
REG_MULTI_SZ
REG_RESOURCE_LIST
REG_FULL_RESOURCE_DESCRIPTOR
REG_RESOURCE_REQUIREMENTS_LIST
REG_QWORD
);
sub get_type_as_string {
my $self = shift;
my $type = $self->get_type;
if (exists $Types[$type]) {
return $Types[$type];
}
else {
# Return unrecognised types as REG_<number>
# REGEDIT displays them as formatted hex numbers, e.g. 0x1f4
return "REG_$type";
}
}
sub get_data_as_string {
my $self = shift;
my $type = $self->get_type;
my $data = $self->get_data;
if (!defined($data)) {
return '(invalid data)';
}
elsif (length($data) == 0) {
return '(no data)';
}
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
return $data;
}
elsif ($type == REG_MULTI_SZ) {
my @data = $self->get_data;
my $i = 0;
return join(' ', map { "[" . $i++ . "] $_" } @data);
}
elsif ($type == REG_DWORD || $type == REG_DWORD_BIG_ENDIAN) {
return sprintf '0x%08x (%u)', $data, $data;
}
else {
return join(' ', unpack('(H2)*', $data));
}
}
sub get_raw_data {
my $self = shift;
return $self->{_data};
}
sub as_string {
my $self = shift;
my $name = $self->get_name;
$name = '(Default)' if $name eq '';
my $type_as_string = $self->get_type_as_string;
my $data_as_string = $self->get_data_as_string;
return "$name ($type_as_string) = $data_as_string";
}
sub print_summary {
my $self = shift;
print $self->as_string, "\n";
}
1;

View File

@ -0,0 +1,540 @@
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;

View File

@ -0,0 +1,207 @@
package Parse::Win32Registry::Win95::Key;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Key);
use Carp;
use Parse::Win32Registry::Base qw(:all);
use Parse::Win32Registry::Win95::Value;
use constant RGKN_ENTRY_LENGTH => 0x1c;
use constant OFFSET_TO_RGKN_BLOCK => 0x20;
sub new {
my $class = shift;
my $regfile = shift;
my $offset = shift; # offset to RGKN key entry relative to start of RGKN
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;
# RGKN Key Entry
# 0x00 dword
# 0x04 dword
# 0x08 dword
# 0x0c dword = offset to parent RGKN entry
# 0x10 dword = offset to first child RGKN entry
# 0x14 dword = offset to next sibling RGKN entry
# 0x18 dword = entry id of RGDB entry
# Extracted offsets are relative to the start of the RGKN block
# Any offset of 0xffffffff marks the end of a list.
# An entry id of 0xffffffff means the RGKN entry has no RGDB entry.
# This occurs for the root key of the registry file.
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $rgkn_entry, RGKN_ENTRY_LENGTH);
if ($bytes_read != RGKN_ENTRY_LENGTH) {
warnf('Could not read RGKN key at 0x%x', $offset);
return;
}
my ($offset_to_parent,
$offset_to_first_child,
$offset_to_next_sibling,
$key_id) = unpack('x12VVVV', $rgkn_entry);
$offset_to_parent += OFFSET_TO_RGKN_BLOCK
if $offset_to_parent != 0xffffffff;
$offset_to_first_child += OFFSET_TO_RGKN_BLOCK
if $offset_to_first_child != 0xffffffff;
$offset_to_next_sibling += OFFSET_TO_RGKN_BLOCK
if $offset_to_next_sibling != 0xffffffff;
my $self = {};
$self->{_regfile} = $regfile;
$self->{_offset} = $offset;
$self->{_length} = RGKN_ENTRY_LENGTH;
$self->{_allocated} = 1;
$self->{_tag} = 'rgkn key';
$self->{_offset_to_parent} = $offset_to_parent;
$self->{_offset_to_first_child} = $offset_to_first_child;
$self->{_offset_to_next_sibling} = $offset_to_next_sibling;
$self->{_id} = $key_id;
bless $self, $class;
# Look up corresponding rgdb entry
my $index = $regfile->{_rgdb_index};
croak 'Missing rgdb index' if !defined $index;
if (exists $index->{$key_id}) {
my $rgdb_key = $index->{$key_id};
$self->{_rgdb_key} = $rgdb_key;
$self->{_name} = $rgdb_key->get_name;
}
else {
$self->{_name} = '';
# Only the root key should have no matching RGDB entry
if (!$self->is_root) {
warnf('Could not find RGDB entry for RGKN key at 0x%x', $offset);
}
}
my $name = $self->{_name};
$self->{_key_path} = defined($parent_key_path)
? "$parent_key_path\\$name"
: $name;
return $self;
}
sub get_timestamp {
return undef;
}
sub get_timestamp_as_string {
return iso8601(undef);
}
sub get_class_name {
return undef;
}
sub is_root {
my $self = shift;
my $offset = $self->{_offset};
my $regfile = $self->{_regfile};
my $rgkn_block = $regfile->get_rgkn;
my $offset_to_root_key = $rgkn_block->{_offset_to_root_key};
# This gives better results than checking id == 0xffffffff
return $offset == $offset_to_root_key;
}
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::Win95::Key->new($regfile,
$offset_to_parent,
$grandparent_key_path);
}
sub get_security {
return undef;
}
sub as_string {
my $self = shift;
return $self->get_path;
}
sub parse_info {
my $self = shift;
my $info = sprintf '0x%x rgkn key len=0x%x par=0x%x,child=0x%x,next=0x%x id=0x%x',
$self->{_offset},
$self->{_length},
$self->{_offset_to_parent},
$self->{_offset_to_first_child},
$self->{_offset_to_next_sibling},
$self->{_id};
return $info;
}
sub get_subkey_iterator {
my $self = shift;
my $regfile = $self->{_regfile};
my $key_path = $self->{_key_path};
my $offset_to_next_key = $self->{_offset_to_first_child};
my $end_of_file = $regfile->get_length;
my $rgkn_block = $regfile->get_rgkn;
my $end_of_rgkn_block = $rgkn_block->get_offset + $rgkn_block->get_length;
return Parse::Win32Registry::Iterator->new(sub {
if ($offset_to_next_key == 0xffffffff) {
return; # no more subkeys
}
if ($offset_to_next_key > $end_of_rgkn_block) {
return;
}
if (my $key = Parse::Win32Registry::Win95::Key->new($regfile,
$offset_to_next_key, $key_path))
{
$offset_to_next_key = $key->{_offset_to_next_sibling};
return $key;
}
else {
return; # no more subkeys
}
});
}
sub get_value_iterator {
my $self = shift;
my $rgdb_key = $self->{_rgdb_key};
if (defined $rgdb_key) {
return $rgdb_key->get_value_iterator;
}
else {
return Parse::Win32Registry::Iterator->new(sub {});
}
}
1;

View File

@ -0,0 +1,177 @@
package Parse::Win32Registry::Win95::Value;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Value);
use Carp;
use Encode;
use Parse::Win32Registry::Base qw(:all);
use constant RGDB_VALUE_HEADER_LENGTH => 0xc;
sub new {
my $class = shift;
my $regfile = shift;
my $offset = shift; # offset to RGDB value entry
croak 'Missing registry file' if !defined $regfile;
croak 'Missing offset' if !defined $offset;
my $fh = $regfile->get_filehandle;
# RGDB Value Entry
# 0x00 dword = value type
# 0x04
# 0x08 word = value name length
# 0x0a word = value data length
# 0x0c = value name [for name length bytes]
# + value data [for data length bytes]
# Value type may just be a word, not a dword;
# following word always appears to be zero.
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $rgdb_value_entry,
RGDB_VALUE_HEADER_LENGTH);
if ($bytes_read != RGDB_VALUE_HEADER_LENGTH) {
warnf('Could not read RGDB value at 0x%x', $offset);
return;
}
my ($type,
$name_length,
$data_length) = unpack('Vx4vv', $rgdb_value_entry);
$bytes_read = sysread($fh, my $name, $name_length);
if ($bytes_read != $name_length) {
warnf('Could not read name for RGDB value at 0x%x', $offset);
return;
}
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
$bytes_read = sysread($fh, my $data, $data_length);
if ($bytes_read != $data_length) {
warnf('Could not read data for RGDB value at 0x%x', $offset);
return;
}
my $self = {};
$self->{_regfile} = $regfile;
$self->{_offset} = $offset;
$self->{_length} = RGDB_VALUE_HEADER_LENGTH + $name_length + $data_length;
$self->{_allocated} = 1;
$self->{_tag} = 'rgdb value';
$self->{_name} = $name;
$self->{_name_length} = $name_length;
$self->{_type} = $type;
$self->{_data} = $data;
$self->{_data_length} = $data_length;
bless $self, $class;
return $self;
}
sub get_data {
my $self = shift;
my $type = $self->get_type;
my $data = $self->{_data};
return if !defined $data; # actually, Win95 value data is always defined
# apply decoding to appropriate data types
if ($type == REG_DWORD) {
if (length($data) == 4) {
$data = unpack('V', $data);
}
else {
# incorrect length for dword data
$data = undef;
}
}
elsif ($type == REG_DWORD_BIG_ENDIAN) {
if (length($data) == 4) {
$data = unpack('N', $data);
}
else {
# incorrect length for dword data
$data = undef;
}
}
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
# Snip off any terminating null.
# Typically, REG_SZ values will not have a terminating null,
# while REG_EXPAND_SZ values will have a terminating null
chop $data if substr($data, -1, 1) eq "\0";
}
elsif ($type == REG_MULTI_SZ) {
# Snip off any terminating nulls
chop $data if substr($data, -1, 1) eq "\0";
chop $data if substr($data, -1, 1) eq "\0";
my @multi_sz = split("\0", $data, -1);
# Make sure there is at least one empty string
@multi_sz = ('') if @multi_sz == 0;
return wantarray ? @multi_sz : join($", @multi_sz);
}
return $data;
}
sub as_regedit_export {
my $self = shift;
my $version = shift || 5;
my $name = $self->get_name;
my $export = $name eq '' ? '@=' : '"' . $name . '"=';
my $type = $self->get_type;
# XXX
# if (!defined $self->{_data}) {
# $name = $name eq '' ? '@' : qq{"$name"};
# return qq{; $name=(invalid data)\n};
# }
if ($type == REG_SZ) {
$export .= '"' . $self->get_data . '"';
$export .= "\n";
}
elsif ($type == REG_BINARY) {
$export .= 'hex:';
$export .= format_octets($self->{_data}, length($export));
}
elsif ($type == REG_DWORD) {
my $data = $self->get_data;
$export .= defined($data)
? sprintf("dword:%08x", $data)
: "dword:";
$export .= "\n";
}
elsif ($type == REG_EXPAND_SZ || $type == REG_MULTI_SZ) {
my $data = $version == 4
? $self->{_data} # raw data
: encode("UCS-2LE", $self->{_data}); # ansi->unicode
$export .= sprintf("hex(%x):", $type);
$export .= format_octets($data, length($export));
}
else {
$export .= sprintf("hex(%x):", $type);
$export .= format_octets($self->{_data}, length($export));
}
return $export;
}
sub parse_info {
my $self = shift;
my $info = sprintf '0x%x rgdb value len=0x%x "%s" type=%d data,len=0x%x',
$self->{_offset},
$self->{_length},
$self->{_name},
$self->{_type},
$self->{_data_length};
return $info;
}
1;

View File

@ -0,0 +1,109 @@
package Parse::Win32Registry::WinNT::Entry;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Entry);
use Carp;
use Parse::Win32Registry::Base qw(:all);
use Parse::Win32Registry::WinNT::Key;
use Parse::Win32Registry::WinNT::Value;
use Parse::Win32Registry::WinNT::Security;
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;
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $entry_header, 8);
if ($bytes_read != 8) {
return;
}
my ($length,
$tag) = unpack('Va2', $entry_header);
my $allocated = 0;
if ($length > 0x7fffffff) {
$allocated = 1;
$length = (0xffffffff - $length) + 1;
}
$tag = '' if $tag !~ /(nk|vk|lh|lf|li|ri|sk)/;
if ($tag eq 'nk') {
if (my $key = Parse::Win32Registry::WinNT::Key->new($regfile,
$offset))
{
$key->regenerate_path;
return $key;
}
}
elsif ($tag eq 'vk') {
if (my $value = Parse::Win32Registry::WinNT::Value->new($regfile,
$offset))
{
return $value;
}
}
elsif ($tag eq 'sk') {
if (my $value = Parse::Win32Registry::WinNT::Security->new($regfile,
$offset))
{
return $value;
}
}
my $self = {};
$self->{_regfile} = $regfile,
$self->{_offset} = $offset,
$self->{_length} = $length,
$self->{_tag} = $tag,
$self->{_allocated} = $allocated,
bless $self, $class;
return $self;
}
sub as_string {
my $self = shift;
my $tag = $self->{_tag};
if ($tag eq 'nk') {
return '(key entry)';
}
elsif ($tag eq 'vk') {
return '(value entry)';
}
elsif ($tag eq 'sk') {
return '(security entry)';
}
elsif ($tag =~ /(lh|lf|li|ri)/) {
return '(subkey list entry)';
}
return '(unidentified entry)';
}
sub parse_info {
my $self = shift;
my $tag = $self->{_tag};
$tag = defined($tag) && $tag ne ''
? $tag . ' '
: '.. ';
my $info = sprintf '0x%x %slen=0x%x alloc=%d',
$self->{_offset},
$tag,
$self->{_length},
$self->{_allocated};
return $info;
}
1;

View File

@ -0,0 +1,297 @@
package Parse::Win32Registry::WinNT::File;
use strict;
use warnings;
use base qw(Parse::Win32Registry::File);
use Carp;
use Encode;
use File::Basename;
use Parse::Win32Registry::Base qw(:all);
use Parse::Win32Registry::WinNT::Key;
use constant REGF_HEADER_LENGTH => 0x200;
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
sub new {
my $class = shift;
my $filename = shift or croak "No filename specified";
open my $fh, '<', $filename or croak "Unable to open '$filename': $!";
# 0x00 dword = 'regf' signature
# 0x04 dword = seq1
# 0x08 dword = seq2
# 0x0c qword = timestamp
# 0x14 dword = major version
# 0x18 dword = minor version
# 0x1c dword = type (0 = registry file, 1 = log file)
# 0x20 dword = (1)
# 0x24 dword = offset to root key
# 0x28 dword = total length of all hbins (excludes header)
# 0x2c dword = (1)
# 0x30 = embedded filename
# Extracted offsets are always relative to first hbin
my $bytes_read = sysread($fh, my $regf_header, REGF_HEADER_LENGTH);
if ($bytes_read != REGF_HEADER_LENGTH) {
warnf('Could not read registry file header');
return;
}
my ($regf_sig,
$seq1,
$seq2,
$timestamp,
$major_version,
$minor_version,
$type,
$offset_to_root_key,
$total_hbin_length,
$embedded_filename,
) = unpack('a4VVa8VVVx4VVx4a64', $regf_header);
$offset_to_root_key += OFFSET_TO_FIRST_HBIN;
if ($regf_sig ne 'regf') {
warnf('Invalid registry file signature');
return;
}
$embedded_filename = unpack('Z*', decode('UCS-2LE', $embedded_filename));
# The header checksum is the xor of the first 127 dwords.
# The checksum is stored in the 128th dword, at offset 0x1fc (508).
my $checksum = 0;
foreach my $x (unpack('V127', $regf_header)) {
$checksum ^= $x;
}
my $embedded_checksum = unpack('x508V', $regf_header);
if ($checksum != $embedded_checksum) {
warnf('Invalid checksum for registry file header');
}
my $self = {};
$self->{_filehandle} = $fh;
$self->{_filename} = $filename;
$self->{_length} = (stat $fh)[7];
$self->{_offset_to_root_key} = $offset_to_root_key;
$self->{_timestamp} = unpack_windows_time($timestamp);
$self->{_embedded_filename} = $embedded_filename;
$self->{_seq1} = $seq1;
$self->{_seq2} = $seq2;
$self->{_version} = "$major_version.$minor_version";
$self->{_type} = $type;
$self->{_total_hbin_length} = $total_hbin_length;
$self->{_embedded_checksum} = $embedded_checksum;
$self->{_security_cache} = {}; # comment out to disable cache
bless $self, $class;
return $self;
}
sub get_root_key {
my $self = shift;
my $offset_to_root_key = $self->{_offset_to_root_key};
my $root_key = Parse::Win32Registry::WinNT::Key->new($self,
$offset_to_root_key);
return $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 =~ /NTUSER/i) {
$fake_root = 'HKEY_CURRENT_USER';
}
elsif ($filename =~ /USRCLASS/i) {
$fake_root = 'HKEY_CLASSES_ROOT';
}
elsif ($filename =~ /SOFTWARE/i) {
$fake_root = 'HKEY_LOCAL_MACHINE\SOFTWARE';
}
elsif ($filename =~ /SYSTEM/i) {
$fake_root = 'HKEY_LOCAL_MACHINE\SYSTEM';
}
elsif ($filename =~ /SAM/i) {
$fake_root = 'HKEY_LOCAL_MACHINE\SAM';
}
elsif ($filename =~ /SECURITY/i) {
$fake_root = 'HKEY_LOCAL_MACHINE\SECURITY';
}
else {
$fake_root = 'HKEY_UNKNOWN';
}
}
$root_key->{_name} = $fake_root;
$root_key->{_key_path} = $fake_root;
return $root_key;
}
sub get_timestamp {
my $self = shift;
return $self->{_timestamp};
}
sub get_timestamp_as_string {
my $self = shift;
return iso8601($self->{_timestamp});
}
sub get_embedded_filename {
my $self = shift;
return $self->{_embedded_filename};
}
sub get_block_iterator {
my $self = shift;
my $offset_to_next_hbin = OFFSET_TO_FIRST_HBIN;
my $end_of_file = $self->{_length};
return Parse::Win32Registry::Iterator->new(sub {
if ($offset_to_next_hbin > $end_of_file) {
return; # no more hbins
}
if (my $hbin = Parse::Win32Registry::WinNT::Hbin->new($self,
$offset_to_next_hbin))
{
return unless $hbin->get_length > 0;
$offset_to_next_hbin += $hbin->get_length;
return $hbin;
}
else {
return; # no more hbins
}
});
}
*get_hbin_iterator = \&get_block_iterator;
sub _dump_security_cache {
my $self = shift;
if (defined(my $cache = $self->{_security_cache})) {
foreach my $offset (sort { $a <=> $b } keys %$cache) {
my $security = $cache->{$offset};
printf '0x%x %s\n', $offset, $security->as_string;
}
}
}
package Parse::Win32Registry::WinNT::Hbin;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Entry);
use Carp;
use Parse::Win32Registry::Base qw(:all);
use Parse::Win32Registry::WinNT::Entry;
use constant HBIN_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;
# 0x00 dword = 'hbin' signature
# 0x04 dword = offset from first hbin to this hbin
# 0x08 dword = length of this hbin / relative offset to next hbin
# 0x14 qword = timestamp (first hbin only)
# Extracted offsets are always relative to first hbin
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $hbin_header, HBIN_HEADER_LENGTH);
if ($bytes_read != HBIN_HEADER_LENGTH) {
return;
}
my ($sig,
$offset_to_hbin,
$length,
$timestamp) = unpack('a4VVx8a8x4', $hbin_header);
if ($sig ne 'hbin') {
return;
}
my $self = {};
$self->{_regfile} = $regfile;
$self->{_offset} = $offset;
$self->{_length} = $length;
$self->{_header_length} = HBIN_HEADER_LENGTH;
$self->{_allocated} = 1;
$self->{_tag} = $sig;
$self->{_timestamp} = unpack_windows_time($timestamp);
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->{_timestamp});
}
sub get_entry_iterator {
my $self = shift;
my $regfile = $self->{_regfile};
my $offset = $self->{_offset};
my $length = $self->{_length};
my $offset_to_next_entry = $offset + HBIN_HEADER_LENGTH;
my $end_of_hbin = $offset + $length;
return Parse::Win32Registry::Iterator->new(sub {
if ($offset_to_next_entry >= $end_of_hbin) {
return; # no more entries
}
if (my $entry = Parse::Win32Registry::WinNT::Entry->new($regfile,
$offset_to_next_entry))
{
return unless $entry->get_length > 0;
$offset_to_next_entry += $entry->get_length;
return $entry;
}
else {
return; # no more entries
}
});
}
1;

View File

@ -0,0 +1,444 @@
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,
$offset_to_parent,
$num_subkeys,
$offset_to_subkey_list,
$num_values,
$offset_to_value_list,
$offset_to_security,
$offset_to_class_name,
$name_length,
$class_name_length,
) = 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);
$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);
}
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;

View File

@ -0,0 +1,157 @@
package Parse::Win32Registry::WinNT::Security;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Entry);
use Carp;
use Parse::Win32Registry::Base qw(:all);
use constant SK_HEADER_LENGTH => 0x18;
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
sub new {
my $class = shift;
my $regfile = shift;
my $offset = shift; # offset to sk record relative to start of file
croak 'Missing registry file' if !defined $regfile;
croak 'Missing offset' if !defined $offset;
if (defined(my $cache = $regfile->{_security_cache})) {
if (exists $cache->{$offset}) {
return $cache->{$offset};
}
}
my $fh = $regfile->get_filehandle;
# 0x00 dword = security length (negative = allocated)
# 0x04 word = 'sk' signature
# 0x08 dword = offset to previous sk
# 0x0c dword = offset to next sk
# 0x10 dword = ref count
# 0x14 dword = length of security descriptor
# 0x18 = start of security descriptor
# Extracted offsets are always relative to first hbin
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $sk_header, SK_HEADER_LENGTH);
if ($bytes_read != SK_HEADER_LENGTH) {
warnf('Could not read security at 0x%x', $offset);
return;
}
my ($length,
$sig,
$offset_to_previous,
$offset_to_next,
$ref_count,
$sd_length,
) = unpack('Va2x2VVVV', $sk_header);
$offset_to_previous += OFFSET_TO_FIRST_HBIN
if $offset_to_previous != 0xffffffff;
$offset_to_next += OFFSET_TO_FIRST_HBIN
if $offset_to_next != 0xffffffff;
my $allocated = 0;
if ($length > 0x7fffffff) {
$allocated = 1;
$length = (0xffffffff - $length) + 1;
}
# allocated should be true
if ($sig ne 'sk') {
warnf('Invalid signature for security at 0x%x', $offset);
return;
}
$bytes_read = sysread($fh, my $sd_data, $sd_length);
if ($bytes_read != $sd_length) {
warnf('Could not read security descriptor for security at 0x%x',
$offset);
return;
}
my $sd = unpack_security_descriptor($sd_data);
if (!defined $sd) {
warnf('Invalid security descriptor for security at 0x%x',
$offset);
# Abandon security object if security descriptor is invalid
return;
}
my $self = {};
$self->{_regfile} = $regfile;
$self->{_offset} = $offset;
$self->{_length} = $length;
$self->{_allocated} = $allocated;
$self->{_tag} = $sig;
$self->{_offset_to_previous} = $offset_to_previous;
$self->{_offset_to_next} = $offset_to_next;
$self->{_ref_count} = $ref_count;
$self->{_security_descriptor_length} = $sd_length;
$self->{_security_descriptor} = $sd;
bless $self, $class;
if (defined(my $cache = $regfile->{_security_cache})) {
$cache->{$offset} = $self;
}
return $self;
}
sub get_previous {
my $self = shift;
my $regfile = $self->{_regfile};
my $offset_to_previous = $self->{_offset_to_previous};
return Parse::Win32Registry::WinNT::Security->new($regfile,
$offset_to_previous);
}
sub get_next {
my $self = shift;
my $regfile = $self->{_regfile};
my $offset_to_next = $self->{_offset_to_next};
return Parse::Win32Registry::WinNT::Security->new($regfile,
$offset_to_next);
}
sub get_reference_count {
my $self = shift;
return $self->{_ref_count};
}
sub get_security_descriptor {
my $self = shift;
return $self->{_security_descriptor};
}
sub as_string {
my $self = shift;
return '(security entry)';
}
sub parse_info {
my $self = shift;
my $info = sprintf '0x%x sk len=0x%x alloc=%d prev=0x%x,next=0x%x refs=%d',
$self->{_offset},
$self->{_length},
$self->{_allocated},
$self->{_offset_to_previous},
$self->{_offset_to_next},
$self->{_ref_count};
return $info;
}
1;

View File

@ -0,0 +1,332 @@
package Parse::Win32Registry::WinNT::Value;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Value);
use Carp;
use Encode;
use Parse::Win32Registry::Base qw(:all);
use constant VK_HEADER_LENGTH => 0x18;
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
sub new {
my $class = shift;
my $regfile = shift;
my $offset = shift; # offset to vk record relative to first hbin
croak 'Missing registry file' if !defined $regfile;
croak 'Missing offset' if !defined $offset;
my $fh = $regfile->get_filehandle;
# 0x00 dword = value length (negative = allocated)
# 0x04 word = 'vk' signature
# 0x06 word = value name length
# 0x08 dword = value data length (bit 31 set => data stored inline)
# 0x0c dword = offset to data/inline data
# 0x10 dword = value type
# 0x14 word = flags (bit 1 set => compressed name)
# 0x16 word
# 0x18 = value name [for value name length bytes]
# Extracted offsets are always relative to first hbin
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $vk_header, VK_HEADER_LENGTH);
if ($bytes_read != VK_HEADER_LENGTH) {
warnf('Could not read value at 0x%x', $offset);
return;
}
my ($length,
$sig,
$name_length,
$data_length,
$offset_to_data,
$type,
$flags,
) = unpack('Va2vVVVv', $vk_header);
my $allocated = 0;
if ($length > 0x7fffffff) {
$allocated = 1;
$length = (0xffffffff - $length) + 1;
}
# allocated should be true
if ($length < VK_HEADER_LENGTH) {
warnf('Invalid value entry length at 0x%x', $offset);
return;
}
if ($sig ne 'vk') {
warnf('Invalid signature for value at 0x%x', $offset);
return;
}
$bytes_read = sysread($fh, my $name, $name_length);
if ($bytes_read != $name_length) {
warnf('Could not read name for value at 0x%x', $offset);
return;
}
if ($flags & 1) {
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
}
else {
$name = decode('UCS-2LE', $name);
};
# If the top bit of the data_length is set, then
# the value is inline and stored in the offset to data field (at 0xc).
my $data;
my $data_inline = $data_length >> 31;
if ($data_inline) {
# REG_DWORDs are always inline, but I've also seen
# REG_SZ, REG_BINARY, REG_EXPAND_SZ, and REG_NONE inline
$data_length &= 0x7fffffff;
if ($data_length > 4) {
warnf("Invalid inline data length for value '%s' at 0x%x",
$name, $offset);
$data = undef;
}
else {
# unpack inline data from header
$data = substr($vk_header, 0xc, $data_length);
}
}
else {
if ($offset_to_data != 0 && $offset_to_data != 0xffffffff) {
$offset_to_data += OFFSET_TO_FIRST_HBIN;
if ($offset_to_data < ($regfile->get_length - $data_length)) {
$data = _extract_data($fh, $offset_to_data, $data_length);
}
else {
warnf("Invalid offset to data for value '%s' at 0x%x",
$name, $offset);
}
}
}
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->{_type} = $type;
$self->{_data} = $data;
$self->{_data_length} = $data_length;
$self->{_data_inline} = $data_inline;
$self->{_offset_to_data} = $offset_to_data;
$self->{_flags} = $flags;
bless $self, $class;
return $self;
}
sub _extract_data {
my $fh = shift;
my $offset_to_data = shift;
my $data_length = shift;
if ($offset_to_data == 0 || $offset_to_data == 0xffffffff) {
return undef;
}
sysseek($fh, $offset_to_data, 0);
my $bytes_read = sysread($fh, my $data_header, 4);
if ($bytes_read != 4) {
warnf('Could not read data at 0x%x', $offset_to_data);
return undef;
}
my ($max_data_length) = unpack('V', $data_header);
my $data_allocated = 0;
if ($max_data_length > 0x7fffffff) {
$data_allocated = 1;
$max_data_length = (0xffffffff - $max_data_length) + 1;
}
# data_allocated should be true
my $data;
if ($data_length > $max_data_length) {
$bytes_read = sysread($fh, my $db_entry, 8);
if ($bytes_read != 8) {
warnf('Could not read data at 0x%x', $offset_to_data);
return undef;
}
my ($sig, $num_data_blocks, $offset_to_data_block_list)
= unpack('a2vV', $db_entry);
if ($sig ne 'db') {
warnf('Invalid signature for big data at 0x%x', $offset_to_data);
return undef;
}
$offset_to_data_block_list += OFFSET_TO_FIRST_HBIN;
sysseek($fh, $offset_to_data_block_list + 4, 0);
$bytes_read = sysread($fh, my $data_block_list, $num_data_blocks * 4);
if ($bytes_read != $num_data_blocks * 4) {
warnf('Could not read data block list at 0x%x',
$offset_to_data_block_list);
return undef;
}
$data = "";
my @offsets = map { OFFSET_TO_FIRST_HBIN + $_ }
unpack("V$num_data_blocks", $data_block_list);
foreach my $offset (@offsets) {
sysseek($fh, $offset, 0);
$bytes_read = sysread($fh, my $block_header, 4);
if ($bytes_read != 4) {
warnf('Could not read data block at 0x%x', $offset);
return undef;
}
my ($block_length) = unpack('V', $block_header);
if ($block_length > 0x7fffffff) {
$block_length = (0xffffffff - $block_length) + 1;
}
$bytes_read = sysread($fh, my $block_data, $block_length - 8);
if ($bytes_read != $block_length - 8) {
warnf('Could not read data block at 0x%x', $offset);
return undef;
}
$data .= $block_data;
}
if (length($data) < $data_length) {
warnf("Insufficient data blocks for data at 0x%x", $offset_to_data);
return undef;
}
$data = substr($data, 0, $data_length);
return $data;
}
else {
$bytes_read = sysread($fh, $data, $data_length);
if ($bytes_read != $data_length) {
warnf("Could not read data at 0x%x", $offset_to_data);
return undef;
}
}
return $data;
}
sub get_data {
my $self = shift;
my $type = $self->get_type;
my $data = $self->{_data};
return if !defined $data;
# apply decoding to appropriate data types
if ($type == REG_DWORD) {
if (length($data) == 4) {
$data = unpack('V', $data);
}
else {
# incorrect length for dword data
$data = undef;
}
}
elsif ($type == REG_DWORD_BIG_ENDIAN) {
if (length($data) == 4) {
$data = unpack('N', $data);
}
else {
# incorrect length for dword data
$data = undef;
}
}
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
$data = decode('UCS-2LE', $data);
# snip off any terminating null
chop $data if substr($data, -1, 1) eq "\0";
}
elsif ($type == REG_MULTI_SZ) {
$data = decode('UCS-2LE', $data);
# snip off any terminating nulls
chop $data if substr($data, -1, 1) eq "\0";
chop $data if substr($data, -1, 1) eq "\0";
my @multi_sz = split("\0", $data, -1);
# make sure there is at least one empty string
@multi_sz = ('') if @multi_sz == 0;
return wantarray ? @multi_sz : join($", @multi_sz);
}
return $data;
}
sub as_regedit_export {
my $self = shift;
my $version = shift || 5;
my $name = $self->get_name;
my $export = $name eq '' ? '@=' : '"' . $name . '"=';
my $type = $self->get_type;
# XXX
# if (!defined $self->{_data}) {
# $name = $name eq '' ? '@' : qq{"$name"};
# return qq{; $name=(invalid data)\n};
# }
if ($type == REG_SZ) {
$export .= '"' . $self->get_data . '"';
$export .= "\n";
}
elsif ($type == REG_BINARY) {
$export .= "hex:";
$export .= format_octets($self->{_data}, length($export));
}
elsif ($type == REG_DWORD) {
my $data = $self->get_data;
$export .= defined($data)
? sprintf("dword:%08x", $data)
: "dword:";
$export .= "\n";
}
elsif ($type == REG_EXPAND_SZ || $type == REG_MULTI_SZ) {
my $data = $version == 4
? encode("ascii", $self->{_data}) # unicode->ascii
: $self->{_data}; # raw data
$export .= sprintf("hex(%x):", $type);
$export .= format_octets($data, length($export));
}
else {
$export .= sprintf("hex(%x):", $type);
$export .= format_octets($self->{_data}, length($export));
}
return $export;
}
sub parse_info {
my $self = shift;
my $info = sprintf '0x%x vk len=0x%x alloc=%d "%s" type=%d',
$self->{_offset},
$self->{_length},
$self->{_allocated},
$self->{_name},
$self->{_type};
if ($self->{_data_inline}) {
$info .= sprintf ' data=inline,len=0x%x',
$self->{_data_length};
}
else {
$info .= sprintf ' data=0x%x,len=0x%x',
$self->{_offset_to_data},
$self->{_data_length};
}
return $info;
}
1;

View File

@ -1,4 +1,4 @@
#! c:\perl\bin\perl.exe
#! /usr/bin/perl
#-------------------------------------------------------------------------
# Rip - RegRipper, CLI version
# Use this utility to run a plugins file or a single plugin against a Reg
@ -347,4 +347,4 @@ sub getTime($$) {
};
$t = 0 if ($t < 0);
return $t;
}
}

View File

@ -1,4 +1,4 @@
#! c:\perl\bin\perl.exe
#! /usr/bin/perl
#-----------------------------------------------------------
# Registry Ripper
# Parse a Registry hive file for data pertinent to an investigation
@ -451,4 +451,4 @@ sub getTime($$) {
};
$t = 0 if ($t < 0);
return $t;
}
}

1834
thirdparty/rr/Parse/Win32Registry.pm vendored Normal file

File diff suppressed because it is too large Load Diff

1107
thirdparty/rr/Parse/Win32Registry/Base.pm vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,151 @@
package Parse::Win32Registry::Entry;
use strict;
use warnings;
use Carp;
use Parse::Win32Registry::Base qw(:all);
sub get_regfile {
my $self = shift;
return $self->{_regfile};
}
sub get_offset {
my $self = shift;
return $self->{_offset};
}
sub get_length {
my $self = shift;
return $self->{_length};
}
sub is_allocated {
my $self = shift;
return $self->{_allocated};
}
sub get_tag {
my $self = shift;
return $self->{_tag};
}
sub as_string {
my $self = shift;
my $tag = $self->{_tag};
$tag = 'unidentified entry' if !defined $tag;
return "($tag)";
}
sub parse_info {
my $self = shift;
my $info = sprintf '0x%x %s len=0x%x',
$self->{_offset},
$self->{_tag},
$self->{_length};
return $info;
}
sub unparsed {
my $self = shift;
return hexdump($self->get_raw_bytes, $self->get_offset);
}
sub get_raw_bytes {
my $self = shift;
my $regfile = $self->{_regfile};
my $fh = $regfile->get_filehandle;
my $offset = $self->{_offset};
my $length = $self->{_length};
if (defined $self->{_header_length}) {
$length = $self->{_header_length};
}
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $buffer, $length);
if ($bytes_read == $length) {
return $buffer;
}
else {
return '';
}
}
sub looks_like_key {
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::Key");
}
sub looks_like_value {
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::Value");
}
sub looks_like_security {
return UNIVERSAL::isa($_[0], "Parse::Win32Registry::WinNT::Security");
}
sub _dumpvar {
my $self = shift;
my $depth = shift || 1;
my $dumpvar = '';
foreach (sort keys %$self) {
$dumpvar .= ' ' x ($depth*2);
$dumpvar .= "$_ => ";
my $var = $self->{$_};
if (!defined $var) {
$dumpvar .= "undef\n";
}
elsif (/offset/ || /_id$/ || /^_unk/) {
$dumpvar .= sprintf "0x%x\n", $var;
}
elsif (/_flags$/) {
$dumpvar .= sprintf "0x%x (0b%b)\n", $var, $var;
}
elsif (/length/ || /bytes_used/) {
$dumpvar .= sprintf "0x%x (%d)\n", $var, $var;
}
elsif (/_data$/) {
if (length($var) == 0) {
$dumpvar .= '(no data)';
}
else {
$dumpvar .= join(' ', unpack('(H2)20', $var));
if (length($var) > 20) {
$dumpvar .= '...';
}
}
$dumpvar .= "\n";
}
elsif (/timestamp$/) {
$dumpvar .= $var . " (" . iso8601($var) . ")\n";
}
elsif ($var =~ /^\d+$/) {
$dumpvar .= sprintf "%d\n", $var;
}
elsif (ref($var)) {
$dumpvar .= "$var\n"; # stringify object ref
}
else {
$dumpvar .= qq{"$var"};
$dumpvar .= ' ';
$dumpvar .= Encode::is_utf8($var) ? "(UTF8)" : "(BYTES)";
$dumpvar .= "\n";
}
}
return $dumpvar;
}
1;

View File

@ -0,0 +1,66 @@
package Parse::Win32Registry::File;
use strict;
use warnings;
sub get_filehandle {
my $self = shift;
return $self->{_filehandle};
}
sub get_filename {
my $self = shift;
return $self->{_filename};
}
sub get_length {
my $self = shift;
return $self->{_length};
}
sub get_entry_iterator {
my $self = shift;
my $entry_iter;
my $block_iter = $self->get_block_iterator;
return Parse::Win32Registry::Iterator->new(sub {
while (1) {
if (defined $entry_iter) {
my $entry = $entry_iter->();
if (defined $entry) {
return $entry;
}
}
# entry iterator is undefined or finished
my $block = $block_iter->();
if (!defined $block) {
return; # block iterator finished
}
$entry_iter = $block->get_entry_iterator;
}
});
}
# method provided for backwards compatibility
sub move_to_first_entry {
my $self = shift;
$self->{_entry_iter} = undef;
}
# method provided for backwards compatibility
sub get_next_entry {
my $self = shift;
my $entry_iter = $self->{_entry_iter};
if (!defined $entry_iter) {
$self->{_entry_iter} = $entry_iter = $self->get_entry_iterator;
}
return $entry_iter->();
}
1;

245
thirdparty/rr/Parse/Win32Registry/Key.pm vendored Normal file
View File

@ -0,0 +1,245 @@
package Parse::Win32Registry::Key;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Entry);
use Carp;
sub get_name {
my $self = shift;
# the root key of a windows 95 registry has no defined name
# but this should be set to '' when created
return $self->{_name};
}
sub get_path {
my $self = shift;
return $self->{_key_path};
}
sub _look_up_subkey {
my $self = shift;
my $subkey_name = shift;
croak 'Missing subkey name' if !defined $subkey_name;
foreach my $subkey ($self->get_list_of_subkeys) {
if (uc $subkey_name eq uc $subkey->{_name}) {
return $subkey;
}
}
return;
}
sub get_subkey {
my $self = shift;
my $subkey_path = shift;
# check for definedness in case key name is '' or '0'
croak "Usage: get_subkey('key name')" if !defined $subkey_path;
my $key = $self;
# Current path component separator is '\' to match that used in Windows.
# split returns nothing if it is given an empty string,
# and without a limit of -1 drops trailing empty fields.
# The following returns a list with a single zero-length string ("")
# for an empty string, as split(/\\/, $subkey_path, -1) returns (),
# an empty list.
my @path_components = index($subkey_path, "\\") == -1
? ($subkey_path)
: split(/\\/, $subkey_path, -1);
my %offsets_seen = ();
$offsets_seen{$key->get_offset} = undef;
foreach my $subkey_name (@path_components) {
if (my $subkey = $key->_look_up_subkey($subkey_name)) {
if (exists $offsets_seen{$subkey->get_offset}) {
return; # found loop
}
$key = $subkey;
$offsets_seen{$key->get_offset} = undef;
}
else { # subkey name not found, abort look up
return;
}
}
return $key;
}
sub get_value {
my $self = shift;
my $value_name = shift;
# check for definedness in case value name is '' or '0'
croak "Usage: get_value('value name')" if !defined $value_name;
foreach my $value ($self->get_list_of_values) {
if (uc $value_name eq uc $value->{_name}) {
return $value;
}
}
return undef;
}
sub print_summary {
my $self = shift;
print $self->as_string, "\n";
}
sub as_regedit_export {
my $self = shift;
return "[" . $self->{_key_path} . "]\n";
}
sub regenerate_path {
my $self = shift;
# ascend to the root
my $key = $self;
my @key_names = ($key->get_name);
my %offsets_seen = ();
while (!$key->is_root) {
$offsets_seen{$key->get_offset}++;
$key = $key->get_parent;
if (!defined $key) { # found an undefined parent key
unshift @key_names, '(Invalid Parent Key)';
last;
}
if (exists $offsets_seen{$key->get_offset}) { # found loop
unshift @key_names, '(Invalid Parent Key)';
last;
}
unshift @key_names, $key->get_name;
}
my $key_path = join('\\', @key_names);
$self->{_key_path} = $key_path;
return $key_path;
}
sub get_value_data {
my $self = shift;
my $value_name = shift;
croak "Usage: get_value_data('value name')" if !defined $value_name;
if (my $value = $self->get_value($value_name)) {
return $value->get_data;
}
return;
}
sub get_mru_list_of_values {
my $self = shift;
my @values = ();
if (my $mrulist = $self->get_value('MRUList')) {
foreach my $ch (split(//, $mrulist->get_data)) {
if (my $value = $self->get_value($ch)) {
push @values, $value;
}
}
}
elsif (my $mrulistex = $self->get_value('MRUListEx')) {
foreach my $item (unpack('V*', $mrulistex->get_data)) {
last if $item == 0xffffffff;
if (my $value = $self->get_value($item)) {
push @values, $value;
}
}
}
return @values;
}
sub get_list_of_subkeys {
my $self = shift;
my $subkey_iter = $self->get_subkey_iterator;
my @subkeys;
while (my $subkey = $subkey_iter->()) {
push @subkeys, $subkey;
}
return @subkeys;
}
sub get_list_of_values {
my $self = shift;
my $value_iter = $self->get_value_iterator;
my @values;
while (my $value = $value_iter->()) {
push @values, $value;
}
return @values;
}
sub get_subtree_iterator {
my $self = shift;
my @start_keys = ($self);
push my (@subkey_iters), Parse::Win32Registry::Iterator->new(sub {
return shift @start_keys;
});
my $value_iter;
my $key; # used to remember key while iterating values
return Parse::Win32Registry::Iterator->new(sub {
if (defined $value_iter && wantarray) {
my $value = $value_iter->();
if (defined $value) {
return ($key, $value);
}
# $value_iter finished, so fetch a new one
# from the (current) $subkey_iter[-1]
}
while (@subkey_iters > 0) {
$key = $subkey_iters[-1]->(); # depth-first
if (defined $key) {
push @subkey_iters, $key->get_subkey_iterator;
$value_iter = $key->get_value_iterator;
return $key;
}
pop @subkey_iters; # $subkey_iter finished, so remove it
}
return;
});
}
sub walk {
my $self = shift;
my $key_enter_func = shift;
my $value_func = shift;
my $key_leave_func = shift;
if (!defined $key_enter_func &&
!defined $value_func &&
!defined $key_leave_func) {
$key_enter_func = sub { print "+ ", $_[0]->get_path, "\n"; };
$value_func = sub { print " '", $_[0]->get_name, "'\n"; };
$key_leave_func = sub { print "- ", $_[0]->get_path, "\n"; };
}
$key_enter_func->($self) if ref $key_enter_func eq 'CODE';
foreach my $value ($self->get_list_of_values) {
$value_func->($value) if ref $value_func eq 'CODE';
}
foreach my $subkey ($self->get_list_of_subkeys) {
$subkey->walk($key_enter_func, $value_func, $key_leave_func);
}
$key_leave_func->($self) if ref $key_leave_func eq 'CODE';
}
1;

View File

@ -0,0 +1,101 @@
package Parse::Win32Registry::Value;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Entry);
use Carp;
use Parse::Win32Registry::Base qw(:all);
sub get_name {
my $self = shift;
return $self->{_name};
}
sub get_type {
my $self = shift;
return $self->{_type};
}
our @Types = qw(
REG_NONE
REG_SZ
REG_EXPAND_SZ
REG_BINARY
REG_DWORD
REG_DWORD_BIG_ENDIAN
REG_LINK
REG_MULTI_SZ
REG_RESOURCE_LIST
REG_FULL_RESOURCE_DESCRIPTOR
REG_RESOURCE_REQUIREMENTS_LIST
REG_QWORD
);
sub get_type_as_string {
my $self = shift;
my $type = $self->get_type;
if (exists $Types[$type]) {
return $Types[$type];
}
else {
# Return unrecognised types as REG_<number>
# REGEDIT displays them as formatted hex numbers, e.g. 0x1f4
return "REG_$type";
}
}
sub get_data_as_string {
my $self = shift;
my $type = $self->get_type;
my $data = $self->get_data;
if (!defined($data)) {
return '(invalid data)';
}
elsif (length($data) == 0) {
return '(no data)';
}
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
return $data;
}
elsif ($type == REG_MULTI_SZ) {
my @data = $self->get_data;
my $i = 0;
return join(' ', map { "[" . $i++ . "] $_" } @data);
}
elsif ($type == REG_DWORD || $type == REG_DWORD_BIG_ENDIAN) {
return sprintf '0x%08x (%u)', $data, $data;
}
else {
return join(' ', unpack('(H2)*', $data));
}
}
sub get_raw_data {
my $self = shift;
return $self->{_data};
}
sub as_string {
my $self = shift;
my $name = $self->get_name;
$name = '(Default)' if $name eq '';
my $type_as_string = $self->get_type_as_string;
my $data_as_string = $self->get_data_as_string;
return "$name ($type_as_string) = $data_as_string";
}
sub print_summary {
my $self = shift;
print $self->as_string, "\n";
}
1;

View File

@ -0,0 +1,540 @@
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;

View File

@ -0,0 +1,207 @@
package Parse::Win32Registry::Win95::Key;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Key);
use Carp;
use Parse::Win32Registry::Base qw(:all);
use Parse::Win32Registry::Win95::Value;
use constant RGKN_ENTRY_LENGTH => 0x1c;
use constant OFFSET_TO_RGKN_BLOCK => 0x20;
sub new {
my $class = shift;
my $regfile = shift;
my $offset = shift; # offset to RGKN key entry relative to start of RGKN
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;
# RGKN Key Entry
# 0x00 dword
# 0x04 dword
# 0x08 dword
# 0x0c dword = offset to parent RGKN entry
# 0x10 dword = offset to first child RGKN entry
# 0x14 dword = offset to next sibling RGKN entry
# 0x18 dword = entry id of RGDB entry
# Extracted offsets are relative to the start of the RGKN block
# Any offset of 0xffffffff marks the end of a list.
# An entry id of 0xffffffff means the RGKN entry has no RGDB entry.
# This occurs for the root key of the registry file.
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $rgkn_entry, RGKN_ENTRY_LENGTH);
if ($bytes_read != RGKN_ENTRY_LENGTH) {
warnf('Could not read RGKN key at 0x%x', $offset);
return;
}
my ($offset_to_parent,
$offset_to_first_child,
$offset_to_next_sibling,
$key_id) = unpack('x12VVVV', $rgkn_entry);
$offset_to_parent += OFFSET_TO_RGKN_BLOCK
if $offset_to_parent != 0xffffffff;
$offset_to_first_child += OFFSET_TO_RGKN_BLOCK
if $offset_to_first_child != 0xffffffff;
$offset_to_next_sibling += OFFSET_TO_RGKN_BLOCK
if $offset_to_next_sibling != 0xffffffff;
my $self = {};
$self->{_regfile} = $regfile;
$self->{_offset} = $offset;
$self->{_length} = RGKN_ENTRY_LENGTH;
$self->{_allocated} = 1;
$self->{_tag} = 'rgkn key';
$self->{_offset_to_parent} = $offset_to_parent;
$self->{_offset_to_first_child} = $offset_to_first_child;
$self->{_offset_to_next_sibling} = $offset_to_next_sibling;
$self->{_id} = $key_id;
bless $self, $class;
# Look up corresponding rgdb entry
my $index = $regfile->{_rgdb_index};
croak 'Missing rgdb index' if !defined $index;
if (exists $index->{$key_id}) {
my $rgdb_key = $index->{$key_id};
$self->{_rgdb_key} = $rgdb_key;
$self->{_name} = $rgdb_key->get_name;
}
else {
$self->{_name} = '';
# Only the root key should have no matching RGDB entry
if (!$self->is_root) {
warnf('Could not find RGDB entry for RGKN key at 0x%x', $offset);
}
}
my $name = $self->{_name};
$self->{_key_path} = defined($parent_key_path)
? "$parent_key_path\\$name"
: $name;
return $self;
}
sub get_timestamp {
return undef;
}
sub get_timestamp_as_string {
return iso8601(undef);
}
sub get_class_name {
return undef;
}
sub is_root {
my $self = shift;
my $offset = $self->{_offset};
my $regfile = $self->{_regfile};
my $rgkn_block = $regfile->get_rgkn;
my $offset_to_root_key = $rgkn_block->{_offset_to_root_key};
# This gives better results than checking id == 0xffffffff
return $offset == $offset_to_root_key;
}
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::Win95::Key->new($regfile,
$offset_to_parent,
$grandparent_key_path);
}
sub get_security {
return undef;
}
sub as_string {
my $self = shift;
return $self->get_path;
}
sub parse_info {
my $self = shift;
my $info = sprintf '0x%x rgkn key len=0x%x par=0x%x,child=0x%x,next=0x%x id=0x%x',
$self->{_offset},
$self->{_length},
$self->{_offset_to_parent},
$self->{_offset_to_first_child},
$self->{_offset_to_next_sibling},
$self->{_id};
return $info;
}
sub get_subkey_iterator {
my $self = shift;
my $regfile = $self->{_regfile};
my $key_path = $self->{_key_path};
my $offset_to_next_key = $self->{_offset_to_first_child};
my $end_of_file = $regfile->get_length;
my $rgkn_block = $regfile->get_rgkn;
my $end_of_rgkn_block = $rgkn_block->get_offset + $rgkn_block->get_length;
return Parse::Win32Registry::Iterator->new(sub {
if ($offset_to_next_key == 0xffffffff) {
return; # no more subkeys
}
if ($offset_to_next_key > $end_of_rgkn_block) {
return;
}
if (my $key = Parse::Win32Registry::Win95::Key->new($regfile,
$offset_to_next_key, $key_path))
{
$offset_to_next_key = $key->{_offset_to_next_sibling};
return $key;
}
else {
return; # no more subkeys
}
});
}
sub get_value_iterator {
my $self = shift;
my $rgdb_key = $self->{_rgdb_key};
if (defined $rgdb_key) {
return $rgdb_key->get_value_iterator;
}
else {
return Parse::Win32Registry::Iterator->new(sub {});
}
}
1;

View File

@ -0,0 +1,177 @@
package Parse::Win32Registry::Win95::Value;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Value);
use Carp;
use Encode;
use Parse::Win32Registry::Base qw(:all);
use constant RGDB_VALUE_HEADER_LENGTH => 0xc;
sub new {
my $class = shift;
my $regfile = shift;
my $offset = shift; # offset to RGDB value entry
croak 'Missing registry file' if !defined $regfile;
croak 'Missing offset' if !defined $offset;
my $fh = $regfile->get_filehandle;
# RGDB Value Entry
# 0x00 dword = value type
# 0x04
# 0x08 word = value name length
# 0x0a word = value data length
# 0x0c = value name [for name length bytes]
# + value data [for data length bytes]
# Value type may just be a word, not a dword;
# following word always appears to be zero.
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $rgdb_value_entry,
RGDB_VALUE_HEADER_LENGTH);
if ($bytes_read != RGDB_VALUE_HEADER_LENGTH) {
warnf('Could not read RGDB value at 0x%x', $offset);
return;
}
my ($type,
$name_length,
$data_length) = unpack('Vx4vv', $rgdb_value_entry);
$bytes_read = sysread($fh, my $name, $name_length);
if ($bytes_read != $name_length) {
warnf('Could not read name for RGDB value at 0x%x', $offset);
return;
}
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
$bytes_read = sysread($fh, my $data, $data_length);
if ($bytes_read != $data_length) {
warnf('Could not read data for RGDB value at 0x%x', $offset);
return;
}
my $self = {};
$self->{_regfile} = $regfile;
$self->{_offset} = $offset;
$self->{_length} = RGDB_VALUE_HEADER_LENGTH + $name_length + $data_length;
$self->{_allocated} = 1;
$self->{_tag} = 'rgdb value';
$self->{_name} = $name;
$self->{_name_length} = $name_length;
$self->{_type} = $type;
$self->{_data} = $data;
$self->{_data_length} = $data_length;
bless $self, $class;
return $self;
}
sub get_data {
my $self = shift;
my $type = $self->get_type;
my $data = $self->{_data};
return if !defined $data; # actually, Win95 value data is always defined
# apply decoding to appropriate data types
if ($type == REG_DWORD) {
if (length($data) == 4) {
$data = unpack('V', $data);
}
else {
# incorrect length for dword data
$data = undef;
}
}
elsif ($type == REG_DWORD_BIG_ENDIAN) {
if (length($data) == 4) {
$data = unpack('N', $data);
}
else {
# incorrect length for dword data
$data = undef;
}
}
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
# Snip off any terminating null.
# Typically, REG_SZ values will not have a terminating null,
# while REG_EXPAND_SZ values will have a terminating null
chop $data if substr($data, -1, 1) eq "\0";
}
elsif ($type == REG_MULTI_SZ) {
# Snip off any terminating nulls
chop $data if substr($data, -1, 1) eq "\0";
chop $data if substr($data, -1, 1) eq "\0";
my @multi_sz = split("\0", $data, -1);
# Make sure there is at least one empty string
@multi_sz = ('') if @multi_sz == 0;
return wantarray ? @multi_sz : join($", @multi_sz);
}
return $data;
}
sub as_regedit_export {
my $self = shift;
my $version = shift || 5;
my $name = $self->get_name;
my $export = $name eq '' ? '@=' : '"' . $name . '"=';
my $type = $self->get_type;
# XXX
# if (!defined $self->{_data}) {
# $name = $name eq '' ? '@' : qq{"$name"};
# return qq{; $name=(invalid data)\n};
# }
if ($type == REG_SZ) {
$export .= '"' . $self->get_data . '"';
$export .= "\n";
}
elsif ($type == REG_BINARY) {
$export .= 'hex:';
$export .= format_octets($self->{_data}, length($export));
}
elsif ($type == REG_DWORD) {
my $data = $self->get_data;
$export .= defined($data)
? sprintf("dword:%08x", $data)
: "dword:";
$export .= "\n";
}
elsif ($type == REG_EXPAND_SZ || $type == REG_MULTI_SZ) {
my $data = $version == 4
? $self->{_data} # raw data
: encode("UCS-2LE", $self->{_data}); # ansi->unicode
$export .= sprintf("hex(%x):", $type);
$export .= format_octets($data, length($export));
}
else {
$export .= sprintf("hex(%x):", $type);
$export .= format_octets($self->{_data}, length($export));
}
return $export;
}
sub parse_info {
my $self = shift;
my $info = sprintf '0x%x rgdb value len=0x%x "%s" type=%d data,len=0x%x',
$self->{_offset},
$self->{_length},
$self->{_name},
$self->{_type},
$self->{_data_length};
return $info;
}
1;

View File

@ -0,0 +1,109 @@
package Parse::Win32Registry::WinNT::Entry;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Entry);
use Carp;
use Parse::Win32Registry::Base qw(:all);
use Parse::Win32Registry::WinNT::Key;
use Parse::Win32Registry::WinNT::Value;
use Parse::Win32Registry::WinNT::Security;
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;
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $entry_header, 8);
if ($bytes_read != 8) {
return;
}
my ($length,
$tag) = unpack('Va2', $entry_header);
my $allocated = 0;
if ($length > 0x7fffffff) {
$allocated = 1;
$length = (0xffffffff - $length) + 1;
}
$tag = '' if $tag !~ /(nk|vk|lh|lf|li|ri|sk)/;
if ($tag eq 'nk') {
if (my $key = Parse::Win32Registry::WinNT::Key->new($regfile,
$offset))
{
$key->regenerate_path;
return $key;
}
}
elsif ($tag eq 'vk') {
if (my $value = Parse::Win32Registry::WinNT::Value->new($regfile,
$offset))
{
return $value;
}
}
elsif ($tag eq 'sk') {
if (my $value = Parse::Win32Registry::WinNT::Security->new($regfile,
$offset))
{
return $value;
}
}
my $self = {};
$self->{_regfile} = $regfile,
$self->{_offset} = $offset,
$self->{_length} = $length,
$self->{_tag} = $tag,
$self->{_allocated} = $allocated,
bless $self, $class;
return $self;
}
sub as_string {
my $self = shift;
my $tag = $self->{_tag};
if ($tag eq 'nk') {
return '(key entry)';
}
elsif ($tag eq 'vk') {
return '(value entry)';
}
elsif ($tag eq 'sk') {
return '(security entry)';
}
elsif ($tag =~ /(lh|lf|li|ri)/) {
return '(subkey list entry)';
}
return '(unidentified entry)';
}
sub parse_info {
my $self = shift;
my $tag = $self->{_tag};
$tag = defined($tag) && $tag ne ''
? $tag . ' '
: '.. ';
my $info = sprintf '0x%x %slen=0x%x alloc=%d',
$self->{_offset},
$tag,
$self->{_length},
$self->{_allocated};
return $info;
}
1;

View File

@ -0,0 +1,297 @@
package Parse::Win32Registry::WinNT::File;
use strict;
use warnings;
use base qw(Parse::Win32Registry::File);
use Carp;
use Encode;
use File::Basename;
use Parse::Win32Registry::Base qw(:all);
use Parse::Win32Registry::WinNT::Key;
use constant REGF_HEADER_LENGTH => 0x200;
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
sub new {
my $class = shift;
my $filename = shift or croak "No filename specified";
open my $fh, '<', $filename or croak "Unable to open '$filename': $!";
# 0x00 dword = 'regf' signature
# 0x04 dword = seq1
# 0x08 dword = seq2
# 0x0c qword = timestamp
# 0x14 dword = major version
# 0x18 dword = minor version
# 0x1c dword = type (0 = registry file, 1 = log file)
# 0x20 dword = (1)
# 0x24 dword = offset to root key
# 0x28 dword = total length of all hbins (excludes header)
# 0x2c dword = (1)
# 0x30 = embedded filename
# Extracted offsets are always relative to first hbin
my $bytes_read = sysread($fh, my $regf_header, REGF_HEADER_LENGTH);
if ($bytes_read != REGF_HEADER_LENGTH) {
warnf('Could not read registry file header');
return;
}
my ($regf_sig,
$seq1,
$seq2,
$timestamp,
$major_version,
$minor_version,
$type,
$offset_to_root_key,
$total_hbin_length,
$embedded_filename,
) = unpack('a4VVa8VVVx4VVx4a64', $regf_header);
$offset_to_root_key += OFFSET_TO_FIRST_HBIN;
if ($regf_sig ne 'regf') {
warnf('Invalid registry file signature');
return;
}
$embedded_filename = unpack('Z*', decode('UCS-2LE', $embedded_filename));
# The header checksum is the xor of the first 127 dwords.
# The checksum is stored in the 128th dword, at offset 0x1fc (508).
my $checksum = 0;
foreach my $x (unpack('V127', $regf_header)) {
$checksum ^= $x;
}
my $embedded_checksum = unpack('x508V', $regf_header);
if ($checksum != $embedded_checksum) {
warnf('Invalid checksum for registry file header');
}
my $self = {};
$self->{_filehandle} = $fh;
$self->{_filename} = $filename;
$self->{_length} = (stat $fh)[7];
$self->{_offset_to_root_key} = $offset_to_root_key;
$self->{_timestamp} = unpack_windows_time($timestamp);
$self->{_embedded_filename} = $embedded_filename;
$self->{_seq1} = $seq1;
$self->{_seq2} = $seq2;
$self->{_version} = "$major_version.$minor_version";
$self->{_type} = $type;
$self->{_total_hbin_length} = $total_hbin_length;
$self->{_embedded_checksum} = $embedded_checksum;
$self->{_security_cache} = {}; # comment out to disable cache
bless $self, $class;
return $self;
}
sub get_root_key {
my $self = shift;
my $offset_to_root_key = $self->{_offset_to_root_key};
my $root_key = Parse::Win32Registry::WinNT::Key->new($self,
$offset_to_root_key);
return $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 =~ /NTUSER/i) {
$fake_root = 'HKEY_CURRENT_USER';
}
elsif ($filename =~ /USRCLASS/i) {
$fake_root = 'HKEY_CLASSES_ROOT';
}
elsif ($filename =~ /SOFTWARE/i) {
$fake_root = 'HKEY_LOCAL_MACHINE\SOFTWARE';
}
elsif ($filename =~ /SYSTEM/i) {
$fake_root = 'HKEY_LOCAL_MACHINE\SYSTEM';
}
elsif ($filename =~ /SAM/i) {
$fake_root = 'HKEY_LOCAL_MACHINE\SAM';
}
elsif ($filename =~ /SECURITY/i) {
$fake_root = 'HKEY_LOCAL_MACHINE\SECURITY';
}
else {
$fake_root = 'HKEY_UNKNOWN';
}
}
$root_key->{_name} = $fake_root;
$root_key->{_key_path} = $fake_root;
return $root_key;
}
sub get_timestamp {
my $self = shift;
return $self->{_timestamp};
}
sub get_timestamp_as_string {
my $self = shift;
return iso8601($self->{_timestamp});
}
sub get_embedded_filename {
my $self = shift;
return $self->{_embedded_filename};
}
sub get_block_iterator {
my $self = shift;
my $offset_to_next_hbin = OFFSET_TO_FIRST_HBIN;
my $end_of_file = $self->{_length};
return Parse::Win32Registry::Iterator->new(sub {
if ($offset_to_next_hbin > $end_of_file) {
return; # no more hbins
}
if (my $hbin = Parse::Win32Registry::WinNT::Hbin->new($self,
$offset_to_next_hbin))
{
return unless $hbin->get_length > 0;
$offset_to_next_hbin += $hbin->get_length;
return $hbin;
}
else {
return; # no more hbins
}
});
}
*get_hbin_iterator = \&get_block_iterator;
sub _dump_security_cache {
my $self = shift;
if (defined(my $cache = $self->{_security_cache})) {
foreach my $offset (sort { $a <=> $b } keys %$cache) {
my $security = $cache->{$offset};
printf '0x%x %s\n', $offset, $security->as_string;
}
}
}
package Parse::Win32Registry::WinNT::Hbin;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Entry);
use Carp;
use Parse::Win32Registry::Base qw(:all);
use Parse::Win32Registry::WinNT::Entry;
use constant HBIN_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;
# 0x00 dword = 'hbin' signature
# 0x04 dword = offset from first hbin to this hbin
# 0x08 dword = length of this hbin / relative offset to next hbin
# 0x14 qword = timestamp (first hbin only)
# Extracted offsets are always relative to first hbin
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $hbin_header, HBIN_HEADER_LENGTH);
if ($bytes_read != HBIN_HEADER_LENGTH) {
return;
}
my ($sig,
$offset_to_hbin,
$length,
$timestamp) = unpack('a4VVx8a8x4', $hbin_header);
if ($sig ne 'hbin') {
return;
}
my $self = {};
$self->{_regfile} = $regfile;
$self->{_offset} = $offset;
$self->{_length} = $length;
$self->{_header_length} = HBIN_HEADER_LENGTH;
$self->{_allocated} = 1;
$self->{_tag} = $sig;
$self->{_timestamp} = unpack_windows_time($timestamp);
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->{_timestamp});
}
sub get_entry_iterator {
my $self = shift;
my $regfile = $self->{_regfile};
my $offset = $self->{_offset};
my $length = $self->{_length};
my $offset_to_next_entry = $offset + HBIN_HEADER_LENGTH;
my $end_of_hbin = $offset + $length;
return Parse::Win32Registry::Iterator->new(sub {
if ($offset_to_next_entry >= $end_of_hbin) {
return; # no more entries
}
if (my $entry = Parse::Win32Registry::WinNT::Entry->new($regfile,
$offset_to_next_entry))
{
return unless $entry->get_length > 0;
$offset_to_next_entry += $entry->get_length;
return $entry;
}
else {
return; # no more entries
}
});
}
1;

View File

@ -0,0 +1,444 @@
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,
$offset_to_parent,
$num_subkeys,
$offset_to_subkey_list,
$num_values,
$offset_to_value_list,
$offset_to_security,
$offset_to_class_name,
$name_length,
$class_name_length,
) = 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);
$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);
}
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;

View File

@ -0,0 +1,157 @@
package Parse::Win32Registry::WinNT::Security;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Entry);
use Carp;
use Parse::Win32Registry::Base qw(:all);
use constant SK_HEADER_LENGTH => 0x18;
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
sub new {
my $class = shift;
my $regfile = shift;
my $offset = shift; # offset to sk record relative to start of file
croak 'Missing registry file' if !defined $regfile;
croak 'Missing offset' if !defined $offset;
if (defined(my $cache = $regfile->{_security_cache})) {
if (exists $cache->{$offset}) {
return $cache->{$offset};
}
}
my $fh = $regfile->get_filehandle;
# 0x00 dword = security length (negative = allocated)
# 0x04 word = 'sk' signature
# 0x08 dword = offset to previous sk
# 0x0c dword = offset to next sk
# 0x10 dword = ref count
# 0x14 dword = length of security descriptor
# 0x18 = start of security descriptor
# Extracted offsets are always relative to first hbin
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $sk_header, SK_HEADER_LENGTH);
if ($bytes_read != SK_HEADER_LENGTH) {
warnf('Could not read security at 0x%x', $offset);
return;
}
my ($length,
$sig,
$offset_to_previous,
$offset_to_next,
$ref_count,
$sd_length,
) = unpack('Va2x2VVVV', $sk_header);
$offset_to_previous += OFFSET_TO_FIRST_HBIN
if $offset_to_previous != 0xffffffff;
$offset_to_next += OFFSET_TO_FIRST_HBIN
if $offset_to_next != 0xffffffff;
my $allocated = 0;
if ($length > 0x7fffffff) {
$allocated = 1;
$length = (0xffffffff - $length) + 1;
}
# allocated should be true
if ($sig ne 'sk') {
warnf('Invalid signature for security at 0x%x', $offset);
return;
}
$bytes_read = sysread($fh, my $sd_data, $sd_length);
if ($bytes_read != $sd_length) {
warnf('Could not read security descriptor for security at 0x%x',
$offset);
return;
}
my $sd = unpack_security_descriptor($sd_data);
if (!defined $sd) {
warnf('Invalid security descriptor for security at 0x%x',
$offset);
# Abandon security object if security descriptor is invalid
return;
}
my $self = {};
$self->{_regfile} = $regfile;
$self->{_offset} = $offset;
$self->{_length} = $length;
$self->{_allocated} = $allocated;
$self->{_tag} = $sig;
$self->{_offset_to_previous} = $offset_to_previous;
$self->{_offset_to_next} = $offset_to_next;
$self->{_ref_count} = $ref_count;
$self->{_security_descriptor_length} = $sd_length;
$self->{_security_descriptor} = $sd;
bless $self, $class;
if (defined(my $cache = $regfile->{_security_cache})) {
$cache->{$offset} = $self;
}
return $self;
}
sub get_previous {
my $self = shift;
my $regfile = $self->{_regfile};
my $offset_to_previous = $self->{_offset_to_previous};
return Parse::Win32Registry::WinNT::Security->new($regfile,
$offset_to_previous);
}
sub get_next {
my $self = shift;
my $regfile = $self->{_regfile};
my $offset_to_next = $self->{_offset_to_next};
return Parse::Win32Registry::WinNT::Security->new($regfile,
$offset_to_next);
}
sub get_reference_count {
my $self = shift;
return $self->{_ref_count};
}
sub get_security_descriptor {
my $self = shift;
return $self->{_security_descriptor};
}
sub as_string {
my $self = shift;
return '(security entry)';
}
sub parse_info {
my $self = shift;
my $info = sprintf '0x%x sk len=0x%x alloc=%d prev=0x%x,next=0x%x refs=%d',
$self->{_offset},
$self->{_length},
$self->{_allocated},
$self->{_offset_to_previous},
$self->{_offset_to_next},
$self->{_ref_count};
return $info;
}
1;

View File

@ -0,0 +1,332 @@
package Parse::Win32Registry::WinNT::Value;
use strict;
use warnings;
use base qw(Parse::Win32Registry::Value);
use Carp;
use Encode;
use Parse::Win32Registry::Base qw(:all);
use constant VK_HEADER_LENGTH => 0x18;
use constant OFFSET_TO_FIRST_HBIN => 0x1000;
sub new {
my $class = shift;
my $regfile = shift;
my $offset = shift; # offset to vk record relative to first hbin
croak 'Missing registry file' if !defined $regfile;
croak 'Missing offset' if !defined $offset;
my $fh = $regfile->get_filehandle;
# 0x00 dword = value length (negative = allocated)
# 0x04 word = 'vk' signature
# 0x06 word = value name length
# 0x08 dword = value data length (bit 31 set => data stored inline)
# 0x0c dword = offset to data/inline data
# 0x10 dword = value type
# 0x14 word = flags (bit 1 set => compressed name)
# 0x16 word
# 0x18 = value name [for value name length bytes]
# Extracted offsets are always relative to first hbin
sysseek($fh, $offset, 0);
my $bytes_read = sysread($fh, my $vk_header, VK_HEADER_LENGTH);
if ($bytes_read != VK_HEADER_LENGTH) {
warnf('Could not read value at 0x%x', $offset);
return;
}
my ($length,
$sig,
$name_length,
$data_length,
$offset_to_data,
$type,
$flags,
) = unpack('Va2vVVVv', $vk_header);
my $allocated = 0;
if ($length > 0x7fffffff) {
$allocated = 1;
$length = (0xffffffff - $length) + 1;
}
# allocated should be true
if ($length < VK_HEADER_LENGTH) {
warnf('Invalid value entry length at 0x%x', $offset);
return;
}
if ($sig ne 'vk') {
warnf('Invalid signature for value at 0x%x', $offset);
return;
}
$bytes_read = sysread($fh, my $name, $name_length);
if ($bytes_read != $name_length) {
warnf('Could not read name for value at 0x%x', $offset);
return;
}
if ($flags & 1) {
$name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
}
else {
$name = decode('UCS-2LE', $name);
};
# If the top bit of the data_length is set, then
# the value is inline and stored in the offset to data field (at 0xc).
my $data;
my $data_inline = $data_length >> 31;
if ($data_inline) {
# REG_DWORDs are always inline, but I've also seen
# REG_SZ, REG_BINARY, REG_EXPAND_SZ, and REG_NONE inline
$data_length &= 0x7fffffff;
if ($data_length > 4) {
warnf("Invalid inline data length for value '%s' at 0x%x",
$name, $offset);
$data = undef;
}
else {
# unpack inline data from header
$data = substr($vk_header, 0xc, $data_length);
}
}
else {
if ($offset_to_data != 0 && $offset_to_data != 0xffffffff) {
$offset_to_data += OFFSET_TO_FIRST_HBIN;
if ($offset_to_data < ($regfile->get_length - $data_length)) {
$data = _extract_data($fh, $offset_to_data, $data_length);
}
else {
warnf("Invalid offset to data for value '%s' at 0x%x",
$name, $offset);
}
}
}
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->{_type} = $type;
$self->{_data} = $data;
$self->{_data_length} = $data_length;
$self->{_data_inline} = $data_inline;
$self->{_offset_to_data} = $offset_to_data;
$self->{_flags} = $flags;
bless $self, $class;
return $self;
}
sub _extract_data {
my $fh = shift;
my $offset_to_data = shift;
my $data_length = shift;
if ($offset_to_data == 0 || $offset_to_data == 0xffffffff) {
return undef;
}
sysseek($fh, $offset_to_data, 0);
my $bytes_read = sysread($fh, my $data_header, 4);
if ($bytes_read != 4) {
warnf('Could not read data at 0x%x', $offset_to_data);
return undef;
}
my ($max_data_length) = unpack('V', $data_header);
my $data_allocated = 0;
if ($max_data_length > 0x7fffffff) {
$data_allocated = 1;
$max_data_length = (0xffffffff - $max_data_length) + 1;
}
# data_allocated should be true
my $data;
if ($data_length > $max_data_length) {
$bytes_read = sysread($fh, my $db_entry, 8);
if ($bytes_read != 8) {
warnf('Could not read data at 0x%x', $offset_to_data);
return undef;
}
my ($sig, $num_data_blocks, $offset_to_data_block_list)
= unpack('a2vV', $db_entry);
if ($sig ne 'db') {
warnf('Invalid signature for big data at 0x%x', $offset_to_data);
return undef;
}
$offset_to_data_block_list += OFFSET_TO_FIRST_HBIN;
sysseek($fh, $offset_to_data_block_list + 4, 0);
$bytes_read = sysread($fh, my $data_block_list, $num_data_blocks * 4);
if ($bytes_read != $num_data_blocks * 4) {
warnf('Could not read data block list at 0x%x',
$offset_to_data_block_list);
return undef;
}
$data = "";
my @offsets = map { OFFSET_TO_FIRST_HBIN + $_ }
unpack("V$num_data_blocks", $data_block_list);
foreach my $offset (@offsets) {
sysseek($fh, $offset, 0);
$bytes_read = sysread($fh, my $block_header, 4);
if ($bytes_read != 4) {
warnf('Could not read data block at 0x%x', $offset);
return undef;
}
my ($block_length) = unpack('V', $block_header);
if ($block_length > 0x7fffffff) {
$block_length = (0xffffffff - $block_length) + 1;
}
$bytes_read = sysread($fh, my $block_data, $block_length - 8);
if ($bytes_read != $block_length - 8) {
warnf('Could not read data block at 0x%x', $offset);
return undef;
}
$data .= $block_data;
}
if (length($data) < $data_length) {
warnf("Insufficient data blocks for data at 0x%x", $offset_to_data);
return undef;
}
$data = substr($data, 0, $data_length);
return $data;
}
else {
$bytes_read = sysread($fh, $data, $data_length);
if ($bytes_read != $data_length) {
warnf("Could not read data at 0x%x", $offset_to_data);
return undef;
}
}
return $data;
}
sub get_data {
my $self = shift;
my $type = $self->get_type;
my $data = $self->{_data};
return if !defined $data;
# apply decoding to appropriate data types
if ($type == REG_DWORD) {
if (length($data) == 4) {
$data = unpack('V', $data);
}
else {
# incorrect length for dword data
$data = undef;
}
}
elsif ($type == REG_DWORD_BIG_ENDIAN) {
if (length($data) == 4) {
$data = unpack('N', $data);
}
else {
# incorrect length for dword data
$data = undef;
}
}
elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
$data = decode('UCS-2LE', $data);
# snip off any terminating null
chop $data if substr($data, -1, 1) eq "\0";
}
elsif ($type == REG_MULTI_SZ) {
$data = decode('UCS-2LE', $data);
# snip off any terminating nulls
chop $data if substr($data, -1, 1) eq "\0";
chop $data if substr($data, -1, 1) eq "\0";
my @multi_sz = split("\0", $data, -1);
# make sure there is at least one empty string
@multi_sz = ('') if @multi_sz == 0;
return wantarray ? @multi_sz : join($", @multi_sz);
}
return $data;
}
sub as_regedit_export {
my $self = shift;
my $version = shift || 5;
my $name = $self->get_name;
my $export = $name eq '' ? '@=' : '"' . $name . '"=';
my $type = $self->get_type;
# XXX
# if (!defined $self->{_data}) {
# $name = $name eq '' ? '@' : qq{"$name"};
# return qq{; $name=(invalid data)\n};
# }
if ($type == REG_SZ) {
$export .= '"' . $self->get_data . '"';
$export .= "\n";
}
elsif ($type == REG_BINARY) {
$export .= "hex:";
$export .= format_octets($self->{_data}, length($export));
}
elsif ($type == REG_DWORD) {
my $data = $self->get_data;
$export .= defined($data)
? sprintf("dword:%08x", $data)
: "dword:";
$export .= "\n";
}
elsif ($type == REG_EXPAND_SZ || $type == REG_MULTI_SZ) {
my $data = $version == 4
? encode("ascii", $self->{_data}) # unicode->ascii
: $self->{_data}; # raw data
$export .= sprintf("hex(%x):", $type);
$export .= format_octets($data, length($export));
}
else {
$export .= sprintf("hex(%x):", $type);
$export .= format_octets($self->{_data}, length($export));
}
return $export;
}
sub parse_info {
my $self = shift;
my $info = sprintf '0x%x vk len=0x%x alloc=%d "%s" type=%d',
$self->{_offset},
$self->{_length},
$self->{_allocated},
$self->{_name},
$self->{_type};
if ($self->{_data_inline}) {
$info .= sprintf ' data=inline,len=0x%x',
$self->{_data_length};
}
else {
$info .= sprintf ' data=0x%x,len=0x%x',
$self->{_offset_to_data},
$self->{_data_length};
}
return $info;
}
1;

View File

@ -1,4 +1,4 @@
#! c:\perl\bin\perl.exe
#! /usr/bin/perl
#-------------------------------------------------------------------------
# Rip - RegRipper, CLI version
# Use this utility to run a plugins file or a single plugin against a Reg
@ -347,4 +347,4 @@ sub getTime($$) {
};
$t = 0 if ($t < 0);
return $t;
}
}

4
thirdparty/rr/rr.pl vendored
View File

@ -1,4 +1,4 @@
#! c:\perl\bin\perl.exe
#!/usr/bin/perl
#-----------------------------------------------------------
# Registry Ripper
# Parse a Registry hive file for data pertinent to an investigation
@ -451,4 +451,4 @@ sub getTime($$) {
};
$t = 0 if ($t < 0);
return $t;
}
}