1package Parse::Win32Registry::WinNT::Key; 2 3use strict; 4use warnings; 5 6use base qw(Parse::Win32Registry::Key); 7 8use Carp; 9use Encode; 10use Parse::Win32Registry::Base qw(:all); 11use Parse::Win32Registry::WinNT::Value; 12use Parse::Win32Registry::WinNT::Security; 13 14use constant NK_HEADER_LENGTH => 0x50; 15use constant OFFSET_TO_FIRST_HBIN => 0x1000; 16 17sub new { 18 my $class = shift; 19 my $regfile = shift; 20 my $offset = shift; # offset to nk record relative to start of file 21 my $parent_key_path = shift; # parent key path (optional) 22 23 croak 'Missing registry file' if !defined $regfile; 24 croak 'Missing offset' if !defined $offset; 25 26 my $fh = $regfile->get_filehandle; 27 28 # 0x00 dword = key length (negative = allocated) 29 # 0x04 word = 'nk' signature 30 # 0x06 word = flags 31 # 0x08 qword = timestamp 32 # 0x10 33 # 0x14 dword = offset to parent 34 # 0x18 dword = number of subkeys 35 # 0x1c 36 # 0x20 dword = offset to subkey list (lf, lh, ri, li) 37 # 0x24 38 # 0x28 dword = number of values 39 # 0x2c dword = offset to value list 40 # 0x30 dword = offset to security 41 # 0x34 dword = offset to class name 42 # 0x38 dword = max subkey name length 43 # 0x3c dword = max class name length 44 # 0x40 dword = max value name length 45 # 0x44 dword = max value data length 46 # 0x48 47 # 0x4c word = key name length 48 # 0x4e word = class name length 49 # 0x50 = key name [for key name length bytes] 50 51 # Extracted offsets are always relative to first hbin 52 53 sysseek($fh, $offset, 0); 54 my $bytes_read = sysread($fh, my $nk_header, NK_HEADER_LENGTH); 55 if ($bytes_read != NK_HEADER_LENGTH) { 56 warnf('Could not read key at 0x%x', $offset); 57 return; 58 } 59 60 my ($length, 61 $sig, 62 $flags, 63 $timestamp, 64 $offset_to_parent, 65 $num_subkeys, 66 $offset_to_subkey_list, 67 $num_values, 68 $offset_to_value_list, 69 $offset_to_security, 70 $offset_to_class_name, 71 $name_length, 72 $class_name_length, 73 ) = unpack('Va2va8x4VVx4Vx4VVVVx20vv', $nk_header); 74 75 $offset_to_parent += OFFSET_TO_FIRST_HBIN 76 if $offset_to_parent != 0xffffffff; 77 $offset_to_subkey_list += OFFSET_TO_FIRST_HBIN 78 if $offset_to_subkey_list != 0xffffffff; 79 $offset_to_value_list += OFFSET_TO_FIRST_HBIN 80 if $offset_to_value_list != 0xffffffff; 81 $offset_to_security += OFFSET_TO_FIRST_HBIN 82 if $offset_to_security != 0xffffffff; 83 $offset_to_class_name += OFFSET_TO_FIRST_HBIN 84 if $offset_to_class_name != 0xffffffff; 85 86 my $allocated = 0; 87 if ($length > 0x7fffffff) { 88 $allocated = 1; 89 $length = (0xffffffff - $length) + 1; 90 } 91 # allocated should be true 92 93 if ($length < NK_HEADER_LENGTH) { 94 warnf('Invalid value entry length at 0x%x', $offset); 95 return; 96 } 97 98 if ($sig ne 'nk') { 99 warnf('Invalid signature for key at 0x%x', $offset); 100 return; 101 } 102 103 $bytes_read = sysread($fh, my $name, $name_length); 104 if ($bytes_read != $name_length) { 105 warnf('Could not read name for key at 0x%x', $offset); 106 return; 107 } 108 109 if ($flags & 0x20) { 110 $name = decode($Parse::Win32Registry::Base::CODEPAGE, $name); 111 } 112 else { 113 $name = decode('UCS-2LE', $name); 114 } 115 116 my $key_path = (defined $parent_key_path) 117 ? "$parent_key_path\\$name" 118 : "$name"; 119 120 my $class_name; 121 if ($offset_to_class_name != 0xffffffff) { 122 sysseek($fh, $offset_to_class_name + 4, 0); 123 $bytes_read = sysread($fh, $class_name, $class_name_length); 124 if ($bytes_read != $class_name_length) { 125 warnf('Could not read class name at 0x%x', $offset_to_class_name); 126 $class_name = undef; 127 } 128 else { 129 $class_name = decode('UCS-2LE', $class_name); 130 } 131 } 132 133 my $self = {}; 134 $self->{_regfile} = $regfile; 135 $self->{_offset} = $offset; 136 $self->{_length} = $length; 137 $self->{_allocated} = $allocated; 138 $self->{_tag} = $sig; 139 $self->{_name} = $name; 140 $self->{_name_length} = $name_length; 141 $self->{_key_path} = $key_path; 142 $self->{_flags} = $flags; 143 $self->{_offset_to_parent} = $offset_to_parent; 144 $self->{_num_subkeys} = $num_subkeys; 145 $self->{_offset_to_subkey_list} = $offset_to_subkey_list; 146 $self->{_num_values} = $num_values; 147 $self->{_offset_to_value_list} = $offset_to_value_list; 148 $self->{_timestamp} = unpack_windows_time($timestamp); 149 $self->{_offset_to_security} = $offset_to_security; 150 $self->{_offset_to_class_name} = $offset_to_class_name; 151 $self->{_class_name_length} = $class_name_length; 152 $self->{_class_name} = $class_name; 153 bless $self, $class; 154 155 return $self; 156} 157 158sub get_timestamp { 159 my $self = shift; 160 161 return $self->{_timestamp}; 162} 163 164sub get_timestamp_as_string { 165 my $self = shift; 166 167 return iso8601($self->get_timestamp); 168} 169 170sub get_class_name { 171 my $self = shift; 172 173 return $self->{_class_name}; 174} 175 176sub is_root { 177 my $self = shift; 178 179 my $flags = $self->{_flags}; 180 return $flags & 4 || $flags & 8; 181} 182 183sub get_parent { 184 my $self = shift; 185 186 my $regfile = $self->{_regfile}; 187 my $offset_to_parent = $self->{_offset_to_parent}; 188 my $key_path = $self->{_key_path}; 189 190 return if $self->is_root; 191 192 my $grandparent_key_path; 193 my @keys = split /\\/, $key_path, -1; 194 if (@keys > 2) { 195 $grandparent_key_path = join('\\', @keys[0..$#keys-2]); 196 } 197 198 return Parse::Win32Registry::WinNT::Key->new($regfile, 199 $offset_to_parent, 200 $grandparent_key_path); 201} 202 203sub get_security { 204 my $self = shift; 205 206 my $regfile = $self->{_regfile}; 207 my $offset_to_security = $self->{_offset_to_security}; 208 my $key_path = $self->{_key_path}; 209 210 if ($offset_to_security == 0xffffffff) { 211 return; 212 } 213 214 return Parse::Win32Registry::WinNT::Security->new($regfile, 215 $offset_to_security, 216 $key_path); 217} 218 219sub as_string { 220 my $self = shift; 221 222 my $string = $self->get_path . ' [' . $self->get_timestamp_as_string . ']'; 223 return $string; 224} 225 226sub parse_info { 227 my $self = shift; 228 229 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', 230 $self->{_offset}, 231 $self->{_length}, 232 $self->{_allocated}, 233 $self->{_name}, 234 $self->{_offset_to_parent}, 235 $self->{_num_subkeys}, $self->{_offset_to_subkey_list}, 236 $self->{_num_values}, $self->{_offset_to_value_list}, 237 $self->{_offset_to_security}, 238 $self->{_offset_to_class_name}; 239 if (defined $self->{_class_name}) { 240 $info .= sprintf ',len=0x%x', $self->{_class_name_length}; 241 } 242 return $info; 243} 244 245sub _get_offsets_to_subkeys { 246 my $self = shift; 247 248 # Offset is passed as a parameter for recursive lists such as 'ri' 249 my $offset_to_subkey_list = shift || $self->{_offset_to_subkey_list}; 250 251 my $regfile = $self->{_regfile}; 252 my $fh = $regfile->get_filehandle; 253 254 return if $offset_to_subkey_list == 0xffffffff 255 || $self->{_num_subkeys} == 0; 256 257 sysseek($fh, $offset_to_subkey_list, 0); 258 my $bytes_read = sysread($fh, my $subkey_list_header, 8); 259 if ($bytes_read != 8) { 260 warnf('Could not read subkey list header at 0x%x', 261 $offset_to_subkey_list); 262 return; 263 } 264 265 # 0x00 dword = subkey list length (negative = allocated) 266 # 0x04 word = 'lf' signature 267 # 0x06 word = number of entries 268 # 0x08 dword = offset to 1st subkey 269 # 0x0c dword = first four characters of the key name 270 # 0x10 dword = offset to 2nd subkey 271 # 0x14 dword = first four characters of the key name 272 # ... 273 274 # 0x00 dword = subkey list length (negative = allocated) 275 # 0x04 word = 'lh' signature 276 # 0x06 word = number of entries 277 # 0x08 dword = offset to 1st subkey 278 # 0x0c dword = hash of the key name 279 # 0x10 dword = offset to 2nd subkey 280 # 0x14 dword = hash of the key name 281 # ... 282 283 # 0x00 dword = subkey list length (negative = allocated) 284 # 0x04 word = 'ri' signature 285 # 0x06 word = number of entries in ri list 286 # 0x08 dword = offset to 1st lf/lh/li list 287 # 0x0c dword = offset to 2nd lf/lh/li list 288 # 0x10 dword = offset to 3rd lf/lh/li list 289 # ... 290 291 # 0x00 dword = subkey list length (negative = allocated) 292 # 0x04 word = 'li' signature 293 # 0x06 word = number of entries in li list 294 # 0x08 dword = offset to 1st subkey 295 # 0x0c dword = offset to 2nd subkey 296 # ... 297 298 # Extracted offsets are always relative to first hbin 299 300 my @offsets_to_subkeys = (); 301 302 my ($length, 303 $sig, 304 $num_entries, 305 ) = unpack('Va2v', $subkey_list_header); 306 307 my $subkey_list_length; 308 if ($sig eq 'lf' || $sig eq 'lh') { 309 $subkey_list_length = 2 * 4 * $num_entries; 310 } 311 elsif ($sig eq 'ri' || $sig eq 'li') { 312 $subkey_list_length = 4 * $num_entries; 313 } 314 else { 315 warnf('Invalid signature for subkey list at 0x%x', 316 $offset_to_subkey_list); 317 return; 318 } 319 320 $bytes_read = sysread($fh, my $subkey_list, $subkey_list_length); 321 if ($bytes_read != $subkey_list_length) { 322 warnf('Could not read subkey list at 0x%x', 323 $offset_to_subkey_list); 324 return; 325 } 326 327 if ($sig eq 'lf') { 328 foreach my $offset (unpack("(Vx4)$num_entries", $subkey_list)) { 329 push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset; 330 } 331 } 332 elsif ($sig eq 'lh') { 333 foreach my $offset (unpack("(Vx4)$num_entries", $subkey_list)) { 334 push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset; 335 } 336 } 337 elsif ($sig eq 'ri') { 338 foreach my $offset (unpack("V$num_entries", $subkey_list)) { 339 my $offsets_ref = 340 $self->_get_offsets_to_subkeys(OFFSET_TO_FIRST_HBIN + $offset); 341 if (defined $offsets_ref && ref $offsets_ref eq 'ARRAY') { 342 push @offsets_to_subkeys, @{ $offsets_ref }; 343 } 344 } 345 } 346 elsif ($sig eq 'li') { 347 foreach my $offset (unpack("V$num_entries", $subkey_list)) { 348 push @offsets_to_subkeys, OFFSET_TO_FIRST_HBIN + $offset; 349 } 350 } 351 352 return \@offsets_to_subkeys; 353} 354 355sub get_subkey_iterator { 356 my $self = shift; 357 358 my $regfile = $self->{_regfile}; 359 my $key_path = $self->{_key_path}; 360 361 my @offsets_to_subkeys = (); 362 if ($self->{_num_subkeys} > 0) { 363 my $offsets_to_subkeys_ref = $self->_get_offsets_to_subkeys; 364 if (defined $offsets_to_subkeys_ref) { 365 @offsets_to_subkeys = @{$self->_get_offsets_to_subkeys}; 366 } 367 } 368 369 return Parse::Win32Registry::Iterator->new(sub { 370 while (defined(my $offset_to_subkey = shift @offsets_to_subkeys)) { 371 my $subkey = Parse::Win32Registry::WinNT::Key->new($regfile, 372 $offset_to_subkey, $key_path); 373 if (defined $subkey) { 374 return $subkey; 375 } 376 } 377 return; # no more offsets to subkeys 378 }); 379} 380 381sub _get_offsets_to_values { 382 my $self = shift; 383 384 my $regfile = $self->{_regfile}; 385 my $fh = $regfile->get_filehandle; 386 my $offset_to_value_list = $self->{_offset_to_value_list}; 387 388 my $num_values = $self->{_num_values}; 389 return if $num_values == 0; 390 # Actually, this could probably just fall through 391 # as unpack("x4V0", ...) would return an empty array. 392 393 my @offsets_to_values = (); 394 395 # 0x00 dword = value list length (negative = allocated) 396 # 0x04 dword = 1st offset 397 # 0x08 dword = 2nd offset 398 # ... 399 400 # Extracted offsets are always relative to first hbin 401 402 sysseek($fh, $offset_to_value_list, 0); 403 my $value_list_length = 0x4 + $num_values * 4; 404 my $bytes_read = sysread($fh, my $value_list, $value_list_length); 405 if ($bytes_read != $value_list_length) { 406 warnf("Could not read value list at 0x%x", 407 $offset_to_value_list); 408 return; 409 } 410 411 foreach my $offset (unpack("x4V$num_values", $value_list)) { 412 push @offsets_to_values, OFFSET_TO_FIRST_HBIN + $offset; 413 } 414 415 return \@offsets_to_values; 416} 417 418sub get_value_iterator { 419 my $self = shift; 420 421 my $regfile = $self->{_regfile}; 422 my $key_path = $self->{_key_path}; 423 424 my @offsets_to_values = (); 425 if ($self->{_num_values} > 0) { 426 my $offsets_to_values_ref = $self->_get_offsets_to_values; 427 if (defined $offsets_to_values_ref) { 428 @offsets_to_values = @{$self->_get_offsets_to_values}; 429 } 430 } 431 432 return Parse::Win32Registry::Iterator->new(sub { 433 while (defined(my $offset_to_value = shift @offsets_to_values)) { 434 my $value = Parse::Win32Registry::WinNT::Value->new($regfile, 435 $offset_to_value); 436 if (defined $value) { 437 return $value; 438 } 439 } 440 return; # no more offsets to values 441 }); 442} 443 4441; 445