Skip to content

Commit a826d87

Browse files
committed
Fixed issues:
- #75 List endpoints by group / status in /endpoints - #74 Implement API endpoint to update endpoints fields - #73 List of ever loaded modules in report - #72 Track list of loaded modules - #61 Integrate with ETW
1 parent da96f78 commit a826d87

32 files changed

+2302
-752
lines changed

api/adminapi_test.go

+100
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,19 @@ package api
22

33
import (
44
"bytes"
5+
"crypto/tls"
56
"encoding/json"
67
"fmt"
78
"io/ioutil"
9+
"math/rand"
810
"net/http"
911
"net/url"
12+
"sync"
1013
"testing"
1114
"time"
1215

1316
"github.com/0xrawsec/golang-evtx/evtx"
17+
"github.com/gorilla/websocket"
1418
)
1519

1620
func doRequest(method, url string) (r AdminAPIResponse) {
@@ -497,3 +501,99 @@ func TestAdminAPIGetEndpointAlerts(t *testing.T) {
497501
t.FailNow()
498502
}
499503
}
504+
505+
func TestEventStream(t *testing.T) {
506+
// cleanup previous data
507+
clean(&mconf, &fconf)
508+
509+
m, mc := prepareTest()
510+
defer func() {
511+
m.Shutdown()
512+
m.Wait()
513+
}()
514+
515+
expctd := float64(20000)
516+
total := float64(0)
517+
sumEps := float64(0)
518+
nclients := float64(4)
519+
slowClients := float64(0)
520+
wg := sync.WaitGroup{}
521+
522+
for i := float64(0); i < nclients; i++ {
523+
u := url.URL{Scheme: "wss", Host: format("localhost:%d", 8001), Path: AdmAPIStreamEvents}
524+
key := mconf.AdminAPI.Users[0].Key
525+
dialer := *websocket.DefaultDialer
526+
dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
527+
t.Logf("connecting to %s", u.String())
528+
c, resp, err := dialer.Dial(u.String(), http.Header{"Api-Key": {key}})
529+
if err != nil {
530+
if err == websocket.ErrBadHandshake {
531+
t.Logf("handshake failed with status %d", resp.StatusCode)
532+
}
533+
t.Errorf("failed to dial: %s", err)
534+
t.FailNow()
535+
}
536+
defer c.Close()
537+
538+
wg.Add(1)
539+
go func() {
540+
defer wg.Done()
541+
recvd := float64(0)
542+
start := time.Now()
543+
slow := false
544+
545+
if rand.Int()%2 == 0 {
546+
slow = true
547+
slowClients++
548+
}
549+
550+
for {
551+
_, _, err := c.ReadMessage()
552+
if err != nil {
553+
break
554+
}
555+
recvd++
556+
if recvd == expctd {
557+
break
558+
}
559+
// simulates a slow client
560+
if slow {
561+
time.Sleep(35 * time.Microsecond)
562+
}
563+
}
564+
eps := recvd / float64(time.Since(start).Seconds())
565+
total += recvd
566+
// we take into account only normal clients
567+
if !slow {
568+
sumEps += eps
569+
t.Logf("Normal client received %.1f EPS", eps)
570+
} else {
571+
t.Logf("Slow client received %.1f EPS", eps)
572+
}
573+
}()
574+
}
575+
576+
mc.PostLogs(readerFromEvents(int(expctd)))
577+
tick := time.NewTicker(60 * time.Second)
578+
loop:
579+
for {
580+
select {
581+
case <-tick.C:
582+
break loop
583+
default:
584+
}
585+
586+
if total == expctd*nclients {
587+
wg.Wait()
588+
break
589+
}
590+
}
591+
592+
if total != expctd*nclients {
593+
t.Errorf("Received less events than expected received=%.0f VS expected=%.0f", total, expctd*nclients)
594+
t.FailNow()
595+
}
596+
597+
t.Logf("Average %.1f EPS/client", sumEps/(nclients-slowClients))
598+
599+
}

