Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@

> /area deps

> /area evasion

> /area other


Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/master.yml
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,10 @@ jobs:
run: |
cd yara
make install
- name: Setup test fixtures
run: |
choco install -y go-task
task --taskfile internal/etw/_fixtures/Taskfile.yml all
- name: Test
shell: bash
run: |
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,10 @@ jobs:
run: |
cd yara
make install
- name: Setup test fixtures
run: |
choco install -y go-task
task --taskfile internal/etw/_fixtures/Taskfile.yml all
- name: Test
shell: bash
run: |
Expand Down
15 changes: 15 additions & 0 deletions configs/fibratus.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,21 @@ handle:
# Indicates if process handles are collected during startup or when a new process is spawn.
enumerate-handles: false

# =============================== Evasion ====================================================

# Tweaks for controlling evasion scanner behaviours. Evasion behaviours can represent strong
# IoC (Indicators of Compromise) such as direct syscall or require a combination of fine-tune
# exceptions to reduce the alert fatigue.
evasion:
# Indicates if evasion detections are enabled global-wise. If disabled, evasion scanner will
# not try to classify ad-hoc evasion techniques.
enabled: true

# Indicates if direct syscall evasion detection is enabled. A direct syscall bypasses Windows
# API functions and calls the underlying system call directly using the syscall instruction,
# skipping the NTDLL stub that normally performs the transition to kernel mode.
#enable-direct-syscall: true

# =============================== Event ===============================================

