Secure Kali Pi (2022)

This is the first part of a 3 part series of blog posts surrounding Kali usage on Raspberry Pi devices. This first post will cover enabling Full Disk Encryption (FDE) on a Raspberry Pi, part two will cover remotely connecting to it, and finally, part three will cover debugging issues we ran into while making these posts, so others can learn how to do so as well.


With everything that has been going on in the world in the last few years, more people are working remotely. We are no exception to this, and today, we are going to be revisiting our “drop box” machine, which has been encrypted thus making it harder to identify if discovered.

The goal is to create a stand-alone “leave behind” headless device that, that if/when discovered, does not make it easy to figure out what we were doing as our data is secure at all times. To accomplish this, we will use Full Disk Encryption (FDE), and allow for it to be remotely unlocked (should the device get restarted for any reason). There will be the option as well to use LUKS Nuke capability should we wish to make the disk inaccessible at any point after we are done with it.
We will be doing this on a Raspberry Pi 4 Model B+, but it also has been tested on a Raspberry Pi 3 Model B as well. You should be able to use most makes/models of similar devices, it may just require a bit of creative adaptations/adjustments in order to secure your own system.

This is an updated process as we have previously covered part of this before. This time we include additional developments, with some community contributions thrown in. We would like to give a shout-out to Richard Nelson (@unixabg) for his automated script. We will touch on this after going through the manual method, as we always recommend you understand what is going on under the hood.


Higher-level overview

Before we dive into the lower-levels of technical details of what we are going to accomplish, let’s take a quick look at our goals that we want to achieve, and break it down:

This might sound like a lot, but it’s rather straightforward even if there are a fair few steps. Once completed, we will have a RPi that will:

  • Boot
  • Get an IP from DHCP
  • Wait for us to connect via SSH using keys
  • Allow us to provide either the LUKS unlock, or LUKS Nuke passphrases

Then down the road when we are done with whatever it is we are wanting to do, the only thing left is to retrieve it …at our leisure!


Installing Kali Linux on a RPi

If you’re following along, be sure to know where you are imaging the file to, and replace /dev/sdX. Don’t blindly copy/paste!

We will be creating our drop box machine on an existing Kali installation. It should be very easy to use other Debian-based distributions, and pretty straight forward for other OSes (except Windows users!)

We first will download the latest stable Kali RPi image. At the time of writing, that’s Kali 2022.2.
We have also chosen the 64-bit image, as we have more than 4GB of RAM, and are not using any HATs (Hardware Attached on Top). The steps for 32-bit would be the same, after adjusting filenames:

$ wget https://kali.download/arm-images/kali-2022.2/kali-linux-2022.2-raspberry-pi-arm64.img.xz
$ xzcat kali-linux-2022.2-raspberry-pi-arm64.img.xz | sudo dd of=/dev/sdX bs=512k status=progress

Preparing the system

Preparing the chroot

We next are going to get things ready for a chroot. Let’s create where we want to mount the microSD card, then mount it:

$ sudo mkdir -vp /mnt/chroot/
$ sudo mount /dev/sdX2 /mnt/chroot/
$ sudo mount /dev/sdX1 /mnt/chroot/boot/
$ sudo mount -t proc none /mnt/chroot/proc
$ sudo mount -t sysfs none /mnt/chroot/sys
$ sudo mount -o bind /dev /mnt/chroot/dev
$ sudo mount -o bind /dev/pts /mnt/chroot/dev/pts
$ sudo apt install -y qemu-user-static
$ sudo cp /usr/bin/qemu-aarch64-static /mnt/chroot/usr/bin/

The last two commands will come in handy ready for initramfs later.


Installing required packages

Now that our system is set up we can use the chroot to set up the RPi image for encryption. Let’s first enter the chroot and install some necessary packages:

$ sudo env LANG=C chroot /mnt/chroot/
┌──(root㉿kali)-[/]
└─# apt update ┌──(root㉿kali)-[/]
└─# apt install -y busybox cryptsetup dropbear-initramfs lvm2

We want to ensure we are on the latest kernel before we get started, so lets also make sure we have them installed:

