1 | #!/opt/bin/perl |
1 | #!/usr/bin/perl |
2 | |
2 | |
3 | # |
3 | # |
4 | # PBCDEDIT - Copyright 2019 Marc A. Lehmann <pbcbedit@schmorp.de> |
4 | # PBCDEDIT - Copyright 2019 Marc A. Lehmann <pbcbedit@schmorp.de> |
5 | # |
5 | # |
6 | # SPDX-License-Identifier: GPL-3.0-or-later |
6 | # SPDX-License-Identifier: GPL-3.0-or-later |
… | |
… | |
17 | # |
17 | # |
18 | # You should have received a copy of the GNU General Public License |
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/>. |
19 | # along with this program. If not, see <https://www.gnu.org/licenses/>. |
20 | # |
20 | # |
21 | |
21 | |
22 | use 5.014; # numerous features |
22 | use 5.014; # numerous features needed |
23 | |
23 | |
24 | our $VERSION = '1.0'; |
24 | our $VERSION = '1.0'; |
25 | our $JSON_VERSION = 1; # the versiobn of the json objects generated by this program |
25 | our $JSON_VERSION = 1; # the versiobn of the json objects generated by this program |
26 | |
26 | |
27 | =head1 NAME |
27 | =head1 NAME |
… | |
… | |
41 | =head1 DESCRIPTION |
41 | =head1 DESCRIPTION |
42 | |
42 | |
43 | This program allows you to create, read and modify Boot Configuration Data |
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. |
44 | (BCD) stores used by Windows Vista and newer versions of Windows. |
45 | |
45 | |
|
|
46 | At this point, it is in relatively early stages of development and has |
|
|
47 | received little to no real-world testing. |
|
|
48 | |
46 | Compared to other BCD editing programs it offers the following unique |
49 | Compared to other BCD editing programs it offers the following unique |
47 | features: |
50 | features: |
48 | |
51 | |
49 | =over |
52 | =over |
50 | |
53 | |
… | |
… | |
106 | The reverse of C<export>: Reads a JSON representation of a BCD data store |
109 | 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. |
110 | from standard input, and creates or replaces the given BCD data store. |
108 | |
111 | |
109 | =item edit F<path> instructions... |
112 | =item edit F<path> instructions... |
110 | |
113 | |
111 | #TODO |
114 | Load a BCD data store, apply some instructions to it, and save it again. |
|
|
115 | |
|
|
116 | See the section L<EDITING BCD DATA STORES>, below, for more info. |
|
|
117 | |
|
|
118 | =item parse F<path> instructions... |
|
|
119 | |
|
|
120 | Same as C<edit>, above, except it doesn't save the data store again. Can |
|
|
121 | be useful to extract some data from it. |
112 | |
122 | |
113 | =item lsblk |
123 | =item lsblk |
114 | |
124 | |
115 | On a GNU/Linux system, you can get a list of partition device descriptors |
125 | 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 |
126 | using this command - the external C<lsblk> command is required, as well as |
… | |
… | |
202 | "hypervisordebugtype" : 0 |
212 | "hypervisordebugtype" : 0 |
203 | }, |
213 | }, |
204 | # ... |
214 | # ... |
205 | } |
215 | } |
206 | |
216 | |
|
|
217 | =head2 Minimal BCD to boot windows |
|
|
218 | |
|
|
219 | Experimentally I found the following BCD is the minimum required to |
|
|
220 | successfully boot any post-XP version of Windows (suitable C<device> and |
|
|
221 | C<osdevice> values, of course): |
|
|
222 | |
|
|
223 | { |
|
|
224 | "{bootmgr}" : { |
|
|
225 | "resumeobject" : "{45b547a7-8ca6-4417-9eb0-a257b61f35b4}" |
|
|
226 | }, |
|
|
227 | |
|
|
228 | "{45b547a7-8ca6-4417-9eb0-a257b61f35b1}" : { |
|
|
229 | "type" : "application::osloader", |
|
|
230 | "description" : "Windows Boot", |
|
|
231 | "device" : "legacypartition=<null>,harddisk,mbr,47cbc08a,1", |
|
|
232 | "osdevice" : "legacypartition=<null>,harddisk,mbr,47cbc08a,1", |
|
|
233 | "path" : "\\Windows\\system32\\winload.exe", |
|
|
234 | "systemroot" : "\\Windows" |
|
|
235 | }, |
|
|
236 | } |
|
|
237 | |
|
|
238 | Note that minimal doesn't mean recommended - Windows itself will add stuff |
|
|
239 | to this during or after boot, and you might or might not run into issues |
|
|
240 | when installing updates as it might not be able to find the F<bootmgr>. |
|
|
241 | |
207 | =head2 The C<meta> key |
242 | =head2 The C<meta> key |
208 | |
243 | |
209 | The C<meta> key is not stored in the BCD data store but is used only |
244 | The C<meta> key is not stored in the BCD data store but is used only |
210 | by PBCDEDIT. It is always generated when exporting, and importing will |
245 | by PBCDEDIT. It is always generated when exporting, and importing will |
211 | be refused when it exists and the version stored inside doesn't store |
246 | be refused when it exists and the version stored inside doesn't store |
… | |
… | |
593 | many questions, but I can walk you through some actual examples using mroe |
628 | many questions, but I can walk you through some actual examples using mroe |
594 | complex aspects. |
629 | complex aspects. |
595 | |
630 | |
596 | =item locate=<block=vhd,<block=file,<locate=<null>,path,\disk.vhdx>,\disk.vhdx>>,element,path |
631 | =item locate=<block=vhd,<block=file,<locate=<null>,path,\disk.vhdx>,\disk.vhdx>>,element,path |
597 | |
632 | |
598 | #todo |
633 | Just like with C declarations, you best treat device descriptors as |
|
|
634 | instructions to find your device and work your way from the inside out: |
|
|
635 | |
|
|
636 | locate=<null>,path,\disk.vhdx |
|
|
637 | |
|
|
638 | First, the innermost device descriptor searches all partitions on the |
|
|
639 | system for a file called F<\disk.vhdx>: |
|
|
640 | |
|
|
641 | block=file,<see above>,\disk.vhdx |
|
|
642 | |
|
|
643 | Next, this takes the device locate has found and finds a file called |
|
|
644 | F<\disk.vhdx> on it. This is the same file locate was using, but that is |
|
|
645 | only because we find the device using the same path as finding the disk |
|
|
646 | image, so this is purely incidental, although quite common. |
|
|
647 | |
|
|
648 | Bext, this file will be opened as a virtual disk: |
|
|
649 | |
|
|
650 | block=vhd,<see above> |
|
|
651 | |
|
|
652 | And finally, inside this disk, another C<locate> will look for a partition |
|
|
653 | with a path as specified in the C<path> element, which most likely will be |
|
|
654 | F<\Windows\system32\winload.exe>: |
|
|
655 | |
|
|
656 | locate=<see above>,element,path |
|
|
657 | |
|
|
658 | As a result, this will boot the first Windows it finds on the first |
|
|
659 | F<disk.vhdx> disk image it can find anywhere. |
599 | |
660 | |
600 | =item locate=<block=vhd,<block=file,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,\win10.vhdx>>,element,path |
661 | =item locate=<block=vhd,<block=file,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,\win10.vhdx>>,element,path |
601 | |
662 | |
602 | #todo |
663 | Pretty much the same as the previous case, but witzh a bit of variance. First, look for a specific partition on |
|
|
664 | an MBR-partitioned disk: |
|
|
665 | |
|
|
666 | partition=<null>,harddisk,mbr,47cbc08a,242643632128 |
|
|
667 | |
|
|
668 | Then open the file F<\win10.vhdx> on that partition: |
|
|
669 | |
|
|
670 | block=file,<see above>,\win10.vhdx |
|
|
671 | |
|
|
672 | Then, again, the file is opened as a virtual disk image: |
|
|
673 | |
|
|
674 | block=vhd,<see above> |
|
|
675 | |
|
|
676 | And again the windows loader (or whatever is in C<path>) will be searched: |
|
|
677 | |
|
|
678 | locate=<see above>,element,path |
603 | |
679 | |
604 | =item {b097d2b2-bc00-11e9-8a9a-525400123456}block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,0,0,0,\boot.wim |
680 | =item {b097d2b2-bc00-11e9-8a9a-525400123456}block<1>=ramdisk,<partition=<null>,harddisk,mbr,47cbc08a,242643632128>,0,0,0,\boot.wim |
605 | |
681 | |
606 | #todo |
682 | This is quite different. First, it starts with a GUID. This GUID belongs |
|
|
683 | to a BCD object of type C<device>, which has additional parameters: |
607 | |
684 | |
|
|
685 | "{b097d2b2-bc00-11e9-8a9a-525400123456}" : { |
|
|
686 | "type" : "device", |
|
|
687 | "description" : "sdi file for ramdisk", |
|
|
688 | "ramdisksdidevice" : "partition=<null>,harddisk,mbr,47cbc08a,1048576", |
|
|
689 | "ramdisksdipath" : "\boot.sdi" |
|
|
690 | }, |
|
|
691 | |
|
|
692 | I will not go into many details, but this specifies a (presumably empty) |
|
|
693 | template ramdisk image (F<\boot.sdi>) that is used to initiaolize the |
|
|
694 | ramdisk. The F<\boot.wim> file is then extracted into it. As you cna also |
|
|
695 | see, this F<.sdi> file resides on a different C<partition>. |
|
|
696 | |
|
|
697 | Continuitn, as always, form the inside out, first this device descriptor |
|
|
698 | finds a specific partition: |
|
|
699 | |
|
|
700 | partition=<null>,harddisk,mbr,47cbc08a,242643632128 |
|
|
701 | |
|
|
702 | And then specifies a C<ramdisk> image on this partition: |
|
|
703 | |
|
|
704 | block<1>=ramdisk,<see above>,0,0,0,\boot.wim |
|
|
705 | |
|
|
706 | I don't know what the purpose of the C<< <1> >> flag value is, but it |
|
|
707 | seems to be always there on this kind of entry. |
|
|
708 | |
|
|
709 | If you have some good examples to add here, feel free to mail me. |
|
|
710 | |
|
|
711 | |
|
|
712 | =head1 EDITING BCD DATA STORES |
|
|
713 | |
|
|
714 | The C<edit> and C<parse> subcommands allow you to read a BCD data store |
|
|
715 | and modify it or extract data from it. This is done by exyecuting a series |
|
|
716 | of "editing instructions" which are explained here. |
|
|
717 | |
|
|
718 | =over |
|
|
719 | |
|
|
720 | =item get I<object> I<element> |
|
|
721 | |
|
|
722 | Reads the BCD element I<element> from the BCD object I<object> and writes |
|
|
723 | it to standard output, followed by a newline. The I<object> can be a GUID |
|
|
724 | or a human-readable alias, or the special string C<{default}>, which will |
|
|
725 | refer to the default BCD object. |
|
|
726 | |
|
|
727 | Example: find description of the default BCD object. |
|
|
728 | |
|
|
729 | pbcdedit parse BCD get "{default}" description |
|
|
730 | |
|
|
731 | =item set I<object> I<element> I<value> |
|
|
732 | |
|
|
733 | Similar to C<get>, but sets the element to the given I<value> instead. |
|
|
734 | |
|
|
735 | Example: change bootmgr default too |
|
|
736 | C<{b097d2ad-bc00-11e9-8a9a-525400123456}>: |
|
|
737 | |
|
|
738 | pbcdedit edit BCD set "{bootmgr}" resumeobject "{b097d2ad-bc00-11e9-8a9a-525400123456}" |
|
|
739 | |
|
|
740 | =item eval I<perlcode> |
|
|
741 | |
|
|
742 | This takes the next argument, interprets it as Perl code and |
|
|
743 | evaluates it. This allows you to do more complicated modifications or |
|
|
744 | extractions. |
|
|
745 | |
|
|
746 | The following variables are predefined for your use: |
|
|
747 | |
|
|
748 | =over |
|
|
749 | |
|
|
750 | =item C<$PATH> |
|
|
751 | |
|
|
752 | The path to the BCD data store, as given to C<edit> or C<parse>. |
|
|
753 | |
|
|
754 | =item C<$BCD> |
|
|
755 | |
|
|
756 | The decoded BCD data store. |
|
|
757 | |
|
|
758 | =item C<$DEFAULT> |
|
|
759 | |
|
|
760 | The default BCD object name. |
|
|
761 | |
|
|
762 | =back |
|
|
763 | |
|
|
764 | The example given for C<get>, above, could be expressed like this with |
|
|
765 | C<eval>: |
|
|
766 | |
|
|
767 | pbcdedit edit BCD eval 'say $BCD->{$DEFAULT}{description}' |
|
|
768 | |
|
|
769 | The example given for C<set> could be expresed like this: |
|
|
770 | |
|
|
771 | pbcdedit edit BCD eval '$BCD->{$DEFAULT}{resumeobject} = "{b097d2ad-bc00-11e9-8a9a-525400123456}"' |
|
|
772 | |
|
|
773 | =item do I<path> |
|
|
774 | |
|
|
775 | Similar to C<eval>, above, but instead of using the argument as perl code, |
|
|
776 | it loads the perl code from the given file and executes it. This makes it |
|
|
777 | easier to write more complicated or larger programs. |
|
|
778 | |
|
|
779 | =back |
608 | |
780 | |
609 | =head1 SEE ALSO |
781 | =head1 SEE ALSO |
610 | |
782 | |
611 | For ideas on what you can do, and some introductory material, try |
783 | For ideas on what you can do, and some introductory material, try |
612 | L<http://www.mistyprojects.co.uk/documents/BCDEdit/index.html>. |
784 | L<http://www.mistyprojects.co.uk/documents/BCDEdit/index.html>. |
… | |
… | |
667 | |
839 | |
668 | # hack used for debugging |
840 | # hack used for debugging |
669 | sub xxd($$) { |
841 | sub xxd($$) { |
670 | open my $xxd, "| xxd | sed -e 's/^/\Q$_[0]\E: /'"; |
842 | open my $xxd, "| xxd | sed -e 's/^/\Q$_[0]\E: /'"; |
671 | syswrite $xxd, $_[1]; |
843 | syswrite $xxd, $_[1]; |
|
|
844 | } |
|
|
845 | |
|
|
846 | sub file_load($) { |
|
|
847 | my ($path) = @_; |
|
|
848 | |
|
|
849 | open my $fh, "<:raw", $path |
|
|
850 | or die "$path: $!\n"; |
|
|
851 | my $size = -s $fh; |
|
|
852 | $size = read $fh, my $buf, $size |
|
|
853 | or die "$path: short read\n"; |
|
|
854 | |
|
|
855 | $buf |
672 | } |
856 | } |
673 | |
857 | |
674 | # sources and resources used for this: |
858 | # sources and resources used for this: |
675 | # registry: |
859 | # registry: |
676 | # https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md |
860 | # https://github.com/msuhanov/regf/blob/master/Windows%20registry%20file%20format%20specification.md |
… | |
… | |
1027 | } |
1211 | } |
1028 | |
1212 | |
1029 | # load and parse registry from file |
1213 | # load and parse registry from file |
1030 | sub regf_load($) { |
1214 | sub regf_load($) { |
1031 | my ($path) = @_; |
1215 | my ($path) = @_; |
1032 | open my $regf, "<:raw", $path |
|
|
1033 | or die "$path: $!\n"; |
|
|
1034 | my $size = -s $regf; |
|
|
1035 | $size = read $regf, my $buf, $size |
|
|
1036 | or die "$path: short read\n"; |
|
|
1037 | |
1216 | |
1038 | regf_decode $buf |
1217 | regf_decode file_load $path |
1039 | } |
1218 | } |
1040 | |
1219 | |
1041 | # encode and save registry to file |
1220 | # encode and save registry to file |
1042 | sub regf_save { |
1221 | sub regf_save { |
1043 | my ($path, $hive) = @_; |
1222 | my ($path, $hive) = @_; |
… | |
… | |
2064 | }]] |
2243 | }]] |
2065 | } |
2244 | } |
2066 | |
2245 | |
2067 | ############################################################################# |
2246 | ############################################################################# |
2068 | |
2247 | |
|
|
2248 | sub bcd_edit_eval { |
|
|
2249 | package pbcdedit; |
|
|
2250 | |
|
|
2251 | our ($PATH, $BCD, $DEFAULT); |
|
|
2252 | |
|
|
2253 | eval shift; |
|
|
2254 | die "$@" if $@; |
|
|
2255 | } |
|
|
2256 | |
|
|
2257 | sub bcd_edit { |
|
|
2258 | my ($path, $bcd, @insns) = @_; |
|
|
2259 | |
|
|
2260 | my $default = $bcd->{"{bootmgr}"}{resumeobject}; |
|
|
2261 | |
|
|
2262 | # prepare "officially visible" variables |
|
|
2263 | local $pbcdedit::PATH = $path; |
|
|
2264 | local $pbcdedit::BCD = $bcd; |
|
|
2265 | local $pbcdedit::DEFAULT = $default; |
|
|
2266 | |
|
|
2267 | while (@insns) { |
|
|
2268 | my $insn = shift @insns; |
|
|
2269 | |
|
|
2270 | if ($insn eq "get") { |
|
|
2271 | my $object = shift @insns; |
|
|
2272 | my $elem = shift @insns; |
|
|
2273 | |
|
|
2274 | $object = $default if $object eq "{default}"; |
|
|
2275 | |
|
|
2276 | print $bcd->{$object}{$elem}, "\n"; |
|
|
2277 | |
|
|
2278 | } elsif ($insn eq "set") { |
|
|
2279 | my $object = shift @insns; |
|
|
2280 | my $elem = shift @insns; |
|
|
2281 | my $value = shift @insns; |
|
|
2282 | |
|
|
2283 | $object = $default if $object eq "{default}"; |
|
|
2284 | |
|
|
2285 | $bcd->{$object}{$elem} = $value; |
|
|
2286 | |
|
|
2287 | } elsif ($insn eq "eval") { |
|
|
2288 | bcd_edit_eval shift @insns; |
|
|
2289 | |
|
|
2290 | } elsif ($insn eq "do") { |
|
|
2291 | my $path = shift @insns; |
|
|
2292 | my $file = file_load $path; |
|
|
2293 | bcd_edit_eval "#line 1 '$path'\n$file"; |
|
|
2294 | |
|
|
2295 | } else { |
|
|
2296 | die "$insn: not a recognized instruction for edit/parse\n"; |
|
|
2297 | } |
|
|
2298 | } |
|
|
2299 | |
|
|
2300 | } |
|
|
2301 | |
|
|
2302 | ############################################################################# |
|
|
2303 | |
2069 | # json to stdout |
2304 | # json to stdout |
2070 | sub prjson($) { |
2305 | sub prjson($) { |
2071 | print $json_coder->encode ($_[0]); |
2306 | print $json_coder->encode ($_[0]); |
2072 | } |
2307 | } |
2073 | |
2308 | |
… | |
… | |
2170 | prjson bcd_decode regf_load shift; |
2405 | prjson bcd_decode regf_load shift; |
2171 | }, |
2406 | }, |
2172 | |
2407 | |
2173 | import => sub { |
2408 | import => sub { |
2174 | regf_save shift, bcd_encode rdjson; |
2409 | regf_save shift, bcd_encode rdjson; |
|
|
2410 | }, |
|
|
2411 | |
|
|
2412 | edit => sub { |
|
|
2413 | my $path = shift; |
|
|
2414 | my $bcd = bcd_decode regf_load $path; |
|
|
2415 | bcd_edit $path, $bcd, @_; |
|
|
2416 | regf_save $path, bcd_encode $bcd; |
|
|
2417 | }, |
|
|
2418 | |
|
|
2419 | parse => sub { |
|
|
2420 | my $path = shift; |
|
|
2421 | my $bcd = bcd_decode regf_load $path; |
|
|
2422 | bcd_edit $path, $bcd, @_; |
2175 | }, |
2423 | }, |
2176 | |
2424 | |
2177 | "export-regf" => sub { |
2425 | "export-regf" => sub { |
2178 | prjson regf_load shift; |
2426 | prjson regf_load shift; |
2179 | |
2427 | |