vtypes

package module
v0.0.0-...-069d4a7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 23, 2024 License: Apache-2.0 Imports: 19 Imported by: 2

README

Vtypes parsing subsystem for Golang and VQL

Design goals

Parsing binary structures is very important for forensic analysis and DFIR - we encounter binary data in many contexts, such as file formats, network traffic and more.

Velociraptor uses VQL to provide the flexibility for users to be able to craft a VQL query in order to retrieve valuable machine state data. Sometimes we need to parse binary data to answer these questions.

While binary parsers written in Golang are typically the best options for speed and memory efficiency, the need to compile a parser into an executable and push it to the endpoint makes it difficult to implement adhoc parsers. Ideally we would like to have a parser fully implemented in VQL, so it can be added to an artifact and pushed to the endpoint without needing to recompile and rebuild anything.

The parser needs to be data driven and descriptive as to the underlying data format, but at the same time capable of parsing more complex binary structures.

We take a lot of inspiration from existing parser frameworks, such as the Rekall vtypes language (which was implemented in Python), but the VQL Vtype parsers are much improved, much faster than python and more flexible.

Overview

The parser is driven by a json data structure called a "Profile". A Profile is simply a data driven description of how structs are layed out and how to parse them.

In order to use the parser, one simply provides a profile definition, and a file (or datablob) to parse. The parser is given an offset and a struct to instantiate. Here is an example of VQL that parses a single struct at offset 10 in the file.

SELECT parse_binary(profile='[ ["Header": 0, ["Signature", 0, "String", {"length": 10}]]]',
                    filename='/path/to/file', struct='Header')
FROM scope()

Profile description.

Profile descriptions are supposed to be easy to understand and quick to write. It is a way of describing how to parse a particular binary type at a high level.

A profile is a list of struct definitions. Each struct definition contains the name of the struct, its size and a list of field definitions.

In turn field definitions are a list of the field's name, its offset (relative to the start of the struct), and its type followed by any options for that type.

Typically a profile is given as JSON serialized string.

Here is an example:

[
  ["Header", 0, [
    ["Signature", 0, "String", {"length": 13}],
    ["CountOfEntries", 14, "uint32"],
    ["Entries", 18, "Array", {"type": "Entry", "count": "x=>x.CountOfEntries"}]
  ]],
  ...
]

In the above example:

  1. There is a single struct called Header.

  2. The size of the header is not specified (it is 0). The size of a struct becomes important when using the struct in an array.

  3. The CountOfEntries field starts at offset 14 into the struct and it is a uint32.

  4. The Entries field starts at offset 18, and contains an array. An array is a collection of other items, and so it must be initialized with the proper options. In this case the array contains items of type "Entry" (which is another struct, not yet defined).

  5. The count of the array is the number of items in the array. Here it is specified as a lambda function.

Lambda functions are VQL snippets that calculate the value of various fields at runtime. The Lambda is passed the struct object currently being parsed, and so can simply express values dependent on the struct's fields.

In the above example, the count of the array is given as the value of the field CountOfEntries. This type of construct is very common in binary structures (where a count or length is specified by another field in the struct).

Lets continue to view the next definition:

["Entry", "x=>x.ModuleLength + 20", [
  ["Offset", 0, "Value", {"value": "x=>x.StartOf"}],
  ["ModuleLength", 8, "uint32"],
  ...
]],

The definition of the Entry struct is given above. The size is also given by a lambda function, this time, the size of the entries is derived from the ModuleLength field. Note how in the above definition, the Entries field is a list of variable sized Entry structs.

Parsers

Struct fields are parsed out using typed parsers. The name of the parser is used at the 3rd entry to its definition:

Simple parsers

These parse primitive types such as int64, uint32 etc.

Struct parsers

Using the name of a struct definition will cause a StructObject to be produced. These look a bit like dict objects in that VQL can simply dereference fields, but fields are parsed lazily (i.e. upon access only). There are also additional properties available:

  1. SizeOf property is the size of the struct (which may be derived from a lambda). For example, x=>x.SizeOf returns the size of the current struct.

  2. StartOf and EndOf properties are the offset to the start and end of the struct.

Array parser

An array is a repeated collection of other types. Therefore the array parser must be initialized with options that specify what the underlying type is, its count etc.

  1. type: The type of the underlying object
  2. count: How many items to include in the array (can be lambda)
  3. max_count: A hard limit on count (default 1000)