┌──(root㉿kali)-[/]
└─# apt install -y kalipi-kernel kalipi-bootloader kalipi-re4son-firmware

Boot options

Next we are going to edit /boot/cmdline.txt and change the root path. The /boot/cmdline.txt file on a RPi device is used to pass the kernel command line options. We will want to change the root path to be /dev/mapper/crypt, and then we will add in cryptdevice=PARTUUID=$partuuid:crypt right after that.

The reason for this is that the kernel needs to know where the root filesystem is, in order to mount it and use it, and since we are encrypting the rootfs later in the post, during boot time it can’t see the unencrypted device either, because of the encryption! While we are changing the name here to “crypt”, you can call it anything you want.

The end result should look like this:

┌──(root㉿kali)-[/]
└─# vim /boot/cmdline.txt ┌──(root㉿kali)-[/]
└─# cat /boot/cmdline.txt
dwc_otg.fiq_fix_enable=2 console=serial0,115200 kgdboc=serial0,115200 console=tty1 root=/dev/mapper/crypt cryptdevice=PARTUUID=ed889dad-02:crypt rootfstype=ext4 fsck.repair=yes rootwait net.ifnames=0

Partition layout

We now need to update the /etc/fstab file, this is a configuration file on the system that contains all available disks, disk partitions, and what options to use when handling them.

Currently it is populated with the UUID of the root filesystem, and we need it to point at the encrypted filesystem that we will be making. In this example, we’ve commented out what the previous root device’s UUID, and point at /dev/mapper/crypt which is what our encrypted filesystem will mount as, once we create it:

┌──(root㉿kali)-[/]
└─# vim /etc/fstab ┌──(root㉿kali)-[/]
└─# cat /etc/fstab
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0 /dev/mapper/crypt / ext4 errors=remount-ro 0 0
#UUID=747bfa7c-edd2-471f-8fff-0ecafc2d3791 / ext4 errors=remount-ro 0 1
LABEL=BOOT /boot vfat defaults 0 2

Configure the encrypted partitions

When using encrypted partitions, we need to edit, or create, if it doesn’t exist, the /etc/crypttab file, which is used by cryptsetup to know what options are needed in order to unlock the encrypted device.

Because this file doesn’t exist, we will create the /etc/crypttab file, and fill it with the options we need:

┌──(root㉿kali)-[/]
└─# echo -e 'crypttPARTUUID=ed889dad-02tnonetluks' > /etc/crypttab

Now we do a little file-system trickery. We create a fake LUKS file-system which will allow cryptsetup to be included in the initramfs because it sees an encrypted partition. When you format any LUKS partitions, you will be prompted for a password, and while normally you will use a strong password, because we are only using this as a hack to include cryptsetup into our initramfs, the password you create at this prompt will not be needed or used past these steps, so you can set it to something short/quick to type. This will happen at the cryptsetup luksFormat step, and you will be prompted for the password you set during cryptsetup luksFormat when you run the cryptsetup luksOpen step.

You will not see any input being typed when entering the password

┌──(root㉿kali)-[/]
└─# dd if=/dev/zero of=/tmp/fakeroot.img bs=1M count=20 ┌──(root㉿kali)-[/]
└─# exit
$ sudo cryptsetup luksFormat /mnt/chroot/tmp/fakeroot.img
$ sudo cryptsetup luksOpen /mnt/chroot/tmp/fakeroot.img crypt
$ sudo mkfs.ext4 /dev/mapper/crypt

Configuring SSH keys

After that we need to copy over OR generate a new ssh key to be added to Dropbear’s authorized_keys file.

If we already have an existing key to copy over:

$ sudo cp ~/.ssh/id_rsa.pub /mnt/chroot/

Alternatively to generate a new key:

$ ssh-keygen -t rsa -b 4096
[...]
Enter file in which to save the key (/home/kali/.ssh/id_rsa): /home/kali/.ssh/id_rsa_dropbear
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/kali/.ssh/id_rsa_dropbear
Your public key has been saved in /home/kali/.ssh/id_rsa_dropbear.pub
[...]
$ sudo cp ~/.ssh/id_rsa_dropbear.pub /mnt/chroot/

