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:

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 so incus 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.

 

Resources:

#kali container #lxc #full distro container