Skip to content

Commit 8d71b62

Browse files
committed
feat(evasion): Evasion scanner
This changeset establishes the architecture for the evasion scanner. Evasion scanner piggybacks on top of different evasion detectors such as direct syscall (being the first evasion behaviour implemented). When the evasion behaviour is detected, the event is decorated with such evasion in its metadata.
1 parent 36430eb commit 8d71b62

File tree

21 files changed

+721
-0
lines changed

21 files changed

+721
-0
lines changed

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,8 @@
6868
6969
> /area deps
7070
71+
> /area evasion
72+
7173
> /area other
7274
7375

.github/workflows/master.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,10 @@ jobs:
169169
run: |
170170
cd yara
171171
make install
172+
- name: Setup test fixtures
173+
run: |
174+
choco install -y go-task
175+
task --taskfile internal/etw/_fixtures/Taskfile.yml all
172176
- name: Test
173177
shell: bash
174178
run: |

.github/workflows/pr.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,10 @@ jobs:
151151
run: |
152152
cd yara
153153
make install
154+
- name: Setup test fixtures
155+
run: |
156+
choco install -y go-task
157+
task --taskfile internal/etw/_fixtures/Taskfile.yml all
154158
- name: Test
155159
shell: bash
156160
run: |

configs/fibratus.yml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,21 @@ handle:
156156
# Indicates if process handles are collected during startup or when a new process is spawn.
157157
enumerate-handles: false
158158

159+
# =============================== Evasion ====================================================
160+
161+
# Tweaks for controlling evasion scanner behaviours. Evasion behaviours can represent strong
162+
# IoC (Indicators of Compromise) such as direct syscall or require a combination of fine-tune
163+
# exceptions to reduce the alert fatigue.
164+
evasion:
165+
# Indicates if evasion detections are enabled global-wise. If disabled, evasion scanner will
166+
# not try to classify ad-hoc evasion techniques.
167+
enabled: true
168+
169+
# Indicates if direct syscall evasion detection is enabled. A direct syscall bypasses Windows
170+
# API functions and calls the underlying system call directly using the syscall instruction,
171+
# skipping the NTDLL stub that normally performs the transition to kernel mode.
172+
#enable-direct-syscall: true
173+
159174
# =============================== Event ===============================================
160175

161176
# The following settings control the state of the event.

