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.