diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 299163e1f..b9db7d909 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -9,16 +9,19 @@ on: - "CONTRIBUTING.md" - "docs/**" +env: + GO_VERSION: 1.21.x + jobs: build: runs-on: windows-latest steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.19.x + go-version: ${{ env.GO_VERSION }} - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Adjust pkg-config prefix shell: bash run: | @@ -37,7 +40,7 @@ jobs: libtool autoconf - name: Cache yara - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache with: path: | @@ -94,7 +97,7 @@ jobs: export PATH="/c/wix:$PATH" export VERSION=0.0.0 ./make.bat pkg - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: "fibratus-amd64.msi" path: "./build/msi/fibratus-0.0.0-amd64.msi" @@ -103,17 +106,17 @@ jobs: runs-on: windows-latest steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.19.x + go-version: ${{ env.GO_VERSION }} - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Build shell: bash run: | export COMMIT=$(echo $GITHUB_SHA | cut -c1-8) ./make.bat - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: "fibratus-amd64-slim.exe" path: "./cmd/fibratus/fibratus.exe" @@ -126,15 +129,15 @@ jobs: continue-on-error: true steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Adjust pkg-config prefix shell: bash run: | sed -i 's/C:\/Python37/C:\/hostedtoolcache\/windows\/Python\/3.7.9\/x64/' pkg-config/python-37.pc - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.19.x + go-version: ${{ env.GO_VERSION }} - name: Setup msys2 uses: msys2/setup-msys2@v2 with: @@ -148,7 +151,7 @@ jobs: libtool autoconf - name: Cache yara - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache with: path: | @@ -176,15 +179,15 @@ jobs: continue-on-error: true steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Adjust pkg-config prefix shell: bash run: | sed -i 's/C:\/Python37/C:\/hostedtoolcache\/windows\/Python\/3.7.9\/x64/' pkg-config/python-37.pc - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.19.x + go-version: ${{ env.GO_VERSION }} - name: Setup msys2 uses: msys2/setup-msys2@v2 with: @@ -198,7 +201,7 @@ jobs: libtool autoconf - name: Cache yara - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache with: path: | @@ -216,7 +219,7 @@ jobs: run: | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin $GOLANGCI_LINT_VER env: - GOLANGCI_LINT_VER: v1.50.1 + GOLANGCI_LINT_VER: v1.55.1 - name: Lint shell: bash run: | diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index dd357f233..a7df99ebc 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -7,16 +7,19 @@ on: paths-ignore: - "docs/**" +env: + GO_VERSION: 1.21.x + jobs: build: runs-on: windows-latest steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.19.x + go-version: ${{ env.GO_VERSION }} - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Adjust pkg-config prefix shell: bash run: | @@ -35,7 +38,7 @@ jobs: libtool autoconf - name: Cache yara - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache with: path: | @@ -79,15 +82,15 @@ jobs: continue-on-error: true steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Adjust pkg-config prefix shell: bash run: | sed -i 's/C:\/Python37/C:\/hostedtoolcache\/windows\/Python\/3.7.9\/x64/' pkg-config/python-37.pc - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.19.x + go-version: ${{ env.GO_VERSION }} - name: Setup msys2 uses: msys2/setup-msys2@v2 with: @@ -101,7 +104,7 @@ jobs: libtool autoconf - name: Cache yara - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache with: path: | @@ -129,15 +132,15 @@ jobs: continue-on-error: true steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Adjust pkg-config prefix shell: bash run: | sed -i 's/C:\/Python37/C:\/hostedtoolcache\/windows\/Python\/3.7.9\/x64/' pkg-config/python-37.pc - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.19.x + go-version: ${{ env.GO_VERSION }} - name: Setup msys2 uses: msys2/setup-msys2@v2 with: @@ -151,7 +154,7 @@ jobs: libtool autoconf - name: Cache yara - uses: actions/cache@v2 + uses: actions/cache@v3 id: cache with: path: | @@ -169,7 +172,7 @@ jobs: run: | curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin $GOLANGCI_LINT_VER env: - GOLANGCI_LINT_VER: v1.50.1 + GOLANGCI_LINT_VER: v1.55.1 - name: Lint shell: bash run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4652b67e8..e72a88677 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -5,16 +5,19 @@ on: tags: - 'v*' +env: + GO_VERSION: 1.21.x + jobs: build: runs-on: windows-latest steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.19.x + go-version: ${{ env.GO_VERSION }} - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Adjust pkg-config prefix shell: bash run: | @@ -33,7 +36,7 @@ jobs: libtool autoconf - name: Cache yara - uses: actions/cache@v2 + uses: actions/cache@v4 id: cache with: path: | @@ -91,7 +94,7 @@ jobs: export PATH="/c/wix:$PATH" export VERSION=${{ steps.get_version.outputs.VERSION }} ./make.bat pkg - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: fibratus-${{ steps.get_version.outputs.VERSION }}-amd64.msi path: "./build/msi/fibratus-${{ steps.get_version.outputs.VERSION }}-amd64.msi" @@ -100,11 +103,11 @@ jobs: runs-on: windows-latest steps: - name: Install Go - uses: actions/setup-go@v2 + uses: actions/setup-go@v4 with: - go-version: 1.19.x + go-version: ${{ env.GO_VERSION }} - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Get version id: get_version shell: bash @@ -133,7 +136,7 @@ jobs: export PATH="/c/wix:$PATH" export VERSION=${{ steps.get_version.outputs.VERSION }} ./make.bat pkg-slim - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: fibratus-${{ steps.get_version.outputs.VERSION }}-slim-amd64.msi path: "./build/msi/fibratus-${{ steps.get_version.outputs.VERSION }}-slim-amd64.msi" @@ -145,17 +148,17 @@ jobs: - build-slim steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v4 - name: Get version id: get_version shell: bash run: | echo ::set-output name=VERSION::$(echo $GITHUB_REF | cut -d / -f 3 | cut -c2-) - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: fibratus-${{ steps.get_version.outputs.VERSION }}-amd64.msi path: build - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: fibratus-${{ steps.get_version.outputs.VERSION }}-slim-amd64.msi path: build diff --git a/.golangci.yml b/.golangci.yml index 57f6eb7bc..d8c4d3429 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -9,7 +9,6 @@ linters: enable: - bodyclose - deadcode - - depguard - errcheck - goconst - golint @@ -45,3 +44,4 @@ issues: linters: - errcheck - scopelint + - nolintlint diff --git a/go.mod b/go.mod index b17f9cfdd..3b03988ac 100644 --- a/go.mod +++ b/go.mod @@ -82,4 +82,4 @@ require ( www.velocidex.com/golang/go-ntfs v0.1.2-0.20230922133004-33eadbbaf1f2 ) -go 1.19 +go 1.21 diff --git a/pkg/config/schema_windows.go b/pkg/config/schema_windows.go index b60eb1c24..80c7ceb55 100644 --- a/pkg/config/schema_windows.go +++ b/pkg/config/schema_windows.go @@ -171,7 +171,7 @@ var schema = ` "blacklist": { "type": "object", "properties": { - "events": {"type": "array", "items": [{"type": "string", "enum": ["CreateProcess", "CreateThread", "TerminateProcess", "TerminateThread", "OpenProcess", "OpenThread", "LoadImage", "UnloadImage", "CreateFile", "CloseFile", "ReadFile", "WriteFile", "DeleteFile", "RenameFile", "SetFileInformation", "EnumDirectory", "RegCreateKey", "RegOpenKey", "RegSetValue", "RegQueryValue", "RegQueryKey", "RegDeleteKey", "RegDeleteValue", "Accept", "Send", "Recv", "Connect", "Disconnect", "Reconnect", "Retransmit", "CreateHandle", "CloseHandle"]}]}, + "events": {"type": "array", "items": [{"type": "string", "enum": ["CreateProcess", "CreateThread", "TerminateProcess", "TerminateThread", "OpenProcess", "OpenThread", "SetThreadContext", "LoadImage", "UnloadImage", "CreateFile", "CloseFile", "ReadFile", "WriteFile", "DeleteFile", "RenameFile", "SetFileInformation", "EnumDirectory", "RegCreateKey", "RegOpenKey", "RegSetValue", "RegQueryValue", "RegQueryKey", "RegDeleteKey", "RegDeleteValue", "Accept", "Send", "Recv", "Connect", "Disconnect", "Reconnect", "Retransmit", "CreateHandle", "CloseHandle"]}]}, "images": {"type": "array", "items": [{"type": "string", "minLength": 1}]} }, "additionalProperties": false diff --git a/pkg/filter/accessor_windows.go b/pkg/filter/accessor_windows.go index 793978999..dd3097d70 100644 --- a/pkg/filter/accessor_windows.go +++ b/pkg/filter/accessor_windows.go @@ -23,6 +23,7 @@ import ( "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" psnap "github.com/rabbitstack/fibratus/pkg/ps" "github.com/rabbitstack/fibratus/pkg/util/cmdline" + "github.com/rabbitstack/fibratus/pkg/util/loldrivers" "path/filepath" "strconv" "strings" @@ -569,10 +570,44 @@ func (t *threadAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value return nil, nil } +// evalLOLDrivers interacts with the loldrivers client to determine +// whether the loaded/dropped driver is malicious or vulnerable. +func evalLOLDrivers(f fields.Field, kevt *kevent.Kevent) (kparams.Value, error) { + filename := kevt.GetParamAsString(kparams.FileName) + isDriver := filepath.Ext(filename) == ".sys" || kevt.Kparams.TryGetBool(kparams.FileIsDriver) + if !isDriver { + return nil, nil + } + ok, driver := loldrivers.GetClient().MatchHash(filename) + if !ok { + return nil, nil + } + if (f == fields.FileIsDriverVulnerable || f == fields.ImageIsDriverVulnerable) && driver.IsVulnerable { + return true, nil + } + if (f == fields.FileIsDriverMalicious || f == fields.ImageIsDriverMalicious) && driver.IsMalicious { + return true, nil + } + return false, nil +} + +// initLOLDriversClient initializes the loldrivers client if the filter expression +// contains any of the relevant fields. +func initLOLDriversClient(flds []fields.Field) { + for _, f := range flds { + if f == fields.FileIsDriverVulnerable || f == fields.FileIsDriverMalicious || + f == fields.ImageIsDriverVulnerable || f == fields.ImageIsDriverMalicious { + loldrivers.InitClient(loldrivers.WithAsyncDownload()) + } + } +} + // fileAccessor extracts file specific values. type fileAccessor struct{} -func (fileAccessor) setFields(fields []fields.Field) {} +func (fileAccessor) setFields(fields []fields.Field) { + initLOLDriversClient(fields) +} func newFileAccessor() accessor { return &fileAccessor{} @@ -609,6 +644,17 @@ func (l *fileAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, return kevt.Kparams.GetUint64(kparams.FileViewSize) case fields.FileViewType: return kevt.GetParamAsString(kparams.FileViewSectionType), nil + case fields.FileIsDriverVulnerable, fields.FileIsDriverMalicious: + if kevt.IsCreateDisposition() && kevt.IsSuccess() { + return evalLOLDrivers(f, kevt) + } + return false, nil + case fields.FileIsDLL: + return kevt.Kparams.GetBool(kparams.FileIsDLL) + case fields.FileIsDriver: + return kevt.Kparams.GetBool(kparams.FileIsDriver) + case fields.FileIsExecutable: + return kevt.Kparams.GetBool(kparams.FileIsExecutable) } return nil, nil } @@ -616,7 +662,9 @@ func (l *fileAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, // imageAccessor extracts image (DLL) event values. type imageAccessor struct{} -func (imageAccessor) setFields(fields []fields.Field) {} +func (imageAccessor) setFields(fields []fields.Field) { + initLOLDriversClient(fields) +} func newImageAccessor() accessor { return &imageAccessor{} @@ -650,6 +698,17 @@ func (i *imageAccessor) get(f fields.Field, kevt *kevent.Kevent) (kparams.Value, return kevt.Kparams.GetTime(kparams.ImageCertNotBefore) case fields.ImageCertAfter: return kevt.Kparams.GetTime(kparams.ImageCertNotAfter) + case fields.ImageIsDriverVulnerable, fields.ImageIsDriverMalicious: + if kevt.IsLoadImage() { + return evalLOLDrivers(f, kevt) + } + return false, nil + case fields.ImageIsDLL: + return kevt.Kparams.GetBool(kparams.FileIsDLL) + case fields.ImageIsDriver: + return kevt.Kparams.GetBool(kparams.FileIsDriver) + case fields.ImageIsExecutable: + return kevt.Kparams.GetBool(kparams.FileIsExecutable) } return nil, nil } @@ -757,7 +816,7 @@ func (pa *peAccessor) parserOpts() []pe.Option { opts = append(opts, pe.WithSymbols()) } if f.IsPeSectionEntropy() { - opts = append(opts, pe.WithSectionEntropy()) + opts = append(opts, pe.WithSections(), pe.WithSectionEntropy()) } if f.IsPeVersionResource() || f.IsPeResourcesMap() { opts = append(opts, pe.WithVersionResources()) diff --git a/pkg/filter/fields/fields.go b/pkg/filter/fields/fields.go index 7194b1479..2b57a7902 100644 --- a/pkg/filter/fields/fields.go +++ b/pkg/filter/fields/fields.go @@ -39,8 +39,8 @@ func (f FieldInfo) IsDeprecated() bool { return f.Deprecation != nil } type Deprecation struct { // Since denotes from which version the field is flagged as deprecated Since string - // Field represents the field by which the deprecated field is superseded - Field Field + // Fields represents the fields by which the deprecated field is superseded + Fields []Field } // Get returns a slice of field information. diff --git a/pkg/filter/fields/fields_windows.go b/pkg/filter/fields/fields_windows.go index 287ad2964..aea0e41bb 100644 --- a/pkg/filter/fields/fields_windows.go +++ b/pkg/filter/fields/fields_windows.go @@ -342,6 +342,16 @@ const ( FileViewSize Field = "file.view.size" // FileViewType represents the type of the mapped view section FileViewType Field = "file.view.type" + // FileIsDriverVulnerable represents the field that denotes whether the created file is a vulnerable driver + FileIsDriverVulnerable Field = "file.is_driver_vulnerable" + // FileIsDriverMalicious represents the field that denotes whether the created file is a malicious driver + FileIsDriverMalicious Field = "file.is_driver_malicious" + // FileIsDLL indicates if the created file is a DLL + FileIsDLL Field = "file.is_dll" + // FileIsDriver indicates if the created file is a driver + FileIsDriver Field = "file.is_driver" + // FileIsExecutable indicates if the created file is an executable + FileIsExecutable Field = "file.is_exec" // RegistryKeyName represents the registry key name RegistryKeyName Field = "registry.key.name" @@ -381,6 +391,16 @@ const ( ImageCertBefore = "image.cert.before" // ImageCertAfter is the field that specifies the certificate won't be valid after this timestamp. ImageCertAfter = "image.cert.after" + // ImageIsDriverVulnerable represents the field that denotes whether loaded driver is vulnerable + ImageIsDriverVulnerable Field = "image.is_driver_vulnerable" + // ImageIsDriverMalicious represents the field that denotes whether the loaded driver is malicious + ImageIsDriverMalicious Field = "image.is_driver_malicious" + // ImageIsDLL indicates if the loaded image is a DLL + ImageIsDLL Field = "image.is_dll" + // ImageIsDriver indicates if the loaded image is a driver + ImageIsDriver Field = "image.is_driver" + // ImageIsExecutable indicates if the loaded image is an executable + ImageIsExecutable Field = "image.is_exec" // MemBaseAddress identifies the field that denotes the allocation base address MemBaseAddress Field = "mem.address" @@ -521,7 +541,7 @@ var fields = map[Field]FieldInfo{ PsPid: {PsPid, "process identifier", kparams.PID, []string{"ps.pid = 1024"}, nil}, PsPpid: {PsPpid, "parent process identifier", kparams.PID, []string{"ps.ppid = 45"}, nil}, PsName: {PsName, "process image name including the file extension", kparams.UnicodeString, []string{"ps.name contains 'firefox'"}, nil}, - PsComm: {PsComm, "process command line", kparams.UnicodeString, []string{"ps.comm contains 'java'"}, &Deprecation{Since: "1.10.0", Field: PsCmdline}}, + PsComm: {PsComm, "process command line", kparams.UnicodeString, []string{"ps.comm contains 'java'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsCmdline}}}, PsCmdline: {PsCmdline, "process command line", kparams.UnicodeString, []string{"ps.cmdline contains 'java'"}, nil}, PsExe: {PsExe, "full name of the process' executable", kparams.UnicodeString, []string{"ps.exe = 'C:\\Windows\\system32\\cmd.exe'"}, nil}, PsArgs: {PsArgs, "process command line arguments", kparams.Slice, []string{"ps.args in ('/cdir', '/-C')"}, nil}, @@ -537,7 +557,7 @@ var fields = map[Field]FieldInfo{ PsModules: {PsModules, "modules loaded by the process", kparams.Slice, []string{"ps.modules in ('crypt32.dll', 'xul.dll')"}, nil}, PsParentName: {PsParentName, "parent process image name including the file extension", kparams.UnicodeString, []string{"ps.parent.name contains 'cmd.exe'"}, nil}, PsParentPid: {PsParentPid, "parent process id", kparams.Uint32, []string{"ps.parent.pid = 4"}, nil}, - PsParentComm: {PsParentComm, "parent process command line", kparams.UnicodeString, []string{"ps.parent.comm contains 'java'"}, &Deprecation{Since: "1.10.0", Field: PsParentCmdline}}, + PsParentComm: {PsParentComm, "parent process command line", kparams.UnicodeString, []string{"ps.parent.comm contains 'java'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsParentCmdline}}}, PsParentCmdline: {PsParentCmdline, "parent process command line", kparams.UnicodeString, []string{"ps.parent.cmdline contains 'java'"}, nil}, PsParentExe: {PsParentExe, "full name of the parent process' executable", kparams.UnicodeString, []string{"ps.parent.exe = 'C:\\Windows\\system32\\explorer.exe'"}, nil}, PsParentArgs: {PsParentArgs, "parent process command line arguments", kparams.Slice, []string{"ps.parent.args in ('/cdir', '/-C')"}, nil}, @@ -553,23 +573,23 @@ var fields = map[Field]FieldInfo{ PsAccessMask: {PsAccessMask, "process desired access rights", kparams.AnsiString, []string{"ps.access.mask = '0x1400'"}, nil}, PsAccessMaskNames: {PsAccessMaskNames, "process desired access rights as a string list", kparams.Slice, []string{"ps.access.mask.names in ('SUSPEND_RESUME')"}, nil}, PsAccessStatus: {PsAccessStatus, "process access status", kparams.UnicodeString, []string{"ps.access.status = 'access is denied.'"}, nil}, - PsSiblingPid: {PsSiblingPid, "created or terminated process identifier", kparams.PID, []string{"ps.sibling.pid = 320"}, &Deprecation{Since: "1.10.0", Field: PsChildPid}}, + PsSiblingPid: {PsSiblingPid, "created or terminated process identifier", kparams.PID, []string{"ps.sibling.pid = 320"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildPid}}}, PsChildPid: {PsChildPid, "created or terminated process identifier", kparams.PID, []string{"ps.child.pid = 320"}, nil}, - PsSiblingName: {PsSiblingName, "created or terminated process name", kparams.UnicodeString, []string{"ps.sibling.name = 'notepad.exe'"}, &Deprecation{Since: "1.10.0", Field: PsChildName}}, + PsSiblingName: {PsSiblingName, "created or terminated process name", kparams.UnicodeString, []string{"ps.sibling.name = 'notepad.exe'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildName}}}, PsChildName: {PsChildName, "created or terminated process name", kparams.UnicodeString, []string{"ps.child.name = 'notepad.exe'"}, nil}, - PsSiblingComm: {PsSiblingComm, "created or terminated process command line", kparams.UnicodeString, []string{"ps.sibling.comm contains '\\k \\v'"}, &Deprecation{Since: "1.10.0", Field: PsChildCmdline}}, + PsSiblingComm: {PsSiblingComm, "created or terminated process command line", kparams.UnicodeString, []string{"ps.sibling.comm contains '\\k \\v'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildCmdline}}}, PsChildCmdline: {PsChildCmdline, "created or terminated process command line", kparams.UnicodeString, []string{"ps.child.cmdline contains '\\k \\v'"}, nil}, - PsSiblingArgs: {PsSiblingArgs, "created process command line arguments", kparams.Slice, []string{"ps.sibling.args in ('/cdir', '/-C')"}, &Deprecation{Since: "1.10.0", Field: PsChildArgs}}, + PsSiblingArgs: {PsSiblingArgs, "created process command line arguments", kparams.Slice, []string{"ps.sibling.args in ('/cdir', '/-C')"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildArgs}}}, PsChildArgs: {PsChildArgs, "created process command line arguments", kparams.Slice, []string{"ps.child.args in ('/cdir', '/-C')"}, nil}, - PsSiblingExe: {PsSiblingExe, "created, terminated, or opened process id", kparams.UnicodeString, []string{"ps.sibling.exe contains '\\Windows\\cmd.exe'"}, &Deprecation{Since: "1.10.0", Field: PsChildExe}}, + PsSiblingExe: {PsSiblingExe, "created, terminated, or opened process id", kparams.UnicodeString, []string{"ps.sibling.exe contains '\\Windows\\cmd.exe'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildExe}}}, PsChildExe: {PsChildExe, "created, terminated, or opened process id", kparams.UnicodeString, []string{"ps.child.exe contains '\\Windows\\cmd.exe'"}, nil}, - PsSiblingSID: {PsSiblingSID, "created or terminated process security identifier", kparams.UnicodeString, []string{"ps.sibling.sid contains 'SERVICE'"}, &Deprecation{Since: "1.10.0", Field: PsChildSID}}, + PsSiblingSID: {PsSiblingSID, "created or terminated process security identifier", kparams.UnicodeString, []string{"ps.sibling.sid contains 'SERVICE'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildSID}}}, PsChildSID: {PsChildSID, "created or terminated process security identifier", kparams.UnicodeString, []string{"ps.child.sid contains 'SERVICE'"}, nil}, - PsSiblingSessionID: {PsSiblingSessionID, "created or terminated process session identifier", kparams.Int16, []string{"ps.sibling.sessionid == 1"}, &Deprecation{Since: "1.10.0", Field: PsChildSessionID}}, + PsSiblingSessionID: {PsSiblingSessionID, "created or terminated process session identifier", kparams.Int16, []string{"ps.sibling.sessionid == 1"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildSessionID}}}, PsChildSessionID: {PsChildSessionID, "created or terminated process session identifier", kparams.Int16, []string{"ps.child.sessionid == 1"}, nil}, - PsSiblingDomain: {PsSiblingDomain, "created or terminated process domain", kparams.UnicodeString, []string{"ps.sibling.domain contains 'SERVICE'"}, &Deprecation{Since: "1.10.0", Field: PsChildDomain}}, + PsSiblingDomain: {PsSiblingDomain, "created or terminated process domain", kparams.UnicodeString, []string{"ps.sibling.domain contains 'SERVICE'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildDomain}}}, PsChildDomain: {PsChildDomain, "created or terminated process domain", kparams.UnicodeString, []string{"ps.child.domain contains 'SERVICE'"}, nil}, - PsSiblingUsername: {PsSiblingUsername, "created or terminated process username", kparams.UnicodeString, []string{"ps.sibling.username contains 'system'"}, &Deprecation{Since: "1.10.0", Field: PsChildUsername}}, + PsSiblingUsername: {PsSiblingUsername, "created or terminated process username", kparams.UnicodeString, []string{"ps.sibling.username contains 'system'"}, &Deprecation{Since: "1.10.0", Fields: []Field{PsChildUsername}}}, PsChildUsername: {PsChildUsername, "created or terminated process username", kparams.UnicodeString, []string{"ps.child.username contains 'system'"}, nil}, PsUUID: {PsUUID, "unique process identifier", kparams.Uint64, []string{"ps.uuid > 6000054355"}, nil}, PsParentUUID: {PsParentUUID, "unique parent process identifier", kparams.Uint64, []string{"ps.parent.uuid > 6000054355"}, nil}, @@ -588,33 +608,43 @@ var fields = map[Field]FieldInfo{ ThreadAccessMaskNames: {ThreadAccessMaskNames, "thread desired access rights as a string list", kparams.Slice, []string{"thread.access.mask.names in ('IMPERSONATE')"}, nil}, ThreadAccessStatus: {ThreadAccessStatus, "thread access status", kparams.UnicodeString, []string{"thread.access.status = 'success'"}, nil}, - ImageName: {ImageName, "full image name", kparams.UnicodeString, []string{"image.name contains 'advapi32.dll'"}, nil}, - ImageBase: {ImageBase, "the base address of process in which the image is loaded", kparams.Address, []string{"image.base.address = 'a65d800000'"}, nil}, - ImageChecksum: {ImageChecksum, "image checksum", kparams.Uint32, []string{"image.checksum = 746424"}, nil}, - ImageSize: {ImageSize, "image size", kparams.Uint32, []string{"image.size > 1024"}, nil}, - ImageDefaultAddress: {ImageDefaultAddress, "default image address", kparams.Address, []string{"image.default.address = '7efe0000'"}, nil}, - ImagePID: {ImagePID, "target process identifier", kparams.Uint32, []string{"image.pid = 80"}, nil}, - ImageSignatureType: {ImageSignatureType, "image signature type", kparams.AnsiString, []string{"image.signature.type != 'NONE'"}, nil}, - ImageSignatureLevel: {ImageSignatureLevel, "image signature level", kparams.AnsiString, []string{"image.signature.level = 'AUTHENTICODE'"}, nil}, - ImageCertSerial: {ImageCertSerial, "image certificate serial number", kparams.UnicodeString, []string{"image.cert.serial = '330000023241fb59996dcc4dff000000000232'"}, nil}, - ImageCertSubject: {ImageCertSubject, "image certificate subject", kparams.UnicodeString, []string{"image.cert.subject contains 'Washington, Redmond, Microsoft Corporation'"}, nil}, - ImageCertIssuer: {ImageCertIssuer, "image certificate CA", kparams.UnicodeString, []string{"image.cert.issuer contains 'Washington, Redmond, Microsoft Corporation'"}, nil}, - ImageCertAfter: {ImageCertAfter, "image certificate expiration date", kparams.Time, []string{"image.cert.after contains '2024-02-01 00:05:42 +0000 UTC'"}, nil}, - ImageCertBefore: {ImageCertBefore, "image certificate enrollment date", kparams.Time, []string{"image.cert.before contains '2024-02-01 00:05:42 +0000 UTC'"}, nil}, - - FileObject: {FileObject, "file object address", kparams.Uint64, []string{"file.object = 18446738026482168384"}, nil}, - FileName: {FileName, "full file name", kparams.UnicodeString, []string{"file.name contains 'mimikatz'"}, nil}, - FileOperation: {FileOperation, "file operation", kparams.AnsiString, []string{"file.operation = 'open'"}, nil}, - FileShareMask: {FileShareMask, "file share mask", kparams.AnsiString, []string{"file.share.mask = 'rw-'"}, nil}, - FileIOSize: {FileIOSize, "file I/O size", kparams.Uint32, []string{"file.io.size > 512"}, nil}, - FileOffset: {FileOffset, "file offset", kparams.Uint64, []string{"file.offset = 1024"}, nil}, - FileType: {FileType, "file type", kparams.AnsiString, []string{"file.type = 'directory'"}, nil}, - FileExtension: {FileExtension, "file extension", kparams.AnsiString, []string{"file.extension = '.dll'"}, nil}, - FileAttributes: {FileAttributes, "file attributes", kparams.Slice, []string{"file.attributes in ('archive', 'hidden')"}, nil}, - FileStatus: {FileStatus, "file operation status message", kparams.UnicodeString, []string{"file.status != 'success'"}, nil}, - FileViewBase: {FileViewBase, "view base address", kparams.Address, []string{"file.view.base = '25d42170000'"}, nil}, - FileViewSize: {FileViewSize, "size of the mapped view", kparams.Uint64, []string{"file.view.size > 1024"}, nil}, - FileViewType: {FileViewType, "type of the mapped view section", kparams.Enum, []string{"file.view.type = 'IMAGE'"}, nil}, + ImageName: {ImageName, "full image name", kparams.UnicodeString, []string{"image.name contains 'advapi32.dll'"}, nil}, + ImageBase: {ImageBase, "the base address of process in which the image is loaded", kparams.Address, []string{"image.base.address = 'a65d800000'"}, nil}, + ImageChecksum: {ImageChecksum, "image checksum", kparams.Uint32, []string{"image.checksum = 746424"}, nil}, + ImageSize: {ImageSize, "image size", kparams.Uint32, []string{"image.size > 1024"}, nil}, + ImageDefaultAddress: {ImageDefaultAddress, "default image address", kparams.Address, []string{"image.default.address = '7efe0000'"}, nil}, + ImagePID: {ImagePID, "target process identifier", kparams.Uint32, []string{"image.pid = 80"}, nil}, + ImageSignatureType: {ImageSignatureType, "image signature type", kparams.AnsiString, []string{"image.signature.type != 'NONE'"}, nil}, + ImageSignatureLevel: {ImageSignatureLevel, "image signature level", kparams.AnsiString, []string{"image.signature.level = 'AUTHENTICODE'"}, nil}, + ImageCertSerial: {ImageCertSerial, "image certificate serial number", kparams.UnicodeString, []string{"image.cert.serial = '330000023241fb59996dcc4dff000000000232'"}, nil}, + ImageCertSubject: {ImageCertSubject, "image certificate subject", kparams.UnicodeString, []string{"image.cert.subject contains 'Washington, Redmond, Microsoft Corporation'"}, nil}, + ImageCertIssuer: {ImageCertIssuer, "image certificate CA", kparams.UnicodeString, []string{"image.cert.issuer contains 'Washington, Redmond, Microsoft Corporation'"}, nil}, + ImageCertAfter: {ImageCertAfter, "image certificate expiration date", kparams.Time, []string{"image.cert.after contains '2024-02-01 00:05:42 +0000 UTC'"}, nil}, + ImageCertBefore: {ImageCertBefore, "image certificate enrollment date", kparams.Time, []string{"image.cert.before contains '2024-02-01 00:05:42 +0000 UTC'"}, nil}, + ImageIsDriverMalicious: {ImageIsDriverMalicious, "indicates if the loaded driver is malicious", kparams.Bool, []string{"image.is_driver_malicious"}, nil}, + ImageIsDriverVulnerable: {ImageIsDriverVulnerable, "indicates if the loaded driver is vulnerable", kparams.Bool, []string{"image.is_driver_vulnerable"}, nil}, + ImageIsDLL: {ImageIsDLL, "indicates if the loaded image is a DLL", kparams.Bool, []string{"image.is_dll'"}, nil}, + ImageIsDriver: {ImageIsDriver, "indicates if the loaded image is a driver", kparams.Bool, []string{"image.is_driver'"}, nil}, + ImageIsExecutable: {ImageIsExecutable, "indicates if the loaded image is an executable", kparams.Bool, []string{"image.is_exec'"}, nil}, + + FileObject: {FileObject, "file object address", kparams.Uint64, []string{"file.object = 18446738026482168384"}, nil}, + FileName: {FileName, "full file name", kparams.UnicodeString, []string{"file.name contains 'mimikatz'"}, nil}, + FileOperation: {FileOperation, "file operation", kparams.AnsiString, []string{"file.operation = 'open'"}, nil}, + FileShareMask: {FileShareMask, "file share mask", kparams.AnsiString, []string{"file.share.mask = 'rw-'"}, nil}, + FileIOSize: {FileIOSize, "file I/O size", kparams.Uint32, []string{"file.io.size > 512"}, nil}, + FileOffset: {FileOffset, "file offset", kparams.Uint64, []string{"file.offset = 1024"}, nil}, + FileType: {FileType, "file type", kparams.AnsiString, []string{"file.type = 'directory'"}, nil}, + FileExtension: {FileExtension, "file extension", kparams.AnsiString, []string{"file.extension = '.dll'"}, nil}, + FileAttributes: {FileAttributes, "file attributes", kparams.Slice, []string{"file.attributes in ('archive', 'hidden')"}, nil}, + FileStatus: {FileStatus, "file operation status message", kparams.UnicodeString, []string{"file.status != 'success'"}, nil}, + FileViewBase: {FileViewBase, "view base address", kparams.Address, []string{"file.view.base = '25d42170000'"}, nil}, + FileViewSize: {FileViewSize, "size of the mapped view", kparams.Uint64, []string{"file.view.size > 1024"}, nil}, + FileViewType: {FileViewType, "type of the mapped view section", kparams.Enum, []string{"file.view.type = 'IMAGE'"}, nil}, + FileIsDriverMalicious: {FileIsDriverMalicious, "indicates if the dropped driver is malicious", kparams.Bool, []string{"file.is_driver_malicious"}, nil}, + FileIsDriverVulnerable: {FileIsDriverVulnerable, "indicates if the dropped driver is vulnerable", kparams.Bool, []string{"file.is_driver_vulnerable"}, nil}, + FileIsDLL: {FileIsDLL, "indicates if the created file is a DLL", kparams.Bool, []string{"file.is_dll'"}, nil}, + FileIsDriver: {FileIsDriver, "indicates if the created file is a driver", kparams.Bool, []string{"file.is_driver'"}, nil}, + FileIsExecutable: {FileIsExecutable, "indicates if the created file is an executable", kparams.Bool, []string{"file.is_exec'"}, nil}, RegistryKeyName: {RegistryKeyName, "fully qualified key name", kparams.UnicodeString, []string{"registry.key.name contains 'HKEY_LOCAL_MACHINE'"}, nil}, RegistryKeyHandle: {RegistryKeyHandle, "registry key object address", kparams.Address, []string{"registry.key.handle = 'FFFFB905D60C2268'"}, nil}, @@ -653,9 +683,9 @@ var fields = map[Field]FieldInfo{ PeFileVersion: {PeFileVersion, "file version supplied at compile-time", kparams.UnicodeString, []string{"pe.file.version = '10.0.18362.693 (WinBuild.160101.0800)'"}, nil}, PeProduct: {PeProduct, "internal product name of the file provided at compile-time", kparams.UnicodeString, []string{"pe.product = 'Microsoft® Windows® Operating System'"}, nil}, PeProductVersion: {PeProductVersion, "internal product version of the file provided at compile-time", kparams.UnicodeString, []string{"pe.product.version = '10.0.18362.693'"}, nil}, - PeIsDLL: {PeIsDLL, "indicates if the loaded image or created file is a DLL", kparams.Bool, []string{"pe.is_dll'"}, nil}, - PeIsDriver: {PeIsDriver, "indicates if the loaded image or created file is a driver", kparams.Bool, []string{"pe.is_driver'"}, nil}, - PeIsExecutable: {PeIsExecutable, "indicates if the loaded image or created file is an executable", kparams.Bool, []string{"pe.is_exec'"}, nil}, + PeIsDLL: {PeIsDLL, "indicates if the loaded image or created file is a DLL", kparams.Bool, []string{"pe.is_dll'"}, &Deprecation{Since: "2.0.0", Fields: []Field{FileIsDLL, ImageIsDLL}}}, + PeIsDriver: {PeIsDriver, "indicates if the loaded image or created file is a driver", kparams.Bool, []string{"pe.is_driver'"}, &Deprecation{Since: "2.0.0", Fields: []Field{FileIsDriver, ImageIsDriver}}}, + PeIsExecutable: {PeIsExecutable, "indicates if the loaded image or created file is an executable", kparams.Bool, []string{"pe.is_exec'"}, &Deprecation{Since: "2.0.0", Fields: []Field{FileIsExecutable, ImageIsExecutable}}}, PeImphash: {PeImphash, "import hash", kparams.AnsiString, []string{"pe.impash = '5d3861c5c547f8a34e471ba273a732b2'"}, nil}, PeIsDotnet: {PeIsDotnet, "indicates if PE contains CLR data", kparams.Bool, []string{"pe.is_dotnet"}, nil}, PeAnomalies: {PeAnomalies, "contains PE anomalies detected during parsing", kparams.Slice, []string{"pe.anomalies in ('number of sections is 0')"}, nil}, diff --git a/pkg/filter/rules.go b/pkg/filter/rules.go index 0b1507aff..91a8f984a 100644 --- a/pkg/filter/rules.go +++ b/pkg/filter/rules.go @@ -513,9 +513,9 @@ func (r *Rules) Compile() error { if deprecated { log.Warnf("%s rule uses the [%s] field which "+ "was deprecated starting from version %s. "+ - "Please consider migrating to [%s] field "+ + "Please consider migrating to %s field(s) "+ "because [%s] will be removed in future versions.", - rule.Name, field, d.Since, d.Field, field) + rule.Name, field, d.Since, d.Fields, field) } } filtersCount.Add(1) diff --git a/pkg/fs/ntfs/ntfs.go b/pkg/fs/ntfs/ntfs.go index 7cfdcecbb..4c5ba7872 100644 --- a/pkg/fs/ntfs/ntfs.go +++ b/pkg/fs/ntfs/ntfs.go @@ -26,6 +26,9 @@ import ( libntfs "www.velocidex.com/golang/go-ntfs/parser" ) +// MaxFullSizeRead specifies the maximum size in bytes for the file data +const MaxFullSizeRead int64 = 1024 * 1024 * 1024 * 50 + // FS provides raw access to Master File Table (MFT) // and file data blobs mounted on the NTFS. type FS struct { @@ -62,6 +65,50 @@ func (fs *FS) Read(path string, offset, size int64) ([]byte, int, error) { return data, n, err } +// ReadFull reads the entire content of the file into the byte buffer. +func (fs *FS) ReadFull(path string) ([]byte, int, error) { + defer func() { + if err := recover(); err != nil { + log.Warnf("unable to read %s from raw device: %v", path, err) + } + }() + ntfs, err := fs.getNTFSContext(path) + if err != nil { + return nil, 0, err + } + defer ntfs.Close() + + // get root MFT entry + root, err := ntfs.GetMFT(5) + if err != nil { + return nil, 0, err + } + // get file size + filename := strings.ReplaceAll(path[3:], "\\", "/") + f, err := root.Open(ntfs, filename) + if err != nil { + return nil, 0, err + } + var size int64 + if stats := libntfs.Stat(ntfs, f); len(stats) > 0 { + size = stats[0].Size + } + if size == 0 { + return nil, 0, nil + } + if size > MaxFullSizeRead { + return nil, 0, nil + } + // read file + data := make([]byte, size) + reader, err := libntfs.GetDataForPath(ntfs, filename) + if err != nil { + return nil, 0, err + } + n, err := reader.ReadAt(data, 0) + return data, n, err +} + // Close disposes all underlying resources. func (fs *FS) Close() error { if fs.dev != nil { diff --git a/pkg/fs/ntfs/ntfs_test.go b/pkg/fs/ntfs/ntfs_test.go index 8dfc67a7d..0a0f1c37c 100644 --- a/pkg/fs/ntfs/ntfs_test.go +++ b/pkg/fs/ntfs/ntfs_test.go @@ -57,3 +57,20 @@ func TestRead(t *testing.T) { require.True(t, n != 0) assert.Equal(t, []byte("ntfs read with a bit more of read"), b[:33]) } + +func TestReadFull(t *testing.T) { + file := filepath.Join(os.TempDir(), "ntfs-read-full.txt") + err := os.WriteFile(file, []byte("ntfs read from mars to sirius where the whales fly into oblivion"), os.ModePerm) + require.NoError(t, err) + defer os.Remove(file) + + fs := NewFS() + defer fs.Close() + + b, n, err := fs.ReadFull(file) + assert.NotNil(t, fs) + require.NoError(t, err) + require.True(t, n != 0) + + assert.Equal(t, []byte("ntfs read from mars to sirius where the whales fly into oblivion"), b) +} diff --git a/pkg/kevent/kevent_windows.go b/pkg/kevent/kevent_windows.go index 4c1d844db..2c4cebe8a 100644 --- a/pkg/kevent/kevent_windows.go +++ b/pkg/kevent/kevent_windows.go @@ -227,9 +227,9 @@ func (e Kevent) CurrentPid() bool { return e.PID == currentPid } // IsState indicates if this event is only used for state management. func (e Kevent) IsState() bool { return e.Type.OnlyState() } -// IsCreatingFile determines if the event is creating a new file. -func (e Kevent) IsCreatingFile() bool { - return e.IsCreateFile() && e.Kparams.MustGetUint32(kparams.FileOperation) != windows.FILE_OPEN +// IsCreateDisposition determines if the file disposition leads to creating a new file. +func (e Kevent) IsCreateDisposition() bool { + return e.IsCreateFile() && e.Kparams.MustGetUint32(kparams.FileOperation) == windows.FILE_CREATE } // RundownKey calculates the rundown event hash. The hash is diff --git a/pkg/kevent/kparam.go b/pkg/kevent/kparam.go index e88fc8dbe..275b33806 100644 --- a/pkg/kevent/kparam.go +++ b/pkg/kevent/kparam.go @@ -137,12 +137,28 @@ func NewKparamFromKcap(name string, typ kparams.Type, value kparams.Value, ktype enum = network.ProtoNames case kparams.RegValueType: enum = key.RegistryValueTypes + case kparams.MemAllocType: + flags = MemAllocationFlags + case kparams.FileViewSectionType: + enum = ViewSectionTypes + case kparams.DNSOpts: + flags = DNSOptsFlags + case kparams.DNSRR: + enum = DNSRecordTypes + case kparams.DNSRcode: + enum = DNSResponseCodes case kparams.DesiredAccess: if ktype == ktypes.OpenProcess { flags = PsAccessRightFlags } else { flags = ThreadAccessRightFlags } + case kparams.MemProtect: + if ktype == ktypes.VirtualAlloc || ktype == ktypes.VirtualFree { + flags = MemProtectionFlags + } else { + flags = ViewProtectionFlags + } } return &Kparam{Name: name, Type: typ, Value: value, Enum: enum, Flags: flags} } diff --git a/pkg/kevent/kparam_windows.go b/pkg/kevent/kparam_windows.go index 11d932f31..8992ec4b9 100644 --- a/pkg/kevent/kparam_windows.go +++ b/pkg/kevent/kparam_windows.go @@ -201,8 +201,10 @@ func (kpars Kparams) MustGetSID() *windows.SID { return sid } -// produceParams parses the event binary layout to extract the parameters. Each event is annotated with the -// schema version number which helps us determine when the event schema changes in order to parse new fields. +// produceParams parses the event binary layout to extract +// the parameters. Each event is annotated with the schema +// version number which helps us determine when the event +// schema changes in order to parse new fields. func (e *Kevent) produceParams(evt *etw.EventRecord) { switch e.Type { case ktypes.ProcessRundown, @@ -312,6 +314,9 @@ func (e *Kevent) produceParams(evt *etw.EventRecord) { e.AppendParam(kparams.ThreadID, kparams.TID, threadID) e.AppendParam(kparams.DesiredAccess, kparams.Flags, desiredAccess, WithFlags(ThreadAccessRightFlags)) e.AppendParam(kparams.NTStatus, kparams.Status, status) + case ktypes.SetThreadContext: + status := evt.ReadUint32(0) + e.AppendParam(kparams.NTStatus, kparams.Status, status) case ktypes.CreateHandle, ktypes.CloseHandle: object := evt.ReadUint64(0) handleID := evt.ReadUint32(8) diff --git a/pkg/kevent/ktypes/ktypes_windows.go b/pkg/kevent/ktypes/ktypes_windows.go index b708f9486..1da9731fa 100644 --- a/pkg/kevent/ktypes/ktypes_windows.go +++ b/pkg/kevent/ktypes/ktypes_windows.go @@ -45,6 +45,8 @@ var ( ThreadRundown = pack(windows.GUID{Data1: 0x3d6fa8d1, Data2: 0xfe05, Data3: 0x11d0, Data4: [8]byte{0x9d, 0xda, 0x0, 0xc0, 0x4f, 0xd7, 0xba, 0x7c}}, 3) // OpenThread identifies the kernel events that are triggered when the process acquires a thread handle OpenThread = pack(windows.GUID{Data1: 0xe02a841c, Data2: 0x75a3, Data3: 0x4fa7, Data4: [8]byte{0xaf, 0xc8, 0xae, 0x09, 0xcf, 0x9b, 0x7f, 0x23}}, 6) + // SetThreadContext identifies the kernel event that is fired when the thread context is changed + SetThreadContext = pack(windows.GUID{Data1: 0xe02a841c, Data2: 0x75a3, Data3: 0x4fa7, Data4: [8]byte{0xaf, 0xc8, 0xae, 0x09, 0xcf, 0x9b, 0x7f, 0x23}}, 4) // MapViewFile represents events that map a view of a file mapping into the address space of a calling process MapViewFile = pack(windows.GUID{Data1: 0x90cbdc39, Data2: 0x4a3e, Data3: 0x11d1, Data4: [8]byte{0x84, 0xf4, 0x0, 0x0, 0xf8, 0x04, 0x64, 0xe3}}, 37) @@ -194,6 +196,8 @@ func (k Ktype) String() string { return "ThreadRundown" case OpenThread: return "OpenThread" + case SetThreadContext: + return "SetThreadContext" case CreateFile: return "CreateFile" case CloseFile: @@ -286,7 +290,7 @@ func (k Ktype) Category() Category { switch k { case CreateProcess, TerminateProcess, OpenProcess, ProcessRundown: return Process - case CreateThread, TerminateThread, OpenThread, ThreadRundown: + case CreateThread, TerminateThread, OpenThread, SetThreadContext, ThreadRundown: return Thread case LoadImage, UnloadImage, ImageRundown: return Image @@ -339,6 +343,8 @@ func (k Ktype) Description() string { return "Terminates a thread within the process" case OpenThread: return "Opens the thread handle" + case SetThreadContext: + return "Sets the thread context" case ReadFile: return "Reads data from the file or I/O device" case WriteFile: diff --git a/pkg/kevent/ktypes/metainfo_windows.go b/pkg/kevent/ktypes/metainfo_windows.go index a67bf40c0..a181ce69f 100644 --- a/pkg/kevent/ktypes/metainfo_windows.go +++ b/pkg/kevent/ktypes/metainfo_windows.go @@ -40,6 +40,7 @@ var kevents = map[Ktype]KeventInfo{ CreateThread: {"CreateThread", Thread, "Creates a thread to execute within the virtual address space of the calling process"}, TerminateThread: {"TerminateThread", Thread, "Terminates a thread within the process"}, OpenThread: {"OpenThread", Thread, "Opens the thread handle"}, + SetThreadContext: {"SetThreadContext", Thread, "Sets the thread context"}, ReadFile: {"ReadFile", File, "Reads data from the file or I/O device"}, WriteFile: {"WriteFile", File, "Writes data to the file or I/O device"}, CreateFile: {"CreateFile", File, "Creates or opens a file or I/O device"}, @@ -94,6 +95,7 @@ var ktypes = map[string]Ktype{ "CreateThread": CreateThread, "TerminateThread": TerminateThread, "OpenThread": OpenThread, + "SetThreadContext": SetThreadContext, "LoadImage": LoadImage, "UnloadImage": UnloadImage, "CreateFile": CreateFile, diff --git a/pkg/kstream/consumer_windows_test.go b/pkg/kstream/consumer_windows_test.go index e5f659582..9d748ee9b 100644 --- a/pkg/kstream/consumer_windows_test.go +++ b/pkg/kstream/consumer_windows_test.go @@ -453,6 +453,16 @@ func TestConsumerEvents(t *testing.T) { }, false, }, + { + "set thread context", + func() error { + return nil + }, + func(e *kevent.Kevent) bool { + return e.CurrentPid() && e.Type == ktypes.SetThreadContext && e.GetParamAsString(kparams.NTStatus) == "Success" + }, + false, + }, } psnap := new(ps.SnapshotterMock) @@ -480,6 +490,7 @@ func TestConsumerEvents(t *testing.T) { EnableMemKevents: true, EnableHandleKevents: true, EnableDNSEvents: true, + EnableAuditAPIEvents: true, } kctrl := NewController(kstreamConfig) diff --git a/pkg/kstream/processors/fs_windows.go b/pkg/kstream/processors/fs_windows.go index dc3bf5b13..3392e7445 100644 --- a/pkg/kstream/processors/fs_windows.go +++ b/pkg/kstream/processors/fs_windows.go @@ -22,17 +22,14 @@ import ( "expvar" "github.com/rabbitstack/fibratus/pkg/config" "github.com/rabbitstack/fibratus/pkg/fs" - libntfs "github.com/rabbitstack/fibratus/pkg/fs/ntfs" "github.com/rabbitstack/fibratus/pkg/handle" htypes "github.com/rabbitstack/fibratus/pkg/handle/types" "github.com/rabbitstack/fibratus/pkg/kevent" "github.com/rabbitstack/fibratus/pkg/kevent/kparams" "github.com/rabbitstack/fibratus/pkg/kevent/ktypes" - "github.com/rabbitstack/fibratus/pkg/pe" "github.com/rabbitstack/fibratus/pkg/sys" "github.com/rabbitstack/fibratus/pkg/util/va" "golang.org/x/sys/windows" - "os" ) var ( @@ -185,23 +182,13 @@ func (f *fsProcessor) processEvent(e *kevent.Kevent) (*kevent.Kevent, error) { ev.AppendEnum(kparams.FileType, uint32(fileinfo.Type), fs.FileTypes) } ev.AppendEnum(kparams.FileOperation, uint32(dispo), fs.FileCreateDispositions) + // parse PE data for created files and append parameters - if ev.IsCreatingFile() && ev.IsSuccess() { - filename := ev.GetParamAsString(kparams.FileName) - // read file data blob from raw device - ntfs := libntfs.NewFS() - defer ntfs.Close() - data, _, err := ntfs.Read(filename, 0, int64(os.Getpagesize())) - if err != nil { - return ev, nil - } - pefile, err := pe.ParseBytes(data, pe.WithSymbols()) + if ev.IsCreateDisposition() && ev.IsSuccess() { + err := parseImageFileCharacteristics(ev) if err != nil { return ev, nil } - ev.AppendParam(kparams.FileIsDLL, kparams.Bool, pefile.IsDLL) - ev.AppendParam(kparams.FileIsDriver, kparams.Bool, pefile.IsDriver) - ev.AppendParam(kparams.FileIsExecutable, kparams.Bool, pefile.IsExecutable) } return ev, nil case ktypes.ReleaseFile: diff --git a/pkg/kstream/processors/image_windows.go b/pkg/kstream/processors/image_windows.go index 6b29564a5..107925e53 100644 --- a/pkg/kstream/processors/image_windows.go +++ b/pkg/kstream/processors/image_windows.go @@ -20,14 +20,11 @@ package processors import ( "expvar" - libntfs "github.com/rabbitstack/fibratus/pkg/fs/ntfs" "github.com/rabbitstack/fibratus/pkg/kevent" "github.com/rabbitstack/fibratus/pkg/kevent/kparams" - "github.com/rabbitstack/fibratus/pkg/pe" "github.com/rabbitstack/fibratus/pkg/ps" "github.com/rabbitstack/fibratus/pkg/util/hashers" "github.com/rabbitstack/fibratus/pkg/util/signature" - "os" ) // signatureErrors counts signature check/verification errors @@ -58,20 +55,10 @@ func (m *imageProcessor) ProcessEvent(e *kevent.Kevent) (*kevent.Kevent, bool, e signatureErrors.Add(1) } // parse PE image data - filename := e.GetParamAsString(kparams.FileName) - ntfs := libntfs.NewFS() - defer ntfs.Close() - data, _, err := ntfs.Read(filename, 0, int64(os.Getpagesize())) + err = parseImageFileCharacteristics(e) if err != nil { return e, false, m.psnap.AddModule(e) } - pefile, err := pe.ParseBytes(data, pe.WithSymbols()) - if err != nil { - return e, false, m.psnap.AddModule(e) - } - e.AppendParam(kparams.FileIsDLL, kparams.Bool, pefile.IsDLL) - e.AppendParam(kparams.FileIsDriver, kparams.Bool, pefile.IsDriver) - e.AppendParam(kparams.FileIsExecutable, kparams.Bool, pefile.IsExecutable) } if e.IsUnloadImage() { pid := e.Kparams.MustGetPid() diff --git a/pkg/kstream/processors/processor.go b/pkg/kstream/processors/processor.go index 87398695c..45462998b 100644 --- a/pkg/kstream/processors/processor.go +++ b/pkg/kstream/processors/processor.go @@ -18,7 +18,13 @@ package processors -import "github.com/rabbitstack/fibratus/pkg/kevent" +import ( + libntfs "github.com/rabbitstack/fibratus/pkg/fs/ntfs" + "github.com/rabbitstack/fibratus/pkg/kevent" + "github.com/rabbitstack/fibratus/pkg/kevent/kparams" + "github.com/rabbitstack/fibratus/pkg/pe" + "os" +) // ProcessorType is an alias for the event processor type type ProcessorType uint8 @@ -76,3 +82,45 @@ func (typ ProcessorType) String() string { return "unknown" } } + +// parseImageFileCharacteristics parses the PE structure for the file path +// residing in the given event parameters. The preferred method for getting +// the file data is accessing the raw device and consuming the blob data. +// If this operation fails, we fallback to using the regular file access. +// The given event is decorated with various parameters extracted from PE +// data. Most notably, parameters that indicate whether the file is a DLL, +// executable image, or a Windows driver. +func parseImageFileCharacteristics(e *kevent.Kevent) error { + filename := e.GetParamAsString(kparams.FileName) + // read file data blob from raw device + ntfs := libntfs.NewFS() + var ( + data []byte + err error + ) + data, _, err = ntfs.Read(filename, 0, int64(os.Getpagesize())) + defer ntfs.Close() + if err != nil { + // if we can't read from the raw device, try regular file access + f, err := os.Open(filename) + if err != nil { + return err + } + defer f.Close() + data = make([]byte, os.Getpagesize()) + if _, err = f.Read(data); err != nil { + return err + } + } + // parse image file + pefile, err := pe.ParseBytes(data, pe.WithSections(), pe.WithSymbols()) + if err != nil { + return err + } + // append parameters + e.AppendParam(kparams.FileIsDLL, kparams.Bool, pefile.IsDLL) + e.AppendParam(kparams.FileIsDriver, kparams.Bool, pefile.IsDriver) + e.AppendParam(kparams.FileIsExecutable, kparams.Bool, pefile.IsExecutable) + + return nil +} diff --git a/pkg/pe/_fixtures/054299e09cea38df2b84e6b29348b418.bin b/pkg/pe/_fixtures/054299e09cea38df2b84e6b29348b418.bin new file mode 100644 index 000000000..5baf48bee Binary files /dev/null and b/pkg/pe/_fixtures/054299e09cea38df2b84e6b29348b418.bin differ diff --git a/pkg/pe/parser.go b/pkg/pe/parser.go index 93e8ab865..8adea3b96 100644 --- a/pkg/pe/parser.go +++ b/pkg/pe/parser.go @@ -295,11 +295,6 @@ func parse(path string, data []byte, options ...Option) (*PE, error) { p.Sections = append(p.Sections, sec) } - // retrieve basic PE data - p.IsDLL = pe.IsDLL() - p.IsDriver = pe.IsDriver() - p.IsExecutable = pe.IsEXE() - // initialize raw section headers for _, sec := range pe.Sections { p.sectionHeaders = append(p.sectionHeaders, sec.Header) @@ -353,8 +348,56 @@ func parse(path string, data []byte, options ...Option) (*PE, error) { } } + p.IsDLL = pe.IsDLL() + p.IsDriver = p.isDriver() + p.IsExecutable = pe.IsEXE() p.IsDotnet = pe.HasCLR p.Anomalies = pe.Anomalies return p, nil } + +// isDriver determines if the PE is a Windows driver. This method is inherited +// from the saferwall parser with the imports defensive check removed as some +// driver samples may not contain an import directory, but section names may +// reveal the PE is a kernel driver. +func (pe *PE) isDriver() bool { + // DIRECTORY_ENTRY_IMPORT may exist, although it may be empty. + // If it imports from "ntoskrnl.exe" or other kernel components it should + // be a driver. + systemDLLs := []string{"ntoskrnl.exe", "hal.dll", "ndis.sys", + "bootvid.dll", "kdcom.dll"} + for _, imp := range pe.Imports { + for _, dll := range systemDLLs { + if strings.ToLower(imp) == dll { + return true + } + } + } + + // If still we couldn't tell, check common driver section with combination + // of IMAGE_SUBSYSTEM_NATIVE or IMAGE_SUBSYSTEM_NATIVE_WINDOWS. + subsystem := peparser.ImageOptionalHeaderSubsystemType(0) + oh32 := peparser.ImageOptionalHeader32{} + oh64 := peparser.ImageOptionalHeader64{} + switch pe.Is64 { + case true: + oh64 = pe.ntHeader.OptionalHeader.(peparser.ImageOptionalHeader64) + subsystem = oh64.Subsystem + case false: + oh32 = pe.ntHeader.OptionalHeader.(peparser.ImageOptionalHeader32) + subsystem = oh32.Subsystem + } + commonDriverSectionNames := []string{"page", "paged", "nonpage", "init"} + for _, section := range pe.Sections { + s := strings.ToLower(section.Name) + for _, driverSection := range commonDriverSectionNames { + if s == driverSection && + (subsystem&peparser.ImageSubsystemNativeWindows != 0 || + subsystem&peparser.ImageSubsystemNative != 0) { + return true + } + } + } + return false +} diff --git a/pkg/pe/parser_test.go b/pkg/pe/parser_test.go index ef566aa91..9443e1384 100644 --- a/pkg/pe/parser_test.go +++ b/pkg/pe/parser_test.go @@ -95,6 +95,12 @@ func TestIsDotnet(t *testing.T) { require.True(t, pe.IsDotnet) } +func TestIsDriver(t *testing.T) { + pe, err := ParseFile("./_fixtures/054299e09cea38df2b84e6b29348b418.bin", WithSections(), WithSymbols()) + require.NoError(t, err) + require.True(t, pe.IsDriver) +} + func TestParseMem(t *testing.T) { var tests = []struct { executable string diff --git a/pkg/util/cmdline/cmdline.go b/pkg/util/cmdline/cmdline.go index 5a07fb237..27763b57f 100644 --- a/pkg/util/cmdline/cmdline.go +++ b/pkg/util/cmdline/cmdline.go @@ -67,6 +67,12 @@ func New(cmdline string) *Cmdline { // a single argument in the process command line. func Split(cmdline string) []string { return splitRegexp.FindAllString(cmdline, -1) } +// ExpandSystemRoot replaces all occurrences of the system root environment variable +// with its respective value. +func ExpandSystemRoot(exe string) string { + return systemRootRegexp.ReplaceAllString(exe, os.Getenv("SystemRoot")) +} + // CleanExe cleans the executable path and rejoins // the rest of the command line arguments. func (c *Cmdline) CleanExe() *Cmdline { diff --git a/pkg/util/fasttemplate/unsafe.go b/pkg/util/fasttemplate/unsafe.go index 0c0b1328d..afa042e92 100644 --- a/pkg/util/fasttemplate/unsafe.go +++ b/pkg/util/fasttemplate/unsafe.go @@ -14,7 +14,6 @@ package fasttemplate import ( - "reflect" "unsafe" ) @@ -22,13 +21,6 @@ func bytesToString(b []byte) string { return *(*string)(unsafe.Pointer(&b)) } -//nolint:govet func stringToBytes(s string) []byte { - sh := (*reflect.StringHeader)(unsafe.Pointer(&s)) - bh := reflect.SliceHeader{ - Data: sh.Data, - Len: sh.Len, - Cap: sh.Len, - } - return *(*[]byte)(unsafe.Pointer(&bh)) + return unsafe.Slice(unsafe.StringData(s), len(s)) } diff --git a/pkg/util/loldrivers/_fixtures/d.sys b/pkg/util/loldrivers/_fixtures/d.sys new file mode 100644 index 000000000..5fa1a5149 Binary files /dev/null and b/pkg/util/loldrivers/_fixtures/d.sys differ diff --git a/pkg/util/loldrivers/_fixtures/iomem.sys b/pkg/util/loldrivers/_fixtures/iomem.sys new file mode 100644 index 000000000..8328f1795 Binary files /dev/null and b/pkg/util/loldrivers/_fixtures/iomem.sys differ diff --git a/pkg/util/loldrivers/client.go b/pkg/util/loldrivers/client.go new file mode 100644 index 000000000..20893afa0 --- /dev/null +++ b/pkg/util/loldrivers/client.go @@ -0,0 +1,283 @@ +/* + * Copyright 2021-2022 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package loldrivers + +import ( + "bytes" + "context" + "crypto" + "crypto/sha1" + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + libntfs "github.com/rabbitstack/fibratus/pkg/fs/ntfs" + "github.com/rabbitstack/fibratus/pkg/util/cmdline" + log "github.com/sirupsen/logrus" + "hash" + "io" + "net/http" + "path/filepath" + "strings" + "sync" + "time" +) + +// apiURL represents the default loldrivers API endpoint +const apiURL = "https://www.loldrivers.io/api/drivers.json" + +// Client is responsible for downloading loldrivers dataset. +// Driver dataset is indexed by SHA hash to provide more +// efficient lookups. +type Client struct { + drivers map[string]Driver + mu sync.Mutex + tick *time.Ticker + options opts +} + +type opts struct { + apiURL string + refreshInterval time.Duration + asyncDownload bool +} + +// Option represents the option for the loldrivers client. +type Option func(o *opts) + +// WithURL sets the API endpoint. +func WithURL(url string) Option { + return func(o *opts) { + o.apiURL = url + } +} + +// WithRefresh sets the refresh interval for loldrivers dataset. +func WithRefresh(interval time.Duration) Option { + return func(o *opts) { + o.refreshInterval = interval + } +} + +// WithAsyncDownload indicates if the initial request to loldrivers API +// is performed in a separate goroutine. +func WithAsyncDownload() Option { + return func(o *opts) { + o.asyncDownload = true + } +} + +var c *Client + +// InitClient initializes the loldrivers by client by fetching the initial dataset. +func InitClient(options ...Option) { + if c == nil { + c = initClient(options...) + } +} + +// GetClient constructs a singleton instance of the loldrivers client. +// If the client wasn't initialized, this function creates a new client +// with default sane settings. +func GetClient() *Client { + if c == nil { + c = initClient() + } + return c +} + +func initClient(options ...Option) *Client { + if c == nil { + var opts opts + for _, opt := range options { + opt(&opts) + } + + if opts.apiURL == "" { + opts.apiURL = apiURL + } + if opts.refreshInterval == 0 { + opts.refreshInterval = time.Hour + } + + c = &Client{ + options: opts, + drivers: make(map[string]Driver), + tick: time.NewTicker(opts.refreshInterval), + } + download := func() { + err := c.download() + if err != nil { + log.Warnf("unable to download loldrivers.io dataset: %v", err) + } + } + // if async download is enabled we'll + // fetch the initial dataset in a new + // goroutine + if opts.asyncDownload { + go download() + } else { + download() + } + // start refresh goroutine + go c.refresh() + } + return c +} + +// MatchHash receives the full path of the driver file and tries to read +// the blob data from the raw device. If it succeeds, then one of the SHA1/SHA256 +// hashes are computed for the read data and the calculated hash is evaluated +// against loldrivers dataset. If the driver can't be read from the file system or +// hash calculation fail, then the driver sample name is asserted against the +// dataset to determine if the driver is either malicious or vulnerable. +func (c *Client) MatchHash(path string) (bool, Driver) { + ntfs := libntfs.NewFS() + defer ntfs.Close() + data, _, err := ntfs.ReadFull(cmdline.ExpandSystemRoot(path)) + if err != nil { + return c.matchPath(path) + } + + r := bytes.NewReader(data) + + c.mu.Lock() + defer c.mu.Unlock() + ok, driver := c.matchHash(crypto.SHA256, r, path) + if !ok { + // the driver sample doesn't have SHA256 hash, try with SHA1 hash + return c.matchHash(crypto.SHA1, r, path) + } + return ok, driver +} + +func (c *Client) matchHash(h crypto.Hash, r io.Reader, path string) (bool, Driver) { + checksum, err := c.calculateHash(h, r) + if err != nil { + return c.matchPath(path) + } + driver, ok := c.drivers[checksum] + return ok, driver +} + +func (c *Client) matchPath(path string) (bool, Driver) { + c.mu.Lock() + defer c.mu.Unlock() + for _, d := range c.drivers { + if d.Filename == "" { + continue + } + if strings.EqualFold(filepath.Base(path), d.Filename) { + return true, d + } + } + return false, Driver{} +} + +func (c *Client) calculateHash(h crypto.Hash, r io.Reader) (string, error) { + var w hash.Hash + switch h { + case crypto.SHA1: + w = sha1.New() + case crypto.SHA256: + w = sha256.New() + default: + return "", fmt.Errorf("%v: invalid hash", h) + } + if _, err := io.Copy(w, r); err != nil { + return "", err + } + return strings.ToLower(hex.EncodeToString(w.Sum(nil))), nil +} + +// Drivers returns a list of all drivers in the dataset. +func (c *Client) Drivers() []Driver { + c.mu.Lock() + defer c.mu.Unlock() + drivers := make([]Driver, 0, len(c.drivers)) + for _, d := range c.drivers { + drivers = append(drivers, d) + } + return drivers +} + +// Clear clears the internal dataset. +func (c *Client) Clear() { + c.mu.Lock() + defer c.mu.Unlock() + c.drivers = make(map[string]Driver) +} + +func (c *Client) download() error { + client := http.Client{ + Timeout: time.Second * 10, + } + ctx, cancel := context.WithTimeout(context.Background(), time.Second*10) + defer cancel() + req, err := http.NewRequestWithContext(ctx, "GET", c.options.apiURL, nil) + if err != nil { + return err + } + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + body, err := io.ReadAll(resp.Body) + if err != nil { + return err + } + var drivers []RawDriver + if err := json.Unmarshal(body, &drivers); err != nil { + return err + } + + c.mu.Lock() + defer c.mu.Unlock() + c.drivers = make(map[string]Driver) + + for _, driver := range drivers { + for _, sample := range driver.KnownVulnerableSamples { + sha := sample.SHA256 + if sha == "" { + sha = sample.SHA1 + } + c.drivers[strings.ToLower(sha)] = Driver{ + Filename: sample.Filename, + SHA1: strings.ToLower(sample.SHA1), + SHA256: strings.ToLower(sample.SHA256), + IsMalicious: driver.isMalicious(), + IsVulnerable: !driver.isMalicious(), + } + } + } + + return nil +} + +func (c *Client) refresh() { + for { + <-c.tick.C + log.Debug("refreshing loldrivers dataset...") + err := c.download() + if err != nil { + log.Warnf("unable to refresh loldrivers dataset: %v", err) + } + } +} diff --git a/pkg/util/loldrivers/client_test.go b/pkg/util/loldrivers/client_test.go new file mode 100644 index 000000000..5992ef527 --- /dev/null +++ b/pkg/util/loldrivers/client_test.go @@ -0,0 +1,91 @@ +/* + * Copyright 2021-2022 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package loldrivers + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "path/filepath" + "testing" + "time" +) + +func TestDownload(t *testing.T) { + assert.Nil(t, c) + InitClient() + require.True(t, len(GetClient().Drivers()) > 0) + assert.NotNil(t, c) + + var expectedSHA256 = "0440ef40c46fdd2b5d86e7feef8577a8591de862cfd7928cdbcc8f47b8fa3ffc" + var foundSHA256 string + + for _, driver := range GetClient().Drivers() { + if driver.Filename == "prokiller64.sys" { + foundSHA256 = driver.SHA256 + break + } + } + + assert.Equal(t, expectedSHA256, foundSHA256) +} + +func TestMatchHash(t *testing.T) { + absPath := func(path string) string { + abs, err := filepath.Abs(path) + if err != nil { + t.Fatal(err) + } + return abs + } + + var tests = []struct { + driverPath string + wantsName string + ok bool + }{ + { + absPath("_fixtures/d.sys"), + "d.sys", + true, + }, + { + absPath("_fixtures/iomem.sys"), + "iomem64.sys", + true, + }, + } + + for _, tt := range tests { + t.Run(tt.driverPath, func(t *testing.T) { + ok, d := GetClient().MatchHash(tt.driverPath) + assert.True(t, ok == tt.ok) + assert.Equal(t, tt.wantsName, d.Filename) + }) + } +} + +func TestRefresh(t *testing.T) { + c = nil + InitClient(WithRefresh(time.Second * 2)) + require.True(t, len(GetClient().Drivers()) > 0) + GetClient().Clear() + require.True(t, len(GetClient().Drivers()) == 0) + time.Sleep(time.Second * 3) + require.True(t, len(GetClient().Drivers()) > 0) +} diff --git a/pkg/util/loldrivers/types.go b/pkg/util/loldrivers/types.go new file mode 100644 index 000000000..c3ef587db --- /dev/null +++ b/pkg/util/loldrivers/types.go @@ -0,0 +1,47 @@ +/* + * Copyright 2021-2022 by Nedim Sabic Sabic + * https://www.fibratus.io + * All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package loldrivers + +// RawDriver contains vulnerable/malicious driver data fetched from loldrivers API endpoint. +type RawDriver struct { + ID string `json:"Id"` + Category string `json:"Category"` + Created string `json:"Created"` + MitreID string `json:"MitreID"` + KnownVulnerableSamples []struct { + Filename string `json:"Filename"` + MD5 string `json:"MD5,omitempty"` + SHA1 string `json:"SHA1,omitempty"` + SHA256 string `json:"SHA256,omitempty"` + } `json:"KnownVulnerableSamples,omitempty"` +} + +func (d RawDriver) isMalicious() bool { + return d.Category == "malicious" +} + +// Driver intermediate structure for storing driver data +// relevant for the hash matching. +type Driver struct { + Filename string + SHA1 string + SHA256 string + IsMalicious bool + IsVulnerable bool +} diff --git a/pkg/util/typesize/typesize.go b/pkg/util/typesize/typesize.go index 429715b05..265eb74f5 100644 --- a/pkg/util/typesize/typesize.go +++ b/pkg/util/typesize/typesize.go @@ -20,7 +20,6 @@ package typesize import "unsafe" -//nolint:unused var ptr uintptr // Pointer returns the pointer size on this machine. diff --git a/rules/macros/macros.yml b/rules/macros/macros.yml index 5c1b04ac9..d79bfc83e 100644 --- a/rules/macros/macros.yml +++ b/rules/macros/macros.yml @@ -55,6 +55,9 @@ - macro: accept_socket expr: kevt.name = 'Accept' +- macro: set_thread_context + expr: kevt.name = 'SetThreadContext' and kevt.arg[status] = 'Success' + - macro: inbound_network expr: (recv_socket or accept_socket) and ((net.sip != 0.0.0.0 or net.dip != 0.0.0.0) and (net.sip not in ('0:0:0:0:0:0:0:1', '::1') or net.dip not in ('0:0:0:0:0:0:0:1', '::1')) and not (cidr_contains(net.sip, '127.0.0.0/8') or cidr_contains(net.dip, '127.0.0.0/8'))) description: | @@ -101,7 +104,7 @@ - macro: load_driver expr: > - (load_module and (image.name iendswith '.sys' or pe.is_driver) + (load_module and (image.name iendswith '.sys' or image.is_driver) or (create_handle and handle.type = 'Driver') description: | @@ -111,7 +114,7 @@ watching for driver objects being created. - macro: unload_driver - expr: unload_image and (image.name iendswith '.sys' or pe.is_driver) + expr: unload_image and (image.name iendswith '.sys' or image.is_driver) - macro: load_unsigned_module expr: > @@ -123,7 +126,7 @@ - macro: load_executable expr: > - load_module and (image.name iendswith '.exe' or pe.is_exec) + load_module and (image.name iendswith '.exe' or image.is_exec) - macro: load_untrusted_executable expr: > diff --git a/rules/persistence_boot_or_logon_autostart_execution.yml b/rules/persistence_boot_or_logon_autostart_execution.yml index 8e19fd78c..102e6230e 100644 --- a/rules/persistence_boot_or_logon_autostart_execution.yml +++ b/rules/persistence_boot_or_logon_autostart_execution.yml @@ -27,7 +27,7 @@ ( file.extension in ('.vbs', '.js', '.jar', '.exe', '.dll', '.com', '.ps1', '.hta', '.cmd', '.vbe') or - (pe.is_exec or pe.is_dll) + (file.is_exec or file.is_dll) ) and file.name imatches startup_locations @@ -117,7 +117,7 @@ processes. condition: > modify_registry - and + and ( (ps.name in script_interpreters or ps.name in ('reg.exe', 'rundll32.exe', 'regsvr32.exe')) or @@ -159,8 +159,8 @@ (modify_registry or create_file) and ( - ps.name in script_interpreters - or + ps.name in script_interpreters + or ps.parent.name in script_interpreters or not pe.is_trusted