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