Parsing a field as an array produces an ArrayObject which has the following properties:

  1. SizeOf, StartOf, EndOf properties as above.
  2. Value property accessed the underlying array.

You can iterate over an ArrayObject with the foreach() plugin:

SELECT * FROM foreach(row=Header.Entries, query={....})

Accessing a member of the foreach will produce an array of each member. e.g. Header.Entries.ModuleLength will just produce a list of length.

String parser

Strings are very common to parse. The string parser can be configured using the following options.

  1. encoding: Can be UTF16 to parse utf16 arrays
  2. term: A terminator - by default this is the null character but you can specify the empty string for no terminator or another sequence of characters.
  3. length, max_length: The length of the string - if not specified we use the terminator to find the end of the string. This can also be a lambda to derive the length from another field.

Documentation

Overview

Implements a binary parsing system.

This implementation is roughly based on the one in Rekall.

Implements a binary parsing system.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func AddModel

func AddModel(profile *Profile)

func Associative

func Associative(scope vfilter.Scope, a vfilter.Any, field string) vfilter.Any

func Debug

func Debug(arg interface{})

func EndOf

func EndOf(obj interface{}) int64

func EvalLambdaAsInt64

func EvalLambdaAsInt64(expression *vfilter.Lambda, scope vfilter.Scope) int64

func EvalLambdaAsString

func EvalLambdaAsString(expression *vfilter.Lambda, scope vfilter.Scope) string

func IsNil

func IsNil(v interface{}) bool

We need to do this stupid check because Go does not allow comparison to nil with interfaces.

func JsonDump

func JsonDump(v interface{})

func MakeScope

func MakeScope() vfilter.Scope

func ScopeDebug

func ScopeDebug(scope vfilter.Scope, fmt string, args ...interface{})

func SizeOf

func SizeOf(obj interface{}) int

func StartOf

func StartOf(obj interface{}) int64

func StringIndent

func StringIndent(v interface{}) string

Types

type ArrayAssociative

type ArrayAssociative struct{}

func (ArrayAssociative) Applicable

func (self ArrayAssociative) Applicable(a vfilter.Any, b vfilter.Any) bool

func (ArrayAssociative) Associative

func (self ArrayAssociative) Associative(scope vfilter.Scope,
	a vfilter.Any, b vfilter.Any) (vfilter.Any, bool)

func (ArrayAssociative) GetMembers

func (self ArrayAssociative) GetMembers(scope vfilter.Scope, a vfilter.Any) []string

type ArrayIterator

type ArrayIterator struct{}

Arrays also participate in the iterator protocol

func (ArrayIterator) Applicable

func (self ArrayIterator) Applicable(a vfilter.Any) bool

func (ArrayIterator) Iterate

func (self ArrayIterator) Iterate(
	ctx context.Context, scope vfilter.Scope, a vfilter.Any) <-chan vfilter.Row

type ArrayObject

type ArrayObject struct {
	// contains filtered or unexported fields
}

func (*ArrayObject) Contents

func (self *ArrayObject) Contents() []interface{}

func (*ArrayObject) End

func (self *ArrayObject) End() int64

func (*ArrayObject) MarshalJSON

func (self *ArrayObject) MarshalJSON() ([]byte, error)

func (*ArrayObject) SetParent

func (self *ArrayObject) SetParent(parent *StructObject)

func (*ArrayObject) Size

func (self *ArrayObject) Size() int

func (*ArrayObject) Start

func (self *ArrayObject) Start() int64

type ArrayParser

type ArrayParser struct {
	// contains filtered or unexported fields
}

func (*ArrayParser) New

func (self *ArrayParser) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*ArrayParser) Parse

func (self *ArrayParser) Parse(
	scope vfilter.Scope,
	reader io.ReaderAt, offset int64) interface{}

type ArrayParserOptions

type ArrayParserOptions struct {
	Type               string
	TypeOptions        *ordereddict.Dict
	Count              int64
	MaxCount           int64
	CountExpression    *vfilter.Lambda
	SentinelExpression *vfilter.Lambda
}

type BitField

type BitField struct {
	StartBit int64  `json:"start_bit"`
	EndBit   int64  `json:"end_bit"`
	Type     string `json:"type"`
	// contains filtered or unexported fields
}

func (*BitField) New

func (self *BitField) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*BitField) Parse

