Over the past few years, I’ve occasionally needed to do some quick forensics on Linux hosts. Each time I do, I find myself stitching together 5-10 different pages of content to pull together the information I need to grab the disk and memory collections.
This is a guide that attempts to pull all of that into one place and will likely serve as a future reference for me, but I hope others can derive some value from it as well.
NOTE: This guide should NOT be used for important cases! This is called “quick and dirty” for a reason. There’s always tradeoffs to be made between time savings and evidence preservation and some of the techniques in here optimize for time savings at the expense of potential data modification or loss.
Obtaining a disk image using DD over netcat and writing the result to a file
nc -l 19000 | bzip2 -d | dd bs=16M of=/path/to/disk_image.dd
Sending host (host being imaged):
dd bs=16M if=/dev/sda[n] | bzip2 -c | nc receiving.host.com 19000
At this point, it’s worth noting you can image the entire disk (e.g. /dev/sda) or just a specific partition (/dev/sda1). Doing the latter will save you some steps down the road. However, if there are multiple partitions you’ll need to analyze, you might as well grab the whole disk.
This is one of those tradeoff situations. If you have time, do all of this on a VM using the same OS and kernel version as your target system and just transfer over the compiled kernel module. If not, you’ll certainly be stomping all over evidence by installing packages and compiling files, but sometimes that can be a tradeoff worth making depending on the situation.
sudo apt-get install lime-forensics-dkms git clone https://github.com/504ensicsLabs/LiME make ## Take note of where the resulting kernel module ends up # Install the kernel module and write out the memory dump to a file sudo insmod /path/to/lime.ko "path=/output_mem.mem format=lime"
Once I’ve transferred over the memory dump, I also like to dump the strings to a file for quick grepping:
strings output_mem.mem > strings.mem
Create a Custom Profile for Volatility
sudo apt install dwarfdump git clone https://github.com/volatilityfoundation/volatility.git cd ./volatility/tools/linux/ make # Zip up the files that comprise of the profile zip $(lsb_release -i -s)_$(uname -r)_profile.zip ./volatility/tools/linux/module.dwarf /boot/System.map-$(uname -r)
Add the custom profile to Volatility on the analysis system
# Copy the zip file created above to volatility/volatility/plugins/overlays/linux cp your_profile_name.zip volatility/volatility/plugins/overlays/linux # Ensure it worked volatility/vol.py --info | grep 'your_profile_name'
At this point, we have a memory dump and a DD image of either the entire disk or a specific parition. If you DD’ed the entire disk, follow this step. If not, you can ignore it.
Determining the offset to specify to forensics tools
Many forensic tools expect the start of a data partition as input and will throw “filesystem not recognized” errors and such if you try to pass an entire disk image to them. To workaround this, you can almost always pass an offset to the tool to specify where in the disk image data should start being read.
# Display details about the filesystem fsstat disk_img.dd Cannot determine file system type
In this case, we can use
mmls to display the partition layout determine the offset of the data partition in our image:
$ mmls disk_img.dd GUID Partition Table (EFI) Offset Sector: 0 Units are in 512-byte sectors Slot Start End Length Description 000: Meta 0000000000 0000000000 0000000001 Safety Table 001: ------- 0000000000 0000002047 0000002048 Unallocated 002: Meta 0000000001 0000000001 0000000001 GPT Header 003: Meta 0000000002 0000000033 0000000032 Partition Table 004: 013 0000002048 0000010239 0000008192 005: 014 0000010240 0000227327 0000217088 006: 000 0000227328 0052428766 0052201439 007: ------- 0052428767 0052428799 0000000033 Unallocated
In this case, the longest section with length
0052201439 is the data partition, so the starting offset will be
0000227328 or just
Now when we pass this offset to
fsstat using the
-o parameter, it’s happy:
$ fsstat -o 227328 disk_image.dd | head FILE SYSTEM INFORMATION -------------------------------------------- File System Type: Ext4 Volume Name: sample-rootfs Volume ID: 73d0d0d862d4c4a5774020dd74020dd32
Dump the filesystem and create a timeline from it
Next, I like to use
fls to dump the file and directory listings of the filesystem (with associated timestamps and metadata) using
Remember to pass the offset if you’re using a full disk image.
fls -o 227328 disk_image.dd -r -p -m / > bodyfile.txt
After that, we can use
mactime to narrow down the timerange (optional) and create a filesystem timeline:
mactime -d -z UTC -b bodyfile.txt 2021-04-01..2021-06-24 > filesystem_timeline.csv
log2timeline will give us a more detailed timeline, including things like relevant events from system logs and much more: https://plaso.readthedocs.io/en/latest/sources/user/Parsers-and-plugins.html
# Create an output file called disk_image.plaso from our disk image # log2timeline is smart enough to ask you which partition(s) you want to analyze. # No need to pass an offset here! log2timeline.py disk_image.plaso disk_image.dd
Once again, we may want to narrow down this timeline a bit, so we can use
psort for that
psort.py -z "UTC" -o L2tcsv disk_image.plaso "date > '2021-04-01 00:00:00' AND date < '2021-06-24 00:00:00'" -w sorted_supertimeline.csv
Mounting the disk read-only
I like exploring the filesystem while I review the timeline, and the best way to do that is to mount it. We have to pass some special options though:
# Create a mountpoint mkdir /mnt/forensics # We actually have to calculate the offset here: # Instead of 227328, it's going to be (227328 * sector_size). In many (but not all!) cases, this is 512 # Therefore, our offset will be 116391936 # Must be root to mount sudo mount -o ro,norecovery,noexec,loop,offset=116391936 -t ext4 disk_image.dd /mnt/forensics
You can now explore the filesystem of the host in a read-only capacity at
Extracting unallocated space
Unallocated space can be useful for digging for deleted file data. Generate it using
blkls disk_image.dd > unallocated_space.blkls
At this point, we now have the following data sources to analyze:
- A full disk image
- A mounted disk image
- A memory dump
- Strings from a memory dump
- A filesystem timeline
- A sorted super timeline
- Unallocated space dump
This is generally enough data for most investigations, or at least serves as a solid starting point for more intensive searches. Hopefully this post comes in handy for anyone looking for a quick forensics workflow.