You will not see any input being typed when entering a passphrase


Configuring for encryption

Going back into the chroot, we need to create a few new files.

First is the zz-cryptsetup hook which adds the files we need for cryptsetup into the initramfs. For it to work, it needs to be marked as executable so that mkinitramfs will run the hook:

$ sudo env LANG=C chroot /mnt/chroot/
┌──(root㉿kali)-[/]
└─# vim /etc/initramfs-tools/hooks/zz-cryptsetup ┌──(root㉿kali)-[/]
└─# cat /etc/initramfs-tools/hooks/zz-cryptsetup
#!/bin/sh
set -e PREREQ=""
prereqs()
{ echo "${PREREQ}"
} case "${1}" in prereqs) prereqs exit 0 ;;
esac . /usr/share/initramfs-tools/hook-functions mkdir -p ${DESTDIR}/cryptroot || true
cat /etc/crypttab >> ${DESTDIR}/cryptroot/crypttab
cat /etc/fstab >> ${DESTDIR}/cryptroot/fstab
cat /etc/crypttab >> ${DESTDIR}/etc/crypttab
cat /etc/fstab >> ${DESTDIR}/etc/fstab
copy_file config /etc/initramfs-tools/unlock.sh /etc/unlock.sh ┌──(root㉿kali)-[/]
└─# chmod +x /etc/initramfs-tools/hooks/zz-cryptsetup

Should you wish to disable it at any point in the future for any reason, simply remove the executable bit.


We edit the modules file for initramfs-tools so that we include the dm-crypt module, and cat the file to verify it is correct:

┌──(root㉿kali)-[/]
└─# grep -q dm_crypt /etc/initramfs-tools/modules || echo dm_crypt >> /etc/initramfs-tools/modules ┌──(root㉿kali)-[/]
└─# cat /etc/initramfs-tools/modules
# List of modules that you want to include in your initramfs.
# They will be loaded at boot time in the order below.
#
# Syntax: module_name [args ...]
#
# You must run update-initramfs(8) to effect this change.
#
# Examples:
#
# raid1
# sd_mod
dm_crypt

Configuring remote SSH unlock

Create an unlock.sh script with the following contents, and then mark it as executable so that the script runs in the initramfs:

┌──(root㉿kali)-[/]
└─# vim /etc/initramfs-tools/unlock.sh ┌──(root㉿kali)-[/]
└─# cat /etc/initramfs-tools/unlock.sh
#!/bin/sh export PATH='/sbin:/bin:/usr/sbin:/usr/bin' while true; do test -e /dev/mapper/crypt && break || cryptsetup luksOpen /dev/disk/by-uuid/$REPLACE_LATER crypt
done /scripts/local-top/cryptroot
for i in $(ps aux | grep 'cryptroot' | grep -v 'grep' | awk '{print $1}'); do kill -9 $i; done
for i in $(ps aux | grep 'askpass' | grep -v 'grep' | awk '{print $1}'); do kill -9 $i; done
for i in $(ps aux | grep 'ask-for-password' | grep -v 'grep' | awk '{print $1}'); do kill -9 $i; done
for i in $(ps aux | grep '\-sh' | grep -v 'grep' | awk '{print $1}'); do kill -9 $i; done
exit 0 ┌──(root㉿kali)-[/]
└─# chmod +x /etc/initramfs-tools/unlock.sh

Next we must add the following to the beginning of /etc/dropbear/initramfs/authorized_keys, which tells it to run this command when we SSH in if the key matches:

┌──(root㉿kali)-[/]
└─# vim /etc/dropbear/initramfs/authorized_keys ┌──(root㉿kali)-[/]
└─# cat /etc/dropbear/initramfs/authorized_keys
command="/etc/unlock.sh; exit"

After doing so, we can append the SSH key that we copied over and then remove it from the card:

┌──(root㉿kali)-[/]
└─# cat id_rsa.pub >> /etc/dropbear/initramfs/authorized_keys && rm -v id_rsa.pub

Once you’re done, /etc/dropbear/initramfs/authorized_keys should look like this:

┌──(root㉿kali)-[/]
└─# cat /etc/dropbear/initramfs/authorized_keys
command="/etc/unlock.sh; exit" ssh-rsa <key> [email protected]

Everything in the authorized_keys file should be one line, as well as a space between the command’s ending " and the ssh key (e.g. [...]exit" ssh-rsa[...])


We now need to edit /usr/share/initramfs-tools/scripts/init-premount/dropbear to add a sleep timer, this allows for networking to start before Dropbear does. It is important to note that when there are updates to the dropbear-initramfs package, this edit will need to be re-added:

┌──(root㉿kali)-[/]
└─# vim /usr/share/initramfs-tools/scripts/init-premount/dropbear ┌──(root㉿kali)-[/]
└─# cat /usr/share/initramfs-tools/scripts/init-premount/dropbear
[ "$BOOT" != nfs ] || configure_networking
sleep 5
run_dropbear &
echo $! >/run/dropbear.pid

Now we enable cryptsetup:

┌──(root㉿kali)-[/]
└─# echo CRYPTSETUP=y >> /etc/cryptsetup-initramfs/conf-hook ┌──(root㉿kali)-[/]
└─# tail /etc/cryptsetup-initramfs/conf-hook
#
# Whether to include the askpass binary to the initramfs image. askpass
# is required for interactive passphrase prompts, and ASKPASS=y (the
# default) is implied when the hook detects that same device needs to be
# unlocked interactively (i.e., not via keyfile nor keyscript) at
# initramfs stage. Setting ASKPASS=n also skips `cryptroot-unlock`
# inclusion as it requires the askpass executable. #ASKPASS=y
CRYPTSETUP=y

Kernel

The next step is important for the people who are following along. What to select, depends on the RPi device you are using, will . Below are five kernel names/editions/flavours which you need to select one of for your needs (please pay attention!):

  • Re4son+ is for 32-bit ARMEL armv6 devices – i.e. RPi1, RPi0, or RPi0w
  • Re4son-v7+ is for 32-bit ARMHF armv7 devices – i.e. RPi2 v1.2, RPi3 or RPi02w
  • Re4son-v8+ is for 64-bit ARM64 armv8 devices – i.e. RPi2 v1.2, RPi3 or RPi02w
  • Re4son-v7l+ is for 32-bit ARMHF armv7 devices – i.e. RPi4 or RPi400 devices
  • Re4son-v8l+ is for 64-bit ARM64 armv8 devices – i.e. RPi4 or RPi400 devices

As a reminder, we are using the RPi4, 64-bit image. So we would need Re4son-v8l+. Please make sure you adjust to your device.
So now we know what kernel name to use, we now need to find what kernel version. This will alter from device to device, and it will also change as and when Kali gets updates At the time of writing, it is 5.15.44 for our RPi:

Keep in mind the kernel versions may change, however the name will not:

┌──(root㉿kali)-[/]
└─# ls -l /lib/modules/ | awk -F" " '{print $9}'
5.15.44-Re4son+
5.15.44-Re4son-v7+
5.15.44-Re4son-v7l+
5.15.44-Re4son-v8+
5.15.44-Re4son-v8l+ ┌──(root㉿kali)-[/]
└─# echo "initramfs initramfs.gz followkernel" >> /boot/config.txt

Keep in mind the kernel versions (5.15.44) may change, however the kernel name (Re4son-v8l+) will not.


Now we need to create the initramfs. This is where the kernel version comes into play:

┌──(root㉿kali)-[/]
└─# mkinitramfs -o /boot/initramfs.gz 5.15.44-Re4son-v8l+

Now we want to ensure that we created the initramfs correctly. If there is no result, then something went wrong:

┌──(root㉿kali)-[/]
└─# lsinitramfs /boot/initramfs.gz | grep cryptsetup
usr/lib/aarch64-linux-gnu/libcryptsetup.so.12
usr/lib/aarch64-linux-gnu/libcryptsetup.so.12.7.0
usr/lib/cryptsetup
usr/lib/cryptsetup-nuke-password
usr/lib/cryptsetup-nuke-password/crypt
usr/lib/cryptsetup/askpass
usr/lib/cryptsetup/askpass.cryptsetup
usr/lib/cryptsetup/functions
usr/sbin/cryptsetup ┌──(root㉿kali)-[/]
└─# lsinitramfs /boot/initramfs.gz | grep authorized
root-Q2iWOODUwk/.ssh/authorized_keys ┌──(root㉿kali)-[/]
└─# lsinitramfs /boot/initramfs.gz | grep unlock.sh
etc/unlock.sh

Disable services

Before we can backup, we have to ensure that rpi-resizerootfs is disabled. This is a service we typically run on all of our ARM devices that resizes the root filesystem partition to increase the size of the partition to the full size of the storage device it is on. Since we are doing this step manually, we want to disable it, so it doesn’t potentially delete our root filesystem and re-make it.

┌──(root㉿kali)-[/]
└─# systemctl disable rpi-resizerootfs

Backup any existing data

Now we can ensure that all the changes are written, then we can encrypt the disk:

