ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/pbcdedit/pbcdedit
Revision: 1.3
Committed: Wed Aug 14 21:27:54 2019 UTC (4 years, 9 months ago) by root
Branch: MAIN
Changes since 1.2: +25 -0 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 #todo
624
625 =item locate=<block=vhd,<block=file,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,\win10.vhdx>>,element,path
626
627 #todo
628
629 =item {b097d2b2-bc00-11e9-8a9a-525400123456}block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,0,0,0,\boot.wim
630
631 #todo
632
633
634 =head1 SEE ALSO
635
636 For ideas on what you can do, and some introductory material, try
637 L<http://www.mistyprojects.co.uk/documents/BCDEdit/index.html>.
638
639 For good reference on BCD objects and elements, see Geoff Chappels pages
640 at L<http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm>.
641
642 =head1 AUTHOR
643
644 Written by Marc A. Lehmann <pbcdedit@schmorp.de>.
645
646 =head1 REPORTING BUGS
647
648 Bugs can be reported dorectly tt he author at L<pcbedit@schmorp.de>.
649
650 =head1 BUGS AND SHORTCOMINGS
651
652 This should be a module. Of a series of modules, even.
653
654 Registry code should preserve classname and security descriptor data, and
655 whatever else is necessary to read and write any registry hive file.
656
657 I am also not happy with device descriptors being strings rather than a
658 data structure, but strings are probably better for command line usage. In
659 any case,. device descriptors could be converted by simply "splitting" at
660 "=" and "," into an array reference, recursively.
661
662 =head1 HOMEPAGE
663
664 Original versions of this program can be found at
665 L<http://software.schmorp.de/pkg/pbcdedit>.
666
667 =head1 COPYRIGHT
668
669 Copyright 2019 Marc A. Lehmann, licensed under GNU GPL version 3 or later,
670 see L<https://gnu.org/licenses/gpl.html>. This is free software: you are
671 free to change and redistribute it. There is NO WARRANTY, to the extent
672 permitted by law.
673
674 =cut
675
676 BEGIN { require "common/sense.pm"; common::sense->import } # common sense is optional, but recommended
677
678 use Data::Dump;
679 use Encode ();
680 use List::Util ();
681 use IO::Handle ();
682 use Time::HiRes ();
683
684 eval { unpack "Q", pack "Q", 1 }
685 or die "perl with 64 bit integer supported required.\n";
686
687 our $JSON = eval { require JSON::XS; JSON::XS:: }
688 // eval { require JSON::PP; JSON::PP:: }
689 // die "either JSON::XS or JSON::PP must be installed\n";
690
691 our $json_coder = $JSON->new->utf8->pretty->canonical->relaxed;
692
693 # hack used for debugging
694 sub xxd($$) {
695 open my $xxd, "| xxd | sed -e 's/^/\Q$_[0]\E: /'";
696 syswrite $xxd, $_[1];
697 }
698
699 # sources and resources used for this:
700 # registry:
701 # https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md
702 # http://amnesia.gtisc.gatech.edu/~moyix/suzibandit.ltd.uk/MSc/
703 # bcd:
704 # http://www.geoffchappell.com/notes/windows/boot/bcd/index.htm
705 # https://docs.microsoft.com/en-us/previous-versions/windows/hardware/design/dn653287(v=vs.85)
706 # bcd devices:
707 # reactos' boot/environ/include/bl.h
708 # windows .mof files
709
710 #############################################################################
711 # registry stuff
712
713 # we use a hardcoded securitya descriptor - full access for everyone
714 my $sid = pack "H*", "010100000000000100000000"; # S-1-1-0 everyone
715 my $ace = pack "C C S< L< a*", 0, 2, 8 + (length $sid), 0x000f003f, $sid; # type flags size mask sid
716 my $sacl = "";
717 my $dacl = pack "C x S< S< x2 a*", 2, 8 + (length $ace), 1, $ace; # rev size count ace*
718 my $sd = pack "C x S< L< L< L< L< a* a* a* a*",
719 # rev flags(SE_DACL_PRESENT SE_SELF_RELATIVE) owner group sacl dacl
720 1, 0x8004,
721 20 + (length $sacl) + (length $dacl),
722 20 + (length $sacl) + (length $dacl) + (length $sid),
723 0, 20,
724 $sacl, $dacl, $sid, $sid;
725 my $sk = pack "a2 x2 x4 x4 x4 L< a*", sk => (length $sd), $sd;
726
727 sub NO_OFS() { 0xffffffff } # file pointer "NULL" value
728
729 sub KEY_HIVE_ENTRY() { 0x0004 }
730 sub KEY_NO_DELETE () { 0x0008 }
731 sub KEY_COMP_NAME () { 0x0020 }
732
733 sub VALUE_COMP_NAME() { 0x0001 }
734
735 my @regf_typename = qw(
736 none sz expand_sz binary dword dword_be link multi_sz
737 resource_list full_resource_descriptor resource_requirements_list
738 qword qword_be
739 );
740
741 my %regf_dec_type = (
742 sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
743 expand_sz => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
744 link => sub { $_[0] =~ s/\x00\x00$//; Encode::decode "UTF-16LE", $_[0] },
745 multi_sz => sub { $_[0] =~ s/(?:\x00\x00)?\x00\x00$//; [ split /\x00/, (Encode::decode "UTF-16LE", $_[0]), -1 ] },
746 dword => sub { unpack "L<", shift },
747 dword_be => sub { unpack "L>", shift },
748 qword => sub { unpack "Q<", shift },
749 qword_be => sub { unpack "Q>", shift },
750 );
751
752 my %regf_enc_type = (
753 sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
754 expand_sz => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
755 link => sub { (Encode::encode "UTF-16LE", $_[0]) . "\x00\x00" },
756 multi_sz => sub { (join "", map +(Encode::encode "UTF-16LE", $_) . "\x00\x00", @{ $_[0] }) . "\x00\x00" },
757 dword => sub { pack "L<", shift },
758 dword_be => sub { pack "L>", shift },
759 qword => sub { pack "Q<", shift },
760 qword_be => sub { pack "Q>", shift },
761 );
762
763 # decode a registry hive
764 sub regf_decode($) {
765 my ($hive) = @_;
766
767 "regf" eq substr $hive, 0, 4
768 or die "not a registry hive\n";
769
770 my ($major, $minor) = unpack "\@20 L< L<", $hive;
771
772 $major == 1
773 or die "registry major version is not 1, but $major\n";
774
775 $minor >= 2 && $minor <= 6
776 or die "registry minor version is $minor, only 2 .. 6 are supported\n";
777
778 my $bins = substr $hive, 4096;
779
780 my $decode_key = sub {
781 my ($ofs) = @_;
782
783 my @res;
784
785 my ($sze, $sig) = unpack "\@$ofs l< a2", $bins;
786
787 $sze < 0
788 or die "key node points to unallocated cell\n";
789
790 $sig eq "nk"
791 or die "expected key node at $ofs, got '$sig'\n";
792
793 my ($flags, $snum, $sofs, $vnum, $vofs, $knamesze) = unpack "\@$ofs ( \@6 S< \@24 L< x4 L< x4 L< L< \@76 S< )", $bins;
794
795 my $kname = unpack "\@$ofs x80 a$knamesze", $bins;
796
797 # classnames, security descriptors
798 #my ($cofs, $xofs, $clen) = unpack "\@$ofs ( \@44 L< L< \@72 S< )", $bins;
799 #if ($cofs != NO_OFS && $clen) {
800 # #warn "cofs $cofs+$clen\n";
801 # xxd substr $bins, $cofs, 16;
802 #}
803
804 $kname = Encode::decode "UTF-16LE", $kname
805 unless $flags & KEY_COMP_NAME;
806
807 if ($vnum && $vofs != NO_OFS) {
808 for ($vofs += 4; $vnum--; $vofs += 4) {
809 my $kofs = unpack "\@$vofs L<", $bins;
810
811 my ($sze, $sig) = unpack "\@$kofs l< a2", $bins;
812
813 $sig eq "vk"
814 or die "key values list contains invalid node (expected vk got '$sig')\n";
815
816 my ($nsze, $dsze, $dofs, $type, $flags) = unpack "\@$kofs x4 x2 S< L< L< L< L<", $bins;
817
818 my $name = substr $bins, $kofs + 24, $nsze;
819
820 $name = Encode::decode "UTF-16LE", $name
821 unless $flags & VALUE_COMP_NAME;
822
823 my $data;
824 if ($dsze & 0x80000000) {
825 $data = substr $bins, $kofs + 12, $dsze & 0x7;
826 } elsif ($dsze > 16344 && $minor > 3) { # big data
827 my ($bsze, $bsig, $bnum, $bofs) = unpack "\@$dofs l< a2 S< L<", $bins;
828
829 for ($bofs += 4; $bnum--; $bofs += 4) {
830 my $dofs = unpack "\@$bofs L<", $bins;
831 my $dsze = unpack "\@$dofs l<", $bins;
832 $data .= substr $bins, $dofs + 4, -$dsze - 4;
833 }
834 $data = substr $data, 0, $dsze; # cells might be longer than data
835 } else {
836 $data = substr $bins, $dofs + 4, $dsze;
837 }
838
839 $type = $regf_typename[$type] if $type < @regf_typename;
840
841 $data = ($regf_dec_type{$type} || sub { unpack "H*", shift })
842 ->($data);
843
844 $res[0]{$name} = [$type, $data];
845 }
846 }
847
848 if ($sofs != NO_OFS) {
849 my $decode_key = __SUB__;
850
851 my $decode_subkeylist = sub {
852 my ($sofs) = @_;
853
854 my ($sze, $sig, $snum) = unpack "\@$sofs l< a2 S<", $bins;
855
856 if ($sig eq "ri") { # index root
857 for (my $lofs = $sofs + 8; $snum--; $lofs += 4) {
858 __SUB__->(unpack "\@$lofs L<", $bins);
859 }
860 } else {
861 my $inc;
862
863 if ($sig eq "li") { # subkey list
864 $inc = 4;
865 } elsif ($sig eq "lf" or $sig eq "lh") { # subkey list with name hints or hashes
866 $inc = 8;
867 } else {
868 die "expected subkey list at $sofs, found '$sig'\n";
869 }
870
871 for (my $lofs = $sofs + 8; $snum--; $lofs += $inc) {
872 my ($name, $data) = $decode_key->(unpack "\@$lofs L<", $bins);
873 $res[1]{$name} = $data;
874 }
875 }
876 };
877
878 $decode_subkeylist->($sofs);
879 }
880
881 ($kname, \@res);
882 };
883
884 my ($rootcell) = unpack "\@36 L<", $hive;
885
886 my ($rname, $root) = $decode_key->($rootcell);
887
888 [$rname, $root]
889 }
890
891 # return a binary windows fILETIME struct
892 sub filetime_now {
893 my ($s, $ms) = Time::HiRes::gettimeofday;
894
895 pack "Q<", $s = ($s * 1_000_000 + $ms) * 10 + 116_444_736_000_000_000
896 }
897
898 # encode a registry hive
899 sub regf_encode($) {
900 my ($hive) = @_;
901
902 my %typeval = map +($regf_typename[$_] => $_), 0 .. $#regf_typename;
903
904 # the filetime is apparently used to verify log file validity,
905 # so by generating a new timestamp the log files *should* automatically
906 # become invalidated and windows would "self-heal" them.
907 # (update: has been verified by reverse engineering)
908 # possibly the fact that the two sequence numbes match might also
909 # make windows think that the hive is not dirty and ignore logs.
910 # (update: has been verified by reverse engineering)
911
912 my $now = filetime_now;
913
914 # we only create a single hbin
915 my $bins = pack "a4 L< L< x8 a8 x4", "hbin", 0, 0, $now;
916
917 # append cell to $bind, return offset
918 my $cell = sub {
919 my ($cell) = @_;
920
921 my $res = length $bins;
922
923 $cell .= "\x00" while 4 != (7 & length $cell); # slow and ugly
924
925 $bins .= pack "l<", -(4 + length $cell);
926 $bins .= $cell;
927
928 $res
929 };
930
931 my $sdofs = $cell->($sk); # add a dummy security descriptor
932 my $sdref = 0; # refcount
933 substr $bins, $sdofs + 8, 4, pack "L<", $sdofs; # flink
934 substr $bins, $sdofs + 12, 4, pack "L<", $sdofs; # blink
935
936 my $encode_key = sub {
937 my ($kname, $kdata, $flags) = @_;
938 my ($values, $subkeys) = @$kdata;
939
940 if ($kname =~ /[^\x00-\xff]/) {
941 $kname = Encode::encode "UTF-16LE", $kname;
942 } else {
943 $flags |= KEY_COMP_NAME;
944 }
945
946 # encode subkeys
947
948 my @snames =
949 map $_->[1],
950 sort { $a->[0] cmp $b->[0] }
951 map [(uc $_), $_],
952 keys %$subkeys;
953
954 # normally, we'd have to encode each name, but we assume one char is at most two utf-16 cp's
955 my $maxsname = 4 * List::Util::max map length, @snames;
956
957 my @sofs = map __SUB__->($_, $subkeys->{$_}, 0), @snames;
958
959 # encode values
960 my $maxvname = 4 * List::Util::max map length, keys %$values;
961 my @vofs;
962 my $maxdsze = 0;
963
964 while (my ($vname, $v) = each %$values) {
965 my $flags = 0;
966
967 if ($vname =~ /[^\x00-\xff]/) {
968 $vname = Encode::encode "UTF-16LE", $kname;
969 } else {
970 $flags |= VALUE_COMP_NAME;
971 }
972
973 my ($type, $data) = @$v;
974
975 $data = ($regf_enc_type{$type} || sub { pack "H*", shift })->($data);
976
977 my $dsze;
978 my $dofs;
979
980 if (length $data <= 4) {
981 $dsze = 0x80000000 | length $data;
982 $dofs = unpack "L<", pack "a4", $data;
983 } else {
984 $dsze = length $data;
985 $dofs = $cell->($data);
986 }
987
988 $type = $typeval{$type} // ($type =~ /^[0-9]+\z/ ? $type : die "cannot encode type '$type'");
989
990 push @vofs, $cell->(pack "a2 S< L< L< L< S< x2 a*",
991 vk => (length $vname), $dsze, $dofs, $type, $flags, $vname);
992
993 $maxdsze = $dsze if $maxdsze < $dsze;
994 }
995
996 # encode key
997
998 my $slist = @sofs ? $cell->(pack "a2 S< L<*", li => (scalar @sofs), @sofs) : NO_OFS;
999 my $vlist = @vofs ? $cell->(pack "L<*", @vofs) : NO_OFS;
1000
1001 my $kdata = pack "
1002 a2 S< a8 x4 x4
1003 L< L< L< L< L< L<
1004 L< L< L< L< L< L<
1005 x4 S< S< a*
1006 ",
1007 nk => $flags, $now,
1008 (scalar @sofs), 0, $slist, NO_OFS, (scalar @vofs), $vlist,
1009 $sdofs, NO_OFS, $maxsname, 0, $maxvname, $maxdsze,
1010 length $kname, 0, $kname;
1011 ++$sdref;
1012
1013 my $res = $cell->($kdata);
1014
1015 substr $bins, $_ + 16, 4, pack "L<", $res
1016 for @sofs;
1017
1018 $res
1019 };
1020
1021 my ($rname, $root) = @$hive;
1022
1023 my $rofs = $encode_key->($rname, $root, KEY_HIVE_ENTRY | KEY_NO_DELETE); # 4 = root key
1024
1025 if (my $pad = -(length $bins) & 4095) {
1026 $pad -= 4;
1027 $bins .= pack "l< x$pad", $pad + 4;
1028 }
1029
1030 substr $bins, $sdofs + 16, 4, pack "L<", $sdref; # sd refcount
1031 substr $bins, 8, 4, pack "L<", length $bins;
1032
1033 my $base = pack "
1034 a4 L< L< a8 L< L< L< L<
1035 L< L< L<
1036 a64
1037 x396
1038 ",
1039 regf => 1974, 1974, $now, 1, 3, 0, 1,
1040 $rofs, length $bins, 1,
1041 (Encode::encode "UTF-16LE", "\\pbcdedit.reg");
1042
1043 my $chksum = List::Util::reduce { $a ^ $b } unpack "L<*", $base;
1044 $chksum = 0xfffffffe if $chksum == 0xffffffff;
1045 $chksum = 1 if $chksum == 0;
1046
1047 $base .= pack "L<", $chksum;
1048
1049 $base = pack "a* \@4095 x1", $base;
1050
1051 $base . $bins
1052 }
1053
1054 # load and parse registry from file
1055 sub regf_load($) {
1056 my ($path) = @_;
1057 open my $regf, "<:raw", $path
1058 or die "$path: $!\n";
1059 my $size = -s $regf;
1060 $size = read $regf, my $buf, $size
1061 or die "$path: short read\n";
1062
1063 regf_decode $buf
1064 }
1065
1066 # encode and save registry to file
1067 sub regf_save {
1068 my ($path, $hive) = @_;
1069
1070 $hive = regf_encode $hive;
1071
1072 open my $regf, ">:raw", "$path~"
1073 or die "$path~: $!\n";
1074 print $regf $hive
1075 or die "$path~: short write\n";
1076 $regf->sync;
1077 close $regf;
1078
1079 rename "$path~", $path;
1080 }
1081
1082 #############################################################################
1083 # bcd stuff
1084
1085 # human-readable alises for GUID object identifiers
1086 our %bcd_objects = (
1087 '{0ce4991b-e6b3-4b16-b23c-5e0d9250e5d9}' => '{emssettings}',
1088 '{1afa9c49-16ab-4a5c-4a90-212802da9460}' => '{resumeloadersettings}',
1089 '{1cae1eb7-a0df-4d4d-9851-4860e34ef535}' => '{default}',
1090 '{313e8eed-7098-4586-a9bf-309c61f8d449}' => '{kerneldbgsettings}',
1091 '{4636856e-540f-4170-a130-a84776f4c654}' => '{dbgsettings}',
1092 '{466f5a88-0af2-4f76-9038-095b170dc21c}' => '{ntldr}',
1093 '{5189b25c-5558-4bf2-bca4-289b11bd29e2}' => '{badmemory}',
1094 '{6efb52bf-1766-41db-a6b3-0ee5eff72bd7}' => '{bootloadersettings}',
1095 '{7254a080-1510-4e85-ac0f-e7fb3d444736}' => '{ssetupefi}',
1096 '{7ea2e1ac-2e61-4728-aaa3-896d9d0a9f0e}' => '{globalsettings}',
1097 '{7ff607e0-4395-11db-b0de-0800200c9a66}' => '{hypervisorsettings}',
1098 '{9dea862c-5cdd-4e70-acc1-f32b344d4795}' => '{bootmgr}',
1099 '{a1943bbc-ea85-487c-97c7-c9ede908a38a}' => '{ostargettemplatepcat}',
1100 '{a5a30fa2-3d06-4e9f-b5f4-a01df9d1fcba}' => '{fwbootmgr}',
1101 '{ae5534e0-a924-466c-b836-758539a3ee3a}' => '{ramdiskoptions}',
1102 '{b012b84d-c47c-4ed5-b722-c0c42163e569}' => '{ostargettemplateefi}',
1103 '{b2721d73-1db4-4c62-bf78-c548a880142d}' => '{memdiag}',
1104 '{cbd971bf-b7b8-4885-951a-fa03044f5d71}' => '{setuppcat}',
1105 '{fa926493-6f1c-4193-a414-58f0b2456d1e}' => '{current}',
1106 );
1107
1108 # default types
1109 our %bcd_object_types = (
1110 '{fwbootmgr}' => 0x10100001,
1111 '{bootmgr}' => 0x10100002,
1112 '{memdiag}' => 0x10200005,
1113 '{ntldr}' => 0x10300006,
1114 '{badmemory}' => 0x20100000,
1115 '{dbgsettings}' => 0x20100000,
1116 '{emssettings}' => 0x20100000,
1117 '{globalsettings}' => 0x20100000,
1118 '{bootloadersettings}' => 0x20200003,
1119 '{hypervisorsettings}' => 0x20200003,
1120 '{kerneldbgsettings}' => 0x20200003,
1121 '{resumeloadersettings}' => 0x20200004,
1122 '{ramdiskoptions}' => 0x30000000,
1123 );
1124
1125 # object types
1126 our %bcd_types = (
1127 0x10100001 => 'application::fwbootmgr',
1128 0x10100002 => 'application::bootmgr',
1129 0x10200003 => 'application::osloader',
1130 0x10200004 => 'application::resume',
1131 0x10100005 => 'application::memdiag',
1132 0x10100006 => 'application::ntldr',
1133 0x10100007 => 'application::setupldr',
1134 0x10400008 => 'application::bootsector',
1135 0x10400009 => 'application::startup',
1136 0x1020000a => 'application::bootapp',
1137 0x20100000 => 'settings',
1138 0x20200001 => 'inherit::fwbootmgr',
1139 0x20200002 => 'inherit::bootmgr',
1140 0x20200003 => 'inherit::osloader',
1141 0x20200004 => 'inherit::resume',
1142 0x20200005 => 'inherit::memdiag',
1143 0x20200006 => 'inherit::ntldr',
1144 0x20200007 => 'inherit::setupldr',
1145 0x20200008 => 'inherit::bootsector',
1146 0x20200009 => 'inherit::startup',
1147 0x20300000 => 'inherit::device',
1148 0x30000000 => 'device',
1149 );
1150
1151 our %rbcd_objects = reverse %bcd_objects;
1152
1153 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;
1154
1155 sub dec_guid($) {
1156 my ($p1, $p2, $p3, $p4, $p5) = unpack "VvvH4H12", shift;
1157 sprintf "%08x-%04x-%04x-%s-%s", $p1, $p2, $p3, $p4, $p5;
1158 }
1159
1160 sub enc_guid($) {
1161 $_[0] =~ /^$RE_GUID\z/o
1162 or return;
1163
1164 pack "VvvH4H12", hex $1, hex $2, hex $3, $4, $5
1165 }
1166
1167 # "wguid" are guids wrapped in curly braces {...} also supporting aliases
1168 sub dec_wguid($) {
1169 my $guid = "{" . (dec_guid shift) . "}";
1170
1171 $bcd_objects{$guid} // $guid
1172 }
1173
1174 sub enc_wguid($) {
1175 my ($guid) = @_;
1176
1177 if (my $alias = $rbcd_objects{$guid}) {
1178 $guid = $alias;
1179 }
1180
1181 $guid =~ /^\{($RE_GUID)\}\z/o
1182 or return;
1183
1184 enc_guid $1
1185 }
1186
1187 sub BCDE_CLASS () { 0xf0000000 }
1188 sub BCDE_CLASS_LIBRARY () { 0x10000000 }
1189 sub BCDE_CLASS_APPLICATION () { 0x20000000 }
1190 sub BCDE_CLASS_DEVICE () { 0x30000000 }
1191 sub BCDE_CLASS_TEMPLATE () { 0x40000000 }
1192
1193 sub BCDE_FORMAT () { 0x0f000000 }
1194 sub BCDE_FORMAT_DEVICE () { 0x01000000 }
1195 sub BCDE_FORMAT_STRING () { 0x02000000 }
1196 sub BCDE_FORMAT_GUID () { 0x03000000 }
1197 sub BCDE_FORMAT_GUID_LIST () { 0x04000000 }
1198 sub BCDE_FORMAT_INTEGER () { 0x05000000 }
1199 sub BCDE_FORMAT_BOOLEAN () { 0x06000000 }
1200 sub BCDE_FORMAT_INTEGER_LIST () { 0x07000000 }
1201
1202 sub dec_device;
1203 sub enc_device;
1204
1205 sub enc_integer($) {
1206 no warnings 'portable'; # ugh
1207 my $value = shift;
1208 $value = oct $value if $value =~ /^0[bBxX]/;
1209 unpack "H*", pack "Q<", $value
1210 }
1211
1212 our %bcde_dec = (
1213 BCDE_FORMAT_DEVICE , \&dec_device,
1214 # # for round-trip verification
1215 # BCDE_FORMAT_DEVICE , sub {
1216 # my $dev = dec_device $_[0];
1217 # $_[0] eq enc_device $dev
1218 # or die "bcd device decoding does not round trip for $_[0]\n";
1219 # $dev
1220 # },
1221 BCDE_FORMAT_STRING , sub { shift },
1222 BCDE_FORMAT_GUID , sub { dec_wguid enc_wguid shift },
1223 BCDE_FORMAT_GUID_LIST , sub { join " ", map dec_wguid enc_wguid $_, @{+shift} },
1224 BCDE_FORMAT_INTEGER , sub { unpack "Q", pack "a8", pack "H*", shift }, # integer might be 4 or 8 bytes - caused by ms coding bugs
1225 BCDE_FORMAT_BOOLEAN , sub { shift eq "00" ? 0 : 1 },
1226 BCDE_FORMAT_INTEGER_LIST, sub { join " ", unpack "Q*", pack "H*", shift }, # not sure if this cna be 4 bytes
1227 );
1228
1229 our %bcde_enc = (
1230 BCDE_FORMAT_DEVICE , sub { binary => enc_device shift },
1231 BCDE_FORMAT_STRING , sub { sz => shift },
1232 BCDE_FORMAT_GUID , sub { sz => "{" . (dec_guid enc_wguid shift) . "}" },
1233 BCDE_FORMAT_GUID_LIST , sub { multi_sz => [map "{" . (dec_guid enc_wguid $_) . "}", split /\s+/, shift ] },
1234 BCDE_FORMAT_INTEGER , sub { binary => enc_integer shift },
1235 BCDE_FORMAT_BOOLEAN , sub { binary => shift ? "01" : "00" },
1236 BCDE_FORMAT_INTEGER_LIST, sub { binary => join "", map enc_integer $_, split /\s+/, shift },
1237 );
1238
1239 # BCD Elements
1240 our %bcde = (
1241 0x11000001 => 'device',
1242 0x12000002 => 'path',
1243 0x12000004 => 'description',
1244 0x12000005 => 'locale',
1245 0x14000006 => 'inherit',
1246 0x15000007 => 'truncatememory',
1247 0x14000008 => 'recoverysequence',
1248 0x16000009 => 'recoveryenabled',
1249 0x1700000a => 'badmemorylist',
1250 0x1600000b => 'badmemoryaccess',
1251 0x1500000c => 'firstmegabytepolicy',
1252 0x1500000d => 'relocatephysical',
1253 0x1500000e => 'avoidlowmemory',
1254 0x1600000f => 'traditionalkseg',
1255 0x16000010 => 'bootdebug',
1256 0x15000011 => 'debugtype',
1257 0x15000012 => 'debugaddress',
1258 0x15000013 => 'debugport',
1259 0x15000014 => 'baudrate',
1260 0x15000015 => 'channel',
1261 0x12000016 => 'targetname',
1262 0x16000017 => 'noumex',
1263 0x15000018 => 'debugstart',
1264 0x12000019 => 'busparams',
1265 0x1500001a => 'hostip',
1266 0x1500001b => 'port',
1267 0x1600001c => 'dhcp',
1268 0x1200001d => 'key',
1269 0x1600001e => 'vm',
1270 0x16000020 => 'bootems',
1271 0x15000022 => 'emsport',
1272 0x15000023 => 'emsbaudrate',
1273 0x12000030 => 'loadoptions',
1274 0x16000040 => 'advancedoptions',
1275 0x16000041 => 'optionsedit',
1276 0x15000042 => 'keyringaddress',
1277 0x11000043 => 'bootstatdevice',
1278 0x12000044 => 'bootstatfilepath',
1279 0x16000045 => 'preservebootstat',
1280 0x16000046 => 'graphicsmodedisabled',
1281 0x15000047 => 'configaccesspolicy',
1282 0x16000048 => 'nointegritychecks',
1283 0x16000049 => 'testsigning',
1284 0x1200004a => 'fontpath',
1285 0x1500004b => 'integrityservices',
1286 0x1500004c => 'volumebandid',
1287 0x16000050 => 'extendedinput',
1288 0x15000051 => 'initialconsoleinput',
1289 0x15000052 => 'graphicsresolution',
1290 0x16000053 => 'restartonfailure',
1291 0x16000054 => 'highestmode',
1292 0x16000060 => 'isolatedcontext',
1293 0x15000065 => 'displaymessage',
1294 0x15000066 => 'displaymessageoverride',
1295 0x16000068 => 'nobootuxtext',
1296 0x16000069 => 'nobootuxprogress',
1297 0x1600006a => 'nobootuxfade',
1298 0x1600006b => 'bootuxreservepooldebug',
1299 0x1600006c => 'bootuxdisabled',
1300 0x1500006d => 'bootuxfadeframes',
1301 0x1600006e => 'bootuxdumpstats',
1302 0x1600006f => 'bootuxshowstats',
1303 0x16000071 => 'multibootsystem',
1304 0x16000072 => 'nokeyboard',
1305 0x15000073 => 'aliaswindowskey',
1306 0x16000074 => 'bootshutdowndisabled',
1307 0x15000075 => 'performancefrequency',
1308 0x15000076 => 'securebootrawpolicy',
1309 0x17000077 => 'allowedinmemorysettings',
1310 0x15000079 => 'bootuxtransitiontime',
1311 0x1600007a => 'mobilegraphics',
1312 0x1600007b => 'forcefipscrypto',
1313 0x1500007d => 'booterrorux',
1314 0x1600007e => 'flightsigning',
1315 0x1500007f => 'measuredbootlogformat',
1316 0x15000080 => 'displayrotation',
1317 0x15000081 => 'logcontrol',
1318 0x16000082 => 'nofirmwaresync',
1319 0x11000084 => 'windowssyspart',
1320 0x16000087 => 'numlock',
1321 0x22000001 => 'bpbstring',
1322 0x24000001 => 'displayorder',
1323 0x21000001 => 'filedevice',
1324 0x21000001 => 'osdevice',
1325 0x25000001 => 'passcount',
1326 0x26000001 => 'pxesoftreboot',
1327 0x22000002 => 'applicationname',
1328 0x24000002 => 'bootsequence',
1329 0x22000002 => 'filepath',
1330 0x22000002 => 'systemroot',
1331 0x25000002 => 'testmix',
1332 0x26000003 => 'cacheenable',
1333 0x26000003 => 'customsettings',
1334 0x23000003 => 'default',
1335 0x25000003 => 'failurecount',
1336 0x23000003 => 'resumeobject',
1337 0x26000004 => 'failuresenabled',
1338 0x26000004 => 'pae',
1339 0x26000004 => 'stampdisks',
1340 0x25000004 => 'testtofail',
1341 0x25000004 => 'timeout',
1342 0x21000005 => 'associatedosdevice',
1343 0x26000005 => 'cacheenable',
1344 0x26000005 => 'resume',
1345 0x25000005 => 'stridefailcount',
1346 0x26000006 => 'debugoptionenabled',
1347 0x25000006 => 'invcfailcount',
1348 0x23000006 => 'resumeobject',
1349 0x25000007 => 'bootux',
1350 0x25000007 => 'matsfailcount',
1351 0x24000007 => 'startupsequence',
1352 0x25000008 => 'bootmenupolicy',
1353 0x25000008 => 'randfailcount',
1354 0x25000009 => 'chckrfailcount',
1355 0x26000010 => 'detecthal',
1356 0x24000010 => 'toolsdisplayorder',
1357 0x22000011 => 'kernel',
1358 0x22000012 => 'hal',
1359 0x22000013 => 'dbgtransport',
1360 0x26000020 => 'displaybootmenu',
1361 0x25000020 => 'nx',
1362 0x26000021 => 'noerrordisplay',
1363 0x25000021 => 'pae',
1364 0x21000022 => 'bcddevice',
1365 0x26000022 => 'winpe',
1366 0x22000023 => 'bcdfilepath',
1367 0x26000024 => 'hormenabled',
1368 0x26000024 => 'hormenabled',
1369 0x26000024 => 'nocrashautoreboot',
1370 0x26000025 => 'hiberboot',
1371 0x26000025 => 'lastknowngood',
1372 0x26000026 => 'oslnointegritychecks',
1373 0x22000026 => 'passwordoverride',
1374 0x26000027 => 'osltestsigning',
1375 0x22000027 => 'pinpassphraseoverride',
1376 0x26000028 => 'processcustomactionsfirst',
1377 0x27000030 => 'customactions',
1378 0x26000030 => 'nolowmem',
1379 0x26000031 => 'persistbootsequence',
1380 0x25000031 => 'removememory',
1381 0x25000032 => 'increaseuserva',
1382 0x26000032 => 'skipstartupsequence',
1383 0x25000033 => 'perfmem',
1384 0x22000040 => 'fverecoveryurl',
1385 0x26000040 => 'vga',
1386 0x22000041 => 'fverecoverymessage',
1387 0x26000041 => 'quietboot',
1388 0x26000042 => 'novesa',
1389 0x26000043 => 'novga',
1390 0x25000050 => 'clustermodeaddressing',
1391 0x26000051 => 'usephysicaldestination',
1392 0x25000052 => 'restrictapiccluster',
1393 0x22000053 => 'evstore',
1394 0x26000054 => 'uselegacyapicmode',
1395 0x26000060 => 'onecpu',
1396 0x25000061 => 'numproc',
1397 0x26000062 => 'maxproc',
1398 0x25000063 => 'configflags',
1399 0x26000064 => 'maxgroup',
1400 0x26000065 => 'groupaware',
1401 0x25000066 => 'groupsize',
1402 0x26000070 => 'usefirmwarepcisettings',
1403 0x25000071 => 'msi',
1404 0x25000072 => 'pciexpress',
1405 0x25000080 => 'safeboot',
1406 0x26000081 => 'safebootalternateshell',
1407 0x26000090 => 'bootlog',
1408 0x26000091 => 'sos',
1409 0x260000a0 => 'debug',
1410 0x260000a1 => 'halbreakpoint',
1411 0x260000a2 => 'useplatformclock',
1412 0x260000a3 => 'forcelegacyplatform',
1413 0x260000a4 => 'useplatformtick',
1414 0x260000a5 => 'disabledynamictick',
1415 0x250000a6 => 'tscsyncpolicy',
1416 0x260000b0 => 'ems',
1417 0x250000c0 => 'forcefailure',
1418 0x250000c1 => 'driverloadfailurepolicy',
1419 0x250000c2 => 'bootmenupolicy',
1420 0x260000c3 => 'onetimeadvancedoptions',
1421 0x260000c4 => 'onetimeoptionsedit',
1422 0x250000e0 => 'bootstatuspolicy',
1423 0x260000e1 => 'disableelamdrivers',
1424 0x250000f0 => 'hypervisorlaunchtype',
1425 0x220000f1 => 'hypervisorpath',
1426 0x260000f2 => 'hypervisordebug',
1427 0x250000f3 => 'hypervisordebugtype',
1428 0x250000f4 => 'hypervisordebugport',
1429 0x250000f5 => 'hypervisorbaudrate',
1430 0x250000f6 => 'hypervisorchannel',
1431 0x250000f7 => 'bootux',
1432 0x260000f8 => 'hypervisordisableslat',
1433 0x220000f9 => 'hypervisorbusparams',
1434 0x250000fa => 'hypervisornumproc',
1435 0x250000fb => 'hypervisorrootprocpernode',
1436 0x260000fc => 'hypervisoruselargevtlb',
1437 0x250000fd => 'hypervisorhostip',
1438 0x250000fe => 'hypervisorhostport',
1439 0x250000ff => 'hypervisordebugpages',
1440 0x25000100 => 'tpmbootentropy',
1441 0x22000110 => 'hypervisorusekey',
1442 0x22000112 => 'hypervisorproductskutype',
1443 0x25000113 => 'hypervisorrootproc',
1444 0x26000114 => 'hypervisordhcp',
1445 0x25000115 => 'hypervisoriommupolicy',
1446 0x26000116 => 'hypervisorusevapic',
1447 0x22000117 => 'hypervisorloadoptions',
1448 0x25000118 => 'hypervisormsrfilterpolicy',
1449 0x25000119 => 'hypervisormmionxpolicy',
1450 0x2500011a => 'hypervisorschedulertype',
1451 0x25000120 => 'xsavepolicy',
1452 0x25000121 => 'xsaveaddfeature0',
1453 0x25000122 => 'xsaveaddfeature1',
1454 0x25000123 => 'xsaveaddfeature2',
1455 0x25000124 => 'xsaveaddfeature3',
1456 0x25000125 => 'xsaveaddfeature4',
1457 0x25000126 => 'xsaveaddfeature5',
1458 0x25000127 => 'xsaveaddfeature6',
1459 0x25000128 => 'xsaveaddfeature7',
1460 0x25000129 => 'xsaveremovefeature',
1461 0x2500012a => 'xsaveprocessorsmask',
1462 0x2500012b => 'xsavedisable',
1463 0x2500012c => 'kerneldebugtype',
1464 0x2200012d => 'kernelbusparams',
1465 0x2500012e => 'kerneldebugaddress',
1466 0x2500012f => 'kerneldebugport',
1467 0x25000130 => 'claimedtpmcounter',
1468 0x25000131 => 'kernelchannel',
1469 0x22000132 => 'kerneltargetname',
1470 0x25000133 => 'kernelhostip',
1471 0x25000134 => 'kernelport',
1472 0x26000135 => 'kerneldhcp',
1473 0x22000136 => 'kernelkey',
1474 0x22000137 => 'imchivename',
1475 0x21000138 => 'imcdevice',
1476 0x25000139 => 'kernelbaudrate',
1477 0x22000140 => 'mfgmode',
1478 0x26000141 => 'event',
1479 0x25000142 => 'vsmlaunchtype',
1480 0x25000144 => 'hypervisorenforcedcodeintegrity',
1481 0x26000145 => 'enablebootdebugpolicy',
1482 0x26000146 => 'enablebootorderclean',
1483 0x26000147 => 'enabledeviceid',
1484 0x26000148 => 'enableffuloader',
1485 0x26000149 => 'enableiuloader',
1486 0x2600014a => 'enablemassstorage',
1487 0x2600014b => 'enablerpmbprovisioning',
1488 0x2600014c => 'enablesecurebootpolicy',
1489 0x2600014d => 'enablestartcharge',
1490 0x2600014e => 'enableresettpm',
1491 0x21000150 => 'systemdatadevice',
1492 0x21000151 => 'osarcdevice',
1493 0x21000153 => 'osdatadevice',
1494 0x21000154 => 'bspdevice',
1495 0x21000155 => 'bspfilepath',
1496 0x26000202 => 'skipffumode',
1497 0x26000203 => 'forceffumode',
1498 0x25000510 => 'chargethreshold',
1499 0x26000512 => 'offmodecharging',
1500 0x25000aaa => 'bootflow',
1501 0x35000001 => 'ramdiskimageoffset',
1502 0x35000002 => 'ramdisktftpclientport',
1503 0x31000003 => 'ramdisksdidevice',
1504 0x32000004 => 'ramdisksdipath',
1505 0x35000005 => 'ramdiskimagelength',
1506 0x36000006 => 'exportascd',
1507 0x35000007 => 'ramdisktftpblocksize',
1508 0x35000008 => 'ramdisktftpwindowsize',
1509 0x36000009 => 'ramdiskmcenabled',
1510 0x3600000a => 'ramdiskmctftpfallback',
1511 0x3600000b => 'ramdisktftpvarwindow',
1512 0x45000001 => 'devicetype',
1513 0x42000002 => 'applicationrelativepath',
1514 0x42000003 => 'ramdiskdevicerelativepath',
1515 0x46000004 => 'omitosloaderelements',
1516 0x47000006 => 'elementstomigrate',
1517 0x46000010 => 'recoveryos',
1518 );
1519
1520 our %rbcde = reverse %bcde;
1521
1522 sub dec_bcde_id($) {
1523 $bcde{$_[0]} // sprintf "custom:%08x", $_[0]
1524 }
1525
1526 sub enc_bcde_id($) {
1527 $_[0] =~ /^custom:([0-9a-fA-F]{8}$)/
1528 ? hex $1
1529 : $rbcde{$_[0]}
1530 }
1531
1532 # decode/encode bcd device element - the horror, no documentaion
1533 # whatsoever, supercomplex, superinconsistent.
1534
1535 our @dev_type = qw(block type1 legacypartition serial udp boot partition vmbus locate);
1536 our @block_type = qw(harddisk floppy cdrom ramdisk type4 file vhd);
1537 our @part_type = qw(gpt mbr raw);
1538
1539 our $NULL_DEVICE = "\x00" x 16;
1540
1541 # biggest bitch to decode, ever
1542 # this decoded a device portion after the GUID
1543 sub dec_device_($);
1544 sub dec_device_($) {
1545 my ($device) = @_;
1546
1547 my $res;
1548
1549 my ($type, $flags, $length, $pad) = unpack "VVVV", substr $device, 0, 4 * 4, "";
1550
1551 $pad == 0
1552 or die "non-zero reserved field in device descriptor\n";
1553
1554 if ($length == 0 && $type == 0 && $flags == 0) {
1555 return ("null", $device);
1556 }
1557
1558 $length >= 16
1559 or die "device element size too small ($length)\n";
1560
1561 $type = $dev_type[$type] // die "$type: unknown device type\n";
1562 #d# warn "t<$type,$flags,$length,$pad>\n";#d#
1563
1564 $res .= $type;
1565 $res .= sprintf "<%x>", $flags if $flags;
1566
1567 my $tail = substr $device, $length - 4 * 4, 1e9, "";
1568
1569 $length == 4 * 4 + length $device
1570 or die "device length mismatch ($length != " . (16 + length $device) . ")\n";
1571
1572 my $dec_path = sub {
1573 my ($path, $error) = @_;
1574
1575 $path =~ /^((?:..)*)\x00\x00\z/s
1576 or die "$error\n";
1577
1578 $path = Encode::decode "UTF-16LE", $1;
1579
1580 $path
1581 };
1582
1583 if ($type eq "partition" or $type eq "legacypartition") {
1584 my $partdata = substr $device, 0, 16, "";
1585 my ($blocktype, $parttype) = unpack "VV", substr $device, 0, 4 * 2, "";
1586
1587 $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1588 $parttype = $part_type[$parttype] // die "unknown partition type\n";
1589
1590 my $diskid = substr $device, 0, 16, "";
1591
1592 $diskid = $parttype eq "gpt"
1593 ? dec_guid substr $diskid, 0, 16
1594 : sprintf "%08x", unpack "V", $diskid;
1595
1596 my $partid = $parttype eq "gpt" ? dec_guid $partdata
1597 : $type eq "partition" ? unpack "Q<", $partdata # byte offset to partition start
1598 : unpack "L<", $partdata; # partition number, one-based
1599
1600 (my $parent, $device) = dec_device_ $device;
1601
1602 $res .= "=";
1603 $res .= "<$parent>";
1604 $res .= ",$blocktype,$parttype,$diskid,$partid";
1605
1606 # PartitionType (gpt, mbr, raw)
1607 # guid | partsig | disknumber
1608
1609 } elsif ($type eq "boot") {
1610 $device =~ s/^\x00{56}\z//
1611 or die "boot device type with extra data not supported\n";
1612
1613 } elsif ($type eq "block") {
1614 my $blocktype = unpack "V", substr $device, 0, 4, "";
1615
1616 $blocktype = $block_type[$blocktype] // die "unknown block device type '$blocktype'\n";
1617
1618 # decode a "file path" structure
1619 my $dec_file = sub {
1620 my ($fver, $flen, $ftype) = unpack "VVV", substr $device, 0, 4 * 3, "";
1621
1622 my $path = substr $device, 0, $flen - 12, "";
1623
1624 $fver == 1
1625 or die "unsupported file descriptor version '$fver'\n";
1626
1627 $ftype == 5
1628 or die "unsupported file descriptor path type '$type'\n";
1629
1630 (my $parent, $path) = dec_device_ $path;
1631
1632 $path = $dec_path->($path, "file device without path");
1633
1634 ($parent, $path)
1635 };
1636
1637 if ($blocktype eq "file") {
1638 my ($parent, $path) = $dec_file->();
1639
1640 $res .= "=file,<$parent>,$path";
1641
1642 } elsif ($blocktype eq "vhd") {
1643 $device =~ s/^\x00{20}//s
1644 or die "virtualdisk has non-zero fields I don't understand\n";
1645
1646 (my $parent, $device) = dec_device_ $device;
1647
1648 $res .= "=vhd,<$parent>";
1649
1650 } elsif ($blocktype eq "ramdisk") {
1651 my ($base, $size, $offset) = unpack "Q< Q< L<", substr $device, 0, 8 + 8 + 4, "";
1652 my ($subdev, $path) = $dec_file->();
1653
1654 $res .= "=ramdisk,<$subdev>,$base,$size,$offset,$path";
1655
1656 } else {
1657 die "unsupported block type '$blocktype'\n";
1658 }
1659
1660 } elsif ($type eq "locate") {
1661 # mode, bcde_id, unknown, string
1662 # we assume locate has _either_ an element id _or_ a path, but not both
1663
1664 my ($mode, $elem, $parent) = unpack "VVV", substr $device, 0, 4 * 3, "";
1665
1666 if ($parent) {
1667 # not sure why this is an offset - it must come after the path
1668 $parent = substr $device, $parent - 4 * 3 - 4 * 4, 1e9, "";
1669 ($parent, my $tail) = dec_device_ $parent;
1670 0 == length $tail
1671 or die "trailing data after locate device parent\n";
1672 } else {
1673 $parent = "null";
1674 }
1675
1676 my $path = $device; $device = "";
1677 $path = $dec_path->($path, "device locate mode without path");
1678
1679 $res .= "=<$parent>,";
1680
1681 if ($mode == 0) { # "Element"
1682 !length $path
1683 or die "device locate mode 0 having non-empty path ($mode, $elem, $path)\n";
1684
1685 $elem = dec_bcde_id $elem;
1686 $res .= "element,$elem";
1687
1688 } elsif ($mode == 1) { # "String"
1689 !$elem
1690 or die "device locate mode 1 having non-zero element\n";
1691
1692 $res .= "path,$path";
1693 } else {
1694 # mode 2 maybe called "ElementChild" with element and parent device? example needed
1695 die "device locate mode '$mode' not supported\n";
1696 }
1697
1698 } elsif ($type eq "vmbus") {
1699 my $type = dec_guid substr $device, 0, 16, "";
1700 my $instance = dec_guid substr $device, 0, 16, "";
1701
1702 $device =~ s/^\x00{24}\z//
1703 or die "vmbus has non-zero fields I don't understand\n";
1704
1705 $res .= "=$type,$instance";
1706
1707 } else {
1708 die "unsupported device type '$type'\n";
1709 }
1710
1711 warn "unexpected trailing device data($res), " . unpack "H*",$device
1712 if length $device;
1713 #length $device
1714 # and die "unexpected trailing device data\n";
1715
1716 ($res, $tail)
1717 }
1718
1719 # decode a full binary BCD device descriptor
1720 sub dec_device($) {
1721 my ($device) = @_;
1722
1723 $device = pack "H*", $device;
1724
1725 my $guid = dec_guid substr $device, 0, 16, "";
1726 $guid = $guid eq "00000000-0000-0000-0000-000000000000"
1727 ? "" : "{$guid}";
1728
1729 eval {
1730 my ($dev, $tail) = dec_device_ $device;
1731
1732 $tail eq ""
1733 or die "unsupported trailing data after device descriptor\n";
1734
1735 "$guid$dev"
1736 # } // scalar ((warn $@), "$guid$fallback")
1737 } // ($guid . "binary=" . unpack "H*", $device)
1738 }
1739
1740 sub indexof($@) {
1741 my $value = shift;
1742
1743 for (0 .. $#_) {
1744 $value eq $_[$_]
1745 and return $_;
1746 }
1747
1748 undef
1749 }
1750
1751 # encode the device portion after the GUID
1752 sub enc_device_;
1753 sub enc_device_ {
1754 my ($device) = @_;
1755
1756 my $enc_path = sub {
1757 my $path = shift;
1758 $path =~ s/\//\\/g;
1759 (Encode::encode "UTF-16LE", $path) . "\x00\x00"
1760 };
1761
1762 my $enc_file = sub {
1763 my ($parent, $path) = @_; # parent and path must already be encoded
1764
1765 $path = $parent . $path;
1766
1767 # fver 1, ftype 5
1768 pack "VVVa*", 1, 12 + length $path, 5, $path
1769 };
1770
1771 my $parse_path = sub {
1772 s/^([\/\\][^<>"|?*\x00-\x1f]*)//
1773 or die "$_: invalid path\n";
1774
1775 $enc_path->($1)
1776 };
1777
1778 my $parse_parent = sub {
1779 my $parent;
1780
1781 if (s/^<//) {
1782 ($parent, $_) = enc_device_ $_;
1783 s/^>//
1784 or die "$device: syntax error: parent device not followed by '>'\n";
1785 } else {
1786 $parent = $NULL_DEVICE;
1787 }
1788
1789 $parent
1790 };
1791
1792 for ($device) {
1793 s/^([a-z]+)//
1794 or die "$_: device does not start with type string\n";
1795
1796 my $type = $1;
1797 my $flags = s/^<([0-9a-fA-F]+)>// ? hex $1 : 0;
1798 my $payload;
1799
1800 if ($type eq "binary") {
1801 s/^=([0-9a-fA-F]+)//
1802 or die "binary type must have a hex string argument\n";
1803
1804 $payload = pack "H*", $1;
1805
1806 } elsif ($type eq "null") {
1807 return ($NULL_DEVICE, $_);
1808
1809 } elsif ($type eq "boot") {
1810 $payload = "\x00" x 56;
1811
1812 } elsif ($type eq "partition" or $type eq "legacypartition") {
1813 s/^=//
1814 or die "$_: missing '=' after $type\n";
1815
1816 my $parent = $parse_parent->();
1817
1818 s/^,//
1819 or die "$_: comma missing after partition parent device\n";
1820
1821 s/^([a-z]+),//
1822 or die "$_: partition does not start with block type (e.g. hd or vhd)\n";
1823 my $blocktype = $1;
1824
1825 s/^([a-z]+),//
1826 or die "$_: partition block type not followed by partiton type\n";
1827 my $parttype = $1;
1828
1829 my ($partdata, $diskdata);
1830
1831 if ($parttype eq "mbr") {
1832 s/^([0-9a-f]{8}),//i
1833 or die "$_: partition mbr disk id malformed (must be e.g. 1234abcd)\n";
1834 $diskdata = pack "Vx12", hex $1;
1835
1836 s/^([0-9]+)//
1837 or die "$_: partition number or offset is missing or malformed (must be decimal)\n";
1838
1839 # the following works for both 64 bit offset and 32 bit partno
1840 $partdata = pack "Q< x8", $1;
1841
1842 } elsif ($parttype eq "gpt") {
1843 s/^($RE_GUID),//
1844 or die "$_: partition disk guid missing or malformed\n";
1845 $diskdata = enc_guid $1;
1846
1847 s/^($RE_GUID)//
1848 or die "$_: partition guid missing or malformed\n";
1849 $partdata = enc_guid $1;
1850
1851 } elsif ($parttype eq "raw") {
1852 s/^([0-9]+)//
1853 or die "$_: partition disk number missing or malformed (must be decimal)\n";
1854
1855 $partdata = pack "L< x12", $1;
1856
1857 } else {
1858 die "$parttype: partition type not supported\n";
1859 }
1860
1861 $payload = pack "a16 L< L< a16 a*",
1862 $partdata,
1863 (indexof $blocktype, @block_type),
1864 (indexof $parttype, @part_type),
1865 $diskdata,
1866 $parent;
1867
1868 } elsif ($type eq "locate") {
1869 s/^=//
1870 or die "$_: missing '=' after $type\n";
1871
1872 my ($mode, $elem, $path);
1873
1874 my $parent = $parse_parent->();
1875
1876 s/^,//
1877 or die "$_: missing comma after locate parent device\n";
1878
1879 if (s/^element,//) {
1880 s/^([0-9a-z]+)//i
1881 or die "$_ locate element must be either name or 8-digit hex id\n";
1882 $elem = enc_bcde_id $1;
1883 $mode = 0;
1884 $path = $enc_path->("");
1885
1886 } elsif (s/^path,//) {
1887 $mode = 1;
1888 $path = $parse_path->();
1889
1890 } else {
1891 die "$_ second locate argument must be subtype (either element or path)\n";
1892 }
1893
1894 if ($parent ne $NULL_DEVICE) {
1895 ($parent, $path) = (4 * 4 + 4 * 3 + length $path, "$path$parent");
1896 } else {
1897 $parent = 0;
1898 }
1899
1900 $payload = pack "VVVa*", $mode, $elem, $parent, $path;
1901
1902 } elsif ($type eq "block") {
1903 s/^=//
1904 or die "$_: missing '=' after $type\n";
1905
1906 s/^([a-z]+),//
1907 or die "$_: block device does not start with block type (e.g. disk)\n";
1908 my $blocktype = $1;
1909
1910 my $blockdata;
1911
1912 if ($blocktype eq "file") {
1913 my $parent = $parse_parent->();
1914 s/^,// or die "$_: comma missing after file block device parent\n";
1915 my $path = $parse_path->();
1916
1917 $blockdata = $enc_file->($parent, $path);
1918
1919 } elsif ($blocktype eq "vhd") {
1920 $blockdata = "\x00" x 20; # ENOTUNDERSTOOD
1921 $blockdata .= $parse_parent->();
1922
1923 } elsif ($blocktype eq "ramdisk") {
1924 my $parent = $parse_parent->();
1925
1926 s/^,(\d+),(\d+),(\d+),//a
1927 or die "$_: missing ramdisk base,size,offset after ramdisk parent device\n";
1928
1929 my ($base, $size, $offset) = ($1, $2, $3);
1930
1931 my $path = $parse_path->();
1932
1933 $blockdata = pack "Q< Q< L< a*", $base, $size, $offset, $enc_file->($parent, $path);
1934
1935 } elsif ($blocktype eq "cdrom" or $blocktype eq "floppy") {
1936 # this is guesswork
1937 s/^(\d+)//a
1938 or die "$_: missing device number for cdrom\n";
1939 $blockdata = pack "V", $1;
1940
1941 } else {
1942 die "$blocktype: unsupported block type (must be file, vhd, ramdisk, floppy, cdrom)\n";
1943 }
1944
1945 $payload = pack "Va*",
1946 (indexof $blocktype, @block_type),
1947 $blockdata;
1948
1949 } elsif ($type eq "vmbus") {
1950 s/^=($RE_GUID)//
1951 or die "$_: malformed or missing vmbus interface type guid\n";
1952 my $type = enc_guid $1;
1953 s/^,($RE_GUID)//
1954 or die "$_: malformed or missing vmbus interface instance guid\n";
1955 my $instance = enc_guid $1;
1956
1957 $payload = pack "a16a16x24", $type, $instance;
1958
1959 } else {
1960 die "$type: not a supported device type (binary, null, boot, legacypartition, partition, block, locate)\n";
1961 }
1962
1963 return (
1964 (pack "VVVVa*", (indexof $type, @dev_type), $flags, 16 + length $payload, 0, $payload),
1965 $_
1966 );
1967 }
1968 }
1969
1970 # encode a full binary BCD device descriptor
1971 sub enc_device {
1972 my ($device) = @_;
1973
1974 my $guid = "\x00" x 16;
1975
1976 if ($device =~ s/^\{([A-Za-z0-9\-]+)\}//) {
1977 $guid = enc_guid $1
1978 or die "$device: does not start with valid guid\n";
1979 }
1980
1981 my ($descriptor, $tail) = enc_device_ $device;
1982
1983 length $tail
1984 and die "$device: garbage after device descriptor\n";
1985
1986 unpack "H*", $guid . $descriptor
1987 }
1988
1989 # decode a registry hive into the BCD structure used by pbcdedit
1990 sub bcd_decode {
1991 my ($hive) = @_;
1992
1993 my %bcd;
1994
1995 my $objects = $hive->[1][1]{Objects}[1];
1996
1997 while (my ($k, $v) = each %$objects) {
1998 my %kv;
1999 $v = $v->[1];
2000
2001 $k = $bcd_objects{$k} // $k;
2002
2003 my $type = $v->{Description}[0]{Type}[1];
2004
2005 if ($type != $bcd_object_types{$k}) {
2006 $type = $bcd_types{$type} // sprintf "0x%08x", $type;
2007 $kv{type} = $type;
2008 }
2009
2010 my $elems = $v->{Elements}[1];
2011
2012 while (my ($k, $v) = each %$elems) {
2013 my $k = hex $k;
2014
2015 my $v = $bcde_dec{$k & BCDE_FORMAT}->($v->[0]{Element}[1]);
2016 my $k = dec_bcde_id $k;
2017
2018 $kv{$k} = $v;
2019 }
2020
2021 $bcd{$k} = \%kv;
2022 }
2023
2024 $bcd{meta} = { version => $JSON_VERSION };
2025
2026 \%bcd
2027 }
2028
2029 # encode a pbcdedit structure into a registry hive
2030 sub bcd_encode {
2031 my ($bcd) = @_;
2032
2033 if (my $meta = $bcd->{meta}) {
2034 $meta->{version} eq $JSON_VERSION
2035 or die "BCD meta version ($meta->{version}) does not match executable version ($JSON_VERSION)\n";
2036 }
2037
2038 my %objects;
2039 my %rbcd_types = reverse %bcd_types;
2040
2041 while (my ($k, $v) = each %$bcd) {
2042 my %kv;
2043
2044 next if $k eq "meta";
2045
2046 $k = lc $k; # I know you windows types!
2047
2048 my $type = $v->{type};
2049
2050 if ($type) {
2051 $type = $type =~ /^(?:0x)[0-9a-fA-F]+$/
2052 ? hex $type
2053 : $rbcd_types{$type} // die "$type: unable to parse bcd object type\n";
2054 }
2055
2056 my $guid = enc_wguid $k
2057 or die "$k: invalid bcd object identifier\n";
2058
2059 # default type if not given
2060 $type //= $bcd_object_types{dec_wguid $guid} // die "$k: unable to deduce bcd object type\n";
2061
2062 my %elem;
2063
2064 while (my ($k, $v) = each %$v) {
2065 next if $k eq "type";
2066
2067 $k = (enc_bcde_id $k) // die "$k: invalid bcde element name or id\n";
2068 $elem{sprintf "%08x", $k} = [{
2069 Element => [ ($bcde_enc{$k & BCDE_FORMAT} // die "$k: unable to encode unknown bcd element type}")->($v)]
2070 }];
2071 }
2072
2073 $guid = dec_guid $guid;
2074
2075 $objects{"{$guid}"} = [undef, {
2076 Description => [{ Type => [dword => $type] }],
2077 Elements => [undef, \%elem],
2078 }];
2079 }
2080
2081 [NewStoreRoot => [undef, {
2082 Description => [{
2083 KeyName => [sz => "BCD00000001"],
2084 System => [dword => 1],
2085 pbcdedit => [sz => $VERSION],
2086 # other values seen: GuidCache => ..., TreatAsSystem => 0x00000001
2087 }],
2088 Objects => [undef, \%objects],
2089 }]]
2090 }
2091
2092 #############################################################################
2093
2094 # json to stdout
2095 sub prjson($) {
2096 print $json_coder->encode ($_[0]);
2097 }
2098
2099 # json from stdin
2100 sub rdjson() {
2101 my $json;
2102 1 while read STDIN, $json, 65536, length $json;
2103 $json_coder->decode ($json)
2104 }
2105
2106 # all subcommands
2107 our %CMD = (
2108 help => sub {
2109 require Pod::Usage;
2110 Pod::Usage::pod2usage (-verbose => 2);
2111 },
2112
2113 objects => sub {
2114 my %rbcd_types = reverse %bcd_types;
2115 $_ = sprintf "%08x", $_ for values %rbcd_types;
2116
2117 if ($_[0] eq "--json") {
2118 my %default_type = %bcd_object_types;
2119 $_ = sprintf "%08x", $_ for values %default_type;
2120
2121 prjson {
2122 version => $JSON_VERSION,
2123 object_alias => \%bcd_objects,
2124 object_type => \%rbcd_types,
2125 object_default_type => \%default_type,
2126 };
2127 } else {
2128 my %rbcd_objects = reverse %bcd_objects;
2129
2130 print "\n";
2131
2132 printf "%-9s %s\n", "Type", "Alias";
2133 for my $tname (sort keys %rbcd_types) {
2134 printf "%-9s %s\n", $rbcd_types{$tname}, $tname;
2135 }
2136
2137 print "\n";
2138
2139 printf "%-39s %-23s %s\n", "Object GUID", "Alias", "(Hex) Default Type";
2140 for my $name (sort keys %rbcd_objects) {
2141 my $guid = $rbcd_objects{$name};
2142 my $type = $bcd_object_types{$name};
2143 my $tname = $bcd_types{$type};
2144
2145 $type = $type ? sprintf "(%08x) %s", $type, $tname : "-";
2146
2147 printf "%-39s %-23s %s\n", $guid, $name, $type;
2148 }
2149
2150 print "\n";
2151 }
2152 },
2153
2154 elements => sub {
2155 my $json = $_[0] eq "--json";
2156
2157 my %format_name = (
2158 BCDE_FORMAT_DEVICE , "device",
2159 BCDE_FORMAT_STRING , "string",
2160 BCDE_FORMAT_GUID , "guid",
2161 BCDE_FORMAT_GUID_LIST , "guid list",
2162 BCDE_FORMAT_INTEGER , "integer",
2163 BCDE_FORMAT_BOOLEAN , "boolean",
2164 BCDE_FORMAT_INTEGER_LIST, "integer list",
2165 );
2166 my %rbcde = reverse %bcde;
2167 $_ = sprintf "%08x", $_ for values %rbcde;
2168
2169 my %element;
2170
2171 unless ($json) {
2172 print "\n";
2173 printf "%-9s %-12s %s\n", "Element", "Format", "Name Alias";
2174 }
2175 for my $name (sort keys %rbcde) {
2176 my $id = $rbcde{$name};
2177 my $format = $format_name{(hex $id) & BCDE_FORMAT};
2178
2179 if ($json) {
2180 $element{$id} = [$format, $name];
2181 } else {
2182 printf "%-9s %-12s %s\n", $id, $format, $name;
2183 }
2184 }
2185 print "\n" unless $json;
2186
2187 prjson {
2188 version => $JSON_VERSION,
2189 element => \%element,
2190 } if $json;
2191
2192 },
2193
2194 export => sub {
2195 prjson bcd_decode regf_load shift;
2196 },
2197
2198 import => sub {
2199 regf_save shift, bcd_encode rdjson;
2200 },
2201
2202 "export-regf" => sub {
2203 prjson regf_load shift;
2204
2205 },
2206
2207 "import-regf" => sub {
2208 regf_save shift, rdjson;
2209 },
2210
2211 lsblk => sub {
2212 printf "%-10s %-8.8s %-6.6s %-3s %s\n", "DEVICE", "LABEL", "FSTYPE", "PT", "DEVICE DESCRIPTOR";
2213
2214 my $lsblk = $json_coder->decode (scalar qx<lsblk --json -o PATH,KNAME,TYPE,PTTYPE,PTUUID,PARTUUID,LABEL,FSTYPE>);
2215
2216 for my $dev (@{ $lsblk->{blockdevices} }) {
2217 my $pr = sub {
2218 printf "%-10s %-8.8s %-6.6s %-3s %s\n",
2219 $dev->{path}, $dev->{label}, $dev->{fstype}, $dev->{pttype}, $_[0];
2220 };
2221
2222 if ($dev->{type} eq "part") {
2223 if ($dev->{pttype} eq "gpt") {
2224 $pr->("partition=<null>,harddisk,gpt,$dev->{ptuuid},$dev->{partuuid}");
2225 } elsif ($dev->{pttype} eq "dos") { # why not "mbr" :(
2226 if ($dev->{partuuid} =~ /^([0-9a-f]{8})-([0-9a-f]{2})\z/i) {
2227 my ($diskid, $partno) = ($1, hex $2);
2228 $pr->("legacypartition=<null>,harddisk,mbr,$diskid,$partno");
2229 if (open my $fh, "/sys/class/block/$dev->{kname}/start") {
2230 my $start = 512 * readline $fh;
2231 $pr->("partition=<null>,harddisk,mbr,$diskid,$start");
2232 }
2233 }
2234 }
2235 }
2236 }
2237 },
2238 );
2239
2240 my $cmd = shift;
2241
2242 unless (exists $CMD{$cmd}) {
2243 warn "Usage: $0 subcommand args...\nTry $0 help\n";
2244 exit 126;
2245 }
2246
2247 $CMD{$cmd}->(@ARGV);
2248