The Pre-boot eXecution Environment (PXE) is useful in case you need to perform many operating system installs and allows you to boot up a system, automatically acquire an IP address via DHCP and start booting a kernel over the network. It is also useful, in cases where you need to administer systems and you would like to start some recovery ISO on a machine without having to haul your recovery tools around.
PXE works by:
and may have to be enabled in the BIOS.
On Debian, the tftpd-hpa
package must be installed:
aptitude install tftpd-hpa
and the /etc/default/tftpd-hpa
file can be checked for the TFTP_DIRECTORY
which is conventionally placed in /srv/tftp
. It is a good idea to edit /etc/default/tftpd-hpa
and modify the listening address to the address of the TFTP
server:
# /etc/default/tftpd-hpa TFTP_USERNAME="tftp" TFTP_DIRECTORY="/srv/tftp" TFTP_ADDRESS="192.168.1.1:69" TFTP_OPTIONS="--secure"
which listens on 192.168.1.1
port 69
.
Next, we install syslinux-common
to grab memdisk
and the rest of the required modules:
aptitude install syslinux-common
Now we can set-up the TFTP
tree in /srv/tftp
. The structure of /srv/tftp
is the following:
+ | +- boot.txt +- iso | + | | | +- debian.iso +- memdisk +- ldlinux.c32 +- libutil.c32 +- libcom32.c32 +- pxelinux.0 +- pxelinux.cfg + | +- default
with the following description of the files:
iso
directory is where we will place our ISO
images.debian.iso
is a Debian net install ISO
file to boot that can be obtained from the Debian website - the image is called mini.iso
and has been renamed here to debian.iso
.ldlinux.c32
, libutil.c32
, libcom32.c32
are copied from /usr/lib/syslinux/modules/bios/
with:cp /usr/lib/syslinux/modules/bios/{ldlinux,libutil,libcom32}.c32 /srv/tftp/
memdisk
is copied from the syslinux-common
package by issuing:cp /usr/lib/syslinux/memdisk /srv/tftp/
pxelinux.0
is copied from the pxelinux
package by issuing:cp /usr/lib/PXELINUX/pxelinux.0 /srv/tftp/
pxelinux.cfg
is a directory despite its extension and contains:default
that can be configured to boot the ISO images (similar to lilo
or grub
configuration files).boot.txt
contains the boot menu which we will have to configure.
Once these files are in-place, we can proceed to configuring the /srv/tftp/pxelinux.cfg/default
and the /srv/tftp/boot.txt
files in order to build a menu of options that a client can boot over the network.
We edit /srv/tftp/pxelinux.cfg/default
in order to include the ISO files that can be booted:
# The boot text DISPLAY boot.txt # Prompt for 10s PROMPT 10 # Don't timeout TIMEOUT 0 # By default boot the Debian ISO DEFAULT debian # The Debian ISO configuration LABEL debian KERNEL memdisk INITRD iso/debian.iso APPEND iso # Boot from Local Hard-Drive LABEL local LOCALBOOT 0
As well as the /srv/tftp/boot.txt
file to list the labels:
########################################################################### ## Wizardry and Steamworks ## ## PXE Boot Menu ## ########################################################################### debian local
which lists debian
since it is our only label / image so far.
The following lines have to added to the subnet declaration:
filename "pxelinux.0"; next-server 192.168.1.1;
where:
filename
points to the pxelinux.0
file in the TFTP root (/srv/tftpd
). You can use an absolute path here, for example /srv/tftpd/pxe/linux/pxelinux.0
in case the pxelinux.0
file and all the other files are in a nested directory from the TFTP root.192.168.1.1
is the IP address of the TFTP server.
Make sure that the ISO images in /srv/tftp/iso/
have read permissions for everybody.
In order to support iPXE as well seamlessly the option is to chain-load iPXE. In order to do this, download the iPXE chain-load boot file and save it to /srv/tftp/
in the same directory as pxelinux.0
(note the new undionly.kpxe
):
+ | +- boot.txt +- iso | + | | | +- debian.iso +- memdisk +- ldlinux.c32 +- libutil.c32 +- libcom32.c32 +- pxelinux.0 +- undionly.kpxe (new) +- pxelinux.cfg + | +- default
After that, remove the DHCP lines:
filename "pxelinux.0"; next-server 192.168.1.1;
and instead define a global class:
class "PC-NetBoot" { match if ( substring(option vendor-class-identifier, 0, 9) = "PXEClient" ); # After iPXE, load PXE. if exists user-class and option user-class = "iPXE" { filename "/srv/tftp/pxe/PC/pxelinux.0"; next-server 172.16.1.1; # If this is a regular PXE client, load PXE. } elsif substring(option vendor-class-identifier, 0, 9) = "PXEClient" { filename "/srv/tftp/pxe/PC/pxelinux.0"; # Otherwise, chainload iPXE. } else { filename "/srv/tftp/pxe/PC/undionly.kpxe"; } }
This class will match all the PXE booting clients. In case the client is using iPXE, the undionly.kpxe
boot-file will be used and the pxelinux.0
will be loaded next. In case it is a regular PXE client, then the pxelinux.0
boot file will be loaded directly - which is what the second clause takes care of.
PXE can be used to boot any ISO image file via the memdisk
kernel over the network but in case the ISO file is too large, the transfer would take too long or the machine transferring the ISO might run out of RAM. Such is the case for Windows ISO install DVDs that are officially distributed with a filesize over , sometimes reaching up to , all of which would have to be transferred over the network and loaded into the RAM of the machine wishing to install Windows.
Fortunately, there is a solution by creating a Windows Pre-Execution ISO that has a fair size of around and can be loaded into RAM in order to stage the real Windows install via a network share.
Download the AIK image file, in this case, for Windows 7 KB3AIK_EN.iso
and then create a WinPE image from the ISO. This can be accomplished under Linux as well as Windows.
For Linux and on Debian:
wimtools
package,KB3AIK_EN.iso
:mkdir -p /mnt/aik mount KB3AIK_EN.iso /mnt/aik
mkwinpeimg
to create a WinPE ISO, for instance, for AMD64 via the -a
parameter:mkwinpeimg -a amd64 --iso --waik-dir=/mnt/aik winpe.iso
winpe.iso
file:mkdir -p /mnt/winpe mount winpe.iso /mnt/winpe
boot.wim
image from within the WinPE mount:cp /mnt/winpe/sources/boot.wim /tmp/boot.wim
boot.wim
file in read-write mode:mkdir -p /mnt/boot.wim wimmountrw /tmp/boot.wim /mnt/boot.wim
/mnt/boot.wim/Windows/System32/startnet.cmd
file to contain the following and make sure to change the settings at the top of the file:@echo off wpeinit rem -- The server with the Samba share containing the Windows install. set INSTALL_SERVER=pxe rem -- The name of the Samba share containg the Windows install. set INSTALL_SHARE=pxe-windows7 rem -- The account credentials for the Samba share. set INSTALL_ACCOUNT=pxe set INSTALL_PASSWORD=pxe rem -- Get the default gateway. for /f "tokens=*" %%s in ('ipconfig ^| find /n "Default Gateway"') do ( set NETCONFIG=%%s ) for /f "tokens=2 delims=:" %%s in ("%NETCONFIG%") do ( set GATEWAY=%%s ) rem -- Remove spaces around the gateway. set GATEWAY=%GATEWAY: =% ping %GATEWAY% -n 3 -w 1000 > NUL ping %INSTALL_SERVER% > NUL net use W: \\%INSTALL_SERVER%\%INSTALL_SHARE% /user:%INSTALL_ACCOUNT% %INSTALL_PASSWORD% W:setup.exe
boot.wim
file and commit the changes:wimunmount /mnt/boot.wim --commit
boot.wim
overlayed:mkwinpeimg -a amd64 --iso -w /tmp/boot.wim --waik-dir=/mnt/aik winpe-updated.iso
umount /mnt/winpe umount /mnt/aik
winpe-updated.iso
to winpe.iso
:cp winpe-updated.iso winpe.iso
winpe.iso
is now the final WinPE ISO that has to be booted via PXE. The next step would be to slipstream some required drivers into the WinPE image.
Drivers can be loaded in the pre-execution envionment via the drivload
command so, for this example, we are going to slipstream libvirt drivers into the WinPE ISO.
wget -c https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/virtio-win.iso
mkdir -p /mnt/virtio-win mount virtio-win.iso /mnt/virtio-win
Now the same procedure as per the previous section is repeated in order to gain read-write access to boot.wim
.
mkdir -p /mnt/boot.wim/Windows/System32/drivers/virtio
cp /mnt/virtio-win/{viostor,NetKVM}/w7/amd64/* /mnt/boot.wim/Windows/System32/drivers/virtio
/mnt/boot.wim/Windows/System32/startnet.cmd
to load the drivers:@echo off rem ------------------------------------------------------------------------------------------------ rem -- CONFIGURATION -- rem ------------------------------------------------------------------------------------------------ rem -- The server with the Samba share containing the Windows install. set INSTALL_SERVER=pxe rem -- The name of the Samba share containg the Windows install. set INSTALL_SHARE=pxe-windows7 rem -- The account credentials for the Samba share. set INSTALL_DOMAIN=WORKGROUP set INSTALL_ACCOUNT=pxe set INSTALL_PASSWORD=pxe rem ------------------------------------------------------------------------------------------------ rem -- INTERNALS -- rem ------------------------------------------------------------------------------------------------ rem -- Load drivers. echo Loading drivers... for /f %%f in ('dir /b drivers\virtio\*.inf') do echo drvload %%f > nul rem -- Start WinPE initialization. echo Windows PE initializing... wpeinit rem -- Not supported on WinPE 7: rem -- wpeutil WaitForNetwork > nul rem -- Get the default gateway. echo Retrieving the default gateway... for /f "tokens=*" %%s in ('ipconfig ^| find /n "Default Gateway"') do ( set NETCONFIG=%%s ) for /f "tokens=2 delims=:" %%s in ("%NETCONFIG%") do ( set GATEWAY=%%s ) rem -- Remove spaces around the gateway. set GATEWAY=%GATEWAY: =% rem -- Set network parameters. echo Optimizing network... rem -- Disable firewall. wpeutil DisableFirewall > nul rem -- Workaround for TCP autotuning bugs. netsh interface tcp set global autotuning=disabled > nul rem -- Start the DNS cache. net start dnscache > nul rem -- Sleep for 10 seconds. echo Waiting 10 seconds for network to settle... ping %GATEWAY% -n 5 > nul ping %INSTALL_SERVER% -n 5 > null rem -- Mount network share. echo Mouting setup files... net use * /delete > nul net use W: \\%INSTALL_SERVER%\%INSTALL_SHARE% /user:%INSTALL_DOMAIN%\%INSTALL_ACCOUNT% %INSTALL_PASSWORD% > nul rem -- Sleep for 5 seconds. echo Waiting 5 seconds for mount to settle... ping %GATEWAY% -n 5 > nul rem -- Start Windows setup. echo Setup starting in 5 seconds... W:setup.exe rem -- Trap and reboot if user aborts setup. echo Rebooting... wpeutil Reboot > nul
Now proceed with the rest of the steps in the previous section in order to commit the changes and generate the new WinPE ISO winpe.iso
.
A Samba share must be set up, corresponding to the settings in the startnet.cmd
script, namely, INSTALL_SERVER
, INSTALL_SHARE
, INSTALL_ACCOUNT
and INSTALL_PASSWORD
. Following the settings in the scripts provided in the previous sections, a corresponding Samba share configuration would be:
[pxe-windows7] comment = PXE Windows 7 Install path = /srv/tftp/pxe/PC/iso/pxe-windows7 valid users = pxe force user = pxe force group = pxe create mask = 0664 force create mode = 0664 force directory mode = 0755 read only = No
Finally, there last requirement is to add a menu entry to PXE to load the WinPE ISO file winpe.iso
that was generated in the previous sections.
The corresponding pxelinux.cfg/default
menu entry would be:
LABEL Windows 7 Install (AMD64) KERNEL memdisk INITRD iso/pxe-windows7.iso APPEND iso raw
where pxe-windows7.iso
is a copy of winpe.iso
- the reason therefore is that the ISO generated in the previous sections has now been fully customized to install Windows 7 and is not a generic WinPE image anymore.
The following script can be used to re-create the WinPE image by downloading all the necessary components, editing the netstart.cmd
file and then bundling everything back together into a new WinPE ISO image.
#!/bin/bash ########################################################################### ## Copyright (C) Wizardry and Steamworks 2020 - License: PD ## ########################################################################### ########################################################################### ## All-in-one dirty script to change the startnet.cmd of a WinPE image ## ## and generate the modified result as a WinPE ISO file bootable via PXE ## ## in order to install Windows from a network share. ## ## ## ## Bonus: the script will slipstream virtio network and storage drivers. ## ## Requirements: wimtools, wget and loopback mount kernel support. ## ########################################################################### wget -c https://download.microsoft.com/download/8/E/9/8E9BBC64-E6F8-457C-9B8D-F6C9A16E6D6A/KB3AIK_EN.iso mkdir -p /mnt/aik mount KB3AIK_EN.iso /mnt/aik mkwinpeimg -a amd64 --iso --waik-dir=/mnt/aik winpe.iso mkdir -p /mnt/winpe mount winpe.iso /mnt/winpe cp /mnt/winpe/sources/boot.wim /tmp/boot.wim mkdir -p /mnt/boot.wim wimmountrw /tmp/boot.wim /mnt/boot.wim # Edit the startnet.cmd script. editor /mnt/boot.wim/Windows/System32/startnet.cmd wget -c https://fedorapeople.org/groups/virt/virtio-win/direct-downloads/latest-virtio/virtio-win.iso #################### BEGIN INJECT VIRTIO DRIVERS ########################## # This section can be removed if not needed. mkdir -p /mnt/virtio-win mount virtio-win.iso /mnt/virtio-win mkdir -p /mnt/boot.wim/Windows/System32/drivers/virtio cp /mnt/virtio-win/{viostor,NetKVM}/w7/amd64/* /mnt/boot.wim/Windows/System32/drivers/virtio ##################### END INJECT VIRTIO DRIVERS ########################### wimunmount /mnt/boot.wim --commit mkwinpeimg -a amd64 --iso -w /tmp/boot.wim --waik-dir=/mnt/aik winpe-updated.iso umount /mnt/winpe umount /mnt/aik cp winpe-updated.iso pxe-windows7.iso
The modified WinPE ISO file transferred over the network contains the necessary credentials to access the Windows install Samba share. In turn, the Samba share that contains the Windows setup files has been created in this section by granting read-write permissions to the pxe
user. Any machine that transfers the WinPE ISO file could peek inside the ISO, extract the startnet.cmd
script and find out the username and password used to access the Samba share.
Even if an attacker gains access just to the Windows setup files, a trojan could be injected into the setup files thereby infecting all machines that install Windows.
There are several mitigations that could be established in order to prevent the misuse of the resources:
/nonexistent
path and the shell to /usr/bin/nologin
via usermod
.read only = Yes
in order to make sure that the Windows setup files cannot be tampered with.
Alternatively, the same type of privilege separation can be established when using a Windows domain controller instead of Samba.
In corporate scenarios where the hardware is well-known in advance and installation keys are available, an unattended Windows setup is possible and perhaps preferable via PXE. This would require the following additional steps to set up:
Autounattend.xml
file that contains all the necessary settings for the Windows setup.Autounattend.xml
into the WinPE image as per the previous sections,startnet.cmd
script presented in the previous sections in order to pass Autounattend.xml
as a parameter to setup.exe
, that is execute on the last line:W:setup.exe Autounattend.xml
instead of just W:setup.exe
in case the Autounattend.xml
file is placed into C:\Windows\System32
directory (the default current working directory when booting the WinPE ISO) of the WinPE ISO file.
In case the Samba share does not seem to mount properly, it may be due to DFS and inspecting the Samba logs will reveal some errors concerning DFS as well as authentication issues. To work around the problem, include the following setting under the [global]
section in the Samba configuration:
host msdfs = No
The extraction and modification of the WinPE image can also be perfromed on Windows and this guide was the equivalent for Linux.
Using the same method, it is possible to install any Windows version - different WinPE images would have to be generated, install DVDs copied to local folders (or bind-mounted), Samba shares created to match the Windows version and PXE entries made to boot the WinPE images to stage the real Windows install.
With all the configuration in place, any machine on the local network will be able to install various operating systems just by connecting the machine to the network and then picking the desired operating system to be installed.
Given that the PXE, Samba and files do not have to be on different servers, perhaps a Raspberry Pi could be used to provide the means to install a bunch of operating systems whilst sill being energy-efficient and a low-cost solution.