# The following settings control the state of the event.
Expand Down
19 changes: 12 additions & 7 deletions internal/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package bootstrap
import (
"context"
"errors"
"github.com/rabbitstack/fibratus/internal/evasion"
"github.com/rabbitstack/fibratus/pkg/aggregator"
"github.com/rabbitstack/fibratus/pkg/alertsender"
"github.com/rabbitstack/fibratus/pkg/api"
Expand Down Expand Up @@ -138,7 +139,7 @@ func NewApp(cfg *config.Config, options ...Option) (*App, error) {
var engine *rules.Engine
var rs *config.RulesCompileResult

if cfg.Filters.Rules.Enabled && !cfg.ForwardMode && !cfg.IsCaptureSet() {
if cfg.Filters.Rules.Enabled && !cfg.ForwardMode && !cfg.IsCaptureSet() && !cfg.IsFilamentSet() {
engine = rules.NewEngine(psnap, cfg)
var err error
rs, err = engine.Compile()
Expand Down Expand Up @@ -203,9 +204,8 @@ func (f *App) Run(args []string) error {
// In case of a regular run, we additionally set up the aggregator.
// The aggregator will grab the events from the queue, assemble them
// into batches and hand over to output sinks.
filamentName := cfg.Filament.Name
if filamentName != "" {
f.filament, err = filament.New(filamentName, f.psnap, f.hsnap, cfg)
if cfg.IsFilamentSet() {
f.filament, err = filament.New(cfg.Filament.Name, f.psnap, f.hsnap, cfg)
if err != nil {
return err
}
Expand Down Expand Up @@ -234,6 +234,10 @@ func (f *App) Run(args []string) error {
f.symbolizer = symbolize.NewSymbolizer(symbolize.NewDebugHelpResolver(cfg), f.psnap, cfg, false)
f.evs.RegisterEventListener(f.symbolizer)
}
// register evasion scanner
if cfg.Evasion.Enabled {
f.evs.RegisterEventListener(evasion.NewScanner(cfg.Evasion))
}
// register rule engine
if f.engine != nil {
f.evs.RegisterEventListener(f.engine)
Expand Down Expand Up @@ -314,9 +318,9 @@ func (f *App) ReadCapture(ctx context.Context, args []string) error {
if err != nil {
return err
}
filamentName := f.config.Filament.Name
if filamentName != "" {
f.filament, err = filament.New(filamentName, f.psnap, f.hsnap, f.config)

if f.config.IsFilamentSet() {
f.filament, err = filament.New(f.config.Filament.Name, f.psnap, f.hsnap, f.config)
if err != nil {
return err
}
Expand Down Expand Up @@ -355,6 +359,7 @@ func (f *App) ReadCapture(ctx context.Context, args []string) error {
return err
}
}

return api.StartServer(f.config)
}

Expand Down
20 changes: 20 additions & 0 deletions internal/etw/_fixtures/Taskfile.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
version: '3'

vars:
SYSWHISPERS3_REPO: https://github.com/klezVirus/SysWhispers3.git
VISUAL_STUDIO_EDITION: Enterprise
VISUAL_STUDIO_VERSION: 2022

tasks:
direct-syscall:
desc: Builds the binary to perform direct syscalls via Syswhispers generated stubs
dir: direct-syscall
cmds:
- git clone {{ .SYSWHISPERS3_REPO }}
- python SysWhispers3/syswhispers.py -a x64 -c msvc -p common -o syscalls
- cmd.exe /c 'C:\"Program Files"\"Microsoft Visual Studio"\{{ .VISUAL_STUDIO_VERSION }}\{{ .VISUAL_STUDIO_EDITION }}\VC\Auxiliary\Build\vcvars64.bat && nmake -f Makefile.msvc'
silent: true

all:
deps:
- direct-syscall
6 changes: 6 additions & 0 deletions internal/etw/_fixtures/direct-syscall/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
SysWhishpers3
*.obj
*.asm
*.exe
syscalls.c
syscalls.h
7 changes: 7 additions & 0 deletions internal/etw/_fixtures/direct-syscall/Makefile.msvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
OPTIONS = -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS-
LIBS = libvcruntime.lib libcmt.lib ucrt.lib kernel32.lib

main:
ML64 /c syscalls-asm.x64.asm /link /NODEFAULTLIB /RELEASE /MACHINE:X64
cl.exe $(OPTIONS) syscalls.c main.c
link.exe /OUT:direct-syscall.exe -nologo $(LIBS) /MACHINE:X64 -subsystem:console -nodefaultlib syscalls-asm.x64.obj syscalls.obj main.obj
9 changes: 9 additions & 0 deletions internal/etw/_fixtures/direct-syscall/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#include "syscalls.h"

#include <Windows.h>

int main(int argc, char* argv[])
{
Sw3NtSetContextThread(-1, NULL);
return 0;
}
123 changes: 123 additions & 0 deletions internal/etw/source_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package etw
import (
"context"
"fmt"
"github.com/rabbitstack/fibratus/internal/evasion"
"github.com/rabbitstack/fibratus/pkg/config"
"github.com/rabbitstack/fibratus/pkg/event"
"github.com/rabbitstack/fibratus/pkg/event/params"
Expand All @@ -41,6 +42,7 @@ import (
"net"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
Expand Down Expand Up @@ -1294,6 +1296,127 @@ func testCallstackEnrichment(t *testing.T, hsnap handle.Snapshotter, psnap ps.Sn
}
}

func containsEvasion(e *event.Event, evasion string) bool {
m := e.GetMeta(event.EvasionsKey)
evas, ok := m.([]string)
if !ok {
return false
}
for _, eva := range evas {
if eva == evasion {
return true
}
}
return false
}

func TestEvasionScanner(t *testing.T) {
var tests = []*struct {
name string
gen func() error
want func(e *event.Event) bool
completed bool
}{
{
"direct syscall",
func() error {
cmd := exec.Command("_fixtures/direct-syscall/direct-syscall.exe")
return cmd.Run()
},
func(e *event.Event) bool {
if strings.Contains(strings.ToLower(e.Callstack.String()), strings.ToLower("direct-syscall.exe")) && e.Type == event.SetThreadContext {
log.Info(e, e.Callstack)
return containsEvasion(e, "direct_syscall")
}
return false
},
false,
},
}

evsConfig := config.EventSourceConfig{
EnableThreadEvents: true,
EnableImageEvents: true,
EnableFileIOEvents: false,
EnableVAMapEvents: true,
EnableNetEvents: true,
EnableRegistryEvents: false,
EnableMemEvents: false,
EnableHandleEvents: false,
EnableDNSEvents: false,
EnableAuditAPIEvents: true,
StackEnrichment: true,
}
evsConfig.Init()

hsnap := new(handle.SnapshotterMock)
hsnap.On("FindByObject", mock.Anything).Return(htypes.Handle{}, false)
hsnap.On("FindHandles", mock.Anything).Return([]htypes.Handle{}, nil)
hsnap.On("Write", mock.Anything).Return(nil)
hsnap.On("Remove", mock.Anything).Return(nil)

cfg := &config.Config{EventSource: evsConfig, Filters: &config.Filters{}}

psnap := ps.NewSnapshotter(hsnap, cfg)

evs := NewEventSource(psnap, hsnap, cfg, nil)

l := &MockListener{}
evs.RegisterEventListener(l)

symbolizer := symbolize.NewSymbolizer(symbolize.NewDebugHelpResolver(cfg), psnap, cfg, true)
defer symbolizer.Close()
evs.RegisterEventListener(symbolizer)

scanner := evasion.NewScanner(evasion.Config{Enabled: true, EnableDirectSyscall: true})
evs.RegisterEventListener(scanner)

require.NoError(t, evs.Open(cfg))
defer evs.Close()

time.Sleep(time.Second * 2)

for _, tt := range tests {
gen := tt.gen
if gen != nil {
log.Infof("executing [%s] evasion test generator", tt.name)
require.NoError(t, gen(), tt.name)
}
}

ntests := len(tests)
timeout := time.After(time.Duration(ntests) * time.Minute)

for {
select {
case e := <-evs.Events():
for _, tt := range tests {
if tt.completed {
continue
}
pred := tt.want
if pred(e) {
t.Logf("PASS: %s", tt.name)
tt.completed = true
ntests--
}
if ntests == 0 {
return
}
}
case err := <-evs.Errors():
t.Fatalf("FAIL: %v", err)
case <-timeout:
for _, tt := range tests {
if !tt.completed {
t.Logf("FAIL: %s", tt.name)
}
}
t.Fatal("FAIL: TestEvasionScanner")
}
}
}

var (
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
kernel32 = windows.NewLazySystemDLL("kernel32.dll")
Expand Down
49 changes: 49 additions & 0 deletions internal/evasion/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2021-present 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 evasion

import (
"github.com/spf13/pflag"
"github.com/spf13/viper"
)

const (
enabled = "evasion.enabled"
enableDirectSyscall = "evasion.enable-direct-syscall"
)

// Config contains the settings that influence the behaviour of the evasion scanner.
type Config struct {
// Enabled indicates if evasion detections are enabled global-wise.
Enabled bool `json:"enabled" yaml:"enabled"`
// EnableDirectSyscall indicates if direct syscall evasion detection is enabled.
EnableDirectSyscall bool `json:"enable-direct-syscall" yaml:"enable-direct-syscall"`
}

// InitFromViper initializes evasion config from Viper.
func (c *Config) InitFromViper(v *viper.Viper) {
c.Enabled = v.GetBool(enabled)
c.EnableDirectSyscall = v.GetBool(enableDirectSyscall)
}

// AddFlags adds evasion config flags to the set.
func AddFlags(flags *pflag.FlagSet) {
flags.Bool(enabled, true, "Indicates if evasion detections are enabled global-wise")
flags.Bool(enableDirectSyscall, true, "Indicates if direct syscall evasion detection is enabled")
}
Loading
Loading