File Coverage

blib/lib/Parse/Win32Registry/WinNT/Value.pm
Criterion Covered Total %
statement 138 187 73.8
branch 62 90 68.8
condition 7 14 50.0
subroutine 12 13 92.3
pod 0 4 0.0
total 219 308 71.1


line stmt bran cond sub pod time code
1             package Parse::Win32Registry::WinNT::Value;
2              
3 13     13   90 use strict;
  13         31  
  13         371  
4 13     13   67 use warnings;
  13         36  
  13         345  
5              
6 13     13   68 use base qw(Parse::Win32Registry::Value);
  13         23  
  13         1295  
7              
8 13     13   90 use Carp;
  13         45  
  13         814  
9 13     13   111 use Encode;
  13         29  
  13         1021  
10 13     13   145 use Parse::Win32Registry::Base qw(:all);
  13         28  
  13         2842  
11              
12 13     13   97 use constant VK_HEADER_LENGTH => 0x18;
  13         27  
  13         902  
13 13     13   92 use constant OFFSET_TO_FIRST_HBIN => 0x1000;
  13         34  
  13         20642  
14              
15             sub new {
16 6124     6124 0 9592 my $class = shift;
17 6124         8734 my $regfile = shift;
18 6124         8795 my $offset = shift; # offset to vk record relative to first hbin
19              
20 6124 50       11314 croak 'Missing registry file' if !defined $regfile;
21 6124 50       9933 croak 'Missing offset' if !defined $offset;
22              
23 6124         15843 my $fh = $regfile->get_filehandle;
24              
25             # 0x00 dword = value length (negative = allocated)
26             # 0x04 word = 'vk' signature
27             # 0x06 word = value name length
28             # 0x08 dword = value data length (bit 31 set => data stored inline)
29             # 0x0c dword = offset to data/inline data
30             # 0x10 dword = value type
31             # 0x14 word = flags (bit 1 set => compressed name)
32             # 0x16 word
33             # 0x18 = value name [for value name length bytes]
34              
35             # Extracted offsets are always relative to first hbin
36              
37 6124         35542 sysseek($fh, $offset, 0);
38 6124         45547 my $bytes_read = sysread($fh, my $vk_header, VK_HEADER_LENGTH);
39 6124 100       16370 if ($bytes_read != VK_HEADER_LENGTH) {
40 1         5 warnf('Could not read value at 0x%x', $offset);
41 1         16 return;
42             }
43              
44 6123         27958 my ($length,
45             $sig,
46             $name_length,
47             $data_length,
48             $offset_to_data,
49             $type,
50             $flags,
51             ) = unpack('Va2vVVVv', $vk_header);
52              
53 6123         11846 my $allocated = 0;
54 6123 50       11198 if ($length > 0x7fffffff) {
55 6123         9121 $allocated = 1;
56 6123         10046 $length = (0xffffffff - $length) + 1;
57             }
58             # allocated should be true
59              
60 6123 50       10978 if ($length < VK_HEADER_LENGTH) {
61 0         0 warnf('Invalid value entry length at 0x%x', $offset);
62 0         0 return;
63             }
64              
65 6123 100       12430 if ($sig ne 'vk') {
66 1         6 warnf('Invalid signature for value at 0x%x', $offset);
67 1         16 return;
68             }
69              
70 6122         37085 $bytes_read = sysread($fh, my $name, $name_length);
71 6122 100       16932 if ($bytes_read != $name_length) {
72 1         5 warnf('Could not read name for value at 0x%x', $offset);
73 1         26 return;
74             }
75              
76 6121 100       11745 if ($flags & 1) {
77 6015         18274 $name = decode($Parse::Win32Registry::Base::CODEPAGE, $name);
78             }
79             else {
80 106         342 $name = decode('UCS-2LE', $name);
81             };
82              
83             # If the top bit of the data_length is set, then
84             # the value is inline and stored in the offset to data field (at 0xc).
85 6121         181863 my $data;
86 6121         10932 my $data_inline = $data_length >> 31;
87 6121 100       10549 if ($data_inline) {
88             # REG_DWORDs are always inline, but I've also seen
89             # REG_SZ, REG_BINARY, REG_EXPAND_SZ, and REG_NONE inline
90 3194         4863 $data_length &= 0x7fffffff;
91 3194 100       5478 if ($data_length > 4) {
92 425         1230 warnf("Invalid inline data length for value '%s' at 0x%x",
93             $name, $offset);
94 425         653 $data = undef;
95             }
96             else {
97             # unpack inline data from header
98 2769         5751 $data = substr($vk_header, 0xc, $data_length);
99             }
100             }
101             else {
102 2927 50 33     10118 if ($offset_to_data != 0 && $offset_to_data != 0xffffffff) {
103 2927         4372 $offset_to_data += OFFSET_TO_FIRST_HBIN;
104 2927 100       8616 if ($offset_to_data < ($regfile->get_length - $data_length)) {
105 2926         5774 $data = _extract_data($fh, $offset_to_data, $data_length);
106             }
107             else {
108 1         4 warnf("Invalid offset to data for value '%s' at 0x%x",
109             $name, $offset);
110             }
111             }
112             }
113              
114 6121         11148 my $self = {};
115 6121         13365 $self->{_regfile} = $regfile;
116 6121         9997 $self->{_offset} = $offset;
117 6121         9834 $self->{_length} = $length;
118 6121         9471 $self->{_allocated} = $allocated;
119 6121         10674 $self->{_tag} = $sig;
120 6121         9791 $self->{_name} = $name;
121 6121         9075 $self->{_name_length} = $name_length;
122 6121         13770 $self->{_type} = $type;
123 6121         10157 $self->{_data} = $data;
124 6121         9132 $self->{_data_length} = $data_length;
125 6121         9427 $self->{_data_inline} = $data_inline;
126 6121         9410 $self->{_offset_to_data} = $offset_to_data;
127 6121         9686 $self->{_flags} = $flags;
128 6121         10039 bless $self, $class;
129              
130 6121         17047 return $self;
131             }
132              
133             sub _extract_data {
134 2926     2926   4083 my $fh = shift;
135 2926         4361 my $offset_to_data = shift;
136 2926         4023 my $data_length = shift;
137              
138 2926 50 33     8991 if ($offset_to_data == 0 || $offset_to_data == 0xffffffff) {
139 0         0 return undef;
140             }
141              
142 2926         16452 sysseek($fh, $offset_to_data, 0);
143 2926         21653 my $bytes_read = sysread($fh, my $data_header, 4);
144 2926 50       7567 if ($bytes_read != 4) {
145 0         0 warnf('Could not read data at 0x%x', $offset_to_data);
146 0         0 return undef;
147             }
148              
149 2926         8017 my ($max_data_length) = unpack('V', $data_header);
150              
151 2926         4755 my $data_allocated = 0;
152 2926 50       5642 if ($max_data_length > 0x7fffffff) {
153 2926         4700 $data_allocated = 1;
154 2926         4996 $max_data_length = (0xffffffff - $max_data_length) + 1;
155             }
156             # data_allocated should be true
157              
158 2926         4135 my $data;
159              
160 2926 50       5050 if ($data_length > $max_data_length) {
161 0         0 $bytes_read = sysread($fh, my $db_entry, 8);
162 0 0       0 if ($bytes_read != 8) {
163 0         0 warnf('Could not read data at 0x%x', $offset_to_data);
164 0         0 return undef;
165             }
166              
167 0         0 my ($sig, $num_data_blocks, $offset_to_data_block_list)
168             = unpack('a2vV', $db_entry);
169 0 0       0 if ($sig ne 'db') {
170 0         0 warnf('Invalid signature for big data at 0x%x', $offset_to_data);
171 0         0 return undef;
172             }
173 0         0 $offset_to_data_block_list += OFFSET_TO_FIRST_HBIN;
174              
175 0         0 sysseek($fh, $offset_to_data_block_list + 4, 0);
176 0         0 $bytes_read = sysread($fh, my $data_block_list, $num_data_blocks * 4);
177 0 0       0 if ($bytes_read != $num_data_blocks * 4) {
178 0         0 warnf('Could not read data block list at 0x%x',
179             $offset_to_data_block_list);
180 0         0 return undef;
181             }
182              
183 0         0 $data = "";
184 0         0 my @offsets = map { OFFSET_TO_FIRST_HBIN + $_ }
  0         0  
185             unpack("V$num_data_blocks", $data_block_list);
186 0         0 foreach my $offset (@offsets) {
187 0         0 sysseek($fh, $offset, 0);
188 0         0 $bytes_read = sysread($fh, my $block_header, 4);
189 0 0       0 if ($bytes_read != 4) {
190 0         0 warnf('Could not read data block at 0x%x', $offset);
191 0         0 return undef;
192             }
193 0         0 my ($block_length) = unpack('V', $block_header);
194 0 0       0 if ($block_length > 0x7fffffff) {
195 0         0 $block_length = (0xffffffff - $block_length) + 1;
196             }
197 0         0 $bytes_read = sysread($fh, my $block_data, $block_length - 8);
198 0 0       0 if ($bytes_read != $block_length - 8) {
199 0         0 warnf('Could not read data block at 0x%x', $offset);
200 0         0 return undef;
201             }
202 0         0 $data .= $block_data;
203             }
204 0 0       0 if (length($data) < $data_length) {
205 0         0 warnf("Insufficient data blocks for data at 0x%x", $offset_to_data);
206 0         0 return undef;
207             }
208 0         0 $data = substr($data, 0, $data_length);
209 0         0 return $data;
210             }
211             else {
212 2926         18202 $bytes_read = sysread($fh, $data, $data_length);
213 2926 50       8123 if ($bytes_read != $data_length) {
214 0         0 warnf("Could not read data at 0x%x", $offset_to_data);
215 0         0 return undef;
216             }
217             }
218 2926         8079 return $data;
219             }
220              
221             sub get_data {
222 292     292 0 1729 my $self = shift;
223              
224 292         813 my $type = $self->get_type;
225              
226 292         551 my $data = $self->{_data};
227 292 100       775 return if !defined $data;
228              
229             # apply decoding to appropriate data types
230 281 100 66     1441 if ($type == REG_DWORD) {
    100          
    100          
    100          
231 89 100       210 if (length($data) == 4) {
232 64         210 $data = unpack('V', $data);
233             }
234             else {
235             # incorrect length for dword data
236 25         44 $data = undef;
237             }
238             }
239             elsif ($type == REG_DWORD_BIG_ENDIAN) {
240 44 100       111 if (length($data) == 4) {
241 24         77 $data = unpack('N', $data);
242             }
243             else {
244             # incorrect length for dword data
245 20         38 $data = undef;
246             }
247             }
248             elsif ($type == REG_SZ || $type == REG_EXPAND_SZ) {
249 42         130 $data = decode('UCS-2LE', $data);
250             # snip off any terminating null
251 42 100       2022 chop $data if substr($data, -1, 1) eq "\0";
252             }
253             elsif ($type == REG_MULTI_SZ) {
254 82         246 $data = decode('UCS-2LE', $data);
255             # snip off any terminating nulls
256 82 100       3783 chop $data if substr($data, -1, 1) eq "\0";
257 82 100       229 chop $data if substr($data, -1, 1) eq "\0";
258 82         297 my @multi_sz = split("\0", $data, -1);
259             # make sure there is at least one empty string
260 82 100       218 @multi_sz = ('') if @multi_sz == 0;
261 82 100       930 return wantarray ? @multi_sz : join($", @multi_sz);
262             }
263              
264 199         1412 return $data;
265             }
266              
267             sub as_regedit_export {
268 53     53 0 135 my $self = shift;
269 53   50     228 my $version = shift || 5;
270              
271 53         171 my $name = $self->get_name;
272 53 100       244 my $export = $name eq '' ? '@=' : '"' . $name . '"=';
273              
274 53         145 my $type = $self->get_type;
275              
276             # XXX
277             # if (!defined $self->{_data}) {
278             # $name = $name eq '' ? '@' : qq{"$name"};
279             # return qq{; $name=(invalid data)\n};
280             # }
281              
282 53 100 66     372 if ($type == REG_SZ) {
    100          
    100          
    100          
283 8         19 my $data = $self->get_data;
284 8 50       26 $data = '' if !defined($data);
285 8         27 $export .= '"' . $data . '"';
286 8         21 $export .= "\n";
287             }
288             elsif ($type == REG_BINARY) {
289 4         12 $export .= "hex:";
290 4         22 $export .= format_octets($self->{_data}, length($export));
291             }
292             elsif ($type == REG_DWORD) {
293 16         45 my $data = $self->get_data;
294 16 100       82 $export .= defined($data)
295             ? sprintf("dword:%08x", $data)
296             : "dword:";
297 16         43 $export .= "\n";
298             }
299             elsif ($type == REG_EXPAND_SZ || $type == REG_MULTI_SZ) {
300             my $data = $version == 4
301             ? encode("ascii", $self->{_data}) # unicode->ascii
302 12 50       37 : $self->{_data}; # raw data
303 12         64 $export .= sprintf("hex(%x):", $type);
304 12         65 $export .= format_octets($data, length($export));
305             }
306             else {
307 13         76 $export .= sprintf("hex(%x):", $type);
308 13         82 $export .= format_octets($self->{_data}, length($export));
309             }
310 53         245 return $export;
311             }
312              
313             sub parse_info {
314 0     0 0   my $self = shift;
315              
316             my $info = sprintf '0x%x vk len=0x%x alloc=%d "%s" type=%d',
317             $self->{_offset},
318             $self->{_length},
319             $self->{_allocated},
320             $self->{_name},
321 0           $self->{_type};
322 0 0         if ($self->{_data_inline}) {
323             $info .= sprintf ' data=inline,len=0x%x',
324 0           $self->{_data_length};
325             }
326             else {
327             $info .= sprintf ' data=0x%x,len=0x%x',
328             $self->{_offset_to_data},
329 0           $self->{_data_length};
330             }
331 0           return $info;
332             }
333              
334             1;