Targets#

Targets (or target) is the terminology we use for the type of source data supported by dissect.target. It includes anything that can, one way or another, be used to describe a certain state of a system.

Some examples include:

  • Raw disk images, virtual disks (.vmdk, .vhdx, etc) and evidence containers (.E01)

  • Virtual machine descriptor files (.vmx, .vmcx, etc)

  • Local live systems (\\.\PhysicalDrive0, /dev/sda)

  • Forensic packages such as .tar archives created by acquire or KAPE output directories

  • … and more

It’s important to be aware of the difference between virtual machine descriptor (like .vmx) and virtual disk (like .vmdk) files. For example, when using a .vmdk with Dissect, only that VMDK will be loaded, but when using a .vmx, all the .vmdk files described in that VMX file will be loaded instead. This ensures that systems with multiple virtual disks work as intended. A .vmx file and .vmdk file are thus both valid targets. Keep this in mind when working with investigative data.

On the technical side, everything eventually gets loaded into a Target Python object. The following sections will go into more detail on how this object works. This is generally only useful information if you’re looking to interact with a target from your own Python code or writing a plugin for dissect.target.

The Target object#

The Target class is your primary interaction point in dissect.target. It represents some system in some specific state, loaded from some target files. Any interaction you want to do with that system is done through the Target class.

Conceptually a target consists of one or more disks, volumes and filesystems, which can be modified or added by Loaders. These items are available as attributes on a Target instance we’ve named t at:

  • t.disks

  • t.volumes

  • t.filesystems

The next important attribute to know about is Target.fs, which is the root filesystem. This object behaves like any other filesystem in dissect.target, but exists to allow other filesystems to be mounted at arbitrary paths or drive letters within the context of a Target. Use this when you need to interact with “the filesystem” of a target.

Finally, there are the Plugins. Plugins are functions that can be executed on a target. They can be as simple as reading the hostname from /etc/hostname, or as advanced as parsing a specific artefact like the Windows event log.

Plugins are dynamically made available on the Target object. For example, if you wanted to run the evtx plugin on a Target instance named t, you would do t.evtx(), which will:

  • Check if the evtx function is already cached, otherwise perform a lookup which will:

    • Find a plugin that exports the evtx function and see if it’s compatible.

    • Cache it and register it on the Target instance t, it is now available as t.evtx.

  • If the plugin function is marked as a property, it will behave like a Python @property.

  • If it’s a function, calling it (t.evtx()) will execute the plugin function.

See also

For more information on how plugins work, please read their documentation at Plugins.

To learn more about the Target object, it’s recommended to read either the Target class documentation or the source code.

Initialisation#

Target initialisation usually starts with finding a loader for a target, unless you’re manually opening a target. If no compatible loader is found, the default behaviour is to treat the target path as a container and add it as a disk. After all of the discovered disks, volumes and/or filesystems are added by a loader (or the default behaviour), the Target object will continue initialisation.

Initialisation roughly consists of the following steps:

  • Iterate Target.disks.

    • Perform volume discovery using volume.open() and add to Target.volumes.

    • Add disk as a raw volume if no additional volumes were discovered.

  • Iterate Target.volumes.

    • Discover and open logical volume managers using volume.open_lvm() and add discovered logical volumes to Target.volumes.

    • Discover and open encrypted volumes using volume.open_encrypted() and add discovered and decrypted volumes to Target.volumes.

    • Perform filesystem discovery using filesystem.open() dissect.target.filesystem.open>() and add discovered filesystems to Target.filesystems.

  • Basic initialisation is now done, OS detection still has to happen but that can fallback to a “no-op” OS plugin, leaving you able to interact with just the disks, volumes and filesystems.

  • Find and iterate over all OS plugins to find the most specific OS.

    • Subclasses of different OS plugins are considered “more specific”, so e.g. DebianPlugin is more specific than LinuxPlugin.

  • OS initialisation is performed, this is can be super simple or extremely complex. Generally it consists of:

    • All the filesystems are mounted at their correct mount points in the root filesystem.

    • Optional additional OS specific parsing or initialisation is performed.

  • The Target is now fully initialised.

Manually opening a target#

There are multiple ways to open a Target object. The recommended way to open a single target is to use the Target.open() method, or Target.open_all() to open multiple targets from a single path. These will return (or yield) a fully initialized and loaded Target object for you to start interacting with.

You can also opt to manually open a target. This can be useful when you’re writing some code that needs to modify attributes, or manually add things like filesystems. For example, a recovery script that scrapes for a filesystem that may have had its superblock or $BOOT destroyed:

from dissect.target import Target

t = Target()
t.filesystems.add(recover_filesystem())
t.apply()

print(t.hostname)

Targets in targets#

Dissect also supports the concept of targets within targets, referred to as child targets. For example, when a target contains a .vmdk file within itself, we can tell dissect.target to load that target from within the context of the first target. This can be useful when dealing with hypervisors.

Say, for example, we opened a Hyper-V target locally from \\.\PhysicalDrive0, we can parse the metadata in ProgramData/Microsoft/Windows/Hyper-V/data.vmcx that tells us where all of the virtual machines are stored. Then we can then use these paths and tell dissect.target to load another target from there. Reading all of these files will still happen from \\.\PhysicalDrive0, passing through the various abstraction layers of dissect.target. This allows Dissect to read the disks from running virtual machines, regardless of locks the operating has on these files.