Loaders#
Loaders are a key component of what makes dissect.target
work. They are responsible for mapping any kind of source
data into something that dissect.target
understands. Loaders can be incredibly complex or incredibly simple,
depending on what you are trying to achieve.
Oftentimes, your data is not in a directly usable state or it’s split over multiple files that would make more sense to use as a single entity, but needs some molding to properly use it. A loader can be seen as a preprocessor of this source data, shaping it into something more useful. All the processing happens transparently or in-memory.
For example:
A loader for
.vmx
(VMware VM metadata) files parses the metadata and loads all of the discovered.vmdk
(VMware virtual disk) files.A loader for
.vbox
(Virtual Box metadata) and.vmcx
(Hyper-V metadata) files does the same as the loader for.vmx
, but for.vdi
,.vhd
and.vhdx
files.A loader for
.vma
(Proxmox VM backup) files can parse the backup and add each enclosed virtual disk stream as a separate disk, without having to extract the.vma
file.A loader for acquire tar files can map all files to virtual in-memory filesystems.
A loader for an EDR agent can use the live API to provide virtual filesystems and registry hives.
A loader for roaming profile managers can parse profile data and map files to a virtual in-memory filesystem and registry keys to a virtual in-memory registry hive.
A loader for iTunes backups can parse the metadata and map all the enclosed files into a virtual in-memory filesystem, providing transparent decryption for any file reads.
These are just some examples of the loaders currently available in dissect.target
, but more are added as support
grows or new use-cases are encountered.
This doesn’t mean that dissect.target
only works with these types of files, however. As mentioned, a loader can
be seen as a preprocessor; a way to work with more complex data or data that is scattered over multiple files.
A target is usually a single file, but a loader for that single file can pull in as many files as it needs.
Using dissect.target
on a single (supported) file, such as a .vmdk
or .E01
works without needing a
loader. In fact, the file support for .E01
actually cheats a little by pulling in more files (the other
.E**
part files) without needing a loader. This is not by accident, as a .E01
file is a completely
self-contained disk container, so it’s possible to address it as such. In contrast, a .vma
or .vmx
file can
produce multiple disk containers, so a loader is required to correctly open each disk container.
It also allows for a separate tool that only consumes the Containers API to support opening
.E01
files.
Writing your own#
There are a few methods of using your own loader in dissect.target
:
Specify the path to your implementation(s) using the
DISSECT_PLUGINS
environment variable.Specify the path to your implementation(s) using the
--plugin-path
argument with the various Dissect Tools.Add a new implementation in the
dissect.target
source tree atdissect/target/loaders
.
The last method requires you to have a source checkout and working development setup of dissect.target
.
This is the recommended method if you intend to contribute your loader back to the project.
See also
Read more about using your own modules in dissect.target
at Loading your own modules.
Interested in developing for Dissect? Read more at Developing for Dissect.
Regardless of which method you use, you can use the boilerplate below. Do keep in mind that the final line is only
required if you’re using either of the DISSECT_PLUGINS
or --plugin-path
options!
from pathlib import Path
from typing import TYPE_CHECKING
from dissect.target.loader import Loader, register
if TYPE_CHECKING:
from dissect.target import Target
class MyLoader(Loader):
@staticmethod
def detect(path: Path) -> bool:
raise NotImplementedError()
def map(self, target: Target) -> None:
raise NotImplementedError()
# This line is necessary if your loader is outside the ``dissect.target`` source tree!
register(__name__, MyLoader.__name__, internal=False)
In the detect()
method you can place detection logic that determines if your loader is compatible with the
given path
, simple return True
if it is and False
if it isn’t. Please note that path
doesn’t
necessarily have to be a path to an actual file on your local filesystem. It can also be a path on a filesystem
of another target (a TargetPath
, more information in Filesystems). It’s also possible to
parse the path as a URI, the LocalLoader
does this for example.
Keep this in mind when writing your own loader, and steer clear of any OS specific APIs such as those from the
Python os
or os.path
module, unless explicitly intended.
If your loader is compatible with a given path, it will be instantiated with the path
as an argument.
If you want, you can provide additional initialization logic in the __init__
function by overriding it:
class MyLoader(Loader):
def __init__(self, path: Path):
# Your additional initialization logic here
super().__init__(path)
The next important method to implement is the map()
method. This is the meat and bones of your loader.
Here you can do all the crazy logic you want for whatever it is you want to load into a Target
.
Common use-cases include opening Containers or mapping things into various (virtual)
Filesystems. You can look at other loaders as an example or explore the documentation for
other various Dissect components to discover all the possibilities.
One important detail to keep in mind is that in your map()
method, the target is in an empty state.
There are no disks, volumes or filesystems yet (you’re adding them, after all), and there’s no operating
system information loaded yet. Another important detail is to understand that you generally don’t have to do
things like volume or filesystem discovery yourself, unless you specifically require so in your loader.
If you add a disk, things like volume and filesystem happen automatically as the Target
object gets
initialized. Read more about this process at Target initialization.
See also
You can refer to the API documentation of the Loader
class for more documentation
on the methods referenced here.
If you are placing your loader in the dissect.target
source tree, you’ll need to register your loader.
Do this by opening dissect/target/loader.py
and add your loader to the bottom by using
register()
:
register("myloader", "MyLoader")