┌──(root㉿kali)-[/]
└─# sync ┌──(root㉿kali)-[/]
└─# exit
$ sudo umount /mnt/chroot/{boot,sys,proc,dev/pts,dev}
$ sudo mkdir -vp /mnt/{backup,encrypted}
$ sudo rsync -avh /mnt/chroot/* /mnt/backup/
$ sudo cryptsetup luksClose crypt
$ sudo umount /mnt/chroot
$ echo -e "dn2nw" | sudo fdisk /dev/sdX
$ echo -e "nnpn2nnnw" | sudo fdisk /dev/sdX

Configure the encrypted partitions

Depending on what device you are using you will have to use one of two commands. If you are using a RPi4 with 4GB or more, use this command:

$ sudo cryptsetup -v -y --cipher aes-cbc-essiv:sha256 --key-size 256 luksFormat /dev/sdX2

Otherwise you will want to use the following which uses an older version of LUKS:

$ sudo cryptsetup -v -y --pbkdf pbkdf2 --cipher aes-cbc-essiv:sha256 --key-size 256 luksFormat /dev/sdX2

Restore our data

Afterwards you can finish restoring data back to the now encrypted partition:

$ sudo cryptsetup -v luksOpen /dev/sdX2 crypt
$ sudo mkfs.ext4 /dev/mapper/crypt
$ sudo mount /dev/mapper/crypt /mnt/encrypted/
$ sudo rsync -avh /mnt/backup/* /mnt/encrypted/
$ sync

The final steps that we have to make are to fix up the /etc/fstab file for the new LUKS UUID, or you can leave it as /dev/mapper/crypt and replace the UUID in our unlock script and remake the initramfs file, this step is important as it will not properly boot if not done, because it won’t have the information to use the encrypted filesystem! Remember to put the information in from YOUR system, as the UUID will be different for every system:

$ sudo mount /dev/sdX1 /mnt/encrypted/boot/
$ sudo mount -t proc none /mnt/encrypted/proc
$ sudo mount -t sysfs none /mnt/encrypted/sys
$ sudo mount -o bind /dev /mnt/encrypted/dev
$ sudo mount -o bind /dev/pts /mnt/encrypted/dev/pts
$ sudo env LANG=C chroot /mnt/encrypted
┌──(root㉿kali)-[/]
└─# blkid /dev/sdX2
/dev/sdX2: UUID="173e2de4-0501-4d8e-9039-a4923bfa5ee7" TYPE="crypto_LUKS" PARTUUID="e1750e08-02" ┌──(root㉿kali)-[/]
└─# cat /etc/fstab
# <file system> <mount point> <type> <options> <dump> <pass>
proc /proc proc defaults 0 0 UUID=173e2de4-0501-4d8e-9039-a4923bfa5ee7 / ext4 errors=remount-ro 0 1
LABEL=BOOT /boot vfat defaults 0 2 ┌──(root㉿kali)-[/]
└─# vim /etc/initramfs-tools/unlock.sh ┌──(root㉿kali)-[/]
└─# cat /etc/initramfs-tools/unlock.sh
#!/bin/sh export PATH='/sbin:/bin:/usr/sbin:/usr/bin' while true; do test -e /dev/mapper/crypt && break || cryptsetup luksOpen /dev/disk/by-uuid/173e2de4-0501-4d8e-9039-a4923bfa5ee7 crypt
done /scripts/local-top/cryptroot
for i in $(ps aux | grep 'cryptroot' | grep -v 'grep' | awk '{print $1}'); do kill -9 $i; done
for i in $(ps aux | grep 'askpass' | grep -v 'grep' | awk '{print $1}'); do kill -9 $i; done
for i in $(ps aux | grep 'ask-for-password' | grep -v 'grep' | awk '{print $1}'); do kill -9 $i; done
for i in $(ps aux | grep '\-sh' | grep -v 'grep' | awk '{print $1}'); do kill -9 $i; done
exit 0 ┌──(root㉿kali)-[/]
└─# vim /etc/crypttab ┌──(root㉿kali)-[/]
└─# cat /etc/crypttab
crypt	PARTUUID=e1750e08-02	none	luks ┌──(root㉿kali)-[/]
└─# mkinitramfs -o /boot/initramfs.gz 5.15.44-Re4son-v8l+

If you get a cryptsetup error here, similar to cryptsetup: ERROR: Couldn't resolve device PARTUUID=ed889dad-02 that means that you did not edit the /etc/crypttab file and put the correct PARTUUID in. The warning about no fsck.luks existing can be ignored, as there is no such thing.


Now we can unmount and close up everything:

┌──(root㉿kali)-[/]
└─# exit
$ sudo umount /mnt/encrypted/{boot,sys,proc,dev/pts,dev}
$ sudo umount /mnt/encrypted
$ sudo cryptsetup luksClose crypt

Earlier, we mentioned the LUKS Nuke capability. If you plan to use it, while booted on your freshly encrypted RPi rootfs, simply run the following command to add the Nuke password and follow the prompt:

[email protected]:~$ sudo dpkg-reconfigure cryptsetup-nuke-password

Stay tuned for part two where we cover remotely connecting to the Raspberry Pi as a dropbox device!

Now how about we get this automated? Thanks to Richard Nelson (@unixabg), anyone who wants to get this all set up in much less time than the manual method and much easier, can!

First things first, let’s download unixabg’s cryptmypi script:

$ git clone https://github.com/unixabg/cryptmypi.git
$ cd cryptmypi/

There are a number of things we want to do before we can run the build scripts however. Let’s go through those together now:

$ cp cryptmypi.conf config/.
$ cat ~/.ssh/id_rsa.pub >> config/authorized_keys

Now we need to edit cryptmypi.conf to change some settings in stage-2. These settings will be personal, but let’s just give you all an example:

$ vim config/cryptmypi.conf
$ cat config/cryptmypi.conf
##################
## cryptmypi settings
##################
# export prefix for hooks
export _VER="2.2-beta" # base and build
export _BASEDIR=$(pwd)
export _BUILDDIR=${_BASEDIR}/cryptmypi-build ##################
## Stage-1
##################
_IMAGEURL=https://kali.download/arm-images/kali-2022.2/kali-linux-2022.2-raspberry-pi-arm64.img.xz # compose package actions
export _PKGSPURGE=""
export _PKGSINSTALL="" # iodine settings
_IODINE_PASSWORD="your iodine password goes here"
_IODINE_DOMAIN="your iodine domain goes here" # final package actions
export _FINALPKGPURGE=""
export _FINALPKGINSTALL="telnet dsniff bettercap" ##################
## Stage-2
##################
# block device
_BLKDEV="/dev/sdb" # luks encryption cipher
_LUKSCIPHER="aes-cbc-essiv:sha256" # luks encryption password
_LUKSPASSWD="toor" # root password
export _ROOTPASSWD="toor"

What we changed here is the block device, LUKS encryption password, and the root password. The image URL can be changed if you would like to use a different image file, so be sure to do that now if need be.

Now the only thing left to do is run both stages’ scripts and follow the instructions. By the end of it, you’ll have a fully encrypted file-system with Dropbear SSH access!

Posted by Contributor