Skip to content

Commit

Permalink
Added a magic() VQL function (#1338)
Browse files Browse the repository at this point in the history
Magic allows the detection of file types by applying various magic
rules on the file content.

Fixes: #1305
  • Loading branch information
scudette authored Oct 18, 2021
1 parent 546d532 commit c49675e
Show file tree
Hide file tree
Showing 9 changed files with 247 additions and 12 deletions.
3 changes: 2 additions & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,5 @@ logs text eol=lf
*.json binary
*.csv binary
*.zip binary
*.index binary
*.index binary
*.log binary
2 changes: 0 additions & 2 deletions artifacts/b0x.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,3 @@ custom:
- files:
- "artifacts/definitions/**/*.yaml"
- "docs/references/vql.yaml"

init: Init
31 changes: 31 additions & 0 deletions artifacts/testdata/server/testcases/magic.in.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
Parameters:
ApacheMagic: |
0 search/1024 "GET Apache Logs
!:strength + 100
sampleLog: |
127.0.0.1 - - [23/Apr/2014:22:58:32 +0200] "GET /index.php HTTP/1.1" 404 207
127.0.0.1 - - [23/Apr/2014:22:58:32 +0200] "GET /index.php HTTP/1.1" 404 207
Queries:
# access.log should be just data here
- SELECT magic(path=srcDir + '/artifacts/testdata/files/notnbt.exe'),
magic(path=srcDir + '/artifacts/testdata/files/test.docx'),
magic(path=srcDir + '/artifacts/testdata/files/access.log')
FROM scope()

# Add some custom definitions. This should trigger our magic now.
- SELECT magic(path=srcDir + '/artifacts/testdata/files/access.log',
magic=ApacheMagic)
FROM scope()

# Using an accessor to get the data allows us to magic arbitrary strings.
- SELECT magic(path=srcDir + '/artifacts/testdata/files/notnbt.exe',
accessor="file"),
magic(path=sampleLog, accessor="data", magic=ApacheMagic)
FROM scope()

# When accessor is not specified we use libmagic itself to access
# files - this allows reporting on directories, symlinks etc.
- SELECT magic(path=srcDir + '/artifacts/testdata/files/')
FROM scope()
20 changes: 20 additions & 0 deletions artifacts/testdata/server/testcases/magic.out.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
SELECT magic(path=srcDir + '/artifacts/testdata/files/notnbt.exe'), magic(path=srcDir + '/artifacts/testdata/files/test.docx'), magic(path=srcDir + '/artifacts/testdata/files/access.log') FROM scope()[
{
"magic(path=srcDir + '/artifacts/testdata/files/notnbt.exe')": "PE32 executable (console) Intel 80386, for MS Windows",
"magic(path=srcDir + '/artifacts/testdata/files/test.docx')": "Microsoft Word 2007+",
"magic(path=srcDir + '/artifacts/testdata/files/access.log')": "ASCII text"
}
]SELECT magic(path=srcDir + '/artifacts/testdata/files/access.log', magic=ApacheMagic) FROM scope()[
{
"magic(path=srcDir + '/artifacts/testdata/files/access.log', magic=ApacheMagic)": "Apache Logs, ASCII text"
}
]SELECT magic(path=srcDir + '/artifacts/testdata/files/notnbt.exe', accessor="file"), magic(path=sampleLog, accessor="data", magic=ApacheMagic) FROM scope()[
{
"magic(path=srcDir + '/artifacts/testdata/files/notnbt.exe', accessor=\"file\")": "PE32 executable (console) Intel 80386, for MS Windows",
"magic(path=sampleLog, accessor=\"data\", magic=ApacheMagic)": "Apache Logs, ASCII text"
}
]SELECT magic(path=srcDir + '/artifacts/testdata/files/') FROM scope()[
{
"magic(path=srcDir + '/artifacts/testdata/files/')": "directory"
}
]
56 changes: 53 additions & 3 deletions docs/references/vql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2248,6 +2248,57 @@
description: A string to lower
required: true
category: basic
- name: magic
description: |
Identify a file using magic rules.
Magic rules are designed to identify a file based on a sequence of
tests. They are a great way of quickly triaging a file type based
on its content, not its name.
Detection is facilitated via libmagic - a common library powering
the unix "file" utility. Velociraptor comes with all of "file"
basic magic signatures.
You can also write your own signatures using the magic syntax (see
https://man7.org/linux/man-pages/man4/magic.4.html )
## Example
The following will check all files in /var/lib applying a custom
magic rule.
```vql
LET Magic = '''
0 search/1024 "GET Apache Logs
!:strength + 100
'''
SELECT FullPath, Size, magic(path=FullPath, magic=Magic)
FROM glob(globs="/var/lib/*")
```
NOTE: `magic()` requires reading the headers of each file which
causes the file to be opened. If you have on-access scanning such
as Windows Defender "Realtime monitoring", applying magic() on
many files (e.g. in a glob) may result in substantial load on the
endpoint.
type: Function
args:
- name: path
type: string
description: Path to open and hash.
required: true
- name: accessor
type: string
description: The accessor to use
- name: type
type: string
description: Magic type (can be empty or 'mime' or 'extension')
- name: magic
type: string
description: Additional magic to load
- name: mail
description: Send Email to a remote server.
type: Plugin
Expand Down Expand Up @@ -2306,7 +2357,7 @@
type: Function
args:
- name: item
type: int64
type: LazyExpr
required: true
category: basic
- name: memoize
Expand Down Expand Up @@ -2342,7 +2393,7 @@
type: Function
args:
- name: item
type: int64
type: LazyExpr
required: true
category: basic
- name: mock
Expand Down Expand Up @@ -4810,4 +4861,3 @@
type: string
description: If set use this key to cache the yara rules.
category: plugin

6 changes: 4 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ require (
github.com/Velocidex/cryptozip v0.0.0-20200812111814-37033c799bd9
github.com/Velocidex/etw v0.0.0-20210723072214-4d0cffd1ff22
github.com/Velocidex/go-elasticsearch/v7 v7.3.1-0.20191001125819-fee0ef9cac6b
github.com/Velocidex/go-magic v0.0.0-20211018155418-c5dc48282f28
github.com/Velocidex/go-yara v1.1.10-0.20210726130504-d5e402efc424
github.com/Velocidex/json v0.0.0-20210402154432-68206e1293d0
github.com/Velocidex/ordereddict v0.0.0-20210502082334-cf5d9045c0d1
Expand Down Expand Up @@ -103,7 +104,7 @@ require (
go.starlark.net v0.0.0-20210602144842-1cdb82c9e17a
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97
golang.org/x/mod v0.4.2
golang.org/x/net v0.0.0-20210716203947-853a461950ff
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c
golang.org/x/text v0.3.7 // indirect
Expand Down Expand Up @@ -135,7 +136,7 @@ require (
github.com/Velocidex/yaml v2.1.0+incompatible // indirect
github.com/alecthomas/colour v0.1.0 // indirect
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 // indirect
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d // indirect
github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a // indirect
github.com/andybalholm/cascadia v1.1.0 // indirect
github.com/aymerick/douceur v0.2.0 // indirect
github.com/beevik/etree v1.1.0 // indirect
Expand Down Expand Up @@ -190,6 +191,7 @@ require (
// replace www.velocidex.com/golang/evtx => /home/mic/projects/evtx
// replace www.velocidex.com/golang/go-ese => /home/mic/projects/go-ese
// replace github.com/Velocidex/ordereddict => /home/mic/projects/ordereddict
// replace github.com/Velocidex/go-magic => /home/mic/projects/go-magic
// replace github.com/Velocidex/go-yara => /home/mic/projects/go-yara
// replace github.com/Velocidex/json => /home/mic/projects/json
// replace github.com/russross/blackfriday/v2 => /home/mic/projects/blackfriday
Expand Down
11 changes: 8 additions & 3 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,10 @@ github.com/Velocidex/etw v0.0.0-20210723072214-4d0cffd1ff22 h1:dDMLVs1Uj4xuKsYH1
github.com/Velocidex/etw v0.0.0-20210723072214-4d0cffd1ff22/go.mod h1:F3/xFKE2Puol7bJKW5xcj/9H2O8n/sJN87Xgm66G6sc=
github.com/Velocidex/go-elasticsearch/v7 v7.3.1-0.20191001125819-fee0ef9cac6b h1:XaAmLVXrqPv60nbiQtzj5Sch7lwz3XH8x5IocQwRPJg=
github.com/Velocidex/go-elasticsearch/v7 v7.3.1-0.20191001125819-fee0ef9cac6b/go.mod h1:draN67DBVJDAVmLWDIJ85CrV0UxmIGfWZ4njukhINQs=
github.com/Velocidex/go-magic v0.0.0-20211017060459-1ffd3ba975ab h1:BSWn8t1fv/4Ikj4ZXRq/46EmXqRj1N2blvkdBuu+WSI=
github.com/Velocidex/go-magic v0.0.0-20211017060459-1ffd3ba975ab/go.mod h1:SJFQu8urHEIgZj3xfx0WBovsCUG/N5xdkuDRD+rbgDU=
github.com/Velocidex/go-magic v0.0.0-20211018155418-c5dc48282f28 h1:3FMhXfGzZR4oNHmV8NizrviyaTv+2SmLuj+43cMJCUQ=
github.com/Velocidex/go-magic v0.0.0-20211018155418-c5dc48282f28/go.mod h1:n9o/44DFcqU/E55pWoIt4sKkxBC3k4JVNqvTAb9kZlI=
github.com/Velocidex/go-yara v1.1.10-0.20210726130504-d5e402efc424 h1:36GhJ1iRd/9yGgvlRWREcPRrWG2H4mF2lEpOvdYP55w=
github.com/Velocidex/go-yara v1.1.10-0.20210726130504-d5e402efc424/go.mod h1:N1A2MzBKorQm3WixuPUSm4gmzGA6i5sYrwHyUBvY5lg=
github.com/Velocidex/json v0.0.0-20210402154432-68206e1293d0 h1:oMdYhZqDdZq0PpVFxGvjhhNhxNl6D7ycR0SuKkbEKac=
Expand Down Expand Up @@ -129,8 +133,9 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafo
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d h1:UQZhZ2O0vMHr2cI+DC1Mbh0TJxzA3RcLoMsFw+aXw7E=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a h1:E/8AP5dFtMhl5KPJz66Kt9G0n+7Sn41Fy1wv9/jHOrc=
github.com/alecthomas/units v0.0.0-20210927113745-59d0afb8317a/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE=
github.com/alexmullins/zip v0.0.0-20180717182244-4affb64b04d0 h1:BVts5dexXf4i+JX8tXlKT0aKoi38JwTXSe+3WUneX0k=
github.com/alexmullins/zip v0.0.0-20180717182244-4affb64b04d0/go.mod h1:FDIQmoMNJJl5/k7upZEnGvgWVZfFeE6qHeN7iCMbCsA=
github.com/andybalholm/cascadia v1.1.0 h1:BuuO6sSfQNFRu1LppgbD25Hr2vLYW25JvxHs5zzsLTo=
Expand Down Expand Up @@ -642,8 +647,8 @@ golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLd
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210716203947-853a461950ff h1:j2EK/QoxYNBsXI4R7fQkkRUk8y6wnOBI+6hgPdP/6Ds=
golang.org/x/net v0.0.0-20210716203947-853a461950ff/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
Expand Down
1 change: 0 additions & 1 deletion gui/velociraptor/src/light-pink.css
Original file line number Diff line number Diff line change
Expand Up @@ -427,7 +427,6 @@ body.light-pink {

/* Shell output */
.light-pink .notebook-output pre {
filter: invert(1);
border-color: var(--border-color);
}

Expand Down
129 changes: 129 additions & 0 deletions vql/tools/magic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// +build cgo

package tools

import (
"context"
"fmt"

"github.com/Velocidex/go-magic/magic"
"github.com/Velocidex/go-magic/magic_files"
"github.com/Velocidex/ordereddict"
"www.velocidex.com/golang/velociraptor/glob"
vql_subsystem "www.velocidex.com/golang/velociraptor/vql"
"www.velocidex.com/golang/vfilter"
"www.velocidex.com/golang/vfilter/arg_parser"
)

const (
magicHandle = "$MagicHandle"
)

type MagicFunctionArgs struct {
Path string `vfilter:"required,field=path,doc=Path to open and hash."`
Accessor string `vfilter:"optional,field=accessor,doc=The accessor to use"`
Type string `vfilter:"optional,field=type,doc=Magic type (can be empty or 'mime' or 'extension')"`
Magic string `vfilter:"optional,field=magic,doc=Additional magic to load"`
}

type MagicFunction struct{}

func (self MagicFunction) Call(
ctx context.Context,
scope vfilter.Scope,
args *ordereddict.Dict) vfilter.Any {

arg := &MagicFunctionArgs{}
err := arg_parser.ExtractArgsWithContext(ctx, scope, args, arg)
if err != nil {
scope.Log("magic: %v", err)
return vfilter.Null{}
}

magic_type := magic.MAGIC_NONE
switch arg.Type {
case "mime":
magic_type = magic.MAGIC_MIME
case "extension":
magic_type = magic.MAGIC_EXTENSION
case "":
magic_type = magic.MAGIC_NONE
default:
scope.Log("magic: unknown type %v", arg.Type)
return vfilter.Null{}
}

var handle *magic.Magic

// Cache key based on type and custom magic.
key := fmt.Sprintf("%s_%s_%d", magicHandle, arg.Type, len(arg.Magic))
cached := vql_subsystem.CacheGet(scope, key)
switch t := cached.(type) {

case error:
return vfilter.Null{}

case nil:
handle = magic.NewMagicHandle(magic_type)
magic_files.LoadDefaultMagic(handle)

// Do we need to load additional magic tests?
if arg.Magic != "" {
handle.LoadBuffer(arg.Magic)
errors := handle.GetError()
if errors != "" {
scope.Log("magic: While loading custom magic: %v", errors)
}
}

// Attach the handle to the root destructor.
vql_subsystem.GetRootScope(scope).
AddDestructor(func() { handle.Close() })
vql_subsystem.CacheSet(scope, key, handle)

case *magic.Magic:
handle = t

default:
// Unexpected value in cache.
return vfilter.Null{}
}

// Just let libmagic handle the path
if arg.Accessor == "" {
return handle.File(arg.Path)
}

// Read a header from the file and pass to the libmagic
accessor, err := glob.GetAccessor(arg.Accessor, scope)
if err != nil {
scope.Log("magic: %v", err)
return vfilter.Null{}
}

fd, err := accessor.Open(arg.Path)
if err != nil {
return vfilter.Null{}
}

buffer := make([]byte, 1024*64)
_, err = fd.Read(buffer)
if err != nil {
return vfilter.Null{}
}

return handle.Buffer(buffer)
}

func (self MagicFunction) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.FunctionInfo {
return &vfilter.FunctionInfo{
Name: "magic",
Doc: "Identify a file using magic rules.",
ArgType: type_map.AddType(scope, &MagicFunctionArgs{}),
Version: 1,
}
}

func init() {
vql_subsystem.RegisterFunction(&MagicFunction{})
}

0 comments on commit c49675e

Please sign in to comment.