ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.6
Committed: Wed Aug 14 22:44:57 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.5: +159 -7 lines
Log Message:
*** empty log message ***

File Contents

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