Skip to content

Commit

Permalink
Added a VQL binary_parse() plugin.
Browse files Browse the repository at this point in the history
  • Loading branch information
scudette committed Aug 18, 2018
1 parent bc37c0d commit 52752e7
Show file tree
Hide file tree
Showing 11 changed files with 626 additions and 233 deletions.
15 changes: 15 additions & 0 deletions artifacts/definitions/linux/kernel_modules.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: Linux.Proc.Modules
description: Module listing via /proc/modules.
parameters:
- name: ProcModules
default: /proc/modules
sources:
- precondition: |
SELECT OS From info() where OS = 'linux'
queries:
- |
SELECT * from split_records(
filenames=ProcModules,
regex='\\s+',
columns=['Name', 'Size', 'UseCount', 'UsedBy', 'Status', 'Address'])
30 changes: 30 additions & 0 deletions artifacts/definitions/linux/known_hosts.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Linux.Ssh.KnownHosts
description: Find and parse ssh known hosts files.
parameters:
- name: sshKnownHostsFiles
default: '.ssh/known_hosts*'
sources:
- precondition: |
SELECT OS From info() where OS = 'linux'
queries:
- |
// For each user on the system, search for authorized_keys files.
LET authorized_keys = SELECT * from foreach(
row={
SELECT Uid, User, Homedir from Artifact.Linux.Sys.Users()
},
query={
SELECT FullPath, Mtime, Ctime, User, Uid from glob(
globs=Homedir + '/' + sshKnownHostsFiles)
})
- |
// For each authorized keys file, extract each line on a different row.
// Note: This duplicates the path, user and uid on each key line.
SELECT * from foreach(
row=authorized_keys,
query={
SELECT Uid, User, FullPath, Line from split_records(
filenames=FullPath, regex="\n", columns=["Line"])
/* Ignore comment lines. */
WHERE not Line =~ "^[^#]+#"
})
92 changes: 92 additions & 0 deletions artifacts/definitions/linux/last_access.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
name: Linux.Sys.LastUserLogin
description: Find and parse system wtmp files. This indicate when the
user last logged in.
parameters:
- name: wtmpGlobs
default: /var/log/wtmp*

# This is automatically generated from dwarf symbols by Rekall:
# gcc -c -g -o /tmp/test.o /tmp/1.c
# rekall dwarfparser /tmp/test.o

# And 1.c is:
# #include "utmp.h"
# struct utmp x;

- name: wtmpProfile
default: |
{
"timeval": [8, {
"tv_sec": [0, ["int"]],
"tv_usec": [4, ["int"]]
}],
"exit_status": [4, {
"e_exit": [2, ["short int"]],
"e_termination": [0, ["short int"]]
}],
"timezone": [8, {
"tz_dsttime": [4, ["int"]],
"tz_minuteswest": [0, ["int"]]
}],
"utmp": [384, {
"__glibc_reserved": [364, ["Array", {
"count": 20,
"target": "char",
"target_args": null
}]],
"ut_addr_v6": [348, ["Array", {
"count": 4,
"target": "int",
"target_args": null
}]],
"ut_exit": [332, ["exit_status"]],
"ut_host": [76, ["String", {
"length": 256
}]],
"ut_id": [40, ["String", {
"length": 4
}]],
"ut_line": [8, ["String", {
"length": 32
}]],
"ut_pid": [4, ["int"]],
"ut_session": [336, ["int"]],
"ut_tv": [340, ["timeval"]],
"ut_type": [0, ["Enumeration", {
"target": "short int",
"choices": {
"0": "EMPTY",
"1": "RUN_LVL",
"2": "BOOT_TIME",
"5": "INIT_PROCESS",
"6": "LOGIN_PROCESS",
"7": "USER_PROCESS",
"8": "DEAD_PROCESS"
}
}]],
"ut_user": [44, ["String", {
"length": 32
}]]
}]
}
sources:
- precondition: |
SELECT OS From info() where OS = 'linux'
queries:
- |
SELECT * from foreach(
row={
SELECT FullPath from glob(globs=split(string=wtmpGlobs, sep=","))
},
query={
SELECT ut_type, ut_id, ut_host as Host,
ut_user as User,
timestamp(epoch=ut_tv.tv_sec) as login_time
FROM binary_parse(
file=FullPath,
profile=wtmpProfile,
iterator="Array",
Target="wtmp"
)
})
58 changes: 58 additions & 0 deletions binary/iterators.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package binary

import (
"encoding/json"
)

// A parser that holds a logical array of elements.
type ArrayParserOptions struct {
Target string `vfilter:"required,field=target"`
}

type ArrayParser struct {
*BaseParser
counter int64
profile *Profile
options *ArrayParserOptions
}

func (self ArrayParser) Copy() Parser {
return &self
}

func (self *ArrayParser) ParseArgs(args *json.RawMessage) error {
return json.Unmarshal(*args, &self.options)
}

// Produce the next iteration in the array.
func (self *ArrayParser) Next(base Object) Object {
result := self.Value(base)
self.counter += 1
return result
}

func (self *ArrayParser) Value(base Object) Object {
parser, pres := self.profile.getParser(self.options.Target)
if !pres {
return &ErrorObject{"Type not found"}
}

return &BaseObject{
name: base.Name(),
type_name: self.options.Target,
offset: base.Offset() + self.counter*parser.Size(
base.Offset(), base.Reader()),
reader: base.Reader(),
parser: parser,
}
}

func NewArrayParser(type_name string, name string,
profile *Profile, options *ArrayParserOptions) *ArrayParser {
return &ArrayParser{&BaseParser{
Name: name, type_name: type_name},
0,
profile,
options,
}
}
52 changes: 20 additions & 32 deletions binary/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,44 +6,32 @@ import (
)

func AddModel(profile *Profile) {
profile.types["unsigned long long"] = &IntParser{
name: "unsigned long long",
converter: func(buf []byte) uint64 {

profile.types["unsigned long long"] = NewIntParser(
"unsigned long long",
func(buf []byte) uint64 {
return uint64(binary.LittleEndian.Uint64(buf))
},
}
profile.types["unsigned short"] = &IntParser{
name: "unsigned short",
converter: func(buf []byte) uint64 {
})
profile.types["unsigned short"] = NewIntParser(
"unsigned short", func(buf []byte) uint64 {
return uint64(binary.LittleEndian.Uint16(buf))
},
}
profile.types["int8"] = &IntParser{
name: "int8",
converter: func(buf []byte) uint64 {
})
profile.types["int8"] = NewIntParser(
"int8", func(buf []byte) uint64 {
return uint64(buf[0])
},
}
profile.types["int16"] = &IntParser{
name: "int16",
converter: func(buf []byte) uint64 {
})
profile.types["int16"] = NewIntParser(
"int16", func(buf []byte) uint64 {
return uint64(binary.LittleEndian.Uint16(buf))
},
}
profile.types["int32"] = &IntParser{
name: "int32",
converter: func(buf []byte) uint64 {
})
profile.types["int32"] = NewIntParser(
"int32", func(buf []byte) uint64 {
return uint64(binary.LittleEndian.Uint32(buf))
},
}
profile.types["String"] = &StringParser{
type_name: "string",
}
})
profile.types["String"] = NewStringParser("string")
profile.types["Enumeration"] = NewEnumeration("Enumeration", profile)

profile.types["Enumeration"] = &Enumeration{
profile: profile,
type_name: "Enumeration",
}
profile.types["Array"] = NewArrayParser("Array", "", profile, nil)

// Aliases
profile.types["int"] = profile.types["int32"]
Expand Down
Loading

0 comments on commit 52752e7

Please sign in to comment.