internal/bootstrap/bootstrap.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ package bootstrap
2121
import (
2222
"context"
2323
"errors"
24+
"github.com/rabbitstack/fibratus/internal/evasion"
2425
"github.com/rabbitstack/fibratus/pkg/aggregator"
2526
"github.com/rabbitstack/fibratus/pkg/alertsender"
2627
"github.com/rabbitstack/fibratus/pkg/api"
@@ -233,6 +234,10 @@ func (f *App) Run(args []string) error {
233234
f.symbolizer = symbolize.NewSymbolizer(symbolize.NewDebugHelpResolver(cfg), f.psnap, cfg, false)
234235
f.evs.RegisterEventListener(f.symbolizer)
235236
}
237+
// register evasion scanner
238+
if cfg.Evasion.Enabled {
239+
f.evs.RegisterEventListener(evasion.NewScanner(cfg.Evasion))
240+
}
236241
// register rule engine
237242
if f.engine != nil {
238243
f.evs.RegisterEventListener(f.engine)
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
version: '3'
2+
3+
vars:
4+
SYSWHISPERS3_REPO: https://github.com/klezVirus/SysWhispers3.git
5+
VISUAL_STUDIO_EDITION: Enterprise
6+
VISUAL_STUDIO_VERSION: 2022
7+
8+
tasks:
9+
direct-syscall:
10+
desc: Builds the binary to perform direct syscalls via Syswhispers generated stubs
11+
dir: direct-syscall
12+
cmds:
13+
- git clone {{ .SYSWHISPERS3_REPO }}
14+
- python SysWhispers3/syswhispers.py -a x64 -c msvc -p common -o syscalls
15+
- cmd.exe /c 'C:\"Program Files"\"Microsoft Visual Studio"\{{ .VISUAL_STUDIO_VERSION }}\{{ .VISUAL_STUDIO_EDITION }}\VC\Auxiliary\Build\vcvars64.bat && nmake -f Makefile.msvc'
16+
silent: true
17+
18+
all:
19+
deps:
20+
- direct-syscall
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
SysWhishpers3
2+
*.obj
3+
*.asm
4+
*.exe
5+
syscalls.c
6+
syscalls.h
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
OPTIONS = -Zp8 -c -nologo -Gy -Os -O1 -GR- -EHa -Oi -GS-
2+
LIBS = libvcruntime.lib libcmt.lib ucrt.lib kernel32.lib
3+
4+
main:
5+
ML64 /c syscalls-asm.x64.asm /link /NODEFAULTLIB /RELEASE /MACHINE:X64
6+
cl.exe $(OPTIONS) syscalls.c main.c
7+
link.exe /OUT:direct-syscall.exe -nologo $(LIBS) /MACHINE:X64 -subsystem:console -nodefaultlib syscalls-asm.x64.obj syscalls.obj main.obj
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#include "syscalls.h"
2+
3+
#include <Windows.h>
4+
5+
int main(int argc, char* argv[])
6+
{
7+
Sw3NtSetContextThread(-1, NULL);
8+
return 0;
9+
}

internal/etw/source_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package etw
2020
import (
2121
"context"
2222
"fmt"
23+
"github.com/rabbitstack/fibratus/internal/evasion"
2324
"github.com/rabbitstack/fibratus/pkg/config"
2425
"github.com/rabbitstack/fibratus/pkg/event"
2526
"github.com/rabbitstack/fibratus/pkg/event/params"
@@ -41,6 +42,7 @@ import (
4142
"net"
4243
"net/http"
4344
"os"
45+
"os/exec"
4446
"path/filepath"
4547
"runtime"
4648
"strings"
@@ -1294,6 +1296,127 @@ func testCallstackEnrichment(t *testing.T, hsnap handle.Snapshotter, psnap ps.Sn
12941296
}
12951297
}
12961298

1299+
func containsEvasion(e *event.Event, evasion string) bool {
1300+
m := e.GetMeta(event.EvasionsKey)
1301+
evas, ok := m.([]string)
1302+
if !ok {
1303+
return false
1304+
}
1305+
for _, eva := range evas {
1306+
if eva == evasion {
1307+
return true
1308+
}
1309+
}
1310+
return false
1311+
}
1312+
1313+
func TestEvasionScanner(t *testing.T) {
1314+
var tests = []*struct {
1315+
name string
1316+
gen func() error
1317+
want func(e *event.Event) bool
1318+
completed bool
1319+
}{
1320+
{
1321+
"direct syscall",
1322+
func() error {
1323+
cmd := exec.Command("_fixtures/direct-syscall/direct-syscall.exe")
1324+
return cmd.Run()
1325+
},
1326+
func(e *event.Event) bool {
1327+
if strings.Contains(strings.ToLower(e.Callstack.String()), strings.ToLower("direct-syscall.exe")) && e.Type == event.SetThreadContext {
1328+
log.Info(e, e.Callstack)
1329+
return containsEvasion(e, "direct_syscall")
1330+
}
1331+
return false
1332+
},
1333+
false,
1334+
},
1335+
}
1336+
1337+
evsConfig := config.EventSourceConfig{
1338+
EnableThreadEvents: true,
1339+
EnableImageEvents: true,
1340+
EnableFileIOEvents: false,
1341+
EnableVAMapEvents: true,
1342+
EnableNetEvents: true,
1343+
EnableRegistryEvents: false,
1344+
EnableMemEvents: false,
1345+
EnableHandleEvents: false,
1346+
EnableDNSEvents: false,
1347+
EnableAuditAPIEvents: true,
1348+
StackEnrichment: true,
1349+
}
1350+
evsConfig.Init()
1351+
1352+
hsnap := new(handle.SnapshotterMock)
1353+
hsnap.On("FindByObject", mock.Anything).Return(htypes.Handle{}, false)
1354+
hsnap.On("FindHandles", mock.Anything).Return([]htypes.Handle{}, nil)
1355+
hsnap.On("Write", mock.Anything).Return(nil)
1356+
hsnap.On("Remove", mock.Anything).Return(nil)
1357+
1358+
cfg := &config.Config{EventSource: evsConfig, Filters: &config.Filters{}}
1359+
1360+
psnap := ps.NewSnapshotter(hsnap, cfg)
1361+
1362+
evs := NewEventSource(psnap, hsnap, cfg, nil)
1363+
1364+
l := &MockListener{}
1365+
evs.RegisterEventListener(l)
1366+
1367+
symbolizer := symbolize.NewSymbolizer(symbolize.NewDebugHelpResolver(cfg), psnap, cfg, true)
1368+
defer symbolizer.Close()
1369+
evs.RegisterEventListener(symbolizer)
1370+
1371+
scanner := evasion.NewScanner(evasion.Config{Enabled: true, EnableDirectSyscall: true})
1372+
evs.RegisterEventListener(scanner)
1373+
1374+
require.NoError(t, evs.Open(cfg))
1375+
defer evs.Close()
1376+
1377+
time.Sleep(time.Second * 2)
1378+
1379+
for _, tt := range tests {
1380+
gen := tt.gen
1381+
if gen != nil {
1382+
log.Infof("executing [%s] evasion test generator", tt.name)
1383+
require.NoError(t, gen(), tt.name)
1384+
}
1385+
}
1386+
1387+
ntests := len(tests)
1388+
timeout := time.After(time.Duration(ntests) * time.Minute)
1389+
1390+
for {
1391+
select {
1392+
case e := <-evs.Events():
1393+
for _, tt := range tests {
1394+
if tt.completed {
1395+
continue
1396+
}
1397+
pred := tt.want
1398+
if pred(e) {
1399+
t.Logf("PASS: %s", tt.name)
1400+
tt.completed = true
1401+
ntests--
1402+
}
1403+
if ntests == 0 {
1404+
return
1405+
}
1406+
}
1407+
case err := <-evs.Errors():
1408+
t.Fatalf("FAIL: %v", err)
1409+
case <-timeout:
1410+
for _, tt := range tests {
1411+
if !tt.completed {
1412+
t.Logf("FAIL: %s", tt.name)
1413+
}
1414+
}
1415+
t.Fatal("FAIL: TestEvasionScanner")
1416+
}
1417+
}
1418+
}
1419+
12971420
var (
12981421
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll")
12991422
kernel32 = windows.NewLazySystemDLL("kernel32.dll")

0 commit comments

Comments
 (0)