Lightweight,full functional Kali container - Kali VM alternative
This is a simple guide provide a more resource friendly alternative to Linux VMs (in this case kali) on a Linux host using Linux Containers (LXC) with GUI support and shared storage.
TLDR; You jump directly to one of the following without me blabbering:
Motivation:
When I have some free time to tackle CTF challenges, I often find myself looking for the tools available in popular distros like Kali and Parrot. While it is possible to install all of these tools individually from source, through a package manager, or using unofficial repositories (such as AUR or BlackArch for Arch Linux), I think having a separate environment ,to avoid cluttering my main system, much nicer.
People usually install Kali or Parrot on a virtual machine, but I find that running an entire Linux kernel in a VM is a huge waste of resources, considering the usecase and that I already have the linux kernel running in memory. This prompted me to look for a more lightweight alternative.
My requirements where the following:
- A simple, fast way to launch this environment from the cli
- No desktop environments, just a kali shell
- Ability open GUI apps on my host’s environment without much hassle
- Avoid loading the linux kernel in memory again
So basically, I was looking for a container that could run the full kali linux distro.
This prompted me to use Linux Containers (LXC) instead of the more popular Docker and Podman. The reasoning is quite simple: LXC is more geared into creating full distribution instead single-process application containers, LXC containers come with an init system out of the box
Also in this tutorial I will be using incus as a container management tool to make the whole process easier. I chose to use incus community driven version of the LXD project after canonical decided to manage the project on there own, but the LXD commands are very similar if you prefer to use that.
Security Considerations:
Disclamer: The container created in this guide could pose a security risk, can learn more about LXC security here.
Since containers use the host’s kernel, if you have root access to a non-rootless container you can almost certainly assume that if the container gets breached, escaping to the host’s root is possible. The container created in this guide is only accessible by members of the “incus-admin” group and you can assume that anyone in this group can escalate their priviledges to root ( similar to the docker group).
Installation steps
First we need to install incus.
$ sudo pacman -S incus
After installing the incus package we need to enable the systemd service
sudo systemctl start incus
Then we need to initialize our incus environment by running the following
incus admin init
Then follow the prompts depending on how you want to configure incus for me I went with mostly the defaults:
Would you like to use clustering? (yes/no) [default=no]:
Do you want to configure a new storage pool? (yes/no) [default=yes]:
Name of the new storage pool [default=default]: storage-pool
Name of the storage backend to use (dir, lvm, lvmcluster) [default=dir]:
Where should this storage pool store its data? [default=/var/lib/incus/storage-pools/default]: /home/nveniz/incus/
Would you like to create a new local network bridge? (yes/no) [default=yes]:
What should the new bridge be called? [default=incusbr0]:
What IPv4 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
What IPv6 address should be used? (CIDR subnet notation, “auto” or “none”) [default=auto]:
Would you like the server to be available over the network? (yes/no) [default=no]:
Would you like stale cached images to be updated automatically? (yes/no) [default=yes]:
Would you like a YAML "init" preseed to be printed? (yes/no) [default=no]:
Minimal install
Now after configuring incus, we can install the kali container by running the following:
You can change the name ‘kali’ to whatever you want, also use the name that you used for the profile name. We are using the cloud image for the cloud-init support that will be helpful in the GUI support steps. Alternatively, you can use the ‘defualt’ image for a bit smaller image size, but it will require some manual step when adding GUI support later.
incus create images:kali/cloud/amd64 kali
# Non cloud image
incus create images:kali/current/amd64 kali
After that we should have a kali container ready to go by starting the container like so
incus start kali
The last steps necessary are the following:
# Add the user that you want
incus exec kali -- useradd kali
# Add the user to sudo group
incus exec kali -- usermod -aG sudo kali
# Optionally to install the kali tools
incus exec kali -- apt update
incus exec kali -- apt install -y kali-linux-default
Finally in order to launch a shell in the container you can run the following:
# Login in with your user credentials
incus exec kali login
# Alternatively
incus exec kali -- su -l kali
GUI support
In order to add GUI support we need to create a ‘GUI’ profile that instract the container on how to mount the pulseaudio, wayland and X11 unix sockets to provide the container with an audio and graphical interface.
Check your host’s $XDG_RUNTIME variable and wayland socker and change the profile file accordingly
echo $XDG_RUNTIME_DIR
# The wayland socket, should be something like wayland-1
ls $XDG_RUNTIME_DIR | grep wayland
Also in the container check the UID and GID of the user that you created, if you are using the cloud image both should be 1001 otherwise 1000
incus exec kali -- id kali
Make sure that you change all the UID, GID as well as the XDG_RUNTIME_DIR path to match the ones on your system. Store the following YML to a temprary file like *gui-profile.yml*
Using default image instead of cloud image
Remove the ‘cloud-init.vendor-data’ in the profile, install the packages by running
incus exec kali -- apt install
as well as add the code that is appended to the /etc/profile (as seen in the write_files) manually by getting a root shell like soincus exec kali bash
.
config:
cloud-init.vendor-data: |
#cloud-config
package_update: true
package_upgrade: true
package_reboot_if_required: true
packages:
- pulseaudio-utils
- mesa-utils
write_files:
- path: /etc/profile
append: true
content: |
if [ ! -d /run/user/$UID ]; then
mkdir -p /run/user/$UID
fi
if [ ! -S /tmp/.X11-unix/X0 ]; then
ln -fs /mnt/X0 /tmp/.X11-unix/X0
fi
if [ ! -S /run/user/$UID/wayland-1 ]; then
ln -fs /mnt/wayland-1 /run/user/$UID/wayland-1
fi
export DISPLAY=:0
export PULSE_SERVER=/mnt/pulse.sock
export WAYLAND_DISPLAY=wayland-1
export XDG_SESSION_TYPE=wayland
export XDG_RUNTIME_DIR=/run/user/$UID
description: Sets up GPU, Wayland, X11 and PulseAudio
devices:
intel-igpu:
# Change this the container's user GID, UID
gid: "1001"
type: gpu
# Change this the container's user GID, UID
uid: "1001"
pulse:
bind: instance
# Change this to match your $XDG_RUNTIME
connect: unix:/run/user/1000/pulse/native
# Change this the container's user GID, UID
gid: "1001"
listen: unix:/mnt/pulse.sock
mode: "0700"
# Change this the container's user GID, UID
security.gid: "1001"
# Change this the container's user GID, UID
security.uid: "1001"
type: proxy
# Change this the container's user GID, UID
uid: "1001"
wayland:
bind: instance
# Change this to match your $XDG_RUNTIME
connect: unix:/run/user/1000/wayland-1
# Change this the container's user GID, UID
gid: "1001"
listen: unix:/mnt/wayland-1
# Change this the container's user GID, UID
mode: "0700"
# Change this the container's user GID, UID
security.gid: "1001"
# Change this the container's user GID, UID
security.uid: "1001"
type: proxy
# Change this the container's user GID, UID
uid: "1001"
x11:
bind: instance
connect: unix:/tmp/.X11-unix/X0
# Change this the container's user GID, UID
gid: "1001"
listen: unix:/mnt/X0
mode: "0700"
# Change this the container's user GID, UID
security.gid: "1001"
# Change this the container's user GID, UID
security.uid: "1001"
type: proxy
# Change this the container's user GID, UID
uid: "1001"
Create the incus profile by running: (You can change the name gui to whatever you want)
incus profile create gui < gui-profile.yml
Now it is time to create the actual Kali container (you can change the name ‘kali’ to whatever you want, also use the name that you used for the profile name)
incus profile assign kali gui
Install the xfce desktop environment package:
incus exec kali -- apt install -y kali-desktop-xfce
Then restart your container
incus restart kali
Now can launch GUI programs by running:
inucs exec kali -- su -l kali firefox
# Or by getting a shell with your user and just executing
firefox &
Shared storage support
In order to add a shared directory with the host we need to add a device either directly to the container or to the profile configuration.
To directly add a shared directory to a container:
incus config device add <container name> <shared storage name> disk source=<path from host> path=<path to container> shift=true
By using the shift=true
option we can translate the container UID, GUIDs to the host. For example if I create a file in the shared directory from the container using the kali (1001) user, on the host it will appear as owned by 1000 if 1001 does not exists.