ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.10
Committed: Wed Aug 14 23:03:07 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.9: +2 -2 lines
Log Message:
*** empty log message ***

File Contents

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