Motivation
When I have some free time to tackle CTF challenges, I often find myself needing various hacking tools that are commonly available in popular distributions like Kali and Parrot. While it’s possible to install all the necessary tools individually from source, through a package manager, or using unofficial repositories (such as AUR or BlackArch for Arch Linux), I prefer to maintain a separate environment to avoid cluttering my main system.
By far the most common way is 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 that my host already has a Linux kernel running. This led me to look into the possibility of using a Kali container instead which would be more resource-efficient but should still have to be persistent and capable of running GUI applications like web browsers and tools such as Burp Suite.
Usually I would use a container engine like Podman to do something like this, but configuring to be more like a full OS with persistence, GUI support and an init system seemed like a hustle. This is where LXC comes into play.
What is LXC?
Linux Containers (LXC) is like the godfather of all popular container engines like docker and podman, where in fact docker initially used LXC until v0.9. The main idea behind LXC is to give the ability to users to run fully fledges Linux environments using cgroups and namespaces to create an isolated environment without the need of fully virtualizing the Linux kernel. So basically LXC was the first attempt to create containers like we know them today, the main difference is that LXC are designed to provide a persistent Linux operating system with an init system, which makes LXC exactly what I was looking for.
What is incus?
As already mentioned LXC is the container runtime, we could potentially use LXC the create a container and manually manage it but there is an easier way to do this using a container management tool. With these tools you can easily create and manage a new container with only a few commands. I personally chose to use incus (the opinionated part) which is the open source and community driven version of the LXD project after canonical decided to manage the project on there own.
Installation steps
The following prerequisites are needed:
-
Internet access
-
A Linux host
-
The incus package installed on your host
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.