ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.8
Committed: Wed Aug 14 22:53:16 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.7: +1 -1 lines
Log Message:
*** empty log message ***

File Contents

# Content
1 #!/usr/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 needed
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 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
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 =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 =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 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
658 =item locate=<block=vhd,<block=file,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,\win10.vhdx>>,element,path
659
660 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
677 =item {b097d2b2-bc00-11e9-8a9a-525400123456}block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,0,0,0,\boot.wim
678
679 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 I don't know what the purpose of the C<< <1> >> flag value is, but it
704 seems to be always there on this kind of entry.
705
706 If you have some good examples to add here, feel free to mail me.
707
708
709 =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 =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 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 # 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 regf_decode file_load $path
1215 }
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 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 # 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 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 "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