How to use btf2json to generate a kernel profile for Volatility 3, without using a virtual machine and entirely within WSL.
My previous guides (here and here) have used dwarf2json, fresh virtual machines, and full debugging kernels. This is a somewhat time intensive process and doesn’t scale that well – you have to generate multiple fresh virtual machines (depending on flavour/variant), install multiple debugging profiles from slow Canonical debugging repositories, to generate multiple profiles. This assumes you have access to an environment you can (or are allowed to) generate new virtual machines, and doing this in an enterprise environments (with considerations like TLS/SSL inspection, EDR registration, host management, etc etc) becomes an incredibly long winded process.
A few days ago, I saw an announcement from Volatility regarding their plugin contest and btf2json caught my eye.
“Valentin Obst: btf2json
The btf2json project is a very promising effort to ease the burden of large-scale Linux memory analysis. By incorporating information in the readily available vmlinuz file, analysts can create Volatility 3 symbol tables without the need for a full debug kernel. Through acquisition techniques that incorporate filesystem data, it appears that the final version of this project will enable analysis with only information stored within the memory sample – a large shift from the currently difficult method to gather this information across Linux systems and distributions.
Related References: https://github.com/vobst/btf2json https://blog.eb9f.de/2024/11/10/btf2json.html Source: https://volatilityfoundation.org/the-2024-volatility-plugin-contest-results-are-in/
The tool is great and it works as expected, however the guide/instructions on GitHub are perhaps a little unclear, so I thought I’d write a guide here on how to practically generate a custom profile using btf2json and without access to a virtual machine.
At a high level, this is the process;
Obtain/generate memory sample (DumpIt for Linux, AVML, etc)
Identify full kernel banner (this is important, don’t skip this step)
Identify & obtain kernel image and linux modules from relevant repository.
Extract kernel image from package
Decompress kernel image (decompress vmlinuz to vmlinux)
Extract system map from linux modules package
Bring it all together with btf2json
Modify volatility3 schema
Test profile
Let’s get started.
Obtain/generate memory sample (DumpIt for Linux, AVML, etc)
For this step, we’ll use a publicly available sample. 13Cubed recently released an analysis challenge on YouTube which involved examination of a memory sample from an Ubuntu machine. The sample is available to download here.
Identify full kernel banner (this is important, don’t skip this step)
We can use the volatility3 plugin ‘banners’ to identify banner information. Be mindful that volatility sometimes outputs raw/escape characters in its output. Although the output below is the full value, make sure you remove any prepended/trailing values (like \b for backspace) are removed. Depending on how messy the output is, this may require some trial and error.
You’ll see that this is important later as btf2json requires you to input this value so it can generate a compatible profile. The variable you provide is going to be converted/encoded in base64, and stored in the profile (as I covered here)
python3 vol.py -v -f /mnt/c/13cubed/memory/memory.vmem bannersLinux version 6.5.0-41-generic (buildd@lcy02-amd64-120) (x86_64-linux-gnu-gcc-12 (Ubuntu 12.3.0-1ubuntu1~22.04) 12.3.0, GNU ld (GNU Binutils for Ubuntu) 2.38) #41~22.04.2-Ubuntu SMP PREEMPT_DYNAMIC Mon Jun 3 11:32:55 UTC 2 (Ubuntu 6.5.0-41.41~22.04.2-generic 6.5.13)
Identify & obtain kernel image and Linux modules from relevant repository.
From the above, we can see we have an Ubuntu host with 6.5.0-41-generic.
A quick search gives us a download location; https://launchpad.net/ubuntu/mantic/amd64/linux-image-6.5.0-41-generic/6.5.0-41.41 and a direct link; http://launchpadlibrarian.net/731521331/linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.deb
Extract kernel image from package
We have the kernel package, but it’s inside a .deb file (which is an Ubuntu package). We don’t want to install it, we want to extract a file from within it. We want to extract vmlinuz-6.5.0-41-generic, but how can we do that? Packages contain data streams and controls streams. If we were to use 7zip (as shown below), you’d be presented with 2 additional zst files (these are compressed with zstd and can’t be uncompressed with 7zip)
$ 7z l linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.debScanning the drive for archives:1 file, 14096990 bytes (14 MiB)Listing archive: linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.deb–Path = linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.debType = ArPhysical Size = 14096990SubType = deb Date Time Attr Size Compressed Name——————- —– ———— ———— ————————2024-05-21 01:11:55 ….. 2172 2172 control.tar.zst2024-05-21 01:11:55 ….. 14094625 14094625 data.tar.zst——————- —– ———— ———— ————————2024-05-21 01:11:55 14096797 14096797 2 files
We can use the dpkg -c command to identify which files we want.
Copy
$ dpkg -c linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.debdrwxr-xr-x root/root 0 2024-05-21 00:11 ./drwxr-xr-x root/root 0 2024-05-21 00:11 ./boot/-rw——- root/root 14327304 2024-05-21 00:11 ./boot/vmlinuz-6.5.0-41-generic[snip]
We can then use dpkg with the fsystarfile option to inspect and ultimately extract the file.
This is the syntax;
dpkg –fsys-tarfile your-downloaded-package.deb | tar xOf – ./path/to/file/inside > dest.file
Using the package above, this would result in the following command (one liner, code block is wrapped)
dpkg –fsys-tarfile linux-image-6.5.0-41-generic_6.5.0-41.41_amd64.deb| tar xOf – ./boot/vmlinuz-6.5.0-41-generic > vmlinuz-6.5.0-41-generic
Decompress kernel image (decompress vmlinuz to vmlinux)
btf2json doesn’t handle compressed linux kernels/images. vmlinuz is the compressed version of vmlinux. The bash script used to extract/decompress the compressed image should be included in your kernel tree (usually /usr/src/kernels/your-kernel-version/scripts/extract-vmlinux). If not, you can download it from GitHub.