File Coverage

blib/lib/Parse/Win32Registry/WinNT/File.pm
Criterion Covered Total %
statement 157 171 91.8
branch 36 48 75.0
condition n/a
subroutine 28 31 90.3
pod 0 7 0.0
total 221 257 85.9


line stmt bran cond sub pod time code
1             package Parse::Win32Registry::WinNT::File;
2              
3 13     13   92 use strict;
  13         29  
  13         394  
4 13     13   66 use warnings;
  13         30  
  13         394  
5              
6 13     13   68 use base qw(Parse::Win32Registry::File);
  13         44  
  13         1445  
7              
8 13     13   96 use Carp;
  13         24  
  13         751  
9 13     13   109 use Encode;
  13         34  
  13         970  
10 13     13   100 use File::Basename;
  13         36  
  13         866  
11 13     13   97 use Parse::Win32Registry::Base qw(:all);
  13         38  
  13         2815  
12 13     13   6103 use Parse::Win32Registry::WinNT::Key;
  13         43  
  13         471  
13              
14 13     13   108 use constant REGF_HEADER_LENGTH => 0x200;
  13         28  
  13         714  
15 13     13   77 use constant OFFSET_TO_FIRST_HBIN => 0x1000;
  13         28  
  13         12355  
16              
17             sub new {
18 40     40 0 6681 my $class = shift;
19 40 100       255 my $filename = shift or croak "No filename specified";
20              
21 39 100       1315 open my $fh, '<', $filename or croak "Unable to open '$filename': $!";
22              
23             # 0x00 dword = 'regf' signature
24             # 0x04 dword = seq1
25             # 0x08 dword = seq2
26             # 0x0c qword = timestamp
27             # 0x14 dword = major version
28             # 0x18 dword = minor version
29             # 0x1c dword = type (0 = registry file, 1 = log file)
30             # 0x20 dword = (1)
31             # 0x24 dword = offset to root key
32             # 0x28 dword = total length of all hbins (excludes header)
33             # 0x2c dword = (1)
34             # 0x30 = embedded filename
35              
36             # Extracted offsets are always relative to first hbin
37              
38 38         373 my $bytes_read = sysread($fh, my $regf_header, REGF_HEADER_LENGTH);
39 38 100       149 if ($bytes_read != REGF_HEADER_LENGTH) {
40 1         6 warnf('Could not read registry file header');
41 1         27 return;
42             }
43              
44 37         277 my ($regf_sig,
45             $seq1,
46             $seq2,
47             $timestamp,
48             $major_version,
49             $minor_version,
50             $type,
51             $offset_to_root_key,
52             $total_hbin_length,
53             $embedded_filename,
54             ) = unpack('a4VVa8VVVx4VVx4a64', $regf_header);
55              
56 37         101 $offset_to_root_key += OFFSET_TO_FIRST_HBIN;
57              
58 37 100       119 if ($regf_sig ne 'regf') {
59 1         6 warnf('Invalid registry file signature');
60 1         27 return;
61             }
62              
63 36         154 $embedded_filename = unpack('Z*', decode('UCS-2LE', $embedded_filename));
64              
65             # The header checksum is the xor of the first 127 dwords.
66             # The checksum is stored in the 128th dword, at offset 0x1fc (508).
67 36         28876 my $checksum = 0;
68 36         273 foreach my $x (unpack('V127', $regf_header)) {
69 4572         6154 $checksum ^= $x;
70             }
71 36         178 my $embedded_checksum = unpack('x508V', $regf_header);
72 36 100       116 if ($checksum != $embedded_checksum) {
73 1         7 warnf('Invalid checksum for registry file header');
74             }
75              
76 36         90 my $self = {};
77 36         97 $self->{_filehandle} = $fh;
78 36         88 $self->{_filename} = $filename;
79 36         483 $self->{_length} = (stat $fh)[7];
80 36         124 $self->{_offset_to_root_key} = $offset_to_root_key;
81 36         154 $self->{_timestamp} = unpack_windows_time($timestamp);
82 36         102 $self->{_embedded_filename} = $embedded_filename;
83 36         79 $self->{_seq1} = $seq1;
84 36         111 $self->{_seq2} = $seq2;
85 36         119 $self->{_version} = "$major_version.$minor_version";
86 36         77 $self->{_type} = $type;
87 36         65 $self->{_total_hbin_length} = $total_hbin_length;
88 36         66 $self->{_embedded_checksum} = $embedded_checksum;
89 36         76 $self->{_security_cache} = {}; # comment out to disable cache
90 36         142 bless $self, $class;
91              
92 36         231 return $self;
93             }
94              
95             sub get_root_key {
96 14     14 0 3980 my $self = shift;
97              
98 14         67 my $offset_to_root_key = $self->{_offset_to_root_key};
99              
100 14         107 my $root_key = Parse::Win32Registry::WinNT::Key->new($self,
101             $offset_to_root_key);
102 14         114 return $root_key;
103             }
104              
105             sub get_virtual_root_key {
106 6     6 0 1912 my $self = shift;
107 6         12 my $fake_root = shift;
108              
109 6         14 my $root_key = $self->get_root_key;
110 6 50       18 return if !defined $root_key;
111              
112 6 50       25 if (!defined $fake_root) {
113             # guess virtual root from filename
114 6         172 my $filename = basename $self->{_filename};
115              
116 6 100       61 if ($filename =~ /NTUSER/i) {
    100          
    100          
    100          
    100          
    50          
117 1         2 $fake_root = 'HKEY_CURRENT_USER';
118             }
119             elsif ($filename =~ /USRCLASS/i) {
120 1         3 $fake_root = 'HKEY_CLASSES_ROOT';
121             }
122             elsif ($filename =~ /SOFTWARE/i) {
123 1         7 $fake_root = 'HKEY_LOCAL_MACHINE\SOFTWARE';
124             }
125             elsif ($filename =~ /SYSTEM/i) {
126 1         3 $fake_root = 'HKEY_LOCAL_MACHINE\SYSTEM';
127             }
128             elsif ($filename =~ /SAM/i) {
129 1         3 $fake_root = 'HKEY_LOCAL_MACHINE\SAM';
130             }
131             elsif ($filename =~ /SECURITY/i) {
132 1         3 $fake_root = 'HKEY_LOCAL_MACHINE\SECURITY';
133             }
134             else {
135 0         0 $fake_root = 'HKEY_UNKNOWN';
136             }
137             }
138              
139 6         20 $root_key->{_name} = $fake_root;
140 6         12 $root_key->{_key_path} = $fake_root;
141              
142 6         16 return $root_key;
143             }
144              
145             sub get_timestamp {
146 1     1 0 4 my $self = shift;
147              
148 1         6 return $self->{_timestamp};
149             }
150              
151             sub get_timestamp_as_string {
152 1     1 0 3 my $self = shift;
153              
154 1         5 return iso8601($self->{_timestamp});
155             }
156              
157             sub get_embedded_filename {
158 2     2 0 1875 my $self = shift;
159              
160 2         16 return $self->{_embedded_filename};
161             }
162              
163             sub get_block_iterator {
164 2     2 0 4 my $self = shift;
165              
166 2         6 my $offset_to_next_hbin = OFFSET_TO_FIRST_HBIN;
167 2         6 my $end_of_file = $self->{_length};
168              
169             return Parse::Win32Registry::Iterator->new(sub {
170 4 50   4   19 if ($offset_to_next_hbin > $end_of_file) {
171 0         0 return; # no more hbins
172             }
173 4 100       22 if (my $hbin = Parse::Win32Registry::WinNT::Hbin->new($self,
174             $offset_to_next_hbin))
175             {
176 2 50       12 return unless $hbin->get_length > 0;
177 2         6 $offset_to_next_hbin += $hbin->get_length;
178 2         6 return $hbin;
179             }
180             else {
181 2         7 return; # no more hbins
182             }
183 2         21 });
184             }
185              
186             *get_hbin_iterator = \&get_block_iterator;
187              
188             sub _dump_security_cache {
189 0     0   0 my $self = shift;
190              
191 0 0       0 if (defined(my $cache = $self->{_security_cache})) {
192 0         0 foreach my $offset (sort { $a <=> $b } keys %$cache) {
  0         0  
193 0         0 my $security = $cache->{$offset};
194 0         0 printf '0x%x %s\n', $offset, $security->as_string;
195             }
196             }
197             }
198              
199              
200             package Parse::Win32Registry::WinNT::Hbin;
201              
202 13     13   111 use strict;
  13         37  
  13         370  
