ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.70
Committed: Sun Sep 8 16:50:39 2019 UTC (4 years, 8 months ago) by root
Branch: MAIN
Changes since 1.69: +3 -2 lines
Log Message:
*** empty log message ***

File Contents

# User Rev Content
1 root 1.7 #!/usr/bin/perl
2 root 1.1
3     #
4     # PBCDEDIT - Copyright 2019 Marc A. Lehmann <pbcbedit@schmorp.de>
5     #
6     # SPDX-License-Identifier: GPL-3.0-or-later
7     #
8     # This program is free software: you can redistribute it and/or modify
9     # it under the terms of the GNU General Public License as published by
10     # the Free Software Foundation, either version 3 of the License, or
11     # (at your option) any later version.
12     #
13     # This program is distributed in the hope that it will be useful,
14     # but WITHOUT ANY WARRANTY; without even the implied warranty of
15     # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16     # GNU General Public License for more details.
17     #
18     # You should have received a copy of the GNU General Public License
19     # along with this program. If not, see <https://www.gnu.org/licenses/>.
20     #
21    
22 root 1.34 use 5.016; # numerous features need 5.14, __SUB__ needs 5.16
23 root 1.1
24 root 1.59 our $VERSION = '1.4';
25 root 1.40 our $JSON_VERSION = 3; # the version of the json objects generated by this program
26 root 1.37
27     our $CHANGELOG = <<EOF;
28 root 1.40
29 root 1.70 - work around lsblk bug sometimes giving "dos" pttype for gpt partitions.
30 root 1.61 - bootmenupolicy in synopsis must be set to 0 for text menu.
31 root 1.70 - minor doc fxes.
32 root 1.60
33 root 1.56 1.4 Thu Aug 22 10:48:22 CEST 2019
34     - new "create" subcommand.
35     - "create" and "edit" try to save and restore ownership/permissions
36     of bcd hives when writing the new file.
37 root 1.51 - editorial fixes to the documentation.
38 root 1.59 - add mininmal hive creation example.
39 root 1.51
40 root 1.45 1.3 Sat Aug 17 07:04:15 CEST 2019
41 root 1.40 - output of pbcdedit elements --json has changed, as it didn't
42     take the reorganisation by classes fully into account.
43     - json schema bumped to 3.
44 root 1.43 - new "bcd-device" and "bcd-legacy-device" subcommands.
45 root 1.44 - implement --json option for lsblk.
46 root 1.40
47 root 1.37 1.2 Fri Aug 16 00:20:41 CEST 2019
48 root 1.46 - bcd element names now depend on the bcd object type they are in,
49 root 1.37 also affects "elements" output.
50     - json schema bumped to 2.
51     - new version command.
52     - numerous minor bugfixes.
53    
54     EOF
55 root 1.1
56     =head1 NAME
57    
58     pbcdedit - portable boot configuration data (BCD) store editor
59    
60     =head1 SYNOPSIS
61    
62     pbcdedit help # output manual page
63 root 1.37 pbcdedit version # output version and changelog
64 root 1.28
65 root 1.1 pbcdedit export path/to/BCD # output BCD hive as JSON
66 root 1.28 pbcdedit import path/to/BCD # convert standard input to BCD hive
67 root 1.1 pbcdedit edit path/to/BCD edit-instructions...
68    
69     pbcdedit objects # list all supported object aliases and types
70     pbcdedit elements # list all supported bcd element aliases
71    
72 root 1.52 # Example: enable text-based boot menu.
73 root 1.61 pbcdedit edit /my/BCD set '{default}' bootmenupolicy 0
74 root 1.52
75     # Example change system device to first partition containing winload.
76     pbcdedit edit /my/BCD \
77     set '{default}' device 'locate=<null>,element,path' \
78     set '{default}' osdevice 'locate=<null>,element,path'
79    
80    
81 root 1.1 =head1 DESCRIPTION
82    
83     This program allows you to create, read and modify Boot Configuration Data
84     (BCD) stores used by Windows Vista and newer versions of Windows.
85    
86 root 1.9 At this point, it is in relatively early stages of development and has
87     received little to no real-world testing.
88    
89 root 1.1 Compared to other BCD editing programs it offers the following unique
90     features:
91    
92     =over
93    
94     =item Can create BCD hives from scratch
95    
96     Practically all other BCD editing programs force you to copy existing BCD
97     stores, which might or might not be copyrighted by Microsoft.
98    
99     =item Does not rely on Windows
100    
101     As the "portable" in the name implies, this program does not rely on
102     C<bcdedit> or other windows programs or libraries, it works on any system
103 root 1.34 that supports at least perl version 5.16.
104 root 1.1
105     =item Decodes and encodes BCD device elements
106    
107     PBCDEDIT can concisely decode and encode BCD device element contents. This
108     is pretty unique, and offers a lot of potential that can't be realised
109     with C<bcdedit> or any programs relying on it.
110    
111     =item Minimal files
112    
113     BCD files written by PBCDEDIT are always "minimal", that is, they don't
114     contain unused data areas and therefore don't contain old and potentially
115     sensitive data.
116    
117     =back
118    
119     The target audience for this program is professionals and tinkerers who
120 root 1.11 are ready to invest time into learning how it works. It is not an easy
121 root 1.26 program to use and requires patience and a good understanding of BCD
122 root 1.1 stores.
123    
124    
125     =head1 SUBCOMMANDS
126    
127 root 1.11 PBCDEDIT expects a subcommand as first argument that tells it what to
128 root 1.1 do. The following subcommands exist:
129    
130     =over
131    
132 root 1.20 =item C<help>
133 root 1.1
134 root 1.11 Displays the whole manual page (this document).
135 root 1.1
136 root 1.37 =item C<version>
137    
138     This outputs the PBCDEDIT version, the JSON schema version it uses and the
139     full log of changes.
140    
141 root 1.20 =item C<export> F<path>
142 root 1.1
143     Reads a BCD data store and writes a JSON representation of it to standard
144     output.
145    
146     The format of the data is explained later in this document.
147    
148 root 1.11 Example: read a BCD store, modify it with an external program, write it
149     again.
150 root 1.1
151     pbcdedit export BCD | modify-json-somehow | pbcdedit import BCD
152    
153 root 1.20 =item C<import> F<path>
154 root 1.1
155     The reverse of C<export>: Reads a JSON representation of a BCD data store
156     from standard input, and creates or replaces the given BCD data store.
157    
158 root 1.20 =item C<edit> F<path> I<instructions...>
159 root 1.1
160 root 1.6 Load a BCD data store, apply some instructions to it, and save it again.
161    
162 root 1.26 See the section L<EDITING BCD STORES>, below, for more info.
163 root 1.6
164 root 1.20 =item C<parse> F<path> I<instructions...>
165 root 1.6
166     Same as C<edit>, above, except it doesn't save the data store again. Can
167     be useful to extract some data from it.
168 root 1.1
169 root 1.56 =item C<create> F<path> I<instructions...>
170    
171     Same as C<edit>, above, except it creates a new data store from scratch if
172     needed. An existing store will be emptied completely.
173    
174 root 1.44 =item C<lsblk> [C<--json>]
175 root 1.1
176     On a GNU/Linux system, you can get a list of partition device descriptors
177     using this command - the external C<lsblk> command is required, as well as
178     a mounted C</sys> file system.
179    
180     The output will be a list of all partitions in the system and C<partition>
181     descriptors for GPT and both C<legacypartition> and C<partition>
182 root 1.11 descriptors for MBR partitions.
183 root 1.1
184 root 1.53 With C<--json> it will print similar information as C<lsblk --json>, but
185 root 1.44 with extra C<bcd_device> and C<bcd_legacy_device> attributes.
186    
187 root 1.43 =item C<bcd-device> F<path>
188    
189     Tries to find the BCD device element for the given device, which currently
190     must be a a partition of some kind. Prints the C<partition=> descriptor as
191     a result, or nothing. Exit status will be true on success, and false on
192     failure.
193    
194     Like C<lsblk>, above, this likely only works on GNU/Linux systems.
195    
196     Example: print the partition descriptor of tghe partition with label DATA.
197    
198     $ pbcdedit bcd-device /dev/disk/by-label/DATA
199     partition=<null>,harddisk,mbr,47cbc08a,213579202560
200    
201     =item C<bcd-legacy-device> F<path>
202    
203     Like above, but uses a C<legacypartition> descriptor instead.
204    
205 root 1.20 =item C<objects> [C<--json>]
206 root 1.1
207 root 1.11 Outputs two tables: a table listing all type aliases with their hex BCD
208 root 1.1 element ID, and all object name aliases with their GUID and default type
209     (if any).
210    
211     With C<--json> it prints similar information as a JSON object, for easier parsing.
212    
213 root 1.20 =item C<elements> [C<--json>]
214 root 1.1
215     Outputs a table of known element aliases with their hex ID and the format
216     type.
217    
218     With C<--json> it prints similar information as a JSON object, for easier parsing.
219    
220 root 1.20 =item C<export-regf> F<path>
221 root 1.1
222 root 1.31 This has nothing to do with BCD stores, but simply exposes PCBEDIT's
223 root 1.30 internal registry hive reader - it takes a registry hive file as argument
224     and outputs a JSON representation of it to standard output.
225 root 1.1
226     Hive versions 1.2 till 1.6 are supported.
227    
228 root 1.20 =item C<import-regf> F<path>
229 root 1.1
230     The reverse of C<export-regf>: reads a JSON representation of a registry
231 root 1.20 hive from standard input and creates or replaces the registry hive file
232     given as argument.
233 root 1.1
234     The written hive will always be in a slightly modified version 1.3
235     format. It's not the format windows would generate, but it should be
236     understood by any conformant hive reader.
237    
238     Note that the representation chosen by PBCDEDIT currently throws away
239 root 1.11 classname data (often used for feeble attempts at hiding stuff by
240 root 1.1 Microsoft) and security descriptors, so if you write anything other than
241     a BCD hive you will most likely destroy it.
242    
243     =back
244    
245    
246 root 1.27 =head1 BCD STORE REPRESENTATION FORMAT
247 root 1.1
248     A BCD data store is represented as a JSON object with one special key,
249     C<meta>, and one key per BCD object. That is, each BCD object becomes
250     one key-value pair in the object, and an additional key called C<meta>
251     contains meta information.
252    
253     Here is an abridged example of a real BCD store:
254    
255     {
256     "meta" : {
257     "version" : 1
258     },
259     "{7ae02178-821d-11e7-8813-1c872c5f5ab0}" : {
260     "type" : "application::osloader",
261     "description" : "Windows 10",
262     "device" : "partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,3ce6aceb-e90c-4fd2-9fba-47cab15f6faf",
263     "osdevice" : "partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,3ce6aceb-e90c-4fd2-9fba-47cab15f6faf",
264     "path" : "\\Windows\\system32\\winload.exe",
265     "systemroot" : "\\Windows"
266     },
267     "{bootloadersettings}" : {
268     "inherit" : "{globalsettings} {hypervisorsettings}"
269     },
270     "{bootmgr}" : {
271     "description" : "Windows Boot Manager",
272     "device" : "partition=<null>,harddisk,mbr,ff3ba63b,1048576",
273     "displayorder" : "{7ae02178-821d-11e7-8813-1c872c5f5ab0}",
274     "inherit" : "{globalsettings}",
275     "displaybootmenu" : 0,
276     "timeout" : 30
277     },
278     "{globalsettings}" : {
279     "inherit" : "{dbgsettings} {emssettings} {badmemory}"
280     },
281     "{hypervisorsettings}" : {
282     "hypervisorbaudrate" : 115200,
283     "hypervisordebugport" : 1,
284     "hypervisordebugtype" : 0
285     },
286     # ...
287     }
288    
289 root 1.3 =head2 Minimal BCD to boot windows
290    
291     Experimentally I found the following BCD is the minimum required to
292 root 1.38 successfully boot any post-XP version of Windows (assuming suitable
293 root 1.39 C<device> and C<osdevice> values, of course, and assuming a BIOS boot -
294     for UEFI, you should use F<winload.efi> instead of F<winload.exe>):
295 root 1.3
296     {
297     "{bootmgr}" : {
298 root 1.36 "default" : "{45b547a7-8ca6-4417-9eb0-a257b61f35b4}"
299 root 1.3 },
300    
301     "{45b547a7-8ca6-4417-9eb0-a257b61f35b1}" : {
302     "type" : "application::osloader",
303     "description" : "Windows Boot",
304     "device" : "legacypartition=<null>,harddisk,mbr,47cbc08a,1",
305     "osdevice" : "legacypartition=<null>,harddisk,mbr,47cbc08a,1",
306     "path" : "\\Windows\\system32\\winload.exe",
307     "systemroot" : "\\Windows"
308     },
309     }
310    
311     Note that minimal doesn't mean recommended - Windows itself will add stuff
312     to this during or after boot, and you might or might not run into issues
313     when installing updates as it might not be able to find the F<bootmgr>.
314    
315 root 1.58 This is how you would create a minimal hive with PBCDEDIT from within
316     GNU/Linux, assuming F</dev/sdc3> is the windows partition, using
317     a random GUID for the osloader and using C<partition> instead of
318     C<legacypartition>:
319 root 1.57
320     osldr="{$(uuidgen)}"
321     part=$(pbcdedit bcd-device /dev/sdc3)
322     pbcdedit create minimal.bcd \
323     set '{bootmgr}' default "$osldr" \
324     set "$osldr" type application::osloader \
325     set "$osldr" description 'Windows Boot' \
326     set "$osldr" device "$part" \
327     set "$osldr" osdevice "$part" \
328 root 1.58 set "$osldr" path '\Windows\system32\winload.exe' \
329 root 1.57 set "$osldr" systemroot '\Windows'
330    
331 root 1.1 =head2 The C<meta> key
332    
333     The C<meta> key is not stored in the BCD data store but is used only
334     by PBCDEDIT. It is always generated when exporting, and importing will
335     be refused when it exists and the version stored inside doesn't store
336 root 1.11 the JSON schema version of PBCDEDIT. This ensures that different and
337     incompatible versions of PBCDEDIT will not read and misinterpret each
338 root 1.1 others data.
339    
340     =head2 The object keys
341    
342     Every other key is a BCD object. There is usually a BCD object for the
343     boot manager, one for every boot option and a few others that store common
344     settings inherited by these.
345    
346     Each BCD object is represented by a GUID wrapped in curly braces. These
347 root 1.11 are usually random GUIDs used only to distinguish BCD objects from each
348 root 1.1 other. When adding a new boot option, you can simply generate a new GUID.
349    
350     Some of these GUIDs are fixed well known GUIDs which PBCDEDIT will decode
351     into human-readable strings such as C<{globalsettings}>, which is the same
352     as C<{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}>.
353    
354     Each BCD, object has an associated type. For example,
355     C<application::osloader> for objects loading Windows via F<winload.exe>,
356     C<application::bootsector> for real mode applications and so on.
357    
358     The type of a object is stored in the pseudo BCD element C<type> (see next
359     section).
360    
361     Some well-known objects have a default type. If an object type matches
362     its default type, then the C<type> element will be omitted. Similarly, if
363     the C<type> element is missing and the BCD object has a default type, the
364     default type will be used when writing a BCD store.
365    
366     Running F<pbcdedit objects> will give you a list of object types,
367     well-known object aliases and their default types.
368    
369     If different string keys in a JSON BCD store map to the same BCD object
370     then a random one will "win" and the others will be discarded. To avoid
371     this, you should always use the "canonical" name of a BCD object, which is
372     the human-readable form (if it exists).
373    
374     =head2 The object values - BCD elements
375    
376     The value of each BCD object entry consists of key-value pairs called BCD
377     elements.
378    
379     BCD elements are identified by a 32 bit number, but to make things
380     simpler PBCDEDIT will replace these with well-known strings such as
381     C<description>, C<device> or C<path>.
382    
383     When PBCDEDIT does not know the BCD element, it will use
384     C<custom:HHHHHHHH>, where C<HHHHHHHH> is the 8-digit hex number of the
385     BCD element. For example, C<device> would be C<custom::11000001>. You can
386     get a list of all BCD elements known to PBCDEDIT by running F<pbcdedit
387     elements>.
388    
389     What was said about duplicate keys mapping to the same object is true for
390     elements as well, so, again, you should always use the canonical name,
391 root 1.11 which is the human readable alias, if known.
392 root 1.1
393     =head3 BCD element types
394    
395     Each BCD element has a type such as I<string> or I<boolean>. This type
396     determines how the value is interpreted, and most of them are pretty easy
397     to explain:
398    
399     =over
400    
401     =item string
402    
403     This is simply a unicode string. For example, the C<description> and
404     C<systemroot> elements both are of this type, one storing a human-readable
405     name for this boot option, the other a file path to the windows root
406     directory:
407    
408     "description" : "Windows 10",
409     "systemroot" : "\\Windows",
410    
411     =item boolean
412    
413 root 1.11 Almost as simple are booleans, which represent I<true>/I<false>,
414 root 1.1 I<on>/I<off> and similar values. In the JSON form, true is represented
415     by the number C<1>, and false is represented by the number C<0>. Other
416     values will be accepted, but PBCDEDIT doesn't guarantee how these are
417     interpreted.
418    
419     For example, C<displaybootmenu> is a boolean that decides whether to
420     enable the C<F8> boot menu. In the example BCD store above, this is
421     disabled:
422    
423     "displaybootmenu" : 0,
424    
425     =item integer
426    
427 root 1.41 Again, very simple, this is a 64 bit integer. It can be either specified
428 root 1.1 as a decimal number, as a hex number (by prefixing it with C<0x>) or as a
429 root 1.11 binary number (prefix C<0b>).
430 root 1.1
431     For example, the boot C<timeout> is an integer, specifying the automatic
432     boot delay in seconds:
433    
434     "timeout" : 30,
435    
436     =item integer list
437    
438     This is a list of 64 bit integers separated by whitespace. It is not used
439 root 1.54 much, so here is a somewhat artificial and untested example of using
440 root 1.1 C<customactions> to specify a certain custom, eh, action to be executed
441     when pressing C<F10> at boot:
442    
443     "customactions" : "0x1000044000001 0x54000001",
444    
445     =item guid
446    
447 root 1.11 This represents a single GUID value wrapped in curly braces. It is used a
448 root 1.1 lot to refer from one BCD object to other one.
449    
450     For example, The C<{bootmgr}> object might refer to a resume boot option
451 root 1.36 using C<default>:
452 root 1.1
453 root 1.36 "default" : "{7ae02178-821d-11e7-8813-1c872c5f5ab0}",
454 root 1.1
455     Human readable aliases are used and allowed.
456    
457     =item guid list
458    
459 root 1.11 Similar to the GUID type, this represents a list of such GUIDs, separated
460 root 1.1 by whitespace from each other.
461    
462     For example, many BCD objects can I<inherit> elements from other BCD
463 root 1.11 objects by specifying the GUIDs of those other objects in a GUID list
464 root 1.1 called surprisingly called C<inherit>:
465    
466     "inherit" : "{dbgsettings} {emssettings} {badmemory}",
467    
468     This example also shows how human readable aliases can be used.
469    
470     =item device
471    
472     This type is why I write I<most> are easy to explain earlier: This type
473     is the pinnacle of Microsoft-typical hacks layered on top of other
474     hacks. Understanding this type took more time than writing all the rest of
475     PBCDEDIT, and because it is so complex, this type has its own subsection
476     below.
477 root 1.54
478 root 1.1 =back
479    
480 root 1.50 =head3 The BCD "device" element type
481 root 1.1
482     Device elements specify, well, devices. They are used for such diverse
483 root 1.11 purposes such as finding a TFTP network boot image, serial ports or VMBUS
484 root 1.1 devices, but most commonly they are used to specify the disk (harddisk,
485 root 1.11 cdrom, ramdisk, vhd...) to boot from.
486 root 1.1
487     The device element is kind of a mini-language in its own which is much
488     more versatile then the limited windows interface to it - BCDEDIT -
489     reveals.
490    
491     While some information can be found on the BCD store and the windows
492     registry, there is pretty much no public information about the device
493     element, so almost everything known about it had to be researched first
494     in the process of writing this script, and consequently, support for BCD
495     device elements is partial only.
496    
497     On the other hand, the expressive power of PBCDEDIT in specifying devices
498 root 1.55 is much greater than BCDEDIT and therefore more can be done with it. The
499 root 1.1 downside is that BCD device elements are much more complicated than what
500     you might think from reading the BCDEDIT documentation.
501    
502     In other words, simple things are complicated, and complicated things are
503     possible.
504    
505     Anyway, the general syntax of device elements is an optional GUID,
506 root 1.11 followed by a device type, optionally followed by hexadecimal flags in
507 root 1.1 angle brackets, optionally followed by C<=> and a comma-separated list of
508     arguments, some of which can be (and often are) in turn devices again.
509    
510     [{GUID}]type[<flags>][=arg,arg...]
511    
512     Here are some examples:
513    
514     boot
515 root 1.70 {b097d29f-bc00-11e9-8a9a-525400123456}block=file,<boot>,\EFI
516 root 1.1 locate=<null>,element,systemroot
517     partition=<null>,harddisk,mbr,47cbc08a,1048576
518     partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,76d39e5f-ad1b-407e-9c05-c81eb83b57dd
519     block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,68720525312>,0,0,0,\Recovery\b097d29e-bc00-11e9-8a9a-525400123456\Winre.wim
520     block=file,<partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,ee3a393a-f0de-4057-9946-88584245ed48>,\
521     binary=050000000000000048000000000000000000000000000000000000000000000000000000000000000
522    
523     I hope you are suitably impressed. I was, too, when I realized decoding
524     these binary blobs is not as easy as I had assumed.
525    
526     The optional prefixed GUID seems to refer to a device BCD object, which
527     can be used to specify more device-specific BCD elements (for example
528     C<ramdisksdidevice> and C<ramdisksdpath>).
529    
530     The flags after the type are omitted when they are C<0>. The only known
531     flag is C<1>, which seems to indicate that the parent device is invalid. I
532     don't claim to fully understand it, but it seems to indicate that the
533     boot manager has to search the device itself. Why the device is specified
534     in the first place escapes me, but a lot of this device stuff seems to be
535     badly hacked together...
536    
537     The types understood and used by PBCDEDIT are as follows (keep in mind
538     that not of all the following is necessarily supported in PBCDEDIT):
539    
540     =over
541    
542 root 1.14 =item C<binary=>I<hex...>
543 root 1.1
544     This type isn't actually a real BCD element type, but a fallback for those
545     cases where PBCDEDIT can't perfectly decode a device element (except for
546     the leading GUID, which it can always decode). In such cases, it will
547     convert the device into this type with a hexdump of the element data.
548    
549 root 1.14 =item C<null>
550 root 1.1
551 root 1.42 This is another special type - sometimes, a device is all zero-filled,
552     which is not valid. This can mark the absence of a device or something
553     PBCDEDIT does not understand, so it decodes it into this special "all
554     zero" type called C<null>.
555 root 1.1
556     It's most commonly found in devices that can use an optional parent
557     device, when no parent device is used.
558    
559 root 1.14 =item C<boot>
560 root 1.1
561     Another type without parameters, this refers to the device that was booted
562     from (nowadays typically the EFI system partition).
563    
564 root 1.14 =item C<vmbus=>I<interfacetype>,I<interfaceinstance>
565 root 1.1
566     This specifies a VMBUS device with the given interface type and interface
567     instance, both of which are "naked" (no curly braces) GUIDs.
568    
569     Made-up example (couldn't find a single example on the web):
570    
571     vmbus=c376c1c3-d276-48d2-90a9-c04748072c60,12345678-a234-b234-c234-d2345678abcd
572    
573 root 1.14 =item C<partition=><I<parent>>,I<devicetype>,I<partitiontype>,I<diskid>,I<partitionid>
574 root 1.1
575 root 1.18 This designates a specific partition on a block device. I<parent> is an
576     optional parent device on which to search on, and is often C<null>. Note
577     that the angle brackets around I<parent> are part of the syntax.
578 root 1.1
579 root 1.17 I<devicetypes> is one of C<harddisk>, C<floppy>, C<cdrom>, C<ramdisk>,
580 root 1.1 C<file> or C<vhd>, where the first three should be self-explaining,
581 root 1.21 C<file> is usually used to locate a file to be used as a disk image,
582     and C<vhd> is used to treat files as virtual harddisks, i.e. F<vhd> and
583     F<vhdx> files.
584 root 1.1
585 root 1.17 The I<partitiontype> is either C<mbr>, C<gpt> or C<raw>, the latter being
586 root 1.1 used for devices without partitions, such as cdroms, where the "partition"
587     is usually the whole device.
588    
589 root 1.17 The I<diskid> identifies the disk or device using a unique signature, and
590     the same is true for the I<partitionid>. How these are interpreted depends
591     on the I<partitiontype>:
592 root 1.1
593     =over
594    
595 root 1.13 =item C<mbr>
596 root 1.1
597     The C<diskid> is the 32 bit disk signature stored at offset 0x1b8 in the
598     MBR, interpreted as a 32 bit unsigned little endian integer and written as
599     hex number. That is, the bytes C<01 02 03 04> would become C<04030201>.
600    
601 root 1.11 Diskpart (using the C<DETAIL> command) and the C<lsblk> command typically
602 root 1.1 found on GNU/Linux systems (using e.g. C<lsblk -o NAME,PARTUUID>) can
603 root 1.18 display the I<diskid>.
604 root 1.1
605 root 1.18 The I<partitionid> is the byte offset(!) of the partition counting from
606 root 1.1 the beginning of the MBR.
607    
608 root 1.18 Example, use the partition on the harddisk with I<diskid> C<47cbc08a>
609 root 1.1 starting at sector C<2048> (= 1048576 / 512).
610    
611     partition=<null>,harddisk,mbr,47cbc08a,1048576
612    
613 root 1.13 =item C<gpt>
614 root 1.1
615 root 1.18 The I<diskid> is the disk GUID/disk identifier GUID from the partition
616     table (as displayed e.g. by F<gdisk>), and the I<partitionid> is the
617     partition unique GUID (displayed using e.g. the F<gdisk> F<i> command).
618 root 1.1
619     Example: use the partition C<76d39e5f-ad1b-407e-9c05-c81eb83b57dd> on GPT
620     disk C<9742e468-9206-48a0-b4e4-c4e9745a356a>.
621    
622     partition=<null>,harddisk,gpt,9742e468-9206-48a0-b4e4-c4e9745a356a,76d39e5f-ad1b-407e-9c05-c81eb83b57dd
623    
624 root 1.14 =item C<raw>
625 root 1.1
626 root 1.18 Instead of I<diskid> and I<partitionid>, this type only accepts a decimal
627 root 1.11 disk number and signifies the whole disk. BCDEDIT cannot display the
628     resulting device, and I am doubtful whether it has a useful effect.
629 root 1.1
630     =back
631    
632 root 1.14 =item C<legacypartition=><I<parent>>,I<devicetype>,I<partitiontype>,I<diskid>,I<partitionid>
633 root 1.1
634     This is exactly the same as the C<partition> type, except for a tiny
635     detail: instead of using the partition start offset, this type uses the
636     partition number for MBR disks. Behaviour other partition types should be
637     the same.
638    
639     The partition number starts at C<1> and skips unused partition, so if
640     there are two primary partitions and another partition inside the extended
641     partition, the primary partitions are number C<1> and C<2> and the
642 root 1.11 partition inside the extended partition is number C<3>, regardless of any
643 root 1.1 gaps.
644    
645 root 1.14 =item C<locate=><I<parent>>,I<locatetype>,I<locatearg>
646 root 1.1
647     This device description will make the bootloader search for a partition
648     with a given path.
649    
650 root 1.18 The I<parent> device is the device to search on (angle brackets are
651     still part of the syntax!) If it is C<null>, then C<locate> will
652 root 1.1 search all disks it can find.
653    
654 root 1.18 I<locatetype> is either C<element> or C<path>, and merely distinguishes
655 root 1.1 between two different ways to specify the path to search for: C<element>
656 root 1.18 uses an element ID (either as hex or as name) as I<locatearg> and C<path>
657     uses a relative path as I<locatearg>.
658 root 1.1
659 root 1.18 Example: find any partition which has the F<magicfile.xxx> path in the
660 root 1.1 root.
661    
662     locate=<null>,path,\magicfile.xxx
663    
664     Example: find any partition which has the path specified in the
665 root 1.18 C<systemroot> element (typically F<\Windows>).
666 root 1.1
667     locate=<null>,element,systemroot
668    
669 root 1.14 =item C<block=>I<devicetype>,I<args...>
670 root 1.1
671     Last not least, the most complex type, C<block>, which... specifies block
672     devices (which could be inside a F<vhdx> file for example).
673    
674 root 1.18 I<devicetypes> is one of C<harddisk>, C<floppy>, C<cdrom>, C<ramdisk>,
675 root 1.47 C<file> or C<vhd> - the same as for C<partition=>.
676 root 1.1
677 root 1.18 The remaining arguments change depending on the I<devicetype>:
678 root 1.1
679     =over
680    
681 root 1.14 =item C<block=file>,<I<parent>>,I<path>
682 root 1.1
683 root 1.18 Interprets the I<parent> device (typically a partition) as a
684 root 1.1 filesystem and specifies a file path inside.
685    
686 root 1.14 =item C<block=vhd>,<I<parent>>
687 root 1.1
688 root 1.18 Pretty much just changes the interpretation of I<parent>, which is
689 root 1.1 usually a disk image (C<block=file,...)>) to be a F<vhd> or F<vhdx> file.
690    
691 root 1.14 =item C<block=ramdisk>,<I<parent>>,I<base>,I<size>,I<offset>,I<path>
692 root 1.1
693 root 1.18 Interprets the I<parent> device as RAM disk, using the (decimal)
694 root 1.1 base address, byte size and byte offset inside a file specified by
695 root 1.18 I<path>. The numbers are usually all C<0> because they can be extracted
696 root 1.1 from the RAM disk image or other parameters.
697    
698     This is most commonly used to boot C<wim> images.
699    
700 root 1.14 =item C<block=floppy>,I<drivenum>
701 root 1.1
702     Refers to a removable drive identified by a number. BCDEDIT cannot display
703 root 1.14 the resulting device, and it is not clear what effect it will have.
704 root 1.1
705 root 1.14 =item C<block=cdrom>,I<drivenum>
706 root 1.1
707     Pretty much the same as C<floppy> but for CD-ROMs.
708    
709     =item anything else
710    
711     Probably not yet implemented. Tell me of your needs...
712    
713     =back
714    
715 root 1.49 =head4 Examples
716 root 1.1
717     This concludes the syntax overview for device elements, but probably
718 root 1.50 leaves many questions open. I can't help with most of them, as I also have
719 root 1.14 many questions, but I can walk you through some actual examples using more
720 root 1.1 complex aspects.
721    
722 root 1.15 =item C<< locate=<block=vhd,<block=file,<locate=<null>,path,\disk.vhdx>,\disk.vhdx>>,element,path >>
723 root 1.1
724 root 1.4 Just like with C declarations, you best treat device descriptors as
725     instructions to find your device and work your way from the inside out:
726    
727     locate=<null>,path,\disk.vhdx
728    
729     First, the innermost device descriptor searches all partitions on the
730     system for a file called F<\disk.vhdx>:
731    
732 root 1.16 block=file,<see above>,\disk.vhdx
733 root 1.4
734     Next, this takes the device locate has found and finds a file called
735     F<\disk.vhdx> on it. This is the same file locate was using, but that is
736     only because we find the device using the same path as finding the disk
737     image, so this is purely incidental, although quite common.
738    
739 root 1.15 Next, this file will be opened as a virtual disk:
740 root 1.4
741 root 1.16 block=vhd,<see above>
742 root 1.4
743     And finally, inside this disk, another C<locate> will look for a partition
744     with a path as specified in the C<path> element, which most likely will be
745     F<\Windows\system32\winload.exe>:
746    
747 root 1.16 locate=<see above>,element,path
748 root 1.4
749     As a result, this will boot the first Windows it finds on the first
750     F<disk.vhdx> disk image it can find anywhere.
751 root 1.1
752 root 1.15 =item C<< locate=<block=vhd,<block=file,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,\win10.vhdx>>,element,path >>
753 root 1.1
754 root 1.15 Pretty much the same as the previous case, but with a bit of
755     variance. First, look for a specific partition on an MBR-partitioned disk:
756 root 1.4
757     partition=<null>,harddisk,mbr,47cbc08a,242643632128
758    
759     Then open the file F<\win10.vhdx> on that partition:
760    
761 root 1.16 block=file,<see above>,\win10.vhdx
762 root 1.4
763     Then, again, the file is opened as a virtual disk image:
764    
765 root 1.16 block=vhd,<see above>
766 root 1.4
767     And again the windows loader (or whatever is in C<path>) will be searched:
768    
769 root 1.16 locate=<see above>,element,path
770 root 1.1
771 root 1.15 =item C<< {b097d2b2-bc00-11e9-8a9a-525400123456}block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,0,0,0,\boot.wim >>
772 root 1.1
773 root 1.4 This is quite different. First, it starts with a GUID. This GUID belongs
774     to a BCD object of type C<device>, which has additional parameters:
775    
776     "{b097d2b2-bc00-11e9-8a9a-525400123456}" : {
777     "type" : "device",
778     "description" : "sdi file for ramdisk",
779     "ramdisksdidevice" : "partition=<null>,harddisk,mbr,47cbc08a,1048576",
780     "ramdisksdipath" : "\boot.sdi"
781     },
782    
783     I will not go into many details, but this specifies a (presumably empty)
784 root 1.15 template ramdisk image (F<\boot.sdi>) that is used to initialize the
785     ramdisk. The F<\boot.wim> file is then extracted into it. As you can also
786 root 1.4 see, this F<.sdi> file resides on a different C<partition>.
787    
788 root 1.15 Continuing, as always, from the inside out, first this device descriptor
789 root 1.4 finds a specific partition:
790    
791     partition=<null>,harddisk,mbr,47cbc08a,242643632128
792    
793     And then specifies a C<ramdisk> image on this partition:
794    
795 root 1.16 block<1>=ramdisk,<see above>,0,0,0,\boot.wim
796 root 1.4
797 root 1.5 I don't know what the purpose of the C<< <1> >> flag value is, but it
798 root 1.4 seems to be always there on this kind of entry.
799 root 1.1
800 root 1.5 If you have some good examples to add here, feel free to mail me.
801    
802 root 1.1
803 root 1.26 =head1 EDITING BCD STORES
804 root 1.6
805     The C<edit> and C<parse> subcommands allow you to read a BCD data store
806 root 1.15 and modify it or extract data from it. This is done by executing a series
807 root 1.6 of "editing instructions" which are explained here.
808    
809     =over
810    
811 root 1.22 =item C<get> I<object> I<element>
812 root 1.6
813     Reads the BCD element I<element> from the BCD object I<object> and writes
814     it to standard output, followed by a newline. The I<object> can be a GUID
815     or a human-readable alias, or the special string C<{default}>, which will
816     refer to the default BCD object.
817    
818     Example: find description of the default BCD object.
819    
820     pbcdedit parse BCD get "{default}" description
821    
822 root 1.22 =item C<set> I<object> I<element> I<value>
823 root 1.6
824     Similar to C<get>, but sets the element to the given I<value> instead.
825    
826 root 1.15 Example: change the bootmgr default too
827 root 1.6 C<{b097d2ad-bc00-11e9-8a9a-525400123456}>:
828    
829 root 1.36 pbcdedit edit BCD set "{bootmgr}" default "{b097d2ad-bc00-11e9-8a9a-525400123456}"
830 root 1.6
831 root 1.22 =item C<eval> I<perlcode>
832 root 1.6
833     This takes the next argument, interprets it as Perl code and
834     evaluates it. This allows you to do more complicated modifications or
835     extractions.
836    
837     The following variables are predefined for your use:
838    
839     =over
840    
841     =item C<$PATH>
842    
843     The path to the BCD data store, as given to C<edit> or C<parse>.
844    
845     =item C<$BCD>
846    
847     The decoded BCD data store.
848    
849     =item C<$DEFAULT>
850    
851     The default BCD object name.
852    
853     =back
854    
855     The example given for C<get>, above, could be expressed like this with
856     C<eval>:
857    
858     pbcdedit edit BCD eval 'say $BCD->{$DEFAULT}{description}'
859    
860 root 1.15 The example given for C<set> could be expressed like this:
861 root 1.6
862 root 1.36 pbcdedit edit BCD eval '$BCD->{"{bootmgr}"{default} = "{b097d2ad-bc00-11e9-8a9a-525400123456}"'
863 root 1.6
864 root 1.22 =item C<do> I<path>
865 root 1.6
866     Similar to C<eval>, above, but instead of using the argument as perl code,
867     it loads the perl code from the given file and executes it. This makes it
868     easier to write more complicated or larger programs.
869    
870     =back
871    
872 root 1.22
873 root 1.1 =head1 SEE ALSO
874    
875 root 1.25 For ideas on what you can do with BCD stores in
876     general, and some introductory material, try
877 root 1.1 L<http://www.mistyprojects.co.uk/documents/BCDEdit/index.html>.
878    
879 root 1.23 For good reference on which BCD objects and
880 root 1.24 elements exist, see Geoff Chappell's pages at
881 root 1.23 L<http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm>.
882 root 1.1
883     =head1 AUTHOR
884    
885 root 1.10 Written by Marc A. Lehmann L<pbcdedit@schmorp.de>.
886 root 1.1
887     =head1 REPORTING BUGS
888    
889 root 1.11 Bugs can be reported directly the author at L<pcbedit@schmorp.de>.
890 root 1.1
891     =head1 BUGS AND SHORTCOMINGS
892    
893     This should be a module. Of a series of modules, even.
894    
895     Registry code should preserve classname and security descriptor data, and
896     whatever else is necessary to read and write any registry hive file.
897    
898     I am also not happy with device descriptors being strings rather than a
899     data structure, but strings are probably better for command line usage. In
900 root 1.15 any case, device descriptors could be converted by simply "splitting" at
901 root 1.1 "=" and "," into an array reference, recursively.
902    
903     =head1 HOMEPAGE
904    
905     Original versions of this program can be found at
906     L<http://software.schmorp.de/pkg/pbcdedit>.
907    
908     =head1 COPYRIGHT
909    
910     Copyright 2019 Marc A. Lehmann, licensed under GNU GPL version 3 or later,
911     see L<https://gnu.org/licenses/gpl.html>. This is free software: you are
912     free to change and redistribute it. There is NO WARRANTY, to the extent
913     permitted by law.
914    
915     =cut
916    
917 root 1.32 # common sense is optional, but recommended
918 root 1.34 BEGIN { eval { require "common/sense.pm"; } && common::sense->import }
919 root 1.1
920 root 1.37 no warnings 'portable'; # avoid 32 bit integer warnings
921    
922 root 1.1 use Encode ();
923     use List::Util ();
924     use IO::Handle ();
925     use Time::HiRes ();
926    
927     eval { unpack "Q", pack "Q", 1 }
928     or die "perl with 64 bit integer supported required.\n";
929    
930     our $JSON = eval { require JSON::XS; JSON::XS:: }
931     // eval { require JSON::PP; JSON::PP:: }
932     // die "either JSON::XS or JSON::PP must be installed\n";
933    
934     our $json_coder = $JSON->new->utf8->pretty->canonical->relaxed;
935    
936     # hack used for debugging
937     sub xxd($$) {
938     open my $xxd, "| xxd | sed -e 's/^/\Q$_[0]\E: /'";
939     syswrite $xxd, $_[1];
940     }
941    
942 root 1.56 # get some meta info on a file (uid, gid, perms)
943     sub stat_get($) {
944     [(stat shift)[4, 5, 2]]
945     }
946    
947     # set stat info on a file
948     sub stat_set($$) {
949     my ($fh_or_path, $stat) = @_;
950    
951     return unless $stat;
952     chown $stat->[0], $stat->[1], $fh_or_path;
953     chmod +($stat->[2] & 07777), $fh_or_path;
954     }
955    
956 root 1.6 sub file_load($) {
957     my ($path) = @_;
958    
959     open my $fh, "<:raw", $path
960     or die "$path: $!\n";
961     my $size = -s $fh;
962     $size = read $fh, my $buf, $size
963     or die "$path: short read\n";
964    
965     $buf
966     }
967    
968 root 1.56 sub file_save($$;$) {
969     my ($path, $data, $stat) = @_;
970    
971     open my $fh, ">:raw", "$path~"
972     or die "$path~: $!\n";
973     print $fh $data
974     or die "$path~: short write\n";
975     stat_set $fh, $stat;
976     $fh->sync;
977     close $fh;
978    
979     rename "$path~", $path;
980     }
981    
982 root 1.29 # sources and resources used for writing pbcdedit
983     #
984 root 1.1 # registry:
985     # https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md
986     # http://amnesia.gtisc.gatech.edu/~moyix/suzibandit.ltd.uk/MSc/
987     # bcd:
988     # http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm
989     # https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653287(v=vs.85)
990     # bcd devices:
991     # reactos' boot/environ/include/bl.h
992     # windows .mof files
993    
994     #############################################################################
995     # registry stuff
996    
997     # we use a hardcoded securitya descriptor - full access for everyone
998     my $sid = pack "H*", "010100000000000100000000"; # S-1-1-0 everyone
999     my $ace = pack "C C S< L< a*", 0, 2, 8 + (length $sid), 0x000f003f, $sid; # type flags size mask sid
1000     my $sacl = "";
1001     my $dacl = pack "C x S< S< x2 a*", 2, 8 + (length $ace), 1, $ace; # rev size count ace*
1002     my $sd = pack "C x S< L< L< L< L< a* a* a* a*",
1003     # rev flags(SE_DACL_PRESENT SE_SELF_RELATIVE) owner group sacl dacl
1004     1, 0x8004,
1005     20 + (length $sacl) + (length $dacl),
1006     20 + (length $sacl) + (length $dacl) + (length $sid),
1007     0, 20,
1008     $sacl, $dacl, $sid, $sid;
1009     my $sk = pack "a2 x2 x4 x4 x4 L< a*", sk => (length $sd), $sd;
1010    
1011     sub NO_OFS() { 0xffffffff } # file pointer "NULL" value
1012    
1013     sub KEY_HIVE_ENTRY() { 0x0004 }
1014     sub KEY_NO_DELETE () { 0x0008 }
1015     sub KEY_COMP_NAME () { 0x0020 }
1016    
1017     sub VALUE_COMP_NAME() { 0x0001 }
1018    
1019     my @regf_typename = qw(
1020     none sz expand_sz binary dword dword_be link multi_sz
1021     resource_list full_resource_descriptor resource_requirements_list
1022     qword qword_be
1023     );
1024    
1025     my %regf_dec_type = (
1026     sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
1027     expand_sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
1028     link => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
1029     multi_sz => sub { $_[0] =~ s/(?:\x00\x00)?\x00\x00$//; [ split /\x00/, (Encode::decode "UTF-16LE", $_[0]), -1 ] },
1030     dword => sub { unpack "L<", shift },
1031     dword_be => sub { unpack "L>", shift },
1032     qword => sub { unpack "Q<", shift },
1033     qword_be => sub { unpack "Q>", shift },
1034     );
1035    
1036     my %regf_enc_type = (
1037     sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
1038     expand_sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
1039     link => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
1040     multi_sz => sub { (join "", map +(Encode::encode "UTF-16LE", $_) . "\x00\x00", @{ $_[0] }) . "\x00\x00" },
1041     dword => sub { pack "L<", shift },
1042     dword_be => sub { pack "L>", shift },
1043     qword => sub { pack "Q<", shift },
1044     qword_be => sub { pack "Q>", shift },
1045     );
1046    
1047     # decode a registry hive
1048     sub regf_decode($) {
1049     my ($hive) = @_;
1050    
1051     "regf" eq substr $hive, 0, 4
1052     or die "not a registry hive\n";
1053    
1054     my ($major, $minor) = unpack "\@20 L< L<", $hive;
1055    
1056     $major == 1
1057     or die "registry major version is not 1, but $major\n";
1058    
1059     $minor >= 2 && $minor <= 6
1060     or die "registry minor version is $minor, only 2 .. 6 are supported\n";
1061    
1062     my $bins = substr $hive, 4096;
1063    
1064     my $decode_key = sub {
1065     my ($ofs) = @_;
1066    
1067     my @res;
1068    
1069     my ($sze, $sig) = unpack "\@$ofs l< a2", $bins;
1070    
1071     $sze < 0
1072     or die "key node points to unallocated cell\n";
1073    
1074     $sig eq "nk"
1075     or die "expected key node at $ofs, got '$sig'\n";
1076    
1077     my ($flags, $snum, $sofs, $vnum, $vofs, $knamesze) = unpack "\@$ofs ( \@6 S< \@24 L< x4 L< x4 L< L< \@76 S< )", $bins;
1078    
1079     my $kname = unpack "\@$ofs x80 a$knamesze", $bins;
1080    
1081     # classnames, security descriptors
1082     #my ($cofs, $xofs, $clen) = unpack "\@$ofs ( \@44 L< L< \@72 S< )", $bins;
1083     #if ($cofs != NO_OFS && $clen) {
1084     # #warn "cofs $cofs+$clen\n";
1085     # xxd substr $bins, $cofs, 16;
1086     #}
1087    
1088     $kname = Encode::decode "UTF-16LE", $kname
1089     unless $flags & KEY_COMP_NAME;
1090    
1091     if ($vnum && $vofs != NO_OFS) {
1092     for ($vofs += 4; $vnum--; $vofs += 4) {
1093     my $kofs = unpack "\@$vofs L<", $bins;
1094    
1095     my ($sze, $sig) = unpack "\@$kofs l< a2", $bins;
1096    
1097     $sig eq "vk"
1098     or die "key values list contains invalid node (expected vk got '$sig')\n";
1099    
1100     my ($nsze, $dsze, $dofs, $type, $flags) = unpack "\@$kofs x4 x2 S< L< L< L< L<", $bins;
1101    
1102     my $name = substr $bins, $kofs + 24, $nsze;
1103    
1104     $name = Encode::decode "UTF-16LE", $name
1105     unless $flags & VALUE_COMP_NAME;
1106    
1107     my $data;
1108     if ($dsze & 0x80000000) {
1109     $data = substr $bins, $kofs + 12, $dsze & 0x7;
1110     } elsif ($dsze > 16344 && $minor > 3) { # big data
1111     my ($bsze, $bsig, $bnum, $bofs) = unpack "\@$dofs l< a2 S< L<", $bins;
1112    
1113     for ($bofs += 4; $bnum--; $bofs += 4) {
1114     my $dofs = unpack "\@$bofs L<", $bins;
1115     my $dsze = unpack "\@$dofs l<", $bins;
1116     $data .= substr $bins, $dofs + 4, -$dsze - 4;
1117     }
1118     $data = substr $data, 0, $dsze; # cells might be longer than data
1119     } else {
1120     $data = substr $bins, $dofs + 4, $dsze;
1121     }
1122    
1123     $type = $regf_typename[$type] if $type < @regf_typename;
1124    
1125     $data = ($regf_dec_type{$type} || sub { unpack "H*", shift })
1126     ->($data);
1127    
1128     $res[0]{$name} = [$type, $data];
1129     }
1130     }
1131    
1132     if ($sofs != NO_OFS) {
1133     my $decode_key = __SUB__;
1134    
1135     my $decode_subkeylist = sub {
1136     my ($sofs) = @_;
1137    
1138     my ($sze, $sig, $snum) = unpack "\@$sofs l< a2 S<", $bins;
1139    
1140     if ($sig eq "ri") { # index root
1141     for (my $lofs = $sofs + 8; $snum--; $lofs += 4) {
1142     __SUB__->(unpack "\@$lofs L<", $bins);
1143     }
1144     } else {
1145     my $inc;
1146    
1147     if ($sig eq "li") { # subkey list
1148     $inc = 4;
1149     } elsif ($sig eq "lf" or $sig eq "lh") { # subkey list with name hints or hashes
1150     $inc = 8;
1151     } else {
1152     die "expected subkey list at $sofs, found '$sig'\n";
1153     }
1154    
1155     for (my $lofs = $sofs + 8; $snum--; $lofs += $inc) {
1156     my ($name, $data) = $decode_key->(unpack "\@$lofs L<", $bins);
1157     $res[1]{$name} = $data;
1158     }
1159     }
1160     };
1161    
1162     $decode_subkeylist->($sofs);
1163     }
1164    
1165     ($kname, \@res);
1166     };
1167    
1168     my ($rootcell) = unpack "\@36 L<", $hive;
1169    
1170     my ($rname, $root) = $decode_key->($rootcell);
1171    
1172     [$rname, $root]
1173     }
1174    
1175 root 1.62 # return a binary windows FILETIME struct
1176 root 1.1 sub filetime_now {
1177     my ($s, $ms) = Time::HiRes::gettimeofday;
1178    
1179 root 1.64 pack "Q<", ($s * 1_000_000 + $ms) * 10
1180     + 116_444_736_000_000_000 # 1970-01-01 00:00:00
1181 root 1.1 }
1182    
1183     # encode a registry hive
1184     sub regf_encode($) {
1185     my ($hive) = @_;
1186    
1187     my %typeval = map +($regf_typename[$_] => $_), 0 .. $#regf_typename;
1188    
1189     # the filetime is apparently used to verify log file validity,
1190     # so by generating a new timestamp the log files *should* automatically
1191     # become invalidated and windows would "self-heal" them.
1192     # (update: has been verified by reverse engineering)
1193 root 1.65 # possibly the fact that the two sequence numbers match might also
1194 root 1.1 # make windows think that the hive is not dirty and ignore logs.
1195     # (update: has been verified by reverse engineering)
1196    
1197     my $now = filetime_now;
1198    
1199     # we only create a single hbin
1200     my $bins = pack "a4 L< L< x8 a8 x4", "hbin", 0, 0, $now;
1201    
1202     # append cell to $bind, return offset
1203     my $cell = sub {
1204     my ($cell) = @_;
1205    
1206     my $res = length $bins;
1207    
1208     $cell .= "\x00" while 4 != (7 & length $cell); # slow and ugly
1209    
1210     $bins .= pack "l<", -(4 + length $cell);
1211     $bins .= $cell;
1212    
1213     $res
1214     };
1215    
1216     my $sdofs = $cell->($sk); # add a dummy security descriptor
1217     my $sdref = 0; # refcount
1218     substr $bins, $sdofs + 8, 4, pack "L<", $sdofs; # flink
1219     substr $bins, $sdofs + 12, 4, pack "L<", $sdofs; # blink
1220    
1221     my $encode_key = sub {
1222     my ($kname, $kdata, $flags) = @_;
1223     my ($values, $subkeys) = @$kdata;
1224    
1225     if ($kname =~ /[^\x00-\xff]/) {
1226     $kname = Encode::encode "UTF-16LE", $kname;
1227     } else {
1228     $flags |= KEY_COMP_NAME;
1229     }
1230    
1231     # encode subkeys
1232    
1233     my @snames =
1234     map $_->[1],
1235     sort { $a->[0] cmp $b->[0] }
1236     map [(uc $_), $_],
1237     keys %$subkeys;
1238    
1239     # normally, we'd have to encode each name, but we assume one char is at most two utf-16 cp's
1240     my $maxsname = 4 * List::Util::max map length, @snames;
1241    
1242     my @sofs = map __SUB__->($_, $subkeys->{$_}, 0), @snames;
1243    
1244     # encode values
1245     my $maxvname = 4 * List::Util::max map length, keys %$values;
1246     my @vofs;
1247     my $maxdsze = 0;
1248    
1249     while (my ($vname, $v) = each %$values) {
1250     my $flags = 0;
1251    
1252     if ($vname =~ /[^\x00-\xff]/) {
1253     $vname = Encode::encode "UTF-16LE", $kname;
1254     } else {
1255     $flags |= VALUE_COMP_NAME;
1256     }
1257    
1258     my ($type, $data) = @$v;
1259    
1260     $data = ($regf_enc_type{$type} || sub { pack "H*", shift })->($data);
1261    
1262     my $dsze;
1263     my $dofs;
1264    
1265     if (length $data <= 4) {
1266     $dsze = 0x80000000 | length $data;
1267     $dofs = unpack "L<", pack "a4", $data;
1268     } else {
1269     $dsze = length $data;
1270     $dofs = $cell->($data);
1271     }
1272    
1273     $type = $typeval{$type} // ($type =~ /^[0-9]+\z/ ? $type : die "cannot encode type '$type'");
1274    
1275     push @vofs, $cell->(pack "a2 S< L< L< L< S< x2 a*",
1276     vk => (length $vname), $dsze, $dofs, $type, $flags, $vname);
1277    
1278     $maxdsze = $dsze if $maxdsze < $dsze;
1279     }
1280    
1281     # encode key
1282    
1283     my $slist = @sofs ? $cell->(pack "a2 S< L<*", li => (scalar @sofs), @sofs) : NO_OFS;
1284     my $vlist = @vofs ? $cell->(pack "L<*", @vofs) : NO_OFS;
1285    
1286     my $kdata = pack "
1287     a2 S< a8 x4 x4
1288     L< L< L< L< L< L<
1289     L< L< L< L< L< L<
1290     x4 S< S< a*
1291     ",
1292     nk => $flags, $now,
1293     (scalar @sofs), 0, $slist, NO_OFS, (scalar @vofs), $vlist,
1294     $sdofs, NO_OFS, $maxsname, 0, $maxvname, $maxdsze,
1295     length $kname, 0, $kname;
1296     ++$sdref;
1297    
1298     my $res = $cell->($kdata);
1299    
1300     substr $bins, $_ + 16, 4, pack "L<", $res
1301     for @sofs;
1302    
1303     $res
1304     };
1305    
1306     my ($rname, $root) = @$hive;
1307    
1308     my $rofs = $encode_key->($rname, $root, KEY_HIVE_ENTRY | KEY_NO_DELETE); # 4 = root key
1309    
1310     if (my $pad = -(length $bins) & 4095) {
1311     $pad -= 4;
1312     $bins .= pack "l< x$pad", $pad + 4;
1313     }
1314    
1315     substr $bins, $sdofs + 16, 4, pack "L<", $sdref; # sd refcount
1316     substr $bins, 8, 4, pack "L<", length $bins;
1317    
1318     my $base = pack "
1319     a4 L< L< a8 L< L< L< L<
1320     L< L< L<
1321     a64
1322     x396
1323     ",
1324     regf => 1974, 1974, $now, 1, 3, 0, 1,
1325     $rofs, length $bins, 1,
1326     (Encode::encode "UTF-16LE", "\\pbcdedit.reg");
1327    
1328     my $chksum = List::Util::reduce { $a ^ $b } unpack "L<*", $base;
1329     $chksum = 0xfffffffe if $chksum == 0xffffffff;
1330     $chksum = 1 if $chksum == 0;
1331    
1332     $base .= pack "L<", $chksum;
1333    
1334     $base = pack "a* \@4095 x1", $base;
1335    
1336     $base . $bins
1337     }
1338    
1339     # load and parse registry from file
1340     sub regf_load($) {
1341     my ($path) = @_;
1342    
1343 root 1.6 regf_decode file_load $path
1344 root 1.1 }
1345    
1346     # encode and save registry to file
1347 root 1.56 sub regf_save($$;$) {
1348     my ($path, $hive, $stat) = @_;
1349 root 1.1
1350     $hive = regf_encode $hive;
1351    
1352 root 1.56 file_save $path, $hive, $stat;
1353 root 1.1 }
1354    
1355     #############################################################################
1356     # bcd stuff
1357    
1358 root 1.66 # human-readable aliases for GUID object identifiers
1359 root 1.1 our %bcd_objects = (
1360     '{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}' => '{emssettings}',
1361     '{1afa9c49-16ab-4a5c-4a90-212802da9460}' => '{resumeloadersettings}',
1362     '{1cae1eb7-a0df-4d4d-9851-4860e34ef535}' => '{default}',
1363     '{313e8eed-7098-4586-a9bf-309c61f8d449}' => '{kerneldbgsettings}',
1364     '{4636856e-540f-4170-a130-a84776f4c654}' => '{dbgsettings}',
1365     '{466f5a88-0af2-4f76-9038-095b170dc21c}' => '{ntldr}',
1366     '{5189b25c-5558-4bf2-bca4-289b11bd29e2}' => '{badmemory}',
1367     '{6efb52bf-1766-41db-a6b3-0ee5eff72bd7}' => '{bootloadersettings}',
1368     '{7254a080-1510-4e85-ac0f-e7fb3d444736}' => '{ssetupefi}',
1369     '{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}' => '{globalsettings}',
1370     '{7ff607e0-4395-11db-b0de-0800200c9a66}' => '{hypervisorsettings}',
1371     '{9dea862c-5cdd-4e70-acc1-f32b344d4795}' => '{bootmgr}',
1372     '{a1943bbc-ea85-487c-97c7-c9ede908a38a}' => '{ostargettemplatepcat}',
1373     '{a5a30fa2-3d06-4e9f-b5f4-a01df9d1fcba}' => '{fwbootmgr}',
1374     '{ae5534e0-a924-466c-b836-758539a3ee3a}' => '{ramdiskoptions}',
1375     '{b012b84d-c47c-4ed5-b722-c0c42163e569}' => '{ostargettemplateefi}',
1376     '{b2721d73-1db4-4c62-bf78-c548a880142d}' => '{memdiag}',
1377     '{cbd971bf-b7b8-4885-951a-fa03044f5d71}' => '{setuppcat}',
1378     '{fa926493-6f1c-4193-a414-58f0b2456d1e}' => '{current}',
1379     );
1380    
1381     # default types
1382     our %bcd_object_types = (
1383     '{fwbootmgr}' => 0x10100001,
1384     '{bootmgr}' => 0x10100002,
1385     '{memdiag}' => 0x10200005,
1386     '{ntldr}' => 0x10300006,
1387     '{badmemory}' => 0x20100000,
1388     '{dbgsettings}' => 0x20100000,
1389     '{emssettings}' => 0x20100000,
1390     '{globalsettings}' => 0x20100000,
1391     '{bootloadersettings}' => 0x20200003,
1392     '{hypervisorsettings}' => 0x20200003,
1393     '{kerneldbgsettings}' => 0x20200003,
1394     '{resumeloadersettings}' => 0x20200004,
1395     '{ramdiskoptions}' => 0x30000000,
1396     );
1397    
1398     # object types
1399     our %bcd_types = (
1400     0x10100001 => 'application::fwbootmgr',
1401     0x10100002 => 'application::bootmgr',
1402     0x10200003 => 'application::osloader',
1403     0x10200004 => 'application::resume',
1404     0x10100005 => 'application::memdiag',
1405     0x10100006 => 'application::ntldr',
1406     0x10100007 => 'application::setupldr',
1407     0x10400008 => 'application::bootsector',
1408     0x10400009 => 'application::startup',
1409     0x1020000a => 'application::bootapp',
1410     0x20100000 => 'settings',
1411     0x20200001 => 'inherit::fwbootmgr',
1412     0x20200002 => 'inherit::bootmgr',
1413     0x20200003 => 'inherit::osloader',
1414     0x20200004 => 'inherit::resume',
1415     0x20200005 => 'inherit::memdiag',
1416     0x20200006 => 'inherit::ntldr',
1417     0x20200007 => 'inherit::setupldr',
1418     0x20200008 => 'inherit::bootsector',
1419     0x20200009 => 'inherit::startup',
1420     0x20300000 => 'inherit::device',
1421     0x30000000 => 'device',
1422     );
1423    
1424     our %rbcd_objects = reverse %bcd_objects;
1425    
1426     our $RE_GUID = qr<([0-9a-f]{8})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{4})-([0-9a-f]{12})>i;
1427    
1428     sub dec_guid($) {
1429     my ($p1, $p2, $p3, $p4, $p5) = unpack "VvvH4H12", shift;
1430     sprintf "%08x-%04x-%04x-%s-%s", $p1, $p2, $p3, $p4, $p5;
1431     }
1432    
1433     sub enc_guid($) {
1434     $_[0] =~ /^$RE_GUID\z/o
1435     or return;
1436    
1437     pack "VvvH4H12", hex $1, hex $2, hex $3, $4, $5
1438     }
1439    
1440     # "wguid" are guids wrapped in curly braces {...} also supporting aliases
1441     sub dec_wguid($) {
1442     my $guid = "{" . (dec_guid shift) . "}";
1443    
1444     $bcd_objects{$guid} // $guid
1445     }
1446    
1447     sub enc_wguid($) {
1448     my ($guid) = @_;
1449    
1450     if (my $alias = $rbcd_objects{$guid}) {
1451     $guid = $alias;
1452     }
1453    
1454     $guid =~ /^\{($RE_GUID)\}\z/o
1455     or return;
1456    
1457     enc_guid $1
1458     }
1459    
1460     sub BCDE_CLASS () { 0xf0000000 }
1461     sub BCDE_CLASS_LIBRARY () { 0x10000000 }
1462     sub BCDE_CLASS_APPLICATION () { 0x20000000 }
1463     sub BCDE_CLASS_DEVICE () { 0x30000000 }
1464     sub BCDE_CLASS_TEMPLATE () { 0x40000000 }
1465    
1466     sub BCDE_FORMAT () { 0x0f000000 }
1467     sub BCDE_FORMAT_DEVICE () { 0x01000000 }
1468     sub BCDE_FORMAT_STRING () { 0x02000000 }
1469     sub BCDE_FORMAT_GUID () { 0x03000000 }
1470     sub BCDE_FORMAT_GUID_LIST () { 0x04000000 }
1471     sub BCDE_FORMAT_INTEGER () { 0x05000000 }
1472     sub BCDE_FORMAT_BOOLEAN () { 0x06000000 }
1473     sub BCDE_FORMAT_INTEGER_LIST () { 0x07000000 }
1474    
1475     sub enc_integer($) {
1476     my $value = shift;
1477     $value = oct $value if $value =~ /^0[bBxX]/;
1478     unpack "H*", pack "Q<", $value
1479     }
1480    
1481 root 1.37 sub enc_device($$);
1482     sub dec_device($$);
1483    
1484 root 1.1 our %bcde_dec = (
1485     BCDE_FORMAT_DEVICE , \&dec_device,
1486     # # for round-trip verification
1487     # BCDE_FORMAT_DEVICE , sub {
1488     # my $dev = dec_device $_[0];
1489     # $_[0] eq enc_device $dev
1490     # or die "bcd device decoding does not round trip for $_[0]\n";
1491     # $dev
1492     # },
1493     BCDE_FORMAT_STRING , sub { shift },
1494     BCDE_FORMAT_GUID , sub { dec_wguid enc_wguid shift },
1495     BCDE_FORMAT_GUID_LIST , sub { join " ", map dec_wguid enc_wguid $_, @{+shift} },
1496     BCDE_FORMAT_INTEGER , sub { unpack "Q", pack "a8", pack "H*", shift }, # integer might be 4 or 8 bytes - caused by ms coding bugs
1497     BCDE_FORMAT_BOOLEAN , sub { shift eq "00" ? 0 : 1 },
1498 root 1.67 BCDE_FORMAT_INTEGER_LIST, sub { join " ", unpack "Q*", pack "H*", shift }, # not sure if this can be 4 bytes
1499 root 1.1 );
1500    
1501     our %bcde_enc = (
1502 root 1.37 BCDE_FORMAT_DEVICE , sub { binary => enc_device $_[0], $_[1] },
1503 root 1.1 BCDE_FORMAT_STRING , sub { sz => shift },
1504     BCDE_FORMAT_GUID , sub { sz => "{" . (dec_guid enc_wguid shift) . "}" },
1505     BCDE_FORMAT_GUID_LIST , sub { multi_sz => [map "{" . (dec_guid enc_wguid $_) . "}", split /\s+/, shift ] },
1506     BCDE_FORMAT_INTEGER , sub { binary => enc_integer shift },
1507     BCDE_FORMAT_BOOLEAN , sub { binary => shift ? "01" : "00" },
1508     BCDE_FORMAT_INTEGER_LIST, sub { binary => join "", map enc_integer $_, split /\s+/, shift },
1509     );
1510    
1511     # BCD Elements
1512 root 1.37 our %bcde_byclass = (
1513     any => {
1514 root 1.68 0x11000001 => 'device',
1515     0x12000002 => 'path',
1516     0x12000004 => 'description',
1517     0x12000005 => 'locale',
1518     0x14000006 => 'inherit',
1519     0x15000007 => 'truncatememory',
1520     0x14000008 => 'recoverysequence',
1521     0x16000009 => 'recoveryenabled',
1522     0x1700000a => 'badmemorylist',
1523     0x1600000b => 'badmemoryaccess',
1524     0x1500000c => 'firstmegabytepolicy',
1525     0x1500000d => 'relocatephysical',
1526     0x1500000e => 'avoidlowmemory',
1527     0x1600000f => 'traditionalkseg',
1528     0x16000010 => 'bootdebug',
1529     0x15000011 => 'debugtype',
1530     0x15000012 => 'debugaddress',
1531     0x15000013 => 'debugport',
1532     0x15000014 => 'baudrate',
1533     0x15000015 => 'channel',
1534     0x12000016 => 'targetname',
1535     0x16000017 => 'noumex',
1536     0x15000018 => 'debugstart',
1537     0x12000019 => 'busparams',
1538     0x1500001a => 'hostip',
1539     0x1500001b => 'port',
1540     0x1600001c => 'dhcp',
1541     0x1200001d => 'key',
1542     0x1600001e => 'vm',
1543     0x16000020 => 'bootems',
1544     0x15000022 => 'emsport',
1545     0x15000023 => 'emsbaudrate',
1546     0x12000030 => 'loadoptions',
1547     0x16000040 => 'advancedoptions',
1548     0x16000041 => 'optionsedit',
1549     0x15000042 => 'keyringaddress',
1550     0x11000043 => 'bootstatdevice',
1551     0x12000044 => 'bootstatfilepath',
1552     0x16000045 => 'preservebootstat',
1553     0x16000046 => 'graphicsmodedisabled',
1554     0x15000047 => 'configaccesspolicy',
1555     0x16000048 => 'nointegritychecks',
1556     0x16000049 => 'testsigning',
1557     0x1200004a => 'fontpath',
1558     0x1500004b => 'integrityservices',
1559     0x1500004c => 'volumebandid',
1560     0x16000050 => 'extendedinput',
1561     0x15000051 => 'initialconsoleinput',
1562     0x15000052 => 'graphicsresolution',
1563     0x16000053 => 'restartonfailure',
1564     0x16000054 => 'highestmode',
1565     0x16000060 => 'isolatedcontext',
1566     0x15000065 => 'displaymessage',
1567     0x15000066 => 'displaymessageoverride',
1568     0x16000068 => 'nobootuxtext',
1569     0x16000069 => 'nobootuxprogress',
1570     0x1600006a => 'nobootuxfade',
1571     0x1600006b => 'bootuxreservepooldebug',
1572     0x1600006c => 'bootuxdisabled',
1573     0x1500006d => 'bootuxfadeframes',
1574     0x1600006e => 'bootuxdumpstats',
1575     0x1600006f => 'bootuxshowstats',
1576     0x16000071 => 'multibootsystem',
1577     0x16000072 => 'nokeyboard',
1578     0x15000073 => 'aliaswindowskey',
1579     0x16000074 => 'bootshutdowndisabled',
1580     0x15000075 => 'performancefrequency',
1581     0x15000076 => 'securebootrawpolicy',
1582     0x17000077 => 'allowedinmemorysettings',
1583     0x15000079 => 'bootuxtransitiontime',
1584     0x1600007a => 'mobilegraphics',
1585     0x1600007b => 'forcefipscrypto',
1586     0x1500007d => 'booterrorux',
1587     0x1600007e => 'flightsigning',
1588     0x1500007f => 'measuredbootlogformat',
1589     0x15000080 => 'displayrotation',
1590     0x15000081 => 'logcontrol',
1591     0x16000082 => 'nofirmwaresync',
1592     0x11000084 => 'windowssyspart',
1593     0x16000087 => 'numlock',
1594     0x26000202 => 'skipffumode',
1595     0x26000203 => 'forceffumode',
1596     0x25000510 => 'chargethreshold',
1597     0x26000512 => 'offmodecharging',
1598     0x25000aaa => 'bootflow',
1599     0x45000001 => 'devicetype',
1600     0x42000002 => 'applicationrelativepath',
1601     0x42000003 => 'ramdiskdevicerelativepath',
1602     0x46000004 => 'omitosloaderelements',
1603     0x47000006 => 'elementstomigrate',
1604     0x46000010 => 'recoveryos',
1605 root 1.37 },
1606     bootapp => {
1607 root 1.68 0x26000145 => 'enablebootdebugpolicy',
1608     0x26000146 => 'enablebootorderclean',
1609     0x26000147 => 'enabledeviceid',
1610     0x26000148 => 'enableffuloader',
1611     0x26000149 => 'enableiuloader',
1612     0x2600014a => 'enablemassstorage',
1613     0x2600014b => 'enablerpmbprovisioning',
1614     0x2600014c => 'enablesecurebootpolicy',
1615     0x2600014d => 'enablestartcharge',
1616     0x2600014e => 'enableresettpm',
1617 root 1.37 },
1618     bootmgr => {
1619 root 1.68 0x24000001 => 'displayorder',
1620     0x24000002 => 'bootsequence',
1621     0x23000003 => 'default',
1622     0x25000004 => 'timeout',
1623     0x26000005 => 'resume',
1624     0x23000006 => 'resumeobject',
1625     0x24000007 => 'startupsequence',
1626     0x24000010 => 'toolsdisplayorder',
1627     0x26000020 => 'displaybootmenu',
1628     0x26000021 => 'noerrordisplay',
1629     0x21000022 => 'bcddevice',
1630     0x22000023 => 'bcdfilepath',
1631     0x26000024 => 'hormenabled',
1632     0x26000025 => 'hiberboot',
1633     0x22000026 => 'passwordoverride',
1634     0x22000027 => 'pinpassphraseoverride',
1635     0x26000028 => 'processcustomactionsfirst',
1636     0x27000030 => 'customactions',
1637     0x26000031 => 'persistbootsequence',
1638     0x26000032 => 'skipstartupsequence',
1639     0x22000040 => 'fverecoveryurl',
1640     0x22000041 => 'fverecoverymessage',
1641 root 1.37 },
1642     device => {
1643 root 1.68 0x35000001 => 'ramdiskimageoffset',
1644     0x35000002 => 'ramdisktftpclientport',
1645     0x31000003 => 'ramdisksdidevice',
1646     0x32000004 => 'ramdisksdipath',
1647     0x35000005 => 'ramdiskimagelength',
1648     0x36000006 => 'exportascd',
1649     0x35000007 => 'ramdisktftpblocksize',
1650     0x35000008 => 'ramdisktftpwindowsize',
1651     0x36000009 => 'ramdiskmcenabled',
1652     0x3600000a => 'ramdiskmctftpfallback',
1653     0x3600000b => 'ramdisktftpvarwindow',
1654 root 1.37 },
1655     memdiag => {
1656 root 1.68 0x25000001 => 'passcount',
1657     0x25000002 => 'testmix',
1658     0x25000003 => 'failurecount',
1659     0x26000003 => 'cacheenable',
1660     0x25000004 => 'testtofail',
1661     0x26000004 => 'failuresenabled',
1662     0x25000005 => 'stridefailcount',
1663     0x26000005 => 'cacheenable',
1664     0x25000006 => 'invcfailcount',
1665     0x25000007 => 'matsfailcount',
1666     0x25000008 => 'randfailcount',
1667     0x25000009 => 'chckrfailcount',
1668 root 1.37 },
1669     ntldr => {
1670 root 1.68 0x22000001 => 'bpbstring',
1671 root 1.37 },
1672     osloader => {
1673 root 1.68 0x21000001 => 'osdevice',
1674     0x22000002 => 'systemroot',
1675     0x23000003 => 'resumeobject',
1676     0x26000004 => 'stampdisks',
1677     0x26000010 => 'detecthal',
1678     0x22000011 => 'kernel',
1679     0x22000012 => 'hal',
1680     0x22000013 => 'dbgtransport',
1681     0x25000020 => 'nx',
1682     0x25000021 => 'pae',
1683     0x26000022 => 'winpe',
1684     0x26000024 => 'nocrashautoreboot',
1685     0x26000025 => 'lastknowngood',
1686     0x26000026 => 'oslnointegritychecks',
1687     0x26000027 => 'osltestsigning',
1688     0x26000030 => 'nolowmem',
1689     0x25000031 => 'removememory',
1690     0x25000032 => 'increaseuserva',
1691     0x25000033 => 'perfmem',
1692     0x26000040 => 'vga',
1693     0x26000041 => 'quietboot',
1694     0x26000042 => 'novesa',
1695     0x26000043 => 'novga',
1696     0x25000050 => 'clustermodeaddressing',
1697     0x26000051 => 'usephysicaldestination',
1698     0x25000052 => 'restrictapiccluster',
1699     0x22000053 => 'evstore',
1700     0x26000054 => 'uselegacyapicmode',
1701     0x26000060 => 'onecpu',
1702     0x25000061 => 'numproc',
1703     0x26000062 => 'maxproc',
1704     0x25000063 => 'configflags',
1705     0x26000064 => 'maxgroup',
1706     0x26000065 => 'groupaware',
1707     0x25000066 => 'groupsize',
1708     0x26000070 => 'usefirmwarepcisettings',
1709     0x25000071 => 'msi',
1710     0x25000072 => 'pciexpress',
1711     0x25000080 => 'safeboot',
1712     0x26000081 => 'safebootalternateshell',
1713     0x26000090 => 'bootlog',
1714     0x26000091 => 'sos',
1715     0x260000a0 => 'debug',
1716     0x260000a1 => 'halbreakpoint',
1717     0x260000a2 => 'useplatformclock',
1718     0x260000a3 => 'forcelegacyplatform',
1719     0x260000a4 => 'useplatformtick',
1720     0x260000a5 => 'disabledynamictick',
1721     0x250000a6 => 'tscsyncpolicy',
1722     0x260000b0 => 'ems',
1723     0x250000c0 => 'forcefailure',
1724     0x250000c1 => 'driverloadfailurepolicy',
1725     0x250000c2 => 'bootmenupolicy',
1726     0x260000c3 => 'onetimeadvancedoptions',
1727     0x260000c4 => 'onetimeoptionsedit',
1728     0x250000e0 => 'bootstatuspolicy',
1729     0x260000e1 => 'disableelamdrivers',
1730     0x250000f0 => 'hypervisorlaunchtype',
1731     0x220000f1 => 'hypervisorpath',
1732     0x260000f2 => 'hypervisordebug',
1733     0x250000f3 => 'hypervisordebugtype',
1734     0x250000f4 => 'hypervisordebugport',
1735     0x250000f5 => 'hypervisorbaudrate',
1736     0x250000f6 => 'hypervisorchannel',
1737     0x250000f7 => 'bootux',
1738     0x260000f8 => 'hypervisordisableslat',
1739     0x220000f9 => 'hypervisorbusparams',
1740     0x250000fa => 'hypervisornumproc',
1741     0x250000fb => 'hypervisorrootprocpernode',
1742     0x260000fc => 'hypervisoruselargevtlb',
1743     0x250000fd => 'hypervisorhostip',
1744     0x250000fe => 'hypervisorhostport',
1745     0x250000ff => 'hypervisordebugpages',
1746     0x25000100 => 'tpmbootentropy',
1747     0x22000110 => 'hypervisorusekey',
1748     0x22000112 => 'hypervisorproductskutype',
1749     0x25000113 => 'hypervisorrootproc',
1750     0x26000114 => 'hypervisordhcp',
1751     0x25000115 => 'hypervisoriommupolicy',
1752     0x26000116 => 'hypervisorusevapic',
1753     0x22000117 => 'hypervisorloadoptions',
1754     0x25000118 => 'hypervisormsrfilterpolicy',
1755     0x25000119 => 'hypervisormmionxpolicy',
1756     0x2500011a => 'hypervisorschedulertype',
1757     0x25000120 => 'xsavepolicy',
1758     0x25000121 => 'xsaveaddfeature0',
1759     0x25000122 => 'xsaveaddfeature1',
1760     0x25000123 => 'xsaveaddfeature2',
1761     0x25000124 => 'xsaveaddfeature3',
1762     0x25000125 => 'xsaveaddfeature4',
1763     0x25000126 => 'xsaveaddfeature5',
1764     0x25000127 => 'xsaveaddfeature6',
1765     0x25000128 => 'xsaveaddfeature7',
1766     0x25000129 => 'xsaveremovefeature',
1767     0x2500012a => 'xsaveprocessorsmask',
1768     0x2500012b => 'xsavedisable',
1769     0x2500012c => 'kerneldebugtype',
1770     0x2200012d => 'kernelbusparams',
1771     0x2500012e => 'kerneldebugaddress',
1772     0x2500012f => 'kerneldebugport',
1773     0x25000130 => 'claimedtpmcounter',
1774     0x25000131 => 'kernelchannel',
1775     0x22000132 => 'kerneltargetname',
1776     0x25000133 => 'kernelhostip',
1777     0x25000134 => 'kernelport',
1778     0x26000135 => 'kerneldhcp',
1779     0x22000136 => 'kernelkey',
1780     0x22000137 => 'imchivename',
1781     0x21000138 => 'imcdevice',
1782     0x25000139 => 'kernelbaudrate',
1783     0x22000140 => 'mfgmode',
1784     0x26000141 => 'event',
1785     0x25000142 => 'vsmlaunchtype',
1786     0x25000144 => 'hypervisorenforcedcodeintegrity',
1787     0x21000150 => 'systemdatadevice',
1788     0x21000151 => 'osarcdevice',
1789     0x21000153 => 'osdatadevice',
1790     0x21000154 => 'bspdevice',
1791     0x21000155 => 'bspfilepath',
1792 root 1.37 },
1793     resume => {
1794 root 1.68 0x21000001 => 'filedevice',
1795     0x22000002 => 'filepath',
1796     0x26000003 => 'customsettings',
1797     0x26000004 => 'pae',
1798     0x21000005 => 'associatedosdevice',
1799     0x26000006 => 'debugoptionenabled',
1800     0x25000007 => 'bootux',
1801     0x25000008 => 'bootmenupolicy',
1802     0x26000024 => 'hormenabled',
1803 root 1.37 },
1804     startup => {
1805 root 1.68 0x26000001 => 'pxesoftreboot',
1806     0x22000002 => 'applicationname',
1807 root 1.37 },
1808     );
1809    
1810     # mask, value => class
1811     our @bcde_typeclass = (
1812     [0x00000000, 0x00000000, 'any'],
1813     [0xf00fffff, 0x1000000a, 'bootapp'],
1814     [0xf0ffffff, 0x2020000a, 'bootapp'],
1815     [0xf00fffff, 0x10000001, 'bootmgr'],
1816     [0xf00fffff, 0x10000002, 'bootmgr'],
1817     [0xf0ffffff, 0x20200001, 'bootmgr'],
1818     [0xf0ffffff, 0x20200002, 'bootmgr'],
1819     [0xf0f00000, 0x20300000, 'device'],
1820     [0xf0000000, 0x30000000, 'device'],
1821     [0xf00fffff, 0x10000005, 'memdiag'],
1822     [0xf0ffffff, 0x20200005, 'memdiag'],
1823     [0xf00fffff, 0x10000006, 'ntldr'],
1824     [0xf00fffff, 0x10000007, 'ntldr'],
1825     [0xf0ffffff, 0x20200006, 'ntldr'],
1826     [0xf0ffffff, 0x20200007, 'ntldr'],
1827     [0xf00fffff, 0x10000003, 'osloader'],
1828     [0xf0ffffff, 0x20200003, 'osloader'],
1829     [0xf00fffff, 0x10000004, 'resume'],
1830     [0xf0ffffff, 0x20200004, 'resume'],
1831     [0xf00fffff, 0x10000009, 'startup'],
1832     [0xf0ffffff, 0x20200009, 'startup'],
1833 root 1.1 );
1834    
1835 root 1.37 our %rbcde_byclass;
1836    
1837     while (my ($k, $v) = each %bcde_byclass) {
1838     $rbcde_byclass{$k} = { reverse %$v };
1839     }
1840    
1841     # decodes (numerical elem, type) to name
1842     sub dec_bcde_id($$) {
1843     for my $class (@bcde_typeclass) {
1844     if (($_[1] & $class->[0]) == $class->[1]) {
1845     if (my $id = $bcde_byclass{$class->[2]}{$_[0]}) {
1846     return $id;
1847     }
1848     }
1849     }
1850 root 1.1
1851 root 1.37 sprintf "custom:%08x", $_[0]
1852 root 1.1 }
1853    
1854 root 1.37 # encodes (elem as name, type)
1855     sub enc_bcde_id($$) {
1856     $_[0] =~ /^custom:(?:0x)?([0-9a-fA-F]{8}$)/
1857     and return hex $1;
1858    
1859     for my $class (@bcde_typeclass) {
1860     if (($_[1] & $class->[0]) == $class->[1]) {
1861     if (my $value = $rbcde_byclass{$class->[2]}{$_[0]}) {
1862     return $value;
1863     }
1864     }
1865     }
1866    
1867     undef
1868 root 1.1 }
1869    
1870     # decode/encode bcd device element - the horror, no documentaion
1871     # whatsoever, supercomplex, superinconsistent.
1872    
1873     our @dev_type = qw(block type1 legacypartition serial udp boot partition vmbus locate);
1874     our @block_type = qw(harddisk floppy cdrom ramdisk type4 file vhd);
1875     our @part_type = qw(gpt mbr raw);
1876    
1877     our $NULL_DEVICE = "\x00" x 16;
1878    
1879     # biggest bitch to decode, ever
1880 root 1.69 # this decodes a device portion after the GUID
1881 root 1.37 sub dec_device_($$);
1882     sub dec_device_($$) {
1883     my ($device, $type) = @_;
1884 root 1.1
1885     my $res;
1886    
1887     my ($type, $flags, $length, $pad) = unpack "VVVV", substr $device, 0, 4 * 4, "";
1888    
1889     $pad == 0
1890     or die "non-zero reserved field in device descriptor\n";
1891    
1892     if ($length == 0 && $type == 0 && $flags == 0) {
1893     return ("null", $device);
1894     }
1895    
1896     $length >= 16
1897     or die "device element size too small ($length)\n";
1898    
1899     $type = $dev_type[$type] // die "$type: unknown device type\n";
1900     #d# warn "t<$type,$flags,$length,$pad>\n";#d#
1901    
1902     $res .= $type;
1903     $res .= sprintf "<%x>", $flags if $flags;
1904    
1905     my $tail = substr $device, $length - 4 * 4, 1e9, "";
1906    
1907     $length == 4 * 4 + length $device
1908     or die "device length mismatch ($length != " . (16 + length $device) . ")\n";
1909    
1910     my $dec_path = sub {
1911     my ($path, $error) = @_;
1912    
1913     $path =~ /^((?:..)*)\x00\x00\z/s
1914     or die "$error\n";
1915    
1916     $path = Encode::decode "UTF-16LE", $1;
1917    
1918     $path
1919     };
1920    
1921     if ($type eq "partition" or $type eq "legacypartition") {
1922     my $partdata = substr $device, 0, 16, "";
1923     my ($blocktype, $parttype) = unpack "VV", substr $device, 0, 4 * 2, "";
1924    
1925     $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1926     $parttype = $part_type[$parttype] // die "unknown partition type\n";
1927    
1928     my $diskid = substr $device, 0, 16, "";
1929    
1930     $diskid = $parttype eq "gpt"
1931     ? dec_guid substr $diskid, 0, 16
1932     : sprintf "%08x", unpack "V", $diskid;
1933    
1934     my $partid = $parttype eq "gpt" ? dec_guid $partdata
1935     : $type eq "partition" ? unpack "Q<", $partdata # byte offset to partition start
1936     : unpack "L<", $partdata; # partition number, one-based
1937    
1938 root 1.37 (my $parent, $device) = dec_device_ $device, $type;
1939 root 1.1
1940     $res .= "=";
1941     $res .= "<$parent>";
1942     $res .= ",$blocktype,$parttype,$diskid,$partid";
1943    
1944     # PartitionType (gpt, mbr, raw)
1945     # guid | partsig | disknumber
1946    
1947     } elsif ($type eq "boot") {
1948     $device =~ s/^\x00{56}\z//
1949     or die "boot device type with extra data not supported\n";
1950    
1951     } elsif ($type eq "block") {
1952     my $blocktype = unpack "V", substr $device, 0, 4, "";
1953    
1954     $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1955    
1956     # decode a "file path" structure
1957     my $dec_file = sub {
1958     my ($fver, $flen, $ftype) = unpack "VVV", substr $device, 0, 4 * 3, "";
1959    
1960     my $path = substr $device, 0, $flen - 12, "";
1961    
1962     $fver == 1
1963     or die "unsupported file descriptor version '$fver'\n";
1964    
1965     $ftype == 5
1966     or die "unsupported file descriptor path type '$type'\n";
1967    
1968 root 1.37 (my $parent, $path) = dec_device_ $path, $type;
1969 root 1.1
1970     $path = $dec_path->($path, "file device without path");
1971    
1972     ($parent, $path)
1973     };
1974    
1975     if ($blocktype eq "file") {
1976     my ($parent, $path) = $dec_file->();
1977    
1978     $res .= "=file,<$parent>,$path";
1979    
1980     } elsif ($blocktype eq "vhd") {
1981     $device =~ s/^\x00{20}//s
1982     or die "virtualdisk has non-zero fields I don't understand\n";
1983    
1984 root 1.37 (my $parent, $device) = dec_device_ $device, $type;
1985 root 1.1
1986     $res .= "=vhd,<$parent>";
1987    
1988     } elsif ($blocktype eq "ramdisk") {
1989     my ($base, $size, $offset) = unpack "Q< Q< L<", substr $device, 0, 8 + 8 + 4, "";
1990     my ($subdev, $path) = $dec_file->();
1991    
1992     $res .= "=ramdisk,<$subdev>,$base,$size,$offset,$path";
1993    
1994     } else {
1995     die "unsupported block type '$blocktype'\n";
1996     }
1997    
1998     } elsif ($type eq "locate") {
1999     # mode, bcde_id, unknown, string
2000     # we assume locate has _either_ an element id _or_ a path, but not both
2001    
2002     my ($mode, $elem, $parent) = unpack "VVV", substr $device, 0, 4 * 3, "";
2003    
2004     if ($parent) {
2005     # not sure why this is an offset - it must come after the path
2006     $parent = substr $device, $parent - 4 * 3 - 4 * 4, 1e9, "";
2007 root 1.37 ($parent, my $tail) = dec_device_ $parent, $type;
2008 root 1.1 0 == length $tail
2009     or die "trailing data after locate device parent\n";
2010     } else {
2011     $parent = "null";
2012     }
2013    
2014     my $path = $device; $device = "";
2015     $path = $dec_path->($path, "device locate mode without path");
2016    
2017     $res .= "=<$parent>,";
2018    
2019     if ($mode == 0) { # "Element"
2020     !length $path
2021     or die "device locate mode 0 having non-empty path ($mode, $elem, $path)\n";
2022    
2023 root 1.37 $elem = dec_bcde_id $elem, $type;
2024 root 1.1 $res .= "element,$elem";
2025    
2026     } elsif ($mode == 1) { # "String"
2027     !$elem
2028     or die "device locate mode 1 having non-zero element\n";
2029    
2030     $res .= "path,$path";
2031     } else {
2032     # mode 2 maybe called "ElementChild" with element and parent device? example needed
2033     die "device locate mode '$mode' not supported\n";
2034     }
2035    
2036     } elsif ($type eq "vmbus") {
2037     my $type = dec_guid substr $device, 0, 16, "";
2038     my $instance = dec_guid substr $device, 0, 16, "";
2039    
2040     $device =~ s/^\x00{24}\z//
2041     or die "vmbus has non-zero fields I don't understand\n";
2042    
2043     $res .= "=$type,$instance";
2044    
2045     } else {
2046     die "unsupported device type '$type'\n";
2047     }
2048    
2049     warn "unexpected trailing device data($res), " . unpack "H*",$device
2050     if length $device;
2051     #length $device
2052     # and die "unexpected trailing device data\n";
2053    
2054     ($res, $tail)
2055     }
2056    
2057     # decode a full binary BCD device descriptor
2058 root 1.37 sub dec_device($$) {
2059     my ($device, $type) = @_;
2060 root 1.1
2061     $device = pack "H*", $device;
2062    
2063     my $guid = dec_guid substr $device, 0, 16, "";
2064     $guid = $guid eq "00000000-0000-0000-0000-000000000000"
2065     ? "" : "{$guid}";
2066    
2067     eval {
2068 root 1.37 my ($dev, $tail) = dec_device_ $device, $type;
2069 root 1.1
2070     $tail eq ""
2071     or die "unsupported trailing data after device descriptor\n";
2072    
2073     "$guid$dev"
2074     # } // scalar ((warn $@), "$guid$fallback")
2075     } // ($guid . "binary=" . unpack "H*", $device)
2076     }
2077    
2078     sub indexof($@) {
2079     my $value = shift;
2080    
2081     for (0 .. $#_) {
2082     $value eq $_[$_]
2083     and return $_;
2084     }
2085    
2086     undef
2087     }
2088    
2089     # encode the device portion after the GUID
2090 root 1.37 sub enc_device_($$);
2091     sub enc_device_($$) {
2092     my ($device, $type) = @_;
2093 root 1.1
2094     my $enc_path = sub {
2095     my $path = shift;
2096     $path =~ s/\//\\/g;
2097     (Encode::encode "UTF-16LE", $path) . "\x00\x00"
2098     };
2099    
2100     my $enc_file = sub {
2101     my ($parent, $path) = @_; # parent and path must already be encoded
2102    
2103     $path = $parent . $path;
2104    
2105     # fver 1, ftype 5
2106     pack "VVVa*", 1, 12 + length $path, 5, $path
2107     };
2108    
2109     my $parse_path = sub {
2110     s/^([\/\\][^<>"|?*\x00-\x1f]*)//
2111     or die "$_: invalid path\n";
2112    
2113     $enc_path->($1)
2114     };
2115    
2116     my $parse_parent = sub {
2117     my $parent;
2118    
2119     if (s/^<//) {
2120 root 1.37 ($parent, $_) = enc_device_ $_, $type;
2121 root 1.1 s/^>//
2122     or die "$device: syntax error: parent device not followed by '>'\n";
2123     } else {
2124     $parent = $NULL_DEVICE;
2125     }
2126    
2127     $parent
2128     };
2129    
2130     for ($device) {
2131     s/^([a-z]+)//
2132     or die "$_: device does not start with type string\n";
2133    
2134     my $type = $1;
2135     my $flags = s/^<([0-9a-fA-F]+)>// ? hex $1 : 0;
2136     my $payload;
2137    
2138     if ($type eq "binary") {
2139     s/^=([0-9a-fA-F]+)//
2140     or die "binary type must have a hex string argument\n";
2141    
2142     $payload = pack "H*", $1;
2143    
2144     } elsif ($type eq "null") {
2145     return ($NULL_DEVICE, $_);
2146    
2147     } elsif ($type eq "boot") {
2148     $payload = "\x00" x 56;
2149    
2150     } elsif ($type eq "partition" or $type eq "legacypartition") {
2151     s/^=//
2152     or die "$_: missing '=' after $type\n";
2153    
2154     my $parent = $parse_parent->();
2155    
2156     s/^,//
2157     or die "$_: comma missing after partition parent device\n";
2158    
2159     s/^([a-z]+),//
2160     or die "$_: partition does not start with block type (e.g. hd or vhd)\n";
2161     my $blocktype = $1;
2162    
2163     s/^([a-z]+),//
2164     or die "$_: partition block type not followed by partiton type\n";
2165     my $parttype = $1;
2166    
2167     my ($partdata, $diskdata);
2168    
2169     if ($parttype eq "mbr") {
2170     s/^([0-9a-f]{8}),//i
2171     or die "$_: partition mbr disk id malformed (must be e.g. 1234abcd)\n";
2172     $diskdata = pack "Vx12", hex $1;
2173    
2174     s/^([0-9]+)//
2175     or die "$_: partition number or offset is missing or malformed (must be decimal)\n";
2176    
2177     # the following works for both 64 bit offset and 32 bit partno
2178     $partdata = pack "Q< x8", $1;
2179    
2180     } elsif ($parttype eq "gpt") {
2181     s/^($RE_GUID),//
2182     or die "$_: partition disk guid missing or malformed\n";
2183     $diskdata = enc_guid $1;
2184    
2185     s/^($RE_GUID)//
2186     or die "$_: partition guid missing or malformed\n";
2187     $partdata = enc_guid $1;
2188    
2189     } elsif ($parttype eq "raw") {
2190     s/^([0-9]+)//
2191     or die "$_: partition disk number missing or malformed (must be decimal)\n";
2192    
2193     $partdata = pack "L< x12", $1;
2194    
2195     } else {
2196     die "$parttype: partition type not supported\n";
2197     }
2198    
2199     $payload = pack "a16 L< L< a16 a*",
2200     $partdata,
2201     (indexof $blocktype, @block_type),
2202     (indexof $parttype, @part_type),
2203     $diskdata,
2204     $parent;
2205    
2206     } elsif ($type eq "locate") {
2207     s/^=//
2208     or die "$_: missing '=' after $type\n";
2209    
2210     my ($mode, $elem, $path);
2211    
2212     my $parent = $parse_parent->();
2213    
2214     s/^,//
2215     or die "$_: missing comma after locate parent device\n";
2216    
2217     if (s/^element,//) {
2218 root 1.37 s/^([0-9a-z:]+)//i
2219 root 1.1 or die "$_ locate element must be either name or 8-digit hex id\n";
2220 root 1.37 $elem = enc_bcde_id $1, $type;
2221 root 1.1 $mode = 0;
2222     $path = $enc_path->("");
2223    
2224     } elsif (s/^path,//) {
2225     $mode = 1;
2226     $path = $parse_path->();
2227    
2228     } else {
2229     die "$_ second locate argument must be subtype (either element or path)\n";
2230     }
2231    
2232     if ($parent ne $NULL_DEVICE) {
2233     ($parent, $path) = (4 * 4 + 4 * 3 + length $path, "$path$parent");
2234     } else {
2235     $parent = 0;
2236     }
2237    
2238     $payload = pack "VVVa*", $mode, $elem, $parent, $path;
2239    
2240     } elsif ($type eq "block") {
2241     s/^=//
2242     or die "$_: missing '=' after $type\n";
2243    
2244     s/^([a-z]+),//
2245     or die "$_: block device does not start with block type (e.g. disk)\n";
2246     my $blocktype = $1;
2247    
2248     my $blockdata;
2249    
2250     if ($blocktype eq "file") {
2251     my $parent = $parse_parent->();
2252     s/^,// or die "$_: comma missing after file block device parent\n";
2253     my $path = $parse_path->();
2254    
2255     $blockdata = $enc_file->($parent, $path);
2256    
2257     } elsif ($blocktype eq "vhd") {
2258     $blockdata = "\x00" x 20; # ENOTUNDERSTOOD
2259     $blockdata .= $parse_parent->();
2260    
2261     } elsif ($blocktype eq "ramdisk") {
2262     my $parent = $parse_parent->();
2263    
2264     s/^,(\d+),(\d+),(\d+),//a
2265     or die "$_: missing ramdisk base,size,offset after ramdisk parent device\n";
2266    
2267     my ($base, $size, $offset) = ($1, $2, $3);
2268    
2269     my $path = $parse_path->();
2270    
2271     $blockdata = pack "Q< Q< L< a*", $base, $size, $offset, $enc_file->($parent, $path);
2272    
2273     } elsif ($blocktype eq "cdrom" or $blocktype eq "floppy") {
2274     # this is guesswork
2275     s/^(\d+)//a
2276     or die "$_: missing device number for cdrom\n";
2277     $blockdata = pack "V", $1;
2278    
2279     } else {
2280     die "$blocktype: unsupported block type (must be file, vhd, ramdisk, floppy, cdrom)\n";
2281     }
2282    
2283     $payload = pack "Va*",
2284     (indexof $blocktype, @block_type),
2285     $blockdata;
2286    
2287     } elsif ($type eq "vmbus") {
2288     s/^=($RE_GUID)//
2289     or die "$_: malformed or missing vmbus interface type guid\n";
2290     my $type = enc_guid $1;
2291     s/^,($RE_GUID)//
2292     or die "$_: malformed or missing vmbus interface instance guid\n";
2293     my $instance = enc_guid $1;
2294    
2295     $payload = pack "a16a16x24", $type, $instance;
2296    
2297 root 1.56 # } elsif ($type eq "udp") {
2298     # $payload = pack "Va16", 1, "12345678";
2299    
2300 root 1.1 } else {
2301     die "$type: not a supported device type (binary, null, boot, legacypartition, partition, block, locate)\n";
2302     }
2303    
2304     return (
2305     (pack "VVVVa*", (indexof $type, @dev_type), $flags, 16 + length $payload, 0, $payload),
2306     $_
2307     );
2308     }
2309     }
2310    
2311     # encode a full binary BCD device descriptor
2312 root 1.37 sub enc_device($$) {
2313     my ($device, $type) = @_;
2314 root 1.1
2315     my $guid = "\x00" x 16;
2316    
2317     if ($device =~ s/^\{([A-Za-z0-9\-]+)\}//) {
2318     $guid = enc_guid $1
2319     or die "$device: does not start with valid guid\n";
2320     }
2321    
2322 root 1.37 my ($descriptor, $tail) = enc_device_ $device, $type;
2323 root 1.1
2324     length $tail
2325     and die "$device: garbage after device descriptor\n";
2326    
2327     unpack "H*", $guid . $descriptor
2328     }
2329    
2330     # decode a registry hive into the BCD structure used by pbcdedit
2331     sub bcd_decode {
2332     my ($hive) = @_;
2333    
2334     my %bcd;
2335    
2336     my $objects = $hive->[1][1]{Objects}[1];
2337    
2338     while (my ($k, $v) = each %$objects) {
2339     my %kv;
2340     $v = $v->[1];
2341    
2342     $k = $bcd_objects{$k} // $k;
2343    
2344     my $type = $v->{Description}[0]{Type}[1];
2345    
2346     if ($type != $bcd_object_types{$k}) {
2347 root 1.37 $kv{type} = $bcd_types{$type} // sprintf "0x%08x", $type;
2348 root 1.1 }
2349    
2350     my $elems = $v->{Elements}[1];
2351    
2352     while (my ($k, $v) = each %$elems) {
2353     my $k = hex $k;
2354    
2355 root 1.37 my $v = $bcde_dec{$k & BCDE_FORMAT}->($v->[0]{Element}[1], $type);
2356     my $k = dec_bcde_id $k, $type;
2357 root 1.1
2358     $kv{$k} = $v;
2359     }
2360    
2361     $bcd{$k} = \%kv;
2362     }
2363    
2364     $bcd{meta} = { version => $JSON_VERSION };
2365    
2366     \%bcd
2367     }
2368    
2369     # encode a pbcdedit structure into a registry hive
2370     sub bcd_encode {
2371     my ($bcd) = @_;
2372    
2373     if (my $meta = $bcd->{meta}) {
2374     $meta->{version} eq $JSON_VERSION
2375     or die "BCD meta version ($meta->{version}) does not match executable version ($JSON_VERSION)\n";
2376     }
2377    
2378     my %objects;
2379     my %rbcd_types = reverse %bcd_types;
2380    
2381     while (my ($k, $v) = each %$bcd) {
2382     my %kv;
2383    
2384     next if $k eq "meta";
2385    
2386     $k = lc $k; # I know you windows types!
2387    
2388     my $type = $v->{type};
2389    
2390     if ($type) {
2391     $type = $type =~ /^(?:0x)[0-9a-fA-F]+$/
2392     ? hex $type
2393     : $rbcd_types{$type} // die "$type: unable to parse bcd object type\n";
2394     }
2395    
2396     my $guid = enc_wguid $k
2397     or die "$k: invalid bcd object identifier\n";
2398    
2399     # default type if not given
2400     $type //= $bcd_object_types{dec_wguid $guid} // die "$k: unable to deduce bcd object type\n";
2401    
2402     my %elem;
2403    
2404     while (my ($k, $v) = each %$v) {
2405     next if $k eq "type";
2406    
2407 root 1.37 $k = (enc_bcde_id $k, $type) // die "$k: invalid bcde element name or id\n";
2408 root 1.1 $elem{sprintf "%08x", $k} = [{
2409     Element => [ ($bcde_enc{$k & BCDE_FORMAT} // die "$k: unable to encode unknown bcd element type}")->($v)]
2410     }];
2411     }
2412    
2413     $guid = dec_guid $guid;
2414    
2415     $objects{"{$guid}"} = [undef, {
2416     Description => [{ Type => [dword => $type] }],
2417     Elements => [undef, \%elem],
2418     }];
2419     }
2420    
2421     [NewStoreRoot => [undef, {
2422     Description => [{
2423     KeyName => [sz => "BCD00000001"],
2424     System => [dword => 1],
2425     pbcdedit => [sz => $VERSION],
2426     # other values seen: GuidCache => ..., TreatAsSystem => 0x00000001
2427     }],
2428     Objects => [undef, \%objects],
2429     }]]
2430     }
2431    
2432     #############################################################################
2433 root 1.29 # edit instructions
2434 root 1.1
2435 root 1.6 sub bcd_edit_eval {
2436     package pbcdedit;
2437    
2438     our ($PATH, $BCD, $DEFAULT);
2439    
2440     eval shift;
2441     die "$@" if $@;
2442     }
2443    
2444     sub bcd_edit {
2445     my ($path, $bcd, @insns) = @_;
2446    
2447 root 1.36 my $default = $bcd->{"{bootmgr}"}{default};
2448 root 1.6
2449     # prepare "officially visible" variables
2450     local $pbcdedit::PATH = $path;
2451     local $pbcdedit::BCD = $bcd;
2452     local $pbcdedit::DEFAULT = $default;
2453    
2454     while (@insns) {
2455     my $insn = shift @insns;
2456    
2457     if ($insn eq "get") {
2458     my $object = shift @insns;
2459     my $elem = shift @insns;
2460    
2461 root 1.15 $object = $object eq "{default}" ? $default : dec_wguid enc_wguid $object;
2462 root 1.6
2463     print $bcd->{$object}{$elem}, "\n";
2464    
2465     } elsif ($insn eq "set") {
2466     my $object = shift @insns;
2467     my $elem = shift @insns;
2468     my $value = shift @insns;
2469    
2470 root 1.15 $object = $object eq "{default}" ? $default : dec_wguid enc_wguid $object;
2471 root 1.6
2472     $bcd->{$object}{$elem} = $value;
2473    
2474     } elsif ($insn eq "eval") {
2475 root 1.35 my $perl = shift @insns;
2476     bcd_edit_eval "#line 1 'eval'\n$perl";
2477 root 1.6
2478     } elsif ($insn eq "do") {
2479     my $path = shift @insns;
2480     my $file = file_load $path;
2481     bcd_edit_eval "#line 1 '$path'\n$file";
2482    
2483     } else {
2484     die "$insn: not a recognized instruction for edit/parse\n";
2485     }
2486     }
2487    
2488     }
2489    
2490     #############################################################################
2491 root 1.43 # other utilities
2492 root 1.6
2493 root 1.1 # json to stdout
2494     sub prjson($) {
2495     print $json_coder->encode ($_[0]);
2496     }
2497    
2498     # json from stdin
2499     sub rdjson() {
2500     my $json;
2501     1 while read STDIN, $json, 65536, length $json;
2502     $json_coder->decode ($json)
2503     }
2504    
2505 root 1.43 sub lsblk() {
2506     my $lsblk = $json_coder->decode (scalar qx<lsblk --json -o PATH,KNAME,MAJ:MIN,TYPE,PTTYPE,PTUUID,PARTUUID,LABEL,FSTYPE>);
2507    
2508     for my $dev (@{ $lsblk->{blockdevices} }) {
2509     if ($dev->{type} eq "part") {
2510 root 1.60
2511     # lsblk sometimes gives a bogus pttype, so we recreate it here
2512     $dev->{pttype} = $dev->{ptuuid} =~ /^$RE_GUID\z/
2513     ? "gpt" : "dos";
2514    
2515 root 1.43 if ($dev->{pttype} eq "gpt") {
2516     $dev->{bcd_device} = "partition=<null>,harddisk,gpt,$dev->{ptuuid},$dev->{partuuid}";
2517     } elsif ($dev->{pttype} eq "dos") { # why not "mbr" :(
2518     if ($dev->{partuuid} =~ /^([0-9a-f]{8})-([0-9a-f]{2})\z/i) {
2519     my ($diskid, $partno) = ($1, hex $2);
2520     $dev->{bcd_legacy_device} = "legacypartition=<null>,harddisk,mbr,$diskid,$partno";
2521     if (open my $fh, "/sys/class/block/$dev->{kname}/start") {
2522     my $start = 512 * readline $fh;
2523     $dev->{bcd_device} = "partition=<null>,harddisk,mbr,$diskid,$start";
2524     }
2525     }
2526     }
2527     }
2528     }
2529    
2530     $lsblk->{blockdevices}
2531     }
2532    
2533     sub prdev($$) {
2534     my ($path, $attribute) = @_;
2535    
2536     # rather than stat'ing and guessing how devices are encoded, we use lsblk for this
2537 root 1.60 my $mm = $json_coder->decode (scalar qx<lsblk -d -o MAJ:MIN -J \Q$path\E>)->{blockdevices}[0]{"maj:min"};
2538 root 1.43
2539     my $lsblk = lsblk;
2540    
2541     for my $dev (@$lsblk) {
2542     if ($dev->{"maj:min"} eq $mm && $dev->{$attribute}) {
2543     say $dev->{$attribute};
2544     exit 0;
2545     }
2546     }
2547    
2548     exit 1;
2549     }
2550    
2551     #############################################################################
2552     # command line parser
2553    
2554 root 1.1 our %CMD = (
2555     help => sub {
2556     require Pod::Usage;
2557     Pod::Usage::pod2usage (-verbose => 2);
2558     },
2559    
2560     objects => sub {
2561     my %rbcd_types = reverse %bcd_types;
2562     $_ = sprintf "%08x", $_ for values %rbcd_types;
2563    
2564     if ($_[0] eq "--json") {
2565     my %default_type = %bcd_object_types;
2566     $_ = sprintf "%08x", $_ for values %default_type;
2567    
2568     prjson {
2569     version => $JSON_VERSION,
2570     object_alias => \%bcd_objects,
2571     object_type => \%rbcd_types,
2572     object_default_type => \%default_type,
2573     };
2574     } else {
2575     my %rbcd_objects = reverse %bcd_objects;
2576    
2577     print "\n";
2578    
2579     printf "%-9s %s\n", "Type", "Alias";
2580     for my $tname (sort keys %rbcd_types) {
2581     printf "%-9s %s\n", $rbcd_types{$tname}, $tname;
2582     }
2583    
2584     print "\n";
2585    
2586     printf "%-39s %-23s %s\n", "Object GUID", "Alias", "(Hex) Default Type";
2587     for my $name (sort keys %rbcd_objects) {
2588 root 1.37 my $guid = $rbcd_objects{$name};
2589     my $type = $bcd_object_types{$name};
2590 root 1.1 my $tname = $bcd_types{$type};
2591    
2592     $type = $type ? sprintf "(%08x) %s", $type, $tname : "-";
2593    
2594     printf "%-39s %-23s %s\n", $guid, $name, $type;
2595     }
2596    
2597     print "\n";
2598     }
2599     },
2600    
2601     elements => sub {
2602     my $json = $_[0] eq "--json";
2603    
2604     my %format_name = (
2605     BCDE_FORMAT_DEVICE , "device",
2606     BCDE_FORMAT_STRING , "string",
2607     BCDE_FORMAT_GUID , "guid",
2608     BCDE_FORMAT_GUID_LIST , "guid list",
2609     BCDE_FORMAT_INTEGER , "integer",
2610     BCDE_FORMAT_BOOLEAN , "boolean",
2611     BCDE_FORMAT_INTEGER_LIST, "integer list",
2612     );
2613    
2614 root 1.40 my @element;
2615 root 1.1
2616 root 1.37 for my $class (sort keys %rbcde_byclass) {
2617     my $rbcde = $rbcde_byclass{$class};
2618    
2619     unless ($json) {
2620     print "\n";
2621     printf "Elements applicable to class(es): $class\n";
2622     printf "%-9s %-12s %s\n", "Element", "Format", "Name Alias";
2623     }
2624     for my $name (sort keys %$rbcde) {
2625     my $id = $rbcde->{$name};
2626     my $format = $format_name{$id & BCDE_FORMAT};
2627 root 1.1
2628 root 1.37 if ($json) {
2629 root 1.40 push @element, [$class, $id * 1, $format, $name];
2630 root 1.37 } else {
2631 root 1.40 $id = sprintf "%08x", $id;
2632 root 1.37 printf "%-9s %-12s %s\n", $id, $format, $name;
2633     }
2634 root 1.1 }
2635     }
2636     print "\n" unless $json;
2637    
2638     prjson {
2639     version => $JSON_VERSION,
2640 root 1.40 element => \@element,
2641 root 1.37 class => \@bcde_typeclass,
2642 root 1.1 } if $json;
2643    
2644     },
2645    
2646     export => sub {
2647     prjson bcd_decode regf_load shift;
2648     },
2649    
2650     import => sub {
2651     regf_save shift, bcd_encode rdjson;
2652     },
2653    
2654 root 1.56 create => sub {
2655     my $path = shift;
2656     my $stat = stat_get $path; # should actually be done at file load time
2657     my $bcd = { };
2658     bcd_edit $path, $bcd, @_;
2659     regf_save $path, bcd_encode $bcd;
2660     stat_set $path, $stat;
2661     },
2662    
2663 root 1.6 edit => sub {
2664     my $path = shift;
2665 root 1.56 my $stat = stat_get $path; # should actually be done at file load time
2666 root 1.6 my $bcd = bcd_decode regf_load $path;
2667     bcd_edit $path, $bcd, @_;
2668     regf_save $path, bcd_encode $bcd;
2669 root 1.56 stat_set $path, $stat;
2670 root 1.6 },
2671    
2672     parse => sub {
2673     my $path = shift;
2674     my $bcd = bcd_decode regf_load $path;
2675     bcd_edit $path, $bcd, @_;
2676     },
2677    
2678 root 1.1 "export-regf" => sub {
2679     prjson regf_load shift;
2680    
2681     },
2682    
2683     "import-regf" => sub {
2684     regf_save shift, rdjson;
2685     },
2686    
2687     lsblk => sub {
2688 root 1.44 my $json = $_[0] eq "--json";
2689    
2690 root 1.43 my $lsblk = lsblk;
2691    
2692 root 1.44 if ($json) {
2693     prjson $lsblk;
2694     } else {
2695     printf "%-10s %-8.8s %-6.6s %-3s %s\n", "DEVICE", "LABEL", "FSTYPE", "PT", "DEVICE DESCRIPTOR";
2696     for my $dev (@$lsblk) {
2697     for my $bcd ($dev->{bcd_device}, $dev->{bcd_legacy_device}) {
2698     printf "%-10s %-8.8s %-6.6s %-3s %s\n",
2699     $dev->{path}, $dev->{label}, $dev->{fstype}, $dev->{pttype}, $bcd
2700     if $bcd;
2701     }
2702 root 1.1 }
2703     }
2704     },
2705 root 1.37
2706 root 1.43 "bcd-device" => sub {
2707     prdev shift, "bcd_device";
2708     },
2709    
2710     "bcd-legacy-device" => sub {
2711     prdev shift, "bcd_legacy_device";
2712     },
2713    
2714 root 1.37 version => sub {
2715     print "\n",
2716     "PBCDEDIT version $VERSION, copyright 2019 Marc A. Lehmann <pbcdedit\@schmorp.de>.\n",
2717     "JSON schema version: $JSON_VERSION\n",
2718     "Licensed under the GNU General Public License Version 3.0, or any later version.\n",
2719     "\n",
2720     $CHANGELOG,
2721     "\n";
2722     },
2723 root 1.1 );
2724    
2725     my $cmd = shift;
2726    
2727     unless (exists $CMD{$cmd}) {
2728     warn "Usage: $0 subcommand args...\nTry $0 help\n";
2729     exit 126;
2730     }
2731    
2732     $CMD{$cmd}->(@ARGV);
2733