diff --git a/api/artifacts.go b/api/artifacts.go index 6a31dee0cfb..c3cf94eb744 100644 --- a/api/artifacts.go +++ b/api/artifacts.go @@ -19,13 +19,13 @@ package api import ( "bytes" + "context" "io/ioutil" "regexp" "strings" "github.com/Velocidex/ordereddict" errors "github.com/go-errors/errors" - context "golang.org/x/net/context" "www.velocidex.com/golang/velociraptor/acls" actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" api_proto "www.velocidex.com/golang/velociraptor/api/proto" diff --git a/api/assets.go b/api/assets.go index d1148cd380b..31f921855a0 100644 --- a/api/assets.go +++ b/api/assets.go @@ -21,6 +21,7 @@ package api import ( "bytes" + "context" "fmt" "html/template" "net/http" @@ -30,7 +31,6 @@ import ( "github.com/andybalholm/brotli" "github.com/gorilla/csrf" "github.com/lpar/gzipped" - context "golang.org/x/net/context" "www.velocidex.com/golang/velociraptor/api/proto" api_utils "www.velocidex.com/golang/velociraptor/api/utils" utils "www.velocidex.com/golang/velociraptor/api/utils" diff --git a/api/authenticators/azure.go b/api/authenticators/azure.go index 348945ab964..36a51527584 100644 --- a/api/authenticators/azure.go +++ b/api/authenticators/azure.go @@ -18,13 +18,13 @@ along with this program. If not, see . package authenticators import ( + "context" "fmt" "io" "io/ioutil" "net/http" "github.com/sirupsen/logrus" - context "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/microsoft" "www.velocidex.com/golang/velociraptor/acls" diff --git a/api/authenticators/github.go b/api/authenticators/github.go index 23c6210b1d5..e4a68698fbc 100644 --- a/api/authenticators/github.go +++ b/api/authenticators/github.go @@ -18,13 +18,13 @@ along with this program. If not, see . package authenticators import ( + "context" "fmt" "io" "io/ioutil" "net/http" "github.com/sirupsen/logrus" - context "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/github" "www.velocidex.com/golang/velociraptor/acls" diff --git a/api/authenticators/google.go b/api/authenticators/google.go index ebd663e91c8..9e55b09948a 100644 --- a/api/authenticators/google.go +++ b/api/authenticators/google.go @@ -18,6 +18,7 @@ along with this program. If not, see . package authenticators import ( + "context" "crypto/rand" "encoding/base64" "fmt" @@ -29,7 +30,6 @@ import ( "github.com/Velocidex/ordereddict" "github.com/gorilla/csrf" "github.com/sirupsen/logrus" - context "golang.org/x/net/context" "golang.org/x/oauth2" "golang.org/x/oauth2/google" "www.velocidex.com/golang/velociraptor/acls" diff --git a/api/authenticators/http.go b/api/authenticators/http.go index a414fcb3281..e4bb7f0c9e8 100644 --- a/api/authenticators/http.go +++ b/api/authenticators/http.go @@ -1,10 +1,10 @@ package authenticators import ( + "context" "net/http" oidc "github.com/coreos/go-oidc/v3/oidc" - context "golang.org/x/net/context" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/vql/networking" ) diff --git a/api/datastore.go b/api/datastore.go index 11bd1aca5a7..1d2e84e6621 100644 --- a/api/datastore.go +++ b/api/datastore.go @@ -1,9 +1,9 @@ package api import ( + "context" "sync" - context "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" diff --git a/api/download.go b/api/download.go index 4ac26b4ae76..79d5a2d2567 100644 --- a/api/download.go +++ b/api/download.go @@ -26,6 +26,7 @@ package api import ( + "context" "fmt" "html" "io" @@ -41,7 +42,6 @@ import ( errors "github.com/go-errors/errors" "github.com/gorilla/schema" - context "golang.org/x/net/context" "www.velocidex.com/golang/velociraptor/acls" actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" "www.velocidex.com/golang/velociraptor/api/authenticators" diff --git a/api/events.go b/api/events.go index d34e2d37b66..f73dba8c14a 100644 --- a/api/events.go +++ b/api/events.go @@ -1,7 +1,8 @@ package api import ( - context "golang.org/x/net/context" + "context" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" diff --git a/api/filesearch.go b/api/filesearch.go index e28c75c3b4c..ccea375b56f 100644 --- a/api/filesearch.go +++ b/api/filesearch.go @@ -2,13 +2,13 @@ package api import ( "bytes" + "context" "encoding/hex" "io" "regexp" "strings" errors "github.com/go-errors/errors" - context "golang.org/x/net/context" "www.velocidex.com/golang/velociraptor/acls" api_proto "www.velocidex.com/golang/velociraptor/api/proto" "www.velocidex.com/golang/velociraptor/file_store" diff --git a/api/flows.go b/api/flows.go index 009ddfd1512..53ef58a59a6 100644 --- a/api/flows.go +++ b/api/flows.go @@ -1,7 +1,8 @@ package api import ( - context "golang.org/x/net/context" + "context" + "www.velocidex.com/golang/velociraptor/acls" api_proto "www.velocidex.com/golang/velociraptor/api/proto" "www.velocidex.com/golang/velociraptor/api/tables" diff --git a/api/health.go b/api/health.go index 727991aa178..1178af23633 100644 --- a/api/health.go +++ b/api/health.go @@ -1,7 +1,8 @@ package api import ( - context "golang.org/x/net/context" + "context" + "www.velocidex.com/golang/velociraptor/api/proto" api_proto "www.velocidex.com/golang/velociraptor/api/proto" ) diff --git a/api/hunts.go b/api/hunts.go index dabb3ca464b..0639aacbd8f 100644 --- a/api/hunts.go +++ b/api/hunts.go @@ -1,6 +1,7 @@ package api import ( + "context" "fmt" "strings" "time" @@ -8,7 +9,6 @@ import ( "github.com/Velocidex/ordereddict" errors "github.com/go-errors/errors" - context "golang.org/x/net/context" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/emptypb" "www.velocidex.com/golang/velociraptor/acls" diff --git a/api/notebooks.go b/api/notebooks.go index 3b4fe1fd0ef..60d799c0c79 100644 --- a/api/notebooks.go +++ b/api/notebooks.go @@ -1,6 +1,7 @@ package api import ( + "context" "os" "strings" "sync" @@ -8,7 +9,6 @@ import ( "github.com/Velocidex/ordereddict" errors "github.com/go-errors/errors" - context "golang.org/x/net/context" "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/emptypb" "www.velocidex.com/golang/velociraptor/acls" diff --git a/api/proxy.go b/api/proxy.go index 58885474d77..5b34cecd03f 100644 --- a/api/proxy.go +++ b/api/proxy.go @@ -18,6 +18,7 @@ along with this program. If not, see . package api import ( + "context" "crypto/tls" "crypto/x509" "fmt" @@ -28,7 +29,6 @@ import ( errors "github.com/go-errors/errors" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" - "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" diff --git a/api/reflect.go b/api/reflect.go index 9fa95a41070..04cb27c78bb 100644 --- a/api/reflect.go +++ b/api/reflect.go @@ -18,10 +18,10 @@ along with this program. If not, see . package api import ( + "context" "regexp" "strings" - context "golang.org/x/net/context" "google.golang.org/protobuf/types/known/emptypb" api_proto "www.velocidex.com/golang/velociraptor/api/proto" artifacts_proto "www.velocidex.com/golang/velociraptor/artifacts/proto" diff --git a/api/reformat.go b/api/reformat.go index b3456ee2f05..201981c67d7 100644 --- a/api/reformat.go +++ b/api/reformat.go @@ -1,7 +1,8 @@ package api import ( - context "golang.org/x/net/context" + "context" + "www.velocidex.com/golang/velociraptor/acls" api_proto "www.velocidex.com/golang/velociraptor/api/proto" "www.velocidex.com/golang/velociraptor/services" diff --git a/api/replication.go b/api/replication.go index 3b8c9360696..4a7914be7e0 100644 --- a/api/replication.go +++ b/api/replication.go @@ -1,6 +1,7 @@ package api import ( + "context" "fmt" "sort" "strings" @@ -11,7 +12,6 @@ import ( "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/sirupsen/logrus" - context "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/peer" "google.golang.org/grpc/status" diff --git a/api/reports.go b/api/reports.go index 36c7cb184aa..11c4677137e 100644 --- a/api/reports.go +++ b/api/reports.go @@ -1,11 +1,11 @@ package api import ( + "context" "fmt" "strings" errors "github.com/go-errors/errors" - context "golang.org/x/net/context" api_proto "www.velocidex.com/golang/velociraptor/api/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/constants" diff --git a/api/secrets.go b/api/secrets.go index b90d13b5217..cc65c939867 100644 --- a/api/secrets.go +++ b/api/secrets.go @@ -1,8 +1,9 @@ package api import ( + "context" + "github.com/Velocidex/ordereddict" - context "golang.org/x/net/context" "google.golang.org/protobuf/types/known/emptypb" "www.velocidex.com/golang/velociraptor/acls" api_proto "www.velocidex.com/golang/velociraptor/api/proto" diff --git a/api/static.go b/api/static.go index f683036da99..4cc033471c5 100644 --- a/api/static.go +++ b/api/static.go @@ -12,6 +12,7 @@ package api import ( "bytes" + "context" "io" "io/fs" "net/http" @@ -24,7 +25,6 @@ import ( errors "github.com/go-errors/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" - context "golang.org/x/net/context" "www.velocidex.com/golang/velociraptor/services" ) diff --git a/api/tables/notebooks.go b/api/tables/notebooks.go index ce2564bfd4c..92af6002026 100644 --- a/api/tables/notebooks.go +++ b/api/tables/notebooks.go @@ -1,7 +1,8 @@ package tables import ( - context "golang.org/x/net/context" + "context" + api_proto "www.velocidex.com/golang/velociraptor/api/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/services" diff --git a/api/tables/table.go b/api/tables/table.go index b97b43c9342..d526bf82aa5 100644 --- a/api/tables/table.go +++ b/api/tables/table.go @@ -18,6 +18,7 @@ along with this program. If not, see . package tables import ( + "context" "io" "regexp" "strings" @@ -25,7 +26,6 @@ import ( "github.com/Velocidex/ordereddict" errors "github.com/go-errors/errors" - context "golang.org/x/net/context" file_store "www.velocidex.com/golang/velociraptor/file_store" "www.velocidex.com/golang/velociraptor/file_store/api" "www.velocidex.com/golang/velociraptor/file_store/path_specs" diff --git a/api/tables/timelines.go b/api/tables/timelines.go index 1595f43d4df..8b5600a6498 100644 --- a/api/tables/timelines.go +++ b/api/tables/timelines.go @@ -1,10 +1,10 @@ package tables import ( + "context" "time" errors "github.com/go-errors/errors" - context "golang.org/x/net/context" api_proto "www.velocidex.com/golang/velociraptor/api/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/json" diff --git a/api/timelines.go b/api/timelines.go index 122d484daae..42f02380be7 100644 --- a/api/timelines.go +++ b/api/timelines.go @@ -1,10 +1,10 @@ package api import ( + "context" "time" "github.com/Velocidex/ordereddict" - context "golang.org/x/net/context" "google.golang.org/protobuf/types/known/emptypb" "www.velocidex.com/golang/velociraptor/acls" api_proto "www.velocidex.com/golang/velociraptor/api/proto" diff --git a/api/tools.go b/api/tools.go index 1aeda220476..39b54aa1b11 100644 --- a/api/tools.go +++ b/api/tools.go @@ -1,7 +1,7 @@ package api import ( - context "golang.org/x/net/context" + "context" "www.velocidex.com/golang/velociraptor/acls" artifacts_proto "www.velocidex.com/golang/velociraptor/artifacts/proto" diff --git a/api/users.go b/api/users.go index 978461242b3..9e0aa88eb33 100644 --- a/api/users.go +++ b/api/users.go @@ -1,11 +1,11 @@ package api import ( + "context" "errors" "sort" "github.com/Velocidex/ordereddict" - context "golang.org/x/net/context" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" diff --git a/api/vfs.go b/api/vfs.go index 07f2417696e..d48e98f9f6b 100644 --- a/api/vfs.go +++ b/api/vfs.go @@ -62,10 +62,10 @@ GetVFSDownloadInfoPath(). package api import ( + "context" "fmt" "strings" - context "golang.org/x/net/context" "www.velocidex.com/golang/velociraptor/acls" actions_proto "www.velocidex.com/golang/velociraptor/actions/proto" api_proto "www.velocidex.com/golang/velociraptor/api/proto" diff --git a/api/vql.go b/api/vql.go index e8ca6e3315d..a179e6d960c 100644 --- a/api/vql.go +++ b/api/vql.go @@ -18,8 +18,9 @@ along with this program. If not, see . package api import ( + "context" + "github.com/Velocidex/ordereddict" - context "golang.org/x/net/context" api_proto "www.velocidex.com/golang/velociraptor/api/proto" config_proto "www.velocidex.com/golang/velociraptor/config/proto" "www.velocidex.com/golang/velociraptor/json" diff --git a/artifacts/definitions/Notebooks/SigmaStudio.yaml b/artifacts/definitions/Notebooks/SigmaStudio.yaml index 2d57aab10e3..a34bf3630ca 100644 --- a/artifacts/definitions/Notebooks/SigmaStudio.yaml +++ b/artifacts/definitions/Notebooks/SigmaStudio.yaml @@ -16,7 +16,10 @@ parameters: default: Windows choices: - Windows + - WindowsEvents - Linux + - LinuxEvents + - name: Debug description: Enable this to match all rules (even if they did not match) in order to see what detections matched. type: bool diff --git a/docs/references/vql.yaml b/docs/references/vql.yaml index 585ad043a35..77d999ff38d 100644 --- a/docs/references/vql.yaml +++ b/docs/references/vql.yaml @@ -10657,7 +10657,134 @@ platforms: - linux_amd64_cgo - name: watch_etw - description: Watch for events from an ETW provider. + description: | + Watch for events from an ETW provider. + + Event Tracing for Windows is a powerful built in monitoring and + eventing system in Windows. This plugin provides an interface to + this capability. + + To learn more about ETW see + https://docs.velociraptor.app/blog/2021/2021-08-18-velociraptor-and-etw/ + or + https://docs.velociraptor.app/blog/2021/2021-09-03-process-spoofing/ + + ## The NT Kernel Logger + + This plugin also provides specialized support for more advanced + loggers. For example the NT Kernel Logger is a special ETW + provider that can report a lot of telemetry. + + The NT Kernel Logger is a special ETW session which can monitor + the following event types: + + * registry - all registry interactions like keys/values + * process - all processes start/stop + * image_load - dll loading and mapping + * network - inbound/outbound connections + * driver - drivers loaded + * file - file io like opening files/deleting files etc + * handles - Any time a kernel handle is created + + Additionally this provider can report a full stack trace for each event. + + You can specify this provider by the GUID + `{9E814AAD-3204-11D2-9A82-006008A86939}` or as a shorthand + `kernel`. This will enabled a special ETW session with support for + this special provider. + + When this provider is used, you can also specify what kind of + events you want to see using the `kernel_tracer_type` + parameter. This is a list of any of the following keywords + `registry`, `process`, `image_load`, `network`, `driver`, `file`, + `handles`. + + Additionally, you can specify which kinds of events will be + decorated with stack traces using the `kernel_tracer_stacks` + parameter. + + ### Example: + + ```vql + SELECT * FROM + watch_etw(guid='kernel', + kernel_tracer_type=['image_load', 'registry', 'file', 'process', 'network'], + kernel_tracer_stacks=['registry', 'network', 'file', 'process']) + ``` + + An example of a file created by Chrome - This example illustrates + the backtrace reported by the Kernel and decorated by Velociraptor + with the function names (when known). + + ```json + { + "System": { + "ID": 0, + "ProcessID": 7672, + "TimeStamp": "2025-01-02T06:21:36.7026893Z", + "Provider": "{90CBDC39-4A3E-11D1-84F4-0000F80464E3}", + "OpCode": 64, + "KernelEventType": "CreateFile" + }, + "EventData": { + "IrpPtr": "0xFFFF830E25838598", + "FileObject": "0xFFFF830E34395DD0", + "TTID": "1124", + "CreateOptions": "33554528", + "FileAttributes": "0", + "ShareAccess": "7", + "OpenPath": "C:\\Users\\Administrator\\AppData\\Local\\Google\\Chrome\\User Data\\Default\\Code Cache\\js\\5926f528a1f0ddd3_0" + }, + "Backtrace": [ + "fileinfo.sys!0x11107", + "ntoskrnl.exe!IofCallDriver", + "ntoskrnl.exe!SePrivilegeCheck", + "ntoskrnl.exe!_setjmpex", + "ntdll.dll!NtCreateFile", + "KernelBase.dll!CreateFileW", + "chrome.dll!0x55bc08", + "chrome.dll!0x834d17", + "chrome.dll!IsSandboxedProcess", + "chrome.dll!IsSandboxedProcess", + "chrome.dll!0xae178", + "chrome.dll!0x966030", + "kernel32.dll!BaseThreadInitThunk", + "ntdll.dll!RtlUserThreadStart" + ] + }, + ``` + + The following is an example of an outbound connection over port 22. + ```json + { + "System": { + "ID": 0, + "ProcessID": 4294967295, + "TimeStamp": "2025-01-02T06:27:38.6957971Z", + "Provider": "{9A280AC0-C8E0-11D1-84E2-00C04FB998A2}", + "OpCode": 12, + "KernelEventType": "ConnectTCPv4" + }, + "EventData": { + "PID": "1748", + "size": "0", + "daddr": "192.168.0.2", + "saddr": "192.168.1.237", + "dport": "22", + "sport": "50068", + "mss": "1460", + "sackopt": "1", + "tsopt": "0", + "wsopt": "1", + "rcvwin": "2098020", + "rcvwinscale": "8", + "sndwinscale": "7", + "seqnum": "0", + "connid": null + }, + } + ``` + type: Plugin args: - name: name @@ -11244,4 +11371,3 @@ - linux_amd64_cgo - windows_386_cgo - windows_amd64_cgo - diff --git a/go.mod b/go.mod index 1ff91bf3ec7..c5a194c69be 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Showmax/go-fqdn v1.0.0 github.com/Velocidex/amsi v0.0.0-20200608120838-e5d93b76f119 - github.com/Velocidex/etw v0.0.0-20231115144702-0b885b292f0f + github.com/Velocidex/etw v0.0.0-20250102072915-dae077e67c53 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.20240309155455-3f491847cec9 @@ -73,7 +73,7 @@ require ( go.starlark.net v0.0.0-20230925163745-10651d5192ab golang.org/x/crypto v0.31.0 golang.org/x/mod v0.21.0 - golang.org/x/net v0.31.0 + golang.org/x/net v0.33.0 golang.org/x/sys v0.28.0 golang.org/x/text v0.21.0 golang.org/x/time v0.5.0 @@ -89,7 +89,7 @@ require ( www.velocidex.com/golang/evtx v0.2.1-0.20240730174545-3e4ff3d96433 www.velocidex.com/golang/go-ese v0.2.1-0.20240919031214-2aa005106db2 www.velocidex.com/golang/go-ntfs v0.2.1-0.20241123135758-e6f7e1f1c474 - www.velocidex.com/golang/go-pe v0.1.1-0.20230228112150-ef2eadf34bc3 + www.velocidex.com/golang/go-pe v0.1.1-0.20250101153735-7a925ba8334b www.velocidex.com/golang/go-prefetch v0.0.0-20240910051453-2385582c1c22 www.velocidex.com/golang/oleparse v0.0.0-20230217092320-383a0121aafe www.velocidex.com/golang/regparser v0.0.0-20240404115756-2169ac0e3c09 @@ -124,6 +124,7 @@ require ( github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.16.8 github.com/aws/aws-sdk-go-v2/service/s3 v1.51.3 github.com/charmbracelet/huh v0.6.0 + github.com/charmbracelet/lipgloss v1.0.0 github.com/clayscode/Go-Splunk-HTTP/splunk/v2 v2.0.1-0.20221027171526-76a36be4fa02 github.com/coreos/go-oidc/v3 v3.11.0 github.com/elastic/go-libaudit/v2 v2.4.0 @@ -194,7 +195,6 @@ require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/charmbracelet/bubbles v0.20.0 // indirect github.com/charmbracelet/bubbletea v1.2.4 // indirect - github.com/charmbracelet/lipgloss v1.0.0 // indirect github.com/charmbracelet/x/ansi v0.5.2 // indirect github.com/charmbracelet/x/exp/strings v0.0.0-20241209212528-0eec74ecaa6f // indirect github.com/charmbracelet/x/term v0.2.1 // indirect diff --git a/go.sum b/go.sum index 565dabb648d..0905d16e065 100644 --- a/go.sum +++ b/go.sum @@ -56,8 +56,8 @@ github.com/Velocidex/chroma v0.6.8-0.20200418131129-82edc291369c h1:ipQHX4FX5HKR github.com/Velocidex/chroma v0.6.8-0.20200418131129-82edc291369c/go.mod h1:sko8vR34/90zvl5QdcUdvzL3J8NKjAUx9va9jPuFNoM= github.com/Velocidex/errors v0.0.0-20221019164655-9ace6bf61e26 h1:VwbeNpMRuS3bRieg7WLaSYIMaI8RjH/wGxd37oj6H1g= github.com/Velocidex/errors v0.0.0-20221019164655-9ace6bf61e26/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/Velocidex/etw v0.0.0-20231115144702-0b885b292f0f h1:j/s1qRr+sBknAcDELkGVqt5TOqDVlVhcNEBJVI+ldUo= -github.com/Velocidex/etw v0.0.0-20231115144702-0b885b292f0f/go.mod h1:F3/xFKE2Puol7bJKW5xcj/9H2O8n/sJN87Xgm66G6sc= +github.com/Velocidex/etw v0.0.0-20250102072915-dae077e67c53 h1:uySW0T4zQyJSe4xcJ94KB14NYXm1cCAOFSJCKXLYb1k= +github.com/Velocidex/etw v0.0.0-20250102072915-dae077e67c53/go.mod h1:1flhaSAPAtB7fS7InVaBJxxmLttgWOlx7rXG6Sftg5U= github.com/Velocidex/file-rotatelogs v0.0.0-20211221020724-d12e4dae4e11 h1:pQY9p6hvmbFKXJg8suzGSG9/t8Ij9ece32GUFIdHgqg= github.com/Velocidex/file-rotatelogs v0.0.0-20211221020724-d12e4dae4e11/go.mod h1:Ya1f4Kowt2GC7gbnu1MbNncvI1Lp3i1plN2xLiETJfg= github.com/Velocidex/fileb0x v1.1.2-0.20241111170537-c093c89cd042 h1:6kWoPVcuntDHv4+OI3ILXt9FTB4yPcs/BRBObu6Q264= @@ -773,8 +773,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.22.0 h1:BzDx2FehcG7jJwgWLELCdmLuxk2i+x9UDpSiss2u0ZA= @@ -966,8 +966,8 @@ www.velocidex.com/golang/go-ese v0.2.1-0.20240919031214-2aa005106db2 h1:f7nj4Nsy www.velocidex.com/golang/go-ese v0.2.1-0.20240919031214-2aa005106db2/go.mod h1:YKxCStqE15c6F/P81oCG0Y5oelDBah2hCdO6P+VPUIQ= www.velocidex.com/golang/go-ntfs v0.2.1-0.20241123135758-e6f7e1f1c474 h1:iaV0M55ZTdVU9nIqcHkQKwUfQOOoswC0eBZsKvlPN/0= www.velocidex.com/golang/go-ntfs v0.2.1-0.20241123135758-e6f7e1f1c474/go.mod h1:itvbHQcnLdTVIDY6fI3lR0zeBwXwBYBdUFtswE0x1vc= -www.velocidex.com/golang/go-pe v0.1.1-0.20230228112150-ef2eadf34bc3 h1:W394TEIFuHFxHY8mzTJPHI5v+M+NLKEHmHn7KY/VpEM= -www.velocidex.com/golang/go-pe v0.1.1-0.20230228112150-ef2eadf34bc3/go.mod h1:agYwYzeeytVtdwkRrvxZAjgIA8SCeM/Tg7Ym2/jBwmA= +www.velocidex.com/golang/go-pe v0.1.1-0.20250101153735-7a925ba8334b h1:hOxQYDyETh4wdnCbM9Il4X+6LwonGdLnsoznqvzw48A= +www.velocidex.com/golang/go-pe v0.1.1-0.20250101153735-7a925ba8334b/go.mod h1:agYwYzeeytVtdwkRrvxZAjgIA8SCeM/Tg7Ym2/jBwmA= www.velocidex.com/golang/go-prefetch v0.0.0-20240910051453-2385582c1c22 h1:Re+YlRCwkHESCIopk0WNLKXMnlnhALvoT4RiunT2qJE= www.velocidex.com/golang/go-prefetch v0.0.0-20240910051453-2385582c1c22/go.mod h1:UNIUmQhflpSTt7TH4o/6O/GiMCjSzIALXe9/zzTKFCw= www.velocidex.com/golang/oleparse v0.0.0-20230217092320-383a0121aafe h1:o9jQWSwKTLhBeavfOk054/HK5yNi6Ni9VHQ6rxYZEi4= diff --git a/vql/parsers/crypto/pubkey.go b/vql/parsers/crypto/pubkey.go index 589515f324c..bab36759e10 100644 --- a/vql/parsers/crypto/pubkey.go +++ b/vql/parsers/crypto/pubkey.go @@ -3,6 +3,7 @@ package crypto import ( "bufio" "bytes" + "context" "io" "io/ioutil" "strings" @@ -12,7 +13,6 @@ import ( "golang.org/x/crypto/openpgp/armor" "golang.org/x/crypto/openpgp/packet" - "golang.org/x/net/context" "www.velocidex.com/golang/velociraptor/acls" crypto_utils "www.velocidex.com/golang/velociraptor/crypto/utils" "www.velocidex.com/golang/velociraptor/vql" diff --git a/vql/tools/azure_upload.go b/vql/tools/azure_upload.go index 7b50404deff..266585ff2cf 100644 --- a/vql/tools/azure_upload.go +++ b/vql/tools/azure_upload.go @@ -13,9 +13,10 @@ package tools import ( + "context" + "github.com/Azure/azure-sdk-for-go/sdk/storage/azblob" "github.com/Velocidex/ordereddict" - "golang.org/x/net/context" "www.velocidex.com/golang/velociraptor/accessors" "www.velocidex.com/golang/velociraptor/acls" "www.velocidex.com/golang/velociraptor/uploads" diff --git a/vql/tools/gcs_pubsub_publish.go b/vql/tools/gcs_pubsub_publish.go index ff82560c29a..a6a4bac148e 100644 --- a/vql/tools/gcs_pubsub_publish.go +++ b/vql/tools/gcs_pubsub_publish.go @@ -4,13 +4,13 @@ package tools import ( + "context" "fmt" "github.com/Velocidex/json" "cloud.google.com/go/pubsub" "github.com/Velocidex/ordereddict" - "golang.org/x/net/context" "google.golang.org/api/option" vql_subsystem "www.velocidex.com/golang/velociraptor/vql" diff --git a/vql/tools/gcs_upload.go b/vql/tools/gcs_upload.go index d70914336cf..de6f18e9792 100644 --- a/vql/tools/gcs_upload.go +++ b/vql/tools/gcs_upload.go @@ -4,6 +4,7 @@ package tools import ( + "context" "crypto/md5" "crypto/sha256" "encoding/hex" @@ -11,7 +12,6 @@ import ( "cloud.google.com/go/storage" "github.com/Velocidex/ordereddict" - "golang.org/x/net/context" "google.golang.org/api/option" "www.velocidex.com/golang/velociraptor/accessors" "www.velocidex.com/golang/velociraptor/acls" diff --git a/vql/tools/s3_upload.go b/vql/tools/s3_upload.go index 23310c42a26..2f6eb2c94be 100644 --- a/vql/tools/s3_upload.go +++ b/vql/tools/s3_upload.go @@ -1,6 +1,7 @@ package tools import ( + "context" "crypto/tls" "errors" "net/http" @@ -12,7 +13,7 @@ import ( "github.com/aws/aws-sdk-go-v2/feature/s3/manager" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/aws/aws-sdk-go-v2/service/s3/types" - "golang.org/x/net/context" + "www.velocidex.com/golang/velociraptor/accessors" "www.velocidex.com/golang/velociraptor/acls" "www.velocidex.com/golang/velociraptor/artifacts" diff --git a/vql/tools/sftp_upload.go b/vql/tools/sftp_upload.go index 43c4dd7346f..5d481155e6f 100644 --- a/vql/tools/sftp_upload.go +++ b/vql/tools/sftp_upload.go @@ -4,6 +4,7 @@ package tools import ( + "context" "encoding/base64" "errors" "fmt" @@ -15,7 +16,6 @@ import ( "github.com/Velocidex/ordereddict" "github.com/pkg/sftp" "golang.org/x/crypto/ssh" - "golang.org/x/net/context" "www.velocidex.com/golang/velociraptor/accessors" "www.velocidex.com/golang/velociraptor/acls" "www.velocidex.com/golang/velociraptor/uploads" diff --git a/vql/tools/smb_upload.go b/vql/tools/smb_upload.go index e8d4afdf5a3..d10a245c4ad 100644 --- a/vql/tools/smb_upload.go +++ b/vql/tools/smb_upload.go @@ -13,13 +13,13 @@ package tools import ( + "context" "errors" "fmt" "strings" "github.com/Velocidex/ordereddict" "github.com/hirochachacha/go-smb2" - "golang.org/x/net/context" "www.velocidex.com/golang/velociraptor/accessors" "www.velocidex.com/golang/velociraptor/accessors/smb" "www.velocidex.com/golang/velociraptor/acls" diff --git a/vql/windows/etw/context.go b/vql/windows/etw/context.go index d45db0b319b..79904f384ae 100644 --- a/vql/windows/etw/context.go +++ b/vql/windows/etw/context.go @@ -5,11 +5,11 @@ package etw import ( "context" + "strings" "sync" "time" "github.com/Velocidex/etw" - "github.com/Velocidex/ordereddict" "golang.org/x/sys/windows" "www.velocidex.com/golang/velociraptor/utils" "www.velocidex.com/golang/vfilter" @@ -41,6 +41,15 @@ type SessionContext struct { registrations map[string]*Registration resolve_map_info bool + + // Options used for the kernel tracer provider. These apply for + // the entire session. + rundown_options etw.RundownOptions + + // Set to true once the session is already processing + is_processing uint64 + + is_kernel_trace bool } // Handle is used to track all watchers. We write the event on to the @@ -70,7 +79,7 @@ func (self *Handle) Close() { } // Try to send but skip closed handles. -func (self *Handle) Send(event vfilter.Row) { +func (self *Handle) Send(event *etw.Event) { self.mu.Lock() defer self.mu.Unlock() @@ -106,13 +115,43 @@ func (self *SessionContext) Session(scope vfilter.Scope) (*etw.Session, error) { return self._Session(scope) } -func (self *SessionContext) _Session(scope vfilter.Scope) (*etw.Session, error) { - // Cache the session for reuse. - if self.session != nil { - return self.session, nil +// The Kernel Trace provider is a bit different: + +// 1. We do not subscribe to the trace provider - the library does +// this automatically. +// 2. Session name has to be etw.KernelTraceSessionName - not user +// selectable. +// 3. Only one provider is available and we do not need to subscribe +// to it. +// 4. The events returned on this session belong to many internal +// providers and are not related to the specific listener +// registered on this session. Therefore for kernel sessions all +// registrations will see all events. +func (self *SessionContext) _KernelTraceSession( + scope vfilter.Scope) (*etw.Session, error) { + + session, err := etw.NewKernelTraceSession( + self.rundown_options, self.processEvent) + if err != nil { + err = etw.KillSession(etw.KernelTraceSessionName) + if err != nil { + return nil, err + } + + session, err = etw.NewKernelTraceSession( + self.rundown_options, self.processEvent) + if err != nil { + return nil, err + } } - // Establish a new ETW session and cache it for next time. + self.is_kernel_trace = true + + return session, nil +} + +func (self *SessionContext) _CreateTraceSession( + scope vfilter.Scope) (*etw.Session, error) { session, err := etw.NewSession(self.name, self.processEvent) if err != nil { @@ -128,50 +167,83 @@ func (self *SessionContext) _Session(scope vfilter.Scope) (*etw.Session, error) } } + return session, nil +} + +func (self *SessionContext) _Session( + scope vfilter.Scope) (*etw.Session, error) { + + var err error + + // Cache the session for reuse. + if self.session != nil { + return self.session, nil + } + + // If the user asked for the kernel trace session we need to + // create a special session here. + if strings.EqualFold(self.name, etw.KernelTraceSessionName) { + self.session, err = self._KernelTraceSession(scope) + } else { + self.session, err = self._CreateTraceSession(scope) + } + if err != nil { + return nil, err + } + self.wg.Add(1) go func() { defer self.wg.Done() - err := session.Process() + err := self.session.Process() if err != nil { scope.Log("etw: Can not start session %v: %v", self.name, err) } }() - self.session = session - return self.session, nil } func (self *SessionContext) processEvent(e *etw.Event) { defer utils.CheckForPanic("processEvent") + e.Props() + event_guid := e.Header.ProviderID.String() + var handlers []*Handle self.mu.Lock() - registration, pres := self.registrations[event_guid] - if !pres { - self.mu.Unlock() - return - } - registration.count++ - handlers := append([]*Handle{}, registration.handles...) - self.mu.Unlock() + // For the kernel trace all handlers will be called with every + // event. + is_kernel_trace := self.is_kernel_trace + if is_kernel_trace { + for _, v := range self.registrations { + handlers = append(handlers, v.handles...) + v.count++ + } - event := ordereddict.NewDict(). - Set("System", e.Header). - Set("ProviderGUID", event_guid) + } else { + registration, pres := self.registrations[event_guid] + if !pres { + self.mu.Unlock() + return + } - data, err := e.EventProperties(self.resolve_map_info) - if err == nil { - event.Set("EventData", data) + handlers = append(handlers, registration.handles...) + registration.count++ } + self.mu.Unlock() + // Send the event to all interested parties. for _, handle := range handlers { - if handle.guid == e.Header.ProviderID { - handle.Send(event) + // The Kernel Trace Provider emits events from many internal + // providers, even if we did not subscribe to them + // specifically. + if is_kernel_trace || + handle.guid == e.Header.ProviderID { + handle.Send(e) } } } @@ -182,13 +254,18 @@ func (self *SessionContext) Stats() []ProviderStat { result := []ProviderStat{} for guid, registration := range self.registrations { - result = append(result, ProviderStat{ + res := ProviderStat{ GUID: guid, EventCount: registration.count, Description: registration.description, Watchers: len(registration.handles), Started: registration.started, - }) + } + + if self.is_kernel_trace { + res.Stats = etw.KernelInfo.Stats() + } + result = append(result, res) } return result @@ -224,18 +301,24 @@ func (self *SessionContext) Register( return nil, nil, err } - err = session.SubscribeToProvider(etw.SessionOptions{ - Guid: guid, - Level: etw.TraceLevel(options.Level), - MatchAnyKeyword: options.AnyKeyword, - MatchAllKeyword: options.AllKeyword, - CaptureState: options.CaptureState, - }) - if err != nil { - scope.Log("etw: Can not add provider to session %v: %v", self.name, err) - return nil, nil, err + // The kernel trace sessions can only contain a single + // provider and we do not need to subscribe to it. + if !self.is_kernel_trace { + opts := etw.SessionOptions{ + Name: self.name, + Guid: guid, + Level: etw.TraceLevel(options.Level), + MatchAnyKeyword: options.AnyKeyword, + MatchAllKeyword: options.AllKeyword, + CaptureState: options.CaptureState, + } + err = session.SubscribeToProvider(opts) + if err != nil { + scope.Log("etw: Can not add provider to session %v: %v", self.name, err) + return nil, nil, err + } + scope.Log("etw: Added provider %v to session %v", guid.String(), self.name) } - scope.Log("etw: Added provider %v to session %v", guid.String(), self.name) } registration.handles = append(registration.handles, handle) diff --git a/vql/windows/etw/options.go b/vql/windows/etw/options.go index c0f01d0ed51..bf28634a8dd 100644 --- a/vql/windows/etw/options.go +++ b/vql/windows/etw/options.go @@ -1,5 +1,10 @@ +//go:build windows && cgo +// +build windows,cgo + package etw +import "github.com/Velocidex/etw" + type ETWOptions struct { AnyKeyword, AllKeyword uint64 Level int64 @@ -8,4 +13,6 @@ type ETWOptions struct { // A description string to be associated with the registration. Description string + + RundownOptions etw.RundownOptions } diff --git a/vql/windows/etw/protocols.go b/vql/windows/etw/protocols.go new file mode 100644 index 00000000000..ee683dd984e --- /dev/null +++ b/vql/windows/etw/protocols.go @@ -0,0 +1,60 @@ +//go:build windows && cgo +// +build windows,cgo + +package etw + +import ( + "github.com/Velocidex/etw" + vql_subsystem "www.velocidex.com/golang/velociraptor/vql" + "www.velocidex.com/golang/vfilter" + "www.velocidex.com/golang/vfilter/protocols" +) + +type etwEventAssociative struct{} + +func (self etwEventAssociative) Applicable(a vfilter.Any, b vfilter.Any) bool { + _, a_ok := a.(*etw.Event) + if !a_ok { + return false + } + + _, b_ok := b.(string) + return b_ok +} +func (self etwEventAssociative) GetMembers(scope vfilter.Scope, a vfilter.Any) []string { + _, a_ok := a.(*etw.Event) + if a_ok { + return []string{"System", "ProviderGUID", "EventData", "Backtrace"} + } + return []string{} +} + +func (self etwEventAssociative) Associative( + scope vfilter.Scope, a vfilter.Any, b vfilter.Any) (vfilter.Any, bool) { + a_value, a_ok := a.(*etw.Event) + if !a_ok { + return vfilter.Null{}, false + } + + b_value, b_ok := b.(string) + if !b_ok { + return vfilter.Null{}, false + } + + switch b_value { + case "System": + return a_value.HeaderProps(), true + case "ProviderGUID": + return a_value.Header.ProviderID.String(), true + case "EventData": + return a_value.Props(), true + case "Backtrace": + return a_value.Backtrace(), true + default: + return protocols.DefaultAssociative{}.Associative(scope, a, b) + } +} + +func init() { + vql_subsystem.RegisterProtocol(&etwEventAssociative{}) +} diff --git a/vql/windows/etw/stats.go b/vql/windows/etw/stats.go index 16d9f02eac3..510c26d4a49 100644 --- a/vql/windows/etw/stats.go +++ b/vql/windows/etw/stats.go @@ -3,7 +3,11 @@ package etw -import "time" +import ( + "time" + + "github.com/Velocidex/ordereddict" +) type ProviderStat struct { SessionName string @@ -12,4 +16,5 @@ type ProviderStat struct { EventCount int Watchers int Started time.Time + Stats *ordereddict.Dict } diff --git a/vql/windows/etw/watch_etw.go b/vql/windows/etw/watch_etw.go index 9b59b4d3667..1f64bd4d59d 100644 --- a/vql/windows/etw/watch_etw.go +++ b/vql/windows/etw/watch_etw.go @@ -5,8 +5,10 @@ package etw import ( "context" + "strings" "time" + "github.com/Velocidex/etw" "github.com/Velocidex/ordereddict" "golang.org/x/sys/windows" "www.velocidex.com/golang/velociraptor/utils" @@ -16,16 +18,18 @@ import ( ) type WatchETWArgs struct { - Name string `vfilter:"optional,field=name,doc=A session name "` - Provider string `vfilter:"required,field=guid,doc=A Provider GUID to watch "` - AnyKeywords uint64 `vfilter:"optional,field=any,doc=Any Keywords "` - AllKeywords uint64 `vfilter:"optional,field=all,doc=All Keywords "` - Level int64 `vfilter:"optional,field=level,doc=Log level (0-5)"` - Stop *vfilter.Lambda `vfilter:"optional,field=stop,doc=If provided we stop watching automatically when this lambda returns true"` - Timeout uint64 `vfilter:"optional,field=timeout,doc=If provided we stop after this much time"` - CaptureState bool `vfilter:"optional,field=capture_state,doc=If true, capture the state of the provider when the event is triggered"` - EnableMapInfo bool `vfilter:"optional,field=enable_map_info,doc=Resolving MapInfo with TdhGetEventMapInformation is very expensive and causes events to be dropped so we disabled it by default. Enable with this flag."` - Description string `vfilter:"optional,field=description,doc=Description for this GUID provider"` + Name string `vfilter:"optional,field=name,doc=A session name "` + Provider string `vfilter:"required,field=guid,doc=A Provider GUID to watch "` + AnyKeywords uint64 `vfilter:"optional,field=any,doc=Any Keywords "` + AllKeywords uint64 `vfilter:"optional,field=all,doc=All Keywords "` + Level int64 `vfilter:"optional,field=level,doc=Log level (0-5)"` + Stop *vfilter.Lambda `vfilter:"optional,field=stop,doc=If provided we stop watching automatically when this lambda returns true"` + Timeout uint64 `vfilter:"optional,field=timeout,doc=If provided we stop after this much time"` + CaptureState bool `vfilter:"optional,field=capture_state,doc=If true, capture the state of the provider when the event is triggered"` + EnableMapInfo bool `vfilter:"optional,field=enable_map_info,doc=Resolving MapInfo with TdhGetEventMapInformation is very expensive and causes events to be dropped so we disabled it by default. Enable with this flag."` + Description string `vfilter:"optional,field=description,doc=Description for this GUID provider"` + KernelTracerTypes []string `vfilter:"optional,field=kernel_tracer_type,doc=A list of event types to fetch from the kernel tracer (can be registry, process, image_load, network, driver, file, thread, handle)"` + KernelTracerStacks []string `vfilter:"optional,field=kernel_tracer_stacks,doc=A list of kernel tracer event types to append stack traces to (can be any of the types accepted by kernel_tracer_type)"` } type WatchETWPlugin struct { @@ -49,23 +53,11 @@ func (self WatchETWPlugin) Call( return } - // Check that we have a valid GUID. - wGuid, err := windows.GUIDFromString(arg.Provider) - if err != nil { - scope.Log("watch_etw: %s", err.Error()) - return - } - // By default listen to DEBUG level logs if arg.Level == 0 { arg.Level = 5 } - // Select a default session name - if arg.Name == "" { - arg.Name = "Velociraptor" - } - if arg.Timeout > 0 { sub_ctx, timeout_cancel := context.WithTimeout( ctx, time.Duration(arg.Timeout)*time.Second) @@ -83,6 +75,52 @@ func (self WatchETWPlugin) Call( Description: arg.Description, } + for _, tracer_type := range arg.KernelTracerTypes { + err := options.RundownOptions.Set(tracer_type) + if err != nil { + scope.Log("watch_etw: Invalid rundown option %v - ignoring", + tracer_type) + } + } + + if len(arg.KernelTracerStacks) > 0 { + options.RundownOptions.StackTracing = &etw.RundownOptions{} + + for _, tracer_type := range arg.KernelTracerStacks { + err := options.RundownOptions.StackTracing.Set(tracer_type) + if err != nil { + scope.Log("watch_etw: Invalid rundown option %v - ignoring", + tracer_type) + } + } + } + + // For the kernel provider we must use a fixed session name. + if arg.Provider == "kernel" { + arg.Provider = etw.KernelTraceControlGUIDString + arg.Level = 255 + } + + if strings.EqualFold(arg.Provider, etw.KernelTraceControlGUIDString) { + if arg.Name != "" && arg.Name != etw.KernelTraceSessionName { + scope.Log("Kernel provider must use fixed session name of %v, overriding", + etw.KernelTraceSessionName) + } + arg.Name = etw.KernelTraceSessionName + } + + // Select a default session name + if arg.Name == "" { + arg.Name = "Velociraptor" + } + + // Check that we have a valid GUID. + wGuid, err := windows.GUIDFromString(arg.Provider) + if err != nil { + scope.Log("watch_etw: %v", err) + return + } + self.RetryTimer = 1 * time.Second for { err = self.WatchOnce(ctx, scope, arg.Stop, output_chan, diff --git a/vql/windows/etw/watcher.go b/vql/windows/etw/watcher.go index a35d73e0e30..0f35d0c08e2 100644 --- a/vql/windows/etw/watcher.go +++ b/vql/windows/etw/watcher.go @@ -45,7 +45,7 @@ func (self *EventTraceWatcherService) Register( scope vfilter.Scope, session_name string, options ETWOptions, wGuid windows.GUID) (closer func(), output_chan chan vfilter.Row, err error) { - session, err := self.SessionContext(session_name, scope) + session, err := self.SessionContext(session_name, scope, options) if err != nil { return nil, nil, err } @@ -54,15 +54,20 @@ func (self *EventTraceWatcherService) Register( } func (self *EventTraceWatcherService) SessionContext( - name string, scope vfilter.Scope) (*SessionContext, error) { + name string, scope vfilter.Scope, options ETWOptions) ( + *SessionContext, error) { + self.mu.Lock() defer self.mu.Unlock() + // TODO: Handle the case when an existing session has different + // options. sessionContext, pres := self.sessions[name] if !pres { sessionContext = &SessionContext{ - name: name, - registrations: make(map[string]*Registration), + name: name, + registrations: make(map[string]*Registration), + rundown_options: options.RundownOptions, } self.sessions[name] = sessionContext } @@ -94,7 +99,8 @@ func writeMetrics( Set("Description", s.Description). Set("Watchers", s.Watchers). Set("EventCount", s.EventCount). - Set("Started", s.Started) + Set("Started", s.Started). + Set("Stats", s.Stats) } }