203 13     13   74 use warnings;
  13         30  
  13         513  
204              
205 13     13   85 use base qw(Parse::Win32Registry::Entry);
  13         31  
  13         1515  
206              
207 13     13   106 use Carp;
  13         28  
  13         868  
208 13     13   107 use Parse::Win32Registry::Base qw(:all);
  13         37  
  13         2897  
209 13     13   5806 use Parse::Win32Registry::WinNT::Entry;
  13         37  
  13         383  
210              
211 13     13   86 use constant HBIN_HEADER_LENGTH => 0x20;
  13         28  
  13         5877  
212              
213             sub new {
214 4     4   10 my $class = shift;
215 4         8 my $regfile = shift;
216 4         10 my $offset = shift;
217              
218 4 50       12 croak 'Missing registry file' if !defined $regfile;
219 4 50       11 croak 'Missing offset' if !defined $offset;
220              
221 4         12 my $fh = $regfile->get_filehandle;
222              
223             # 0x00 dword = 'hbin' signature
224             # 0x04 dword = offset from first hbin to this hbin
225             # 0x08 dword = length of this hbin / relative offset to next hbin
226             # 0x14 qword = timestamp (first hbin only)
227              
228             # Extracted offsets are always relative to first hbin
229              
230 4         34 sysseek($fh, $offset, 0);
231 4         49 my $bytes_read = sysread($fh, my $hbin_header, HBIN_HEADER_LENGTH);
232 4 100       17 if ($bytes_read != HBIN_HEADER_LENGTH) {
233 2         12 return;
234             }
235              
236 2         14 my ($sig,
237             $offset_to_hbin,
238             $length,
239             $timestamp) = unpack('a4VVx8a8x4', $hbin_header);
240              
241 2 50       7 if ($sig ne 'hbin') {
242 0         0 return;
243             }
244              
245 2         6 my $self = {};
246 2         7 $self->{_regfile} = $regfile;
247 2         17 $self->{_offset} = $offset;
248 2         8 $self->{_length} = $length;
249 2         7 $self->{_header_length} = HBIN_HEADER_LENGTH;
250 2         5 $self->{_allocated} = 1;
251 2         6 $self->{_tag} = $sig;
252 2         10 $self->{_timestamp} = unpack_windows_time($timestamp);
253 2         8 bless $self, $class;
254              
255 2         16 return $self;
256             }
257              
258             sub get_timestamp {
259 0     0   0 my $self = shift;
260              
261 0         0 return $self->{_timestamp};
262             }
263              
264             sub get_timestamp_as_string {
265 0     0   0 my $self = shift;
266              
267 0         0 return iso8601($self->{_timestamp});
268             }
269              
270             sub get_entry_iterator {
271 2     2   6 my $self = shift;
272              
273 2         16 my $regfile = $self->{_regfile};
274 2         11 my $offset = $self->{_offset};
275 2         5 my $length = $self->{_length};
276              
277 2         7 my $offset_to_next_entry = $offset + HBIN_HEADER_LENGTH;
278 2         4 my $end_of_hbin = $offset + $length;
279              
280             return Parse::Win32Registry::Iterator->new(sub {
281 72 100   72   184 if ($offset_to_next_entry >= $end_of_hbin) {
282 2         8 return; # no more entries
283             }
284 70 50       242 if (my $entry = Parse::Win32Registry::WinNT::Entry->new($regfile,
285             $offset_to_next_entry))
286             {
287 70 50       224 return unless $entry->get_length > 0;
288 70         163 $offset_to_next_entry += $entry->get_length;
289 70         183 return $entry;
290             }
291             else {
292 0           return; # no more entries
293             }
294 2         17 });
295             }
296              
297             1;