func (self *BitField) Parse(
	scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

type Ender

type Ender interface {
	End() int64
}

type EnumerationParser

type EnumerationParser struct {
	// contains filtered or unexported fields
}

func (*EnumerationParser) New

func (self *EnumerationParser) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*EnumerationParser) Parse

func (self *EnumerationParser) Parse(
	scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

type EnumerationParserOptions

type EnumerationParserOptions struct {
	Type        string
	TypeOptions *ordereddict.Dict
	Choices     map[int64]string
}

type EpochTimestamp

type EpochTimestamp struct {
	// contains filtered or unexported fields
}

func (*EpochTimestamp) New

func (self *EpochTimestamp) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*EpochTimestamp) Parse

func (self *EpochTimestamp) Parse(
	scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

type EpochTimestampOptions

type EpochTimestampOptions struct {
	Type        string
	TypeOptions *ordereddict.Dict
	Factor      int64
}

type FatTimestamp

type FatTimestamp struct {
	// contains filtered or unexported fields
}

func (*FatTimestamp) New

func (self *FatTimestamp) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*FatTimestamp) Parse

func (self *FatTimestamp) Parse(
	scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

type FieldDefinition

type FieldDefinition struct {
	Name string
	// Offset within the struct
	Offset int64

	// Alternatively offset may be given as an expression.
	OffsetExpression string

	// Name of the type of parser in this field.
	Type string

	// Options to the type
	Options *ordereddict.Dict
}

func (*FieldDefinition) UnmarshalJSON

func (self *FieldDefinition) UnmarshalJSON(p []byte) error

type Flags

type Flags struct {
	// contains filtered or unexported fields
}

func (*Flags) New

func (self *Flags) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*Flags) Parse

func (self *Flags) Parse(
	scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

type FlagsOptions

type FlagsOptions struct {
	Type        string
	TypeOptions *ordereddict.Dict
	Bitmap      map[int64]string
	Bits        []int64
}

Accepts option bitmap: name (string) -> bit number

type IntParser

type IntParser struct {
	// contains filtered or unexported fields
}

Parse various sizes of ints.

func NewIntParser

func NewIntParser(type_name string, size int, converter func(buf []byte) interface{}) *IntParser

func (*IntParser) DebugString

func (self *IntParser) DebugString(scope vfilter.Scope, offset int64, reader io.ReaderAt) string

func (*IntParser) New

func (self *IntParser) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

IntParser does not take options

func (*IntParser) Parse

func (self *IntParser) Parse(scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

func (*IntParser) Size

func (self *IntParser) Size() int

type NullParser

type NullParser struct{}

A parser that always returns NULL

func (NullParser) New

func (self NullParser) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (NullParser) Parse

func (self NullParser) Parse(
	scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

type ParseAtOffset

type ParseAtOffset struct {
	// contains filtered or unexported fields
}

A parser that parses its delegate at a particular offset

func (*ParseAtOffset) New

func (self *ParseAtOffset) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*ParseAtOffset) Parse

func (self *ParseAtOffset) Parse(scope vfilter.Scope,
	reader io.ReaderAt, offset int64) interface{}

NOTE: offset is the offset to the start of the struct.

type Parser

type Parser interface {
	Parse(scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

	// Given options, this returns a new configured parser
	New(profile *Profile, options *ordereddict.Dict) (Parser, error)
}

Parsers are objects which know how to parse a particular type. Parsers are instantiated once and reused many times.

type PointerParser

type PointerParser struct {
	// contains filtered or unexported fields
}

func (*PointerParser) New

func (self *PointerParser) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*PointerParser) Parse

func (self *PointerParser) Parse(
	scope vfilter.Scope,
	reader io.ReaderAt, offset int64) interface{}

type PointerParserOptions

type PointerParserOptions struct {
	Type        string
	TypeOptions *ordereddict.Dict
}

type Profile

type Profile struct {
	// contains filtered or unexported fields
}

func NewProfile

func NewProfile() *Profile

func (*Profile) AddParser

func (self *Profile) AddParser(type_name string, parser Parser)

func (*Profile) GetParser

func (self *Profile) GetParser(name string, options *ordereddict.Dict) (Parser, error)

func (*Profile) ObjectSize

func (self *Profile) ObjectSize(scope vfilter.Scope,
	name string, reader io.ReaderAt, offset int64) int

func (*Profile) Parse

func (self *Profile) Parse(scope vfilter.Scope, type_name string,
	reader io.ReaderAt, offset int64) (interface{}, error)

For example: type_name = "Array" options = { "Target": "int"}

func (*Profile) ParseStructDefinitions

func (self *Profile) ParseStructDefinitions(definitions string) (err error)

Build the profile from definitions given in the vtypes language.

type ProfileParser

type ProfileParser struct {
	// contains filtered or unexported fields
}

func (*ProfileParser) New

func (self *ProfileParser) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*ProfileParser) Parse

func (self *ProfileParser) Parse(
	scope vfilter.Scope,
	reader io.ReaderAt, offset int64) interface{}

type ProfileParserOptions

type ProfileParserOptions struct {
	Type        string
	TypeOptions *ordereddict.Dict
	Offset      *vfilter.Lambda
}

type Sizer

type Sizer interface {
	Size() int
}

type Starter

type Starter interface {
	Start() int64
}

Return the start and end of the object

type StringParser

type StringParser struct {
	// contains filtered or unexported fields
}

func (*StringParser) New

func (self *StringParser) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*StringParser) Parse

