Skip to content

Commit

Permalink
Added hunt_update() VQL function to allow stopping/starting hunt (Vel…
Browse files Browse the repository at this point in the history
…ocidex#2587)

Also added relaunch to hunt_add() to more easily relaunch the hunt
  • Loading branch information
scudette authored Mar 25, 2023
1 parent 6c90df8 commit 4f2b1bc
Show file tree
Hide file tree
Showing 4 changed files with 211 additions and 7 deletions.
48 changes: 42 additions & 6 deletions docs/references/vql.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2261,6 +2261,9 @@
type: string
description: If a flow id is specified we do not create a new flow, but instead
add this flow_id to the hunt.
- name: relaunch
type: bool
description: If specified we relaunch the hunt on this client again.
category: server
- name: hunt_delete
description: 'Delete a hunt. '
Expand Down Expand Up @@ -2318,6 +2321,23 @@
type: bool
description: If set we return less columns.
category: server
- name: hunt_update
description: Update a hunt.
type: Function
args:
- name: hunt_id
type: string
description: The hunt to update
required: true
- name: stop
type: bool
description: Stop the hunt
- name: start
type: bool
description: Start the hunt
- name: description
type: string
description: Update hunt description
- name: hunts
description: |
Retrieve the list of hunts.
Expand Down Expand Up @@ -2751,11 +2771,6 @@
- name: lookupSID
description: Get information about the SID.
type: Function
args:
- name: sid
type: string
description: 'A SID to lookup using LookupAccountSid '
required: true
category: windows
- name: lowcase
type: Function
Expand Down Expand Up @@ -3723,6 +3738,9 @@
- name: accessor
type: string
description: The accessor to use.
- name: base_offset
type: int64
description: The offset in the file for the base address.
category: parsers
- name: parse_pkcs7
description: Parse a DER encoded pkcs7 string into an object.
Expand Down Expand Up @@ -4026,6 +4044,23 @@
type: string
description: The accessor to use to parse the path with
category: plugin
- name: pe_dump
description: Dump a PE file from process memory.
type: Function
args:
- name: pid
type: uint64
description: The pid to dump.
required: true
- name: base_offset
type: int64
description: The offset in the file for the base address.
required: true
- name: in_memory
type: uint64
description: By default we store to a tempfile and return the path. If this option
is larger than 0, we prepare the file in a memory buffer at the specified limit,
to avoid AV alerts on disk access.
- name: pipe
description: |
A pipe allows plugins that use files to read data from a vql
Expand Down Expand Up @@ -4318,7 +4353,8 @@
args:
- name: pid
type: int64
description: A process ID to list. If not provided list all processes.
description: A pid to list. If this is provided we are able to operate much faster
by only opening a single process.
category: plugin
- name: query
description: Launch a subquery and materialize it into a list of rows.
Expand Down
14 changes: 13 additions & 1 deletion services/notebook/initial.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ LET ColumnTypes <= dict(
/*
# Flows with ERROR status
*/
SELECT ClientId,
LET ERRORS = SELECT ClientId,
client_info(client_id=ClientId).os_info.hostname AS Hostname,
FlowId, Flow.start_time As StartedTime,
Flow.state AS FlowState, Flow.status as FlowStatus,
Expand All @@ -297,6 +297,18 @@ SELECT ClientId,
FROM hunt_flows(hunt_id=HuntId)
WHERE FlowState =~ 'ERROR'
-- Uncomment the below to reissue the exact same hunt to the errored clients
-- SELECT *,
-- hunt_add(client_id=ClientId, hunt_id=HuntId, relaunch=TRUE) AS NewCollection
-- FROM ERRORS
-- Uncomment the below to reissue a new collection and add to the same hunt
-- SELECT *,
-- hunt_add(client_id=ClientId, hunt_id=HuntId,
-- flow_id=collect_client(artifacts="UpdateArtifactName").flow_id) AS NewCollection
-- FROM ERRORS
SELECT * FROM ERRORS
/*
## Flows with RUNNING status
*/
Expand Down
61 changes: 61 additions & 0 deletions vql/server/hunts/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/Velocidex/ordereddict"
"github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto"
"www.velocidex.com/golang/velociraptor/acls"
api_proto "www.velocidex.com/golang/velociraptor/api/proto"
flows_proto "www.velocidex.com/golang/velociraptor/flows/proto"
Expand Down Expand Up @@ -279,6 +280,7 @@ type AddToHuntFunctionArg struct {
ClientId string `vfilter:"required,field=client_id"`
HuntId string `vfilter:"required,field=hunt_id"`
FlowId string `vfilter:"optional,field=flow_id,doc=If a flow id is specified we do not create a new flow, but instead add this flow_id to the hunt."`
Relaunch bool `vfilter:"optional,field=relaunch,doc=If specified we relaunch the hunt on this client again."`
}

type AddToHuntFunction struct{}
Expand Down Expand Up @@ -311,6 +313,65 @@ func (self *AddToHuntFunction) Call(ctx context.Context,
return vfilter.Null{}
}

// Relaunch the collection.
if arg.Relaunch {
hunt_dispatcher, err := services.GetHuntDispatcher(config_obj)
if err != nil {
return vfilter.Null{}
}

hunt_obj, pres := hunt_dispatcher.GetHunt(arg.HuntId)
if !pres || hunt_obj == nil ||
hunt_obj.StartRequest == nil ||
hunt_obj.StartRequest.CompiledCollectorArgs == nil {
scope.Log("hunt_add: Hunt id not found %v", arg.HuntId)
return vfilter.Null{}
}

launcher, err := services.GetLauncher(config_obj)
if err != nil {
return vfilter.Null{}
}

// Launch the collection against a client. We assume it is
// already compiled because hunts always pre-compile their
// artifacts.
request := proto.Clone(hunt_obj.StartRequest).(*flows_proto.ArtifactCollectorArgs)
request.ClientId = arg.ClientId

// Generate a new flow id for each request
request.FlowId = ""

arg.FlowId, err = launcher.ScheduleArtifactCollectionFromCollectorArgs(
ctx, config_obj, request, hunt_obj.StartRequest.CompiledCollectorArgs,
func() {
// Notify the client about it.
notifier, err := services.GetNotifier(config_obj)
if err == nil {
notifier.NotifyListener(ctx,
config_obj, arg.ClientId, "collect_client")
}
})
if err != nil {
scope.Log("hunt_add: %v", err)
return vfilter.Null{}
}

err = hunt_dispatcher.MutateHunt(ctx, config_obj,
&api_proto.HuntMutation{
HuntId: arg.HuntId,
Assignment: &api_proto.FlowAssignment{
ClientId: arg.ClientId,
FlowId: arg.FlowId,
}})
if err != nil {
scope.Log("hunt_add: %v", err)
return vfilter.Null{}
}

return arg.FlowId
}

// Send this
if arg.FlowId != "" {
err = journal.PushRowsToArtifact(ctx, config_obj,
Expand Down
95 changes: 95 additions & 0 deletions vql/server/hunts/stop.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package hunts

import (
"context"

"github.com/Velocidex/ordereddict"
"www.velocidex.com/golang/velociraptor/acls"
api_proto "www.velocidex.com/golang/velociraptor/api/proto"
"www.velocidex.com/golang/velociraptor/services"
vql_subsystem "www.velocidex.com/golang/velociraptor/vql"
"www.velocidex.com/golang/vfilter"
"www.velocidex.com/golang/vfilter/arg_parser"
)

type UpdateHuntFunctionArg struct {
HuntId string `vfilter:"required,field=hunt_id,doc=The hunt to update"`
Stop bool `vfilter:"optional,field=stop,doc=Stop the hunt"`
Start bool `vfilter:"optional,field=start,doc=Start the hunt"`
Description string `vfilter:"optional,field=description,doc=Update hunt description"`
}

type UpdateHuntFunction struct{}

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

err := vql_subsystem.CheckAccess(scope, acls.START_HUNT)
if err != nil {
scope.Log("hunt_update: %v", err)
return vfilter.Null{}
}

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

config_obj, ok := vql_subsystem.GetServerConfig(scope)
if !ok {
scope.Log("hunt_update: GetServerConfig not found")
return vfilter.Null{}
}

hunt_dispatcher, err := services.GetHuntDispatcher(config_obj)
if err != nil {
scope.Log("hunt_update: %v", err)
return vfilter.Null{}
}

if arg.Start || arg.Stop {
state := api_proto.Hunt_STOPPED
if arg.Start {
state = api_proto.Hunt_RUNNING
}

err = hunt_dispatcher.MutateHunt(
ctx, config_obj, &api_proto.HuntMutation{
HuntId: arg.HuntId,
State: state,
})
if err != nil {
scope.Log("hunt_update: %v", err)
return vfilter.Null{}
}
}

if arg.Description != "" {
err = hunt_dispatcher.MutateHunt(
ctx, config_obj, &api_proto.HuntMutation{
HuntId: arg.HuntId,
Description: arg.Description,
})
if err != nil {
scope.Log("hunt_update: %v", err)
return vfilter.Null{}
}
}

return arg.HuntId
}

func (self UpdateHuntFunction) Info(scope vfilter.Scope, type_map *vfilter.TypeMap) *vfilter.FunctionInfo {
return &vfilter.FunctionInfo{
Name: "hunt_update",
Doc: "Update a hunt.",
ArgType: type_map.AddType(scope, &UpdateHuntFunctionArg{}),
}
}

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

0 comments on commit 4f2b1bc

Please sign in to comment.