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