api/api_client.go

-3
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@ func (cc *ClientConfig) ManagerIP() net.IP {
5050
}
5151

5252
func (cc *ClientConfig) DialContext(ctx context.Context, network, addr string) (con net.Conn, err error) {
53-
log.Infof("Dial")
5453
dialer := net.Dialer{
5554
Timeout: 30 * time.Second,
5655
KeepAlive: 30 * time.Second,
@@ -68,8 +67,6 @@ func (cc *ClientConfig) DialContext(ctx context.Context, network, addr string) (
6867
}
6968

7069
func (cc *ClientConfig) DialTLSContext(ctx context.Context, network, addr string) (net.Conn, error) {
71-
72-
log.Infof("Dial TLS")
7370
c, err := tls.Dial(network, addr, &tls.Config{InsecureSkipVerify: cc.Unsafe})
7471

7572
if err != nil {

api/endpoint.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type Endpoint struct {
2222
Group string `json:"group"`
2323
Key string `json:"key,omitempty"`
2424
Command *Command `json:"command,omitempty"`
25-
Score int `json:"score"`
25+
Score float64 `json:"score"`
2626
Status string `json:"status"`
2727
LastDetection time.Time `json:"last-detection"`
2828
LastConnection time.Time `json:"last-connection"`

api/forwarder.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"sync"
1111
"time"
1212

13-
"github.com/0xrawsec/golang-evtx/evtx"
1413
"github.com/0xrawsec/golang-utils/fileutils"
1514
"github.com/0xrawsec/golang-utils/fsutil"
1615
"github.com/0xrawsec/golang-utils/fsutil/fswalker"
@@ -65,9 +64,10 @@ func NewForwarder(c *ForwarderConfig) (*Forwarder, error) {
6564
// Initialize the Forwarder
6665
// TODO: better organize forwarder configuration
6766
co := Forwarder{
68-
fwdConfig: c,
69-
TimeTresh: time.Second * 10,
70-
EventTresh: 50,
67+
fwdConfig: c,
68+
TimeTresh: time.Second * 10,
69+
// Writing events too quickly has a perf impact
70+
EventTresh: 500,
7171
Pipe: new(bytes.Buffer),
7272
stop: make(chan bool),
7373
done: make(chan bool),
@@ -122,10 +122,10 @@ func (f *Forwarder) ArchiveLogs() {
122122
}
123123

124124
// PipeEvent pipes an event to be sent through the forwarder
125-
func (f *Forwarder) PipeEvent(e *evtx.GoEvtxMap) {
125+
func (f *Forwarder) PipeEvent(event interface{}) {
126126
f.Lock()
127127
defer f.Unlock()
128-
f.Pipe.Write(evtx.ToJSON(e))
128+
f.Pipe.Write(utils.Json(event))
129129
f.Pipe.WriteByte('\n')
130130
f.EventsPiped++
131131
}

api/forwarder_test.go

+33-19
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
11
package api
22

33
import (
4+
"bytes"
45
"encoding/json"
56
"fmt"
7+
"io"
68
"io/ioutil"
79
"math/rand"
810
"os"
911
"path/filepath"
12+
"strings"
1013
"sync"
1114
"testing"
1215
"time"
1316

14-
"github.com/0xrawsec/golang-evtx/evtx"
1517
"github.com/0xrawsec/golang-utils/log"
18+
"github.com/0xrawsec/golang-utils/readers"
1619
"github.com/0xrawsec/golang-utils/scanner"
1720
"github.com/0xrawsec/golang-utils/sync/semaphore"
21+
"github.com/0xrawsec/whids/event"
22+
"github.com/0xrawsec/whids/utils"
1823
)
1924

2025
var (
@@ -50,35 +55,44 @@ var (
5055
},
5156
}
5257

53-
events = []string{
54-
// regular log
55-
`{"Event":{"EventData":{"EventType":"CreateKey","Image":"C:\\Windows\\servicing\\TrustedInstaller.exe","ProcessGuid":"{49F1AF32-38C1-5AC7-0000-00105E5D0B00}","ProcessId":"2544","TargetObject":"HKLM\\SOFTWARE\\Microsoft\\EnterpriseCertificates\\Disallowed","UtcTime":"2018-04-06 20:07:14.423"},"System":{"Channel":"Microsoft-Windows-Sysmon/Operational","Computer":"CALDERA02.caldera.loc","Correlation":{},"EventID":"12","EventRecordID":"886970","Execution":{"ProcessID":"1456","ThreadID":"1712"},"Keywords":"0x8000000000000000","Level":"4","Opcode":"0","Provider":{"Guid":"{5770385F-C22A-43E0-BF4C-06F5698FFBD9}","Name":"Microsoft-Windows-Sysmon"},"Security":{"UserID":"S-1-5-18"},"Task":"12","TimeCreated":{"SystemTime":"2018-04-06T09:07:14.424360200Z"},"Version":"2"}}}`,
56-
// alert log
57-
`{"Event":{"EventData":{"CreationUtcTime":"2018-02-26 16:28:13.169","Image":"C:\\Program Files\\cagent\\cagent.exe","ProcessGuid":"{49F1AF32-11B0-5A90-0000-0010594E0100}","ProcessId":"1216","TargetFilename":"C:\\commander.exe","UtcTime":"2018-02-26 16:28:13.169"},"GeneInfo":{"Criticality":10,"Signature":["ExecutableFileCreated","NewExeCreatedInRoot"]},"System":{"Channel":"Microsoft-Windows-Sysmon/Operational","Computer":"CALDERA01.caldera.loc","Correlation":{},"EventID":"11","EventRecordID":"1274413","Execution":{"ProcessID":"1408","ThreadID":"1652"},"Keywords":"0x8000000000000000","Level":"4","Opcode":"0","Provider":{"Guid":"{5770385F-C22A-43E0-BF4C-06F5698FFBD9}","Name":"Microsoft-Windows-Sysmon"},"Security":{"UserID":"S-1-5-18"},"Task":"11","TimeCreated":{"SystemTime":"2018-02-26T16:28:13.185436300Z"},"Version":"2"}}}`,
58-
`{"Event":{"EventData":{"CommandLine":"\"powershell\" -command -","Company":"Microsoft Corporation","CurrentDirectory":"C:\\Windows\\system32\\","Description":"Windows PowerShell","FileVersion":"6.1.7600.16385 (win7_rtm.090713-1255)","Hashes":"SHA1=5330FEDAD485E0E4C23B2ABE1075A1F984FDE9FC,MD5=852D67A27E454BD389FA7F02A8CBE23F,SHA256=A8FDBA9DF15E41B6F5C69C79F66A26A9D48E174F9E7018A371600B866867DAB8,IMPHASH=F2C0E8A5BD10DBC167455484050CD683","Image":"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe","IntegrityLevel":"System","LogonGuid":"{49F1AF32-11AE-5A90-0000-0020E7030000}","LogonId":"0x3e7","ParentCommandLine":"C:\\commander.exe -f","ParentImage":"C:\\commander.exe","ParentProcessGuid":"{49F1AF32-359D-5A94-0000-0010A9530C00}","ParentProcessId":"3068","ProcessGuid":"{49F1AF32-35A0-5A94-0000-0010FE5E0C00}","ProcessId":"1244","Product":"Microsoft® Windows® Operating System","TerminalSessionId":"0","User":"NT AUTHORITY\\SYSTEM","UtcTime":"2018-02-26 16:28:16.514"},"GeneInfo":{"Criticality":10,"Signature":["HeurSpawnShell","PowershellStdin"]},"System":{"Channel":"Microsoft-Windows-Sysmon/Operational","Computer":"CALDERA01.caldera.loc","Correlation":{},"EventID":"1","EventRecordID":"1274784","Execution":{"ProcessID":"1408","ThreadID":"1652"},"Keywords":"0x8000000000000000","Level":"4","Opcode":"0","Provider":{"Guid":"{5770385F-C22A-43E0-BF4C-06F5698FFBD9}","Name":"Microsoft-Windows-Sysmon"},"Security":{"UserID":"S-1-5-18"},"Task":"1","TimeCreated":{"SystemTime":"2018-04-06T16:28:16.530122800Z"},"Version":"5"}}}`,
59-
`{"Event":{"EventData":{"CallTrace":"C:\\Windows\\SYSTEM32\\ntdll.dll+4d61a|C:\\Windows\\system32\\KERNELBASE.dll+19577|UNKNOWN(000000001ABD2A68)","GrantedAccess":"0x143a","SourceImage":"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe","SourceProcessGUID":"{49F1AF32-3922-5A94-0000-0010E3581900}","SourceProcessId":"1916","SourceThreadId":"2068","TargetImage":"C:\\Windows\\system32\\lsass.exe","TargetProcessGUID":"{49F1AF32-11AD-5A90-0000-00102F6F0000}","TargetProcessId":"472","UtcTime":"2018-02-26 16:43:26.380"},"GeneInfo":{"Criticality":10,"Signature":["HeurMaliciousAccess","MaliciousLsassAccess","SuspWriteAccess","SuspiciousLsassAccess"]},"System":{"Channel":"Microsoft-Windows-Sysmon/Operational","Computer":"CALDERA01.caldera.loc","Correlation":{},"EventID":"10","EventRecordID":"1293693","Execution":{"ProcessID":"1408","ThreadID":"1652"},"Keywords":"0x8000000000000000","Level":"4","Opcode":"0","Provider":{"Guid":"{5770385F-C22A-43E0-BF4C-06F5698FFBD9}","Name":"Microsoft-Windows-Sysmon"},"Security":{"UserID":"S-1-5-18"},"Task":"10","TimeCreated":{"SystemTime":"2018-02-26T16:43:26.447894800Z"},"Version":"3"}}}`,
60-
}
58+
eventFile = "./data/events.json"
59+
events = make([]event.EdrEvent, 0)
6160
)
6261

63-
func emitEvents(count int) (ce chan *evtx.GoEvtxMap) {
64-
timecreatedPath := evtx.Path("/Event/System/TimeCreated/SystemTime")
65-
ce = make(chan *evtx.GoEvtxMap)
62+
func init() {
63+
data, err := ioutil.ReadFile(eventFile)
64+
if err != nil {
65+
panic(err)
66+
}
67+
for line := range readers.Readlines(bytes.NewBuffer(data)) {
68+
event := event.EdrEvent{}
69+
json.Unmarshal(line, &event)
70+
events = append(events, event)
71+
}
72+
}
73+
74+
func emitEvents(count int) (ce chan *event.EdrEvent) {
75+
ce = make(chan *event.EdrEvent)
6676
go func() {
6777
defer close(ce)
6878
for i := 0; i < count; i++ {
69-
e := new(evtx.GoEvtxMap)
7079
i := rand.Int() % len(events)
71-
err := json.Unmarshal([]byte(events[i]), e)
72-
e.Set(&timecreatedPath, time.Now().Format(time.RFC3339Nano))
73-
if err != nil {
74-
log.Errorf("Cannot unmarshall event")
75-
}
76-
ce <- e
80+
e := events[i]
81+
e.Event.System.TimeCreated.SystemTime = time.Now()
82+
ce <- &e
7783
}
7884
}()
7985
return
8086
}
8187

88+
func readerFromEvents(count int) io.Reader {
89+
tmp := make([]string, 0, count)
90+
for event := range emitEvents(count) {
91+
tmp = append(tmp, string(utils.Json(event)))
92+
}
93+
return bytes.NewBufferString(strings.Join(tmp, "\n"))
94+
}
95+
8296
func countLinesInGzFile(filepath string) int {
8397
var line int
8498
fd, err := os.Open(filepath)

api/log_streamer.go

+28-47
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,36 @@ import (
55
"sync"
66
"time"
77

8-
"github.com/0xrawsec/golang-evtx/evtx"
98
"github.com/0xrawsec/golang-utils/datastructs"
9+
"github.com/0xrawsec/whids/event"
1010
)
1111

1212
type LogStream struct {
1313
closed bool
14-
S chan evtx.GoEvtxMap
14+
queue datastructs.Fifo
15+
S chan *event.EdrEvent
1516
}
1617

17-
func (s *LogStream) Stream(e evtx.GoEvtxMap) bool {
18-
for {
19-
if s.closed {
20-
close(s.S)
21-
return false
22-
}
23-
select {
24-
case s.S <- e:
25-
return true
26-
default:
27-
time.Sleep(time.Millisecond * 10)
28-
}
18+
func (s *LogStream) Queue(e *event.EdrEvent) bool {
19+
if s.closed {
20+
return false
2921
}
22+
s.queue.Push(e)
23+
return true
24+
}
25+
26+
func (s *LogStream) Stream() {
27+
go func() {
28+
defer close(s.S)
29+
for !s.closed {
30+
if i := s.queue.Pop(); i != nil {
31+
e := i.Value.(*event.EdrEvent)
32+
s.S <- e
33+
} else {
34+
time.Sleep(time.Millisecond * 50)
35+
}
36+
}
37+
}()
3038
}
3139

3240
func (s *LogStream) Close() {
@@ -35,21 +43,19 @@ func (s *LogStream) Close() {
3543

3644
type EventStreamer struct {
3745
sync.RWMutex
38-
queue datastructs.Fifo
3946
streams map[int]*LogStream
4047
}
4148

4249
func NewEventStreamer() *EventStreamer {
4350
return &EventStreamer{
44-
queue: datastructs.Fifo{},
4551
streams: map[int]*LogStream{},
4652
}
4753
}
4854

4955
func (s *EventStreamer) NewStream() *LogStream {
5056
s.Lock()
5157
defer s.Unlock()
52-
ls := &LogStream{S: make(chan evtx.GoEvtxMap)}
58+
ls := &LogStream{S: make(chan *event.EdrEvent), queue: datastructs.Fifo{}}
5359
s.streams[s.newId()] = ls
5460
return ls
5561
}
@@ -64,40 +70,15 @@ func (s *EventStreamer) newId() int {
6470
}
6571
}
6672

67-
func (s *EventStreamer) Queue(e evtx.GoEvtxMap) {
73+
func (s *EventStreamer) Queue(e *event.EdrEvent) {
6874
s.Lock()
6975
defer s.Unlock()
7076
// we queue only if there is at least a stream open
7177
if len(s.streams) > 0 {
72-
s.queue.Push(e)
73-
}
74-
}
75-
76-
func (s *EventStreamer) Stream() {
77-
go func() {
78-
for {
79-
if i := s.queue.Pop(); i != nil {
80-
e := i.Value.(evtx.GoEvtxMap)
81-
for id, stream := range s.streams {
82-
if ok := stream.Stream(e); !ok {
83-
s.delStream(id)
84-
}
85-
}
86-
} else {
87-
// we sleep only if there is nothing to stream
88-
// to minimize delay
89-
time.Sleep(time.Millisecond * 50)
78+
for id, stream := range s.streams {
79+
if ok := stream.Queue(e); !ok {
80+
delete(s.streams, id)
9081
}
9182
}
92-
}()
93-
}
94-
95-
func (s *EventStreamer) delStream(id int) {
96-
s.Lock()
97-
defer s.Unlock()
98-
delete(s.streams, id)
99-
}
100-
101-
func (s *EventStreamer) Close() {
102-
83+
}
10384
}

0 commit comments

Comments
 (0)