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   69 use strict;
  13         103  
  13         477  
4 13     13   92 use warnings;
  13         23  
  13         428  
5              
6 13     13   64 use base qw(Parse::Win32Registry::File);
  13         22  
  13         922  
7              
8 13     13   62 use Carp;
  13         24  
  13         734  
9 13     13   64 use Encode;
  13         31  
  13         1062  
10 13     13   74 use File::Basename;
  13         35  
  13         907  
11 13     13   82 use Parse::Win32Registry::Base qw(:all);
  13         61  
  13         3721  
12 13     13   8651 use Parse::Win32Registry::WinNT::Key;
  13         43  
  13         477  
13              
14 13     13   95 use constant REGF_HEADER_LENGTH => 0x200;
  13         33  
  13         690  
15 13     13   75 use constant OFFSET_TO_FIRST_HBIN => 0x1000;
  13         26  
  13         15603  
16              
17             sub new {
18 40     40 0 4174 my $class = shift;
19 40 100       398 my $filename = shift or croak "No filename specified";
20              
21 39 100       1310 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         238 my $bytes_read = sysread($fh, my $regf_header, REGF_HEADER_LENGTH);
39 38 100       126 if ($bytes_read != REGF_HEADER_LENGTH) {
40 1         4 warnf('Could not read registry file header');
41 1         24 return;
42             }
43              
44 37         283 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         80 $offset_to_root_key += OFFSET_TO_FIRST_HBIN;
57              
58 37 100       145 if ($regf_sig ne 'regf') {
59 1         5 warnf('Invalid registry file signature');
60 1         25 return;
61             }
62              
63 36         151 $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         43262 my $checksum = 0;
68 36         397 foreach my $x (unpack('V127', $regf_header)) {
69 4572         4772 $checksum ^= $x;
70             }
71 36         195 my $embedded_checksum = unpack('x508V', $regf_header);
72 36 100       124 if ($checksum != $embedded_checksum) {
73 1         5 warnf('Invalid checksum for registry file header');
74             }
75              
76 36         72 my $self = {};
77 36         105 $self->{_filehandle} = $fh;
78 36         82 $self->{_filename} = $filename;
79 36         411 $self->{_length} = (stat $fh)[7];
80 36         86 $self->{_offset_to_root_key} = $offset_to_root_key;
81 36         168 $self->{_timestamp} = unpack_windows_time($timestamp);
82 36         88 $self->{_embedded_filename} = $embedded_filename;
83 36         71 $self->{_seq1} = $seq1;
84 36         98 $self->{_seq2} = $seq2;
85 36         112 $self->{_version} = "$major_version.$minor_version";
86 36         72 $self->{_type} = $type;
87 36         73 $self->{_total_hbin_length} = $total_hbin_length;
88 36         65 $self->{_embedded_checksum} = $embedded_checksum;
89 36         82 $self->{_security_cache} = {}; # comment out to disable cache
90 36         202 bless $self, $class;
91              
92 36         240 return $self;
93             }
94              
95             sub get_root_key {
96 14     14 0 5115 my $self = shift;
97              
98 14         83 my $offset_to_root_key = $self->{_offset_to_root_key};
99              
100 14         117 my $root_key = Parse::Win32Registry::WinNT::Key->new($self,
101             $offset_to_root_key);
102 14         48 return $root_key;
103             }
104              
105             sub get_virtual_root_key {
106 6     6 0 3007 my $self = shift;
107 6         13 my $fake_root = shift;
108              
109 6         18 my $root_key = $self->get_root_key;
110 6 50       17 return if !defined $root_key;
111              
112 6 50       14 if (!defined $fake_root) {
113             # guess virtual root from filename
114 6         246 my $filename = basename $self->{_filename};
115              
116 6 100       92 if ($filename =~ /NTUSER/i) {
    100          
    100          
    100          
    100          
    50          
117 1         3 $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         2 $fake_root = 'HKEY_LOCAL_MACHINE\SOFTWARE';
124             }
125             elsif ($filename =~ /SYSTEM/i) {
126 1         4 $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         4 $fake_root = 'HKEY_LOCAL_MACHINE\SECURITY';
133             }
134             else {
135 0         0 $fake_root = 'HKEY_UNKNOWN';
136             }
137             }
138              
139 6         22 $root_key->{_name} = $fake_root;
140 6         10 $root_key->{_key_path} = $fake_root;
141              
142 6         18 return $root_key;
143             }
144              
145             sub get_timestamp {
146 1     1 0 2 my $self = shift;
147              
148 1         7 return $self->{_timestamp};
149             }
150              
151             sub get_timestamp_as_string {
152 1     1 0 2 my $self = shift;
153              
154 1         5 return iso8601($self->{_timestamp});
155             }
156              
157             sub get_embedded_filename {
158 2     2 0 1261 my $self = shift;
159              
160 2         15 return $self->{_embedded_filename};
161             }
162              
163             sub get_block_iterator {
164 2     2 0 5 my $self = shift;
165              
166 2         4 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   13 if ($offset_to_next_hbin > $end_of_file) {
171 0         0 return; # no more hbins
172             }
173 4 100       21 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         5 return $hbin;
179             }
180             else {
181 2         7 return; # no more hbins
182             }
183 2         23 });
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   91 use strict;
  13         39  
  13         540  
203 13     13   82 use warnings;
  13         31  
  13         712  
204              
205 13     13   72 use base qw(Parse::Win32Registry::Entry);
  13         31  
  13         1348  
206              
207 13     13   81 use Carp;
  13         30  
  13         1054  
208 13     13   79 use Parse::Win32Registry::Base qw(:all);
  13         25  
  13         4065  
209 13     13   10495 use Parse::Win32Registry::WinNT::Entry;
  13         36  
  13         415  
210              
211 13     13   87 use constant HBIN_HEADER_LENGTH => 0x20;
  13         24  
  13         6758  
212              
213             sub new {
214 4     4   121 my $class = shift;
215 4         9 my $regfile = shift;
216 4         6 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         19 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         22 sysseek($fh, $offset, 0);
231 4         33 my $bytes_read = sysread($fh, my $hbin_header, HBIN_HEADER_LENGTH);
232 4 100       12 if ($bytes_read != HBIN_HEADER_LENGTH) {
233 2         8 return;
234             }
235              
236 2         12 my ($sig,
237             $offset_to_hbin,
238             $length,
239             $timestamp) = unpack('a4VVx8a8x4', $hbin_header);
240              
241 2 50       9 if ($sig ne 'hbin') {
242 0         0 return;
243             }
244              
245 2         5 my $self = {};
246 2         6 $self->{_regfile} = $regfile;
247 2         4 $self->{_offset} = $offset;
248 2         5 $self->{_length} = $length;
249 2         5 $self->{_header_length} = HBIN_HEADER_LENGTH;
250 2         6 $self->{_allocated} = 1;
251 2         4 $self->{_tag} = $sig;
252 2         11 $self->{_timestamp} = unpack_windows_time($timestamp);
253 2         35 bless $self, $class;
254              
255 2         18 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   5 my $self = shift;
272              
273 2         5 my $regfile = $self->{_regfile};
274 2         4 my $offset = $self->{_offset};
275 2         6 my $length = $self->{_length};
276              
277 2         4 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   181 if ($offset_to_next_entry >= $end_of_hbin) {
282 2         8 return; # no more entries
283             }
284 70 50       294 if (my $entry = Parse::Win32Registry::WinNT::Entry->new($regfile,
285             $offset_to_next_entry))
286             {
287 70 50       582 return unless $entry->get_length > 0;
288 70         352 $offset_to_next_entry += $entry->get_length;
289 70         200 return $entry;
290             }
291             else {
292 0           return; # no more entries
293             }
294 2         17 });
295             }
296              
297             1;