For google search: VirtualBox Build Guide, VirtualBox Build Instructions, VirtualBox 7.0.14 Build
The official Virtualbox Linux build instructions are outdated.
The following are a list of steps that will work to build a debug version of Virtualbox on Ubuntu 22.04. This has been tested on February 21, 2024.
I have yet to figure out how to actually debug the host kernel. This requires a nested virtualization set up which I have not gotten to work.
As far as I understand it though, you need an AMD CPU that supports nested virtualization. Simply supporting AMD-V is not enough, as my CPU is a Ryzen 5 5600X and that did not work.
However, if you stick to just Ring 3 research (i.e the actual userspace Virtualbox process), this is not necessary.
This guide is meant to showcase how to build a non-nested single VM setup. It assumes the host and guest are both running Linux.
First, download the Virtualbox source code. As of February 21, 2024, you can wget
this link: https://download.virtualbox.org/virtualbox/7.0.14/VirtualBox-7.0.14a.tar.bz2
Next, install dependencies by running the following:
sudo apt-get install acpica-tools chrpath doxygen g++-multilib libasound2-dev libcap-dev \
libcurl4-openssl-dev libdevmapper-dev libidl-dev libopus-dev libpam0g-dev \
libpulse-dev libqt5opengl5-dev libqt5x11extras5-dev qttools5-dev libsdl1.2-dev libsdl-ttf2.0-dev \
libssl-dev libvpx-dev libxcursor-dev libxinerama-dev libxml2-dev libxml2-utils \
libxmu-dev libxrandr-dev make nasm python3-dev python2-dev qttools5-dev-tools \
texlive texlive-fonts-extra texlive-latex-extra unzip xsltproc \
\
default-jdk libstdc++5 libxslt1-dev linux-kernel-headers makeself \
mesa-common-dev subversion yasm zlib1g-dev glslang-tools \
libc6-dev-i386 lib32stdc++6 \
docbook-xsl docbook-xml \
pylint python3-psycopg2 python3-willow python3-pil
Now, sudo su
into a root terminal and just copy paste the following to make the required 32-bit symbolic links (including the newlines. It will work):
ln -s libX11.so.6 /usr/lib32/libX11.so
ln -s libXTrap.so.6 /usr/lib32/libXTrap.so
ln -s libXt.so.6 /usr/lib32/libXt.so
ln -s libXtst.so.6 /usr/lib32/libXtst.so
ln -s libXmu.so.6 /usr/lib32/libXmu.so
ln -s libXext.so.6 /usr/lib32/libXext.so
Finally, from the Virtualbox source directory, run the following command to configure the build:
./configure --disable-hardening --disable-alsa --disable-pulse --build-libxml2 -d
Note that the build should also work with pulse
and alsa
enabled, but I disabled them here. You do need to build libxml2
from source though, the Ubuntu 22.04 libxml2
doesn't work.
Finally, you can run the following to start the build process:
source ./env.sh
kmk BUILD_TYPE=debug
After this is done, you can cd
into out/linux.amd64/debug/bin
and run the following to get the vboxdrv
kernel driver compiled and loaded:
cd out/linux.amd64/debug/bin/
./load.sh
Finally, you'll need to gain RW permissions for the kernel driver:
sudo chmod 777 /dev/vboxdrv
Finally, you can start up Virtualbox from within the out/linux.amd64/debug/bin
directory. It's the binary called Virtualbox
.
I personally used an Ubuntu 22.04 server distro as the guest VM for testing and debugging.
If you need to enable ASAN, you can use the exact same configuration as above with the following build command:
kmk BUILD_TYPE=debug VBOX_WITH_GCC_SANITIZER=1
Note that this does use the GCC ASAN. The Clang ASAN currently does not work, as shown in Config.kmk
:
else ifdef VBOX_WITH_CLANG_SANITIZER ## @todo make work...
The first step is to set up SSH connectivity with the guest. I prefer to write any PoCs on the host, and then scp
it over to the guest to run it.
Assuming the guest is using NAT, you can do the following:
- Go to the guest VM's Settings.
- Under the Network tab, choose the correct adapter (usually just "Adapter 1").
- Click on Advanced, then click the "Port Forwarding" button.
- Set the options as follows:
- Host Port:
22222
- Guest Port:
22
- Start up the guest, and install
openssh-server
. Add your host's pubkey toauthorized_keys
and etc.
You can now SSH into the guest from the host by connecting to 127.0.0.1:22222
. For example, you can use the following SSH config (placed in ~/.ssh/config
).
I like being able to login as root
directly, so you can configure your SSH server to allow that as well:
Host guest
HostName 127.0.0.1
User root
Port 22222
Firstly, please refer to the userspace template README for help with actually communicating with the devices and triggering the code. The example I provided is ran in userspace, but you can do the same with a Linux kernel module if you'd like (there are example links for that in there too).
I'll have better examples up soon, just need some time 😁
I prefer to write the exploit on the host, and then use a Makefile
like the following to run it on the guest:
all:
gcc -static exploit.c -o exploit
scp exploit guest:~/
ssh guest '~/exploit'
compile:
gcc -static exploit.c -o exploit
For a kernel module, the following Makefile
can be used (you can replace $(shell uname -r)
with the actual build directory that you need to use):
obj-m := exploit.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
scp exploit.ko guest:~/
ssh guest '~/exploit.sh'
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
The exploit.sh
script looks like this:
rmmod exploit
insmod ./exploit.ko
Note that for some reason, sometimes the ssh
command in the Makefile
will hang. I'm not entirely sure why that happens, but whenever it does, I just SSH into the guest directly and run the exploit. It's somewhat rare, but it does happen.
After you launch the guest VM, use ps aux | grep VirtualBoxVM
to find the process to attach to:
$ ps aux | grep VirtualBoxVM
faith 50882 28.0 5.4 4576636 887180 ? Sl 18:19 0:48 /home/faith/VirtualBox-7.0.14/out/linux.amd64/debug/bin/VirtualBoxVM --comment Guest --startvm 452d674a-92fb-46df-807e-537d88268935 --no-startvm-errormsgbox
In this case, the process ID is 50882
. You can start GDB with the binary located at out/linux.amd64/debug/bin/VirtualBoxVM
and then attach to this process. You will have full symbols and line by line debugging this way.
I ran into some issues where breakpoints weren't being hit even though they clearly should be. One such example was when I was doing MMIO reads / writes, where some specific reads / writes (at specific indices and with specific values) just weren't hitting the MMIO handlers that I was expecting to hit.
I have yet to figure out exactly why this is the case, but my suspicion is that it's somehow being handled in Ring-0 (i.e the host kernel), and since I don't have the debugger set up there, it's impossible for the breakpoint to be hit.
Although this is annoying, I found that the majority of the complex code for the devices I was testing was handled in Ring-3 (i.e the host userland). Therefore, I was able to do the MMIO reads and writes and just assume they were happening correctly, and then set up breakpoints in the complex logic to later check whether my writes were successful.