In the last few months I’ve been getting back to studying pentesting and ethical hacking in general. While playing around in platforms like HackTheBox or trying to get flags in vulnerable VMs, I soon realized that I should be doing it from a VM instead of from my own machine, mainly for security reasons but for organization and convenience as well, the tools and files that you need to download quickly add up and it starts to become a mess.
This post will try to describe my path putting together a NixOS config that can generate a virtual machine so I can fool around without breaking or compromising my main machine.
Some requirements
I recently migrated to a workflow with a tilling window manager and nvim so I wanted to have the same workflow in this VM. I wanted a basic version of my actual machine that could be reproducible, where I could reuse my dotfiles and config and easily add the tools that I would need, I also wanted it to be easy to generate a new and clean instance of the VM.
So, why not Kali?
This is would be a perfect use case for Kali, I would probably just need to do a few customizations and it would be good to go, saving me an incredible amount of hours and saving you of reading this post.
But as a beginner in cybersecurity, having everything preinstalled bothers me, I like to think that searching for, installing and maintaning your own set of tools can give you great insight in how they work. Not that I want to write everything from scratch but I want to have “my own” Kali Linux, or in other words, I want to gather my own collection of tools and learn how they work in the meantime.
The naive approach
My main machine is running on Arch, so my first approach was to simply export the list of what I had installed via pacman, make a script to download this list and clone my dotfiles to my home, run this script in an Arch VM and then everything would work, right? eh, kinda.
It worked but had some problems, this naive approach was important to realize that I did not want a exact copy of my machine, I wanted my workflow tied together with a specific list of packages that not necessarily should be in the main machine, for instance, I did not want things like metasploit or gobuster to be in the list of things installed in my main machine.
The other important thing that I saw with this approach is that this list of packages is going to grow a lot, so I needed a easy way of adding something to this list and could not be restricted to some specific package repository, so I also needed a structured way of building it from source.
So, why NixOS?
Mainly because I was curious about it and wanted to give it a go, so why not? But NixOS covers pretty much all the requirements that I had:
- Nothing is preinstalled, I can start from scratch
- I can add new tools very easily
- If the tool that I want is not on nixpkgs, NixOS provides a way of packaging it yourself
- I can reuse my dotfiles
- A tool that generates a QEMU VM image from a nix config already exists
From now on we will be getting into the details of how the config that I put together works, so it is a good time for a disclaimer, I am by no means a NixOS expert and just started messing around with it, so I’m pretty confident that may be better and simpler ways to achieve the same results.
It’s good to note that the objective of this is not to be some kind of universal thing like Kali Linux, but to be my pentesting lab/environment that you can take inspiration or ideas from it, I know I had to look a lot at other people’s configs so I could build this one.
An overview
You can check the github repo here, I will be adding code snippets to illustrate what I’m talking about but you can follow through the repository as well.
There are three parts of the config that I would like to give a quick intro to what they are and how they can be used:
Nix Flakes
Nix Flakes are basically a way to manage dependencies in the Nix ecosystem, you can define inputs and outputs to a Flake, these inputs and outputs can be another Flake, so it is possible to reuse or chain them to achieve some result. You can read more about Flakes here.
In my config I’m using flake mainly to initialize Home-Manager and transform everything into a VM image using NixOS Generators.
|
|
Home-Manager
Home-Manager allows us to declaratively manage a user’s packages and dotfiles, these will be applied only to the user profile, not globally. In my config I try to keep the system configuration at configuration.nix and the packages and dotfiles at home.nix.
We need to initialize Home-Manager and define where our user configuration will be, in my config I do this at flake.nix:
|
|
Here at the “modules” property we are importing our general configuration (configuration.nix) and our user configuration (controlled by Home-Manager) into the generator. If you want to know more about Home-Manager take a look at its manual and options.
NixOS Generators
Because the objective of this config is to output a VM image, I’m using NixOS generators a project to generate a VM in various formats using the nix configuration.
|
|
Oh my dotfiles!
I spent a good amount of time in my dotfiles, mainly messing around in awesomeWM and neovim, so I already have a stablished config and workflow. While browsing through other people’s configs I saw that was pretty common to configure everything in nix files and Home-Manager, but I did not want to migrate all my dotfiles to a nix format and ended up simply “putting” my config in the generated home folder.
There is two properties that I used to put my dotfiles in the VM home folder:
xdg.configFile
will set the files “/home/user/.config/”home.file
will set the files directly at “/home/user/”
|
|
This basically just gets files from a repo and put them in the required folders so the applications can read their configuration. It is important to use the recursive
option when copying a folder with its subfolders.
While test-driving the VM I needed a collection of wordlists to try and find some directories, so I decided to add one to the config as an example, I created a separated file that it all it does is fetch a repo from github:
|
|
In the beginning of the home.nix
file where I declare the variables I bring the result from this file into the current context and use it to save the repo at ~/wordlists/seclists
:
|
|
Adding packages
This is very very simple if the package already exists into nixpkgs, you can search for packages in here.
With Home-Manager the property that declares the packages to be installed for the user is home.packages
, check this example from my home.nix
:
|
|
I had a requirement that it should be possible to build something from source, or simply install the binary, in case I needed to add some package that is not already in nixpkgs, so I tried to find some cybersec tools that were not packaged already and it was much harder than I expected, there are a lot of well known tools in nixpkgs.
I ended up finding that Raccoon, a tool for recon and information gathering, was not in nixpkgs and decided to give it a try to package it myself, after a few hours I had this file:
|
|
This snippet just uses the fetchPypi
function to get the binary from PyPi and buildPythonPackage
to install it. This was the same experience when I tried to build gobuster from source as an example, I just used the buildGoModule
function, these helper functions are provided by NixOS and help a lot when packaging things yourself.
A tip that helped me a lot is to look at how other tools were already packaged, you can see this at the nixpkgs repository, take the gobuster example, this is how it was packaged.
It took hours because of my non-existent experience with nix and the python ecosystem. I recommend that you read a little about the nix language itself, it was not that straight forward for me to understand what was happening in some examples or documentation that I found.
After this we just need to add our self-packaged programs to our package list in home.nix
:
|
|
The callPackage
function is just a convenience function that makes it so we do not need to pass all of the inputs if they are already present in the context, in this case all of the functions that we need are in the pkgs
input. If you want to know more about this checkout this nix pill.
Lets try it out
Due to the Flakes being an experimental feature, to interact with it we need to explicitly enable Flakes on our nix installation, this process changes depending on your system, so just follow this guide.
After enabling Flakes, we can run the following command where the flake.nix
file is located:
$ nix build ./#qcow
The path after the “#” is related to the name you gave in the flake.nix
file:
|
|
This command will download all the dependencies and build your VM image from the config, after the build is finished there will be result
folder with a .qcow2
file inside it, remember that you can change settings so that the flake will output the image in another format, check this list of supported formats.
The steps to launch this VM will change based on how you want to run it, in my case I wanted to use QEMU/KVM so I made a script that uses virsh
and virt-install
:
|
|
This script basically deletes any old image and installs a new one, I recommend always copying the image out from the result
folder and installing the copy so that you always have a clean image ready to go.
Keep in mind that the progress you make in a VM instance will be stored in the .qcow2
file.
So after running the install script we get a new window:
And I think this ends it, now we a have a reproducible, easily configurable and very flexible VM that we can break with no worries!