ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.4
Committed: Wed Aug 14 22:03:36 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.3: +69 -3 lines
Log Message:
*** empty log message ***

File Contents

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