Record Descriptors

Most output generated by Dissect tools takes the form of records.

You can create your own records by defining a record descriptor:

from dissect.target.helpers.record import TargetRecordDescriptor


FirewallRecord = TargetRecordDescriptor(
    "application/firewall",
    [
        ("datetime", "ts"),
        ("net.ipaddress", "ip"),
        ("uint16", "port"),
        ("string", "protocol"),
    ],
)

A record is composed of various field types and their corresponding names.

The TargetRecordDescriptor() will add basic target information to complete your record. You can now generate records by filling the structure like this:

firewall_record = FirewallRecord(
    ts=time.time(),
    ip="0.0.0.0",
    port=8080,
    protocol="tcp",
    _target=t
)

Target related information like hostname and domain will be automatically added if you provide your target by setting the _target field. There are some default record descriptors extending TargetRecordDescriptor() available for common scenarios:

Record Descriptor

Additional Fields

ChildTargetRecord

  • type (string)

  • path (path)

UnixUserRecord

  • name (string)

  • passwd (string)

  • uid (varint)

  • gid (varint)

  • gecos (string)

  • home (path)

  • shell (string)

  • source (string)

WindowsUserRecord

  • sid (string)

  • name (string)

  • home (path)

If you create your own record descriptor, it is often practical to use existing ones as a basis. This helps to keep the naming uniform. To extend existing record descriptors use create_extended_descriptor().

For instance, to create a record descriptor for browser downloads you could use:

from dissect.target.helpers.record import create_extended_descriptor

Descriptor = create_extended_descriptor([UserRecordDescriptorExtension])

DownloadRecord = Descriptor(
    "browser/download",  [
        ("uri", "url"),
        ("filesize", "size"),
    ]
)

You can now combine data from several sources in one record:

DownloadRecord(
    url="http://example.com/download.zip",
    size=123,
    _target=t,
    _user=u
)

Note that source fields (_target, _user) from the extended record descriptors are provided using keywords starting with an underscore.

It is also possible to use multiple existing record descriptor extensions as the basis of your new descriptor:

from dissect.target.helpers.record import create_extended_descriptor

UserRegistryRecordDescriptor = create_extended_descriptor(
    [
        RegistryRecordDescriptorExtension,
        UserRecordDescriptorExtension,
    ]
)

The following record descriptor extensions are available:

Record Descriptor Extension

Fields

Source Field

RegistryRecordDescriptorExtension

  • regf_hive_path

  • regf_key_path

_key

TargetRecordDescriptorExtension

  • domain

  • hostname

_target

UserRecordDescriptorExtension

  • username

  • user_id

  • user_group

  • user_home

_user

GroupedRecord

A GroupedRecord holds multiple records and offers a flat view of the records. Suppose you wish to record an event system that contains events with triggers and events with actions. You could use a GroupedRecord to compose records reflecting these combinations:

from flow.record import GroupedRecord

if action:
    yield GroupedRecord("event/grouped", [event, action])
elif trigger:
    yield GroupedRecord("event/grouped", [event, trigger])

If two records have the same fieldname, the first one will prevail.

Warning

Note that, this record type cannot be used to nest records. Nesting records is not possible.

DynamicDescriptor

The DynamicDescriptor() function returns a plain record descriptor with the provided types. This function can be used if your plugin creates its own record descriptor dynamically but you still wish to provide certain field types through the export decorator.

Let’s say you create a dynamic descriptor with fields fields:

yield TargetRecordDescriptor("sql/table", fields)(
    _target=self.target,
    **values,
)

In this case you might want to communicate that fields at least contains a digest type:

@export(record=DynamicDescriptor(["digest"]))

This allows other tools that are interested in records having specific field types to check if your plugin function provides this.