func (self *StringParser) Parse(
	scope vfilter.Scope,
	reader io.ReaderAt, offset int64) interface{}

type StringParserOptions

type StringParserOptions struct {
	Length           int64
	LengthExpression *vfilter.Lambda
	MaxLength        int64
	Term             string
	TermExpression   *vfilter.Lambda
	Encoding         string
}

type StructAssociative

type StructAssociative struct{}

func (StructAssociative) Applicable

func (self StructAssociative) Applicable(a vfilter.Any, b vfilter.Any) bool

func (StructAssociative) Associative

func (self StructAssociative) Associative(scope vfilter.Scope,
	a vfilter.Any, b vfilter.Any) (vfilter.Any, bool)

func (StructAssociative) GetMembers

func (self StructAssociative) GetMembers(scope vfilter.Scope, a vfilter.Any) []string

type StructDefinition

type StructDefinition struct {
	Name           string
	Size           int
	SizeExpression string
	Fields         []*FieldDefinition
}

func (*StructDefinition) UnmarshalJSON

func (self *StructDefinition) UnmarshalJSON(p []byte) error

func (*StructDefinition) UnmarshalYAML

func (self *StructDefinition) UnmarshalYAML(unmarshal func(v interface{}) error) error

type StructObject

type StructObject struct {
	// contains filtered or unexported fields
}

A Lazy object representing the struct

func (*StructObject) End

func (self *StructObject) End() int64

func (*StructObject) Get

func (self *StructObject) Get(field string) (interface{}, bool)

func (*StructObject) MarshalJSON

func (self *StructObject) MarshalJSON() ([]byte, error)

func (*StructObject) Parent

func (self *StructObject) Parent() vfilter.Any

func (*StructObject) Size

func (self *StructObject) Size() int

Get the size of the struct - it can either be fixed, or derived using a lambda expression.

func (*StructObject) Start

func (self *StructObject) Start() int64

type StructParser

type StructParser struct {
	// contains filtered or unexported fields
}

func NewStructParser

func NewStructParser(type_name string, size int) *StructParser

func (*StructParser) AddField

func (self *StructParser) AddField(field_name string, parser *ParseAtOffset)

func (*StructParser) New

func (self *StructParser) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

StructParser does not take options

func (*StructParser) Parse

func (self *StructParser) Parse(
	scope vfilter.Scope,
	reader io.ReaderAt, offset int64) interface{}

func (*StructParser) Size

func (self *StructParser) Size() int

type Union

type Union struct {
	Selector *vfilter.Lambda

	Choices map[string]Parser
	// contains filtered or unexported fields
}

func (*Union) New

func (self *Union) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*Union) Parse

func (self *Union) Parse(
	scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

type ValueParser

type ValueParser struct {
	// contains filtered or unexported fields
}

A ValueParser can either represent a static value, or an expression.

func (*ValueParser) New

func (self *ValueParser) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*ValueParser) Parse

func (self *ValueParser) Parse(scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

type WinFileTime

type WinFileTime struct {
	*EpochTimestamp
}

func (*WinFileTime) New

func (self *WinFileTime) New(profile *Profile, options *ordereddict.Dict) (Parser, error)

func (*WinFileTime) Parse

func (self *WinFileTime) Parse(
	scope vfilter.Scope, reader io.ReaderAt, offset int64) interface{}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL