From db9c027e3d9c65d66f6b5c5f530cb7a48aaf6a03 Mon Sep 17 00:00:00 2001 From: lprat Date: Wed, 12 May 2021 13:43:39 +0200 Subject: [PATCH 1/4] netstat ioc check --- module_config/netscan_linux.go | 3 + module_config/netscan_windows.go | 5 + report/formatter.go | 14 +++ report/report.go | 6 + report/target.go | 1 + scanner/netscan/netscan.go | 191 +++++++++++++++++++++++++++++++ 6 files changed, 220 insertions(+) create mode 100644 module_config/netscan_linux.go create mode 100644 module_config/netscan_windows.go create mode 100644 scanner/netscan/netscan.go diff --git a/module_config/netscan_linux.go b/module_config/netscan_linux.go new file mode 100644 index 0000000..c6bcfc6 --- /dev/null +++ b/module_config/netscan_linux.go @@ -0,0 +1,3 @@ +package config + +import _ "github.com/spyre-project/spyre/scanner/netscan" diff --git a/module_config/netscan_windows.go b/module_config/netscan_windows.go new file mode 100644 index 0000000..e322c80 --- /dev/null +++ b/module_config/netscan_windows.go @@ -0,0 +1,5 @@ +// +build amd64 + +package config + +import _ "github.com/spyre-project/spyre/scanner/netscan" diff --git a/report/formatter.go b/report/formatter.go index 47e3d51..2f79535 100644 --- a/report/formatter.go +++ b/report/formatter.go @@ -49,6 +49,10 @@ func (f *formatterPlain) formatProcEntry(w io.Writer, p ps.Process, description, w.Write([]byte{'\n'}) } +func (f *formatterPlain) formatNetstatEntry(w io.Writer, description, message string, extra ...string) { + fmt.Fprintf(w, "%s %s %s: %s%s\n", time.Now().Format(time.RFC3339), spyre.Hostname, description, message, fmtExtra(extra)) +} + func (f *formatterPlain) formatMessage(w io.Writer, format string, a ...interface{}) { f.emitTimeStamp(w) if format[len(format)-1] != '\n' { @@ -98,6 +102,11 @@ func (f *formatterTSJSON) formatProcEntry(w io.Writer, p ps.Process, description f.emitRecord(w, extra...) } +func (f *formatterTSJSON) formatNetstatEntry(w io.Writer, description, message string, extra ...string) { + extra = append([]string{"timestamp_desc", description, "message", message}, extra...) + f.emitRecord(w, extra...) +} + func (f *formatterTSJSON) formatMessage(w io.Writer, format string, a ...interface{}) { extra := []string{"timestamp_desc", "msg", "message", fmt.Sprintf(format, a...)} f.emitRecord(w, extra...) @@ -140,6 +149,11 @@ func (f *formatterTSJSONLines) formatProcEntry(w io.Writer, p ps.Process, descri f.emitRecord(w, extra...) } +func (f *formatterTSJSONLines) formatNetstatEntry(w io.Writer, description, message string, extra ...string) { + extra = append([]string{"timestamp_desc", description, "message", message}, extra...) + f.emitRecord(w, extra...) +} + func (f *formatterTSJSONLines) formatMessage(w io.Writer, format string, a ...interface{}) { extra := []string{"timestamp_desc", "msg", "message", fmt.Sprintf(format, a...)} f.emitRecord(w, extra...) diff --git a/report/report.go b/report/report.go index 88dbcc7..d3f135f 100644 --- a/report/report.go +++ b/report/report.go @@ -41,6 +41,12 @@ func AddProcInfo(proc ps.Process, description, message string, extra ...string) } } +func AddNetstatInfo(description, message string, extra ...string) { + for _, t := range targets { + t.formatNetstatEntry(t.writer, description, message, extra...) + } +} + // Close shuts down all reporting targets func Close() { for _, t := range targets { diff --git a/report/target.go b/report/target.go index 5d40cf0..4fd26a5 100644 --- a/report/target.go +++ b/report/target.go @@ -17,6 +17,7 @@ import ( type formatter interface { formatFileEntry(w io.Writer, f afero.File, description, message string, extra ...string) formatProcEntry(w io.Writer, p ps.Process, description, message string, extra ...string) + formatNetstatEntry(w io.Writer, description, message string, extra ...string) formatMessage(w io.Writer, format string, a ...interface{}) finish(w io.Writer) } diff --git a/scanner/netscan/netscan.go b/scanner/netscan/netscan.go new file mode 100644 index 0000000..90e1509 --- /dev/null +++ b/scanner/netscan/netscan.go @@ -0,0 +1,191 @@ +// +build linux amd64 + +package netscan + +import ( + "fmt" + "strings" + + "github.com/cakturk/go-netstat/netstat" + "github.com/spyre-project/spyre/config" + "github.com/spyre-project/spyre/log" + "github.com/spyre-project/spyre/report" + "github.com/spyre-project/spyre/scanner" +) + +func init() { scanner.RegisterSystemScanner(&systemScanner{}) } + +type systemScanner struct { + iocs []eventIOC +} + +type eventIOC struct { + Dip []string `json:"dip"` + Sip []string `json:"sip"` + Sport []int `json:"sport"` + Dport []int `json:"dport"` + Pname []string `json:"pname"` + NPname []string `json:"notpname"` + State []string `json:"state"` + Proto string `json:"proto"` + Description string `json:"description"` +} + +type iocFile struct { + Keys []eventIOC `json:"netstat"` +} + +func (s *systemScanner) Name() string { return "Netstat" } + +func (s *systemScanner) Init() error { + iocFiles := config.IocFiles + if len(iocFiles) == 0 { + iocFiles = []string{"ioc.json"} + } + for _, file := range iocFiles { + var current iocFile + if err := config.ReadIOCs(file, ¤t); err != nil { + log.Error(err.Error()) + } + for _, ioc := range current.Keys { + s.iocs = append(s.iocs, ioc) + } + } + return nil +} + +func intInSlice(a int, list []int) bool { + if len(list) == 0 { + return true + } + for _, b := range list { + if b == a { + return true + } + } + return false +} + +func stringInSlice(a string, list []string) bool { + if len(list) == 0 { + return true + } + for _, b := range list { + if strings.EqualFold(b, a) { + return true + } + } + return false +} + +func nstringInSlice(a string, list []string) bool { + if len(list) == 0 { + return false + } + for _, b := range list { + if strings.EqualFold(b, a) { + return true + } + } + return false +} + +func (s *systemScanner) Scan() error { + tsocks, err := netstat.TCPSocks(netstat.NoopFilter) + if err != nil { + log.Debugf("Error to get TCP socks : %s", err) + } + usocks, err := netstat.UDPSocks(netstat.NoopFilter) + if err != nil { + log.Debugf("Error to get UDP socks : %s", err) + } + for _, ioc := range s.iocs { + //netCheck(ioc.Dip, ioc.Sip, ioc.Sport, ioc.Dport, ioc.Pname, ioc.State) + for _, e := range tsocks { + //fmt.Printf("%v\n", e) + pid := "unknown" + proc_name := "unknown" + port_src := fmt.Sprintf("%d", e.LocalAddr.Port) + port_dst := fmt.Sprintf("%d", e.RemoteAddr.Port) + uid := fmt.Sprintf("%d", e.UID) + + if !(strings.EqualFold(ioc.Proto, "tcp") || ioc.Proto == "*" || ioc.Proto == "") { + continue + } + if e.Process != nil { + proc_name = fmt.Sprintf("%s", e.Process.Name) + pid = fmt.Sprintf("%d", e.Process.Pid) + if !(stringInSlice(e.Process.Name, ioc.Pname)) { + continue + } + if nstringInSlice(e.Process.Name, ioc.NPname) { + continue + } + } + dip := e.RemoteAddr.IP.String() + if !(stringInSlice(dip, ioc.Dip)) { + continue + } + sip := e.LocalAddr.IP.String() + if !(stringInSlice(sip, ioc.Sip)) { + continue + } + if !(intInSlice(int(e.LocalAddr.Port), ioc.Sport)) { + continue + } + if !(intInSlice(int(e.RemoteAddr.Port), ioc.Dport)) { + continue + } + state := fmt.Sprintf("%s", e.State) + if !(stringInSlice(state, ioc.State)) { + continue + } + message := fmt.Sprintf("Found netstat rule: %s on TCP %v",ioc.Description, e) + report.AddNetstatInfo("ioc_on_netstat", message, + "rule", ioc.Description, "State", state, "ip_src", sip, "ip_dst", dip, "uid", uid, "PID", pid, "Process", proc_name, "port_dst", port_dst, "port_src", port_src, "proto", "TCP") + } + for _, e := range usocks { + pid := "unknown" + proc_name := "unknown" + port_src := fmt.Sprintf("%d", e.LocalAddr.Port) + port_dst := fmt.Sprintf("%d", e.RemoteAddr.Port) + uid := fmt.Sprintf("%d", e.UID) + //fmt.Printf("%v\n", e) + if !(strings.EqualFold(ioc.Proto, "udp") || ioc.Proto == "*" || ioc.Proto == "") { + continue + } + if e.Process != nil { + proc_name = fmt.Sprintf("%s", e.Process.Name) + pid = fmt.Sprintf("%d", e.Process.Pid) + if !(stringInSlice(e.Process.Name, ioc.Pname)) { + continue + } + if nstringInSlice(e.Process.Name, ioc.NPname) { + continue + } + } + dip := e.RemoteAddr.IP.String() + if !(stringInSlice(dip, ioc.Dip)) { + continue + } + sip := e.LocalAddr.IP.String() + if !(stringInSlice(sip, ioc.Sip)) { + continue + } + if !(intInSlice(int(e.LocalAddr.Port), ioc.Sport)) { + continue + } + if !(intInSlice(int(e.RemoteAddr.Port), ioc.Dport)) { + continue + } + state := fmt.Sprintf("%s", e.State) + if !(stringInSlice(state, ioc.State)) { + continue + } + message := fmt.Sprintf("Found netstat rule: %s on UDP %v",ioc.Description, e) + report.AddNetstatInfo("ioc_on_netstat", message, + "rule", ioc.Description, "State", state, "ip_src", sip, "ip_dst", dip, "uid", uid, "PID", pid, "Process", proc_name, "port_dst", port_dst, "port_src", port_src, "proto", "UDP") + } + } + return nil +} From a4f795a2ab3a6ecd35441b0d6b360da44eb9a2e8 Mon Sep 17 00:00:00 2001 From: Hilko Bengen Date: Wed, 28 Jul 2021 16:22:44 +0200 Subject: [PATCH 2/4] wip: Minor code formatting --- scanner/netscan/netscan.go | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/scanner/netscan/netscan.go b/scanner/netscan/netscan.go index 90e1509..08e046f 100644 --- a/scanner/netscan/netscan.go +++ b/scanner/netscan/netscan.go @@ -140,9 +140,19 @@ func (s *systemScanner) Scan() error { if !(stringInSlice(state, ioc.State)) { continue } - message := fmt.Sprintf("Found netstat rule: %s on TCP %v",ioc.Description, e) - report.AddNetstatInfo("ioc_on_netstat", message, - "rule", ioc.Description, "State", state, "ip_src", sip, "ip_dst", dip, "uid", uid, "PID", pid, "Process", proc_name, "port_dst", port_dst, "port_src", port_src, "proto", "TCP") + message := fmt.Sprintf("Found netstat rule: %s on TCP %v", ioc.Description, e) + report.AddNetstatInfo( + "ioc_on_netstat", message, + "rule", ioc.Description, + "State", state, + "ip_src", sip, + "ip_dst", dip, + "uid", uid, + "PID", pid, + "Process", proc_name, + "port_dst", port_dst, + "port_src", port_src, + "proto", "TCP") } for _, e := range usocks { pid := "unknown" @@ -182,9 +192,19 @@ func (s *systemScanner) Scan() error { if !(stringInSlice(state, ioc.State)) { continue } - message := fmt.Sprintf("Found netstat rule: %s on UDP %v",ioc.Description, e) - report.AddNetstatInfo("ioc_on_netstat", message, - "rule", ioc.Description, "State", state, "ip_src", sip, "ip_dst", dip, "uid", uid, "PID", pid, "Process", proc_name, "port_dst", port_dst, "port_src", port_src, "proto", "UDP") + message := fmt.Sprintf("Found netstat rule: %s on UDP %v", ioc.Description, e) + report.AddNetstatInfo( + "ioc_on_netstat", message, + "rule", ioc.Description, + "State", state, + "ip_src", sip, + "ip_dst", dip, + "uid", uid, + "PID", pid, + "Process", proc_name, + "port_dst", port_dst, + "port_src", port_src, + "proto", "UDP") } } return nil From 4d45d1efd10c98b8e246db35d1159e586a288a16 Mon Sep 17 00:00:00 2001 From: Hilko Bengen Date: Wed, 28 Jul 2021 16:25:09 +0200 Subject: [PATCH 3/4] wip: fix compile, worry about ioc.json later --- go.mod | 1 + go.sum | 2 + .../{netscan_windows.go => netscan.go} | 2 +- module_config/netscan_linux.go | 3 -- scanner/netscan/netscan.go | 39 ++++++++++--------- 5 files changed, 25 insertions(+), 22 deletions(-) rename module_config/{netscan_windows.go => netscan.go} (75%) delete mode 100644 module_config/netscan_linux.go diff --git a/go.mod b/go.mod index e25b3c7..f9fe8e5 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/spyre-project/spyre go 1.11 require ( + github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 github.com/hillu/go-archive-zip-crypto v0.0.0-20200712202847-bd5cf365dd44 github.com/hillu/go-ntdll v0.0.0-20210404124636-a6f426aa8d92 github.com/hillu/go-yara/v4 v4.1.0 diff --git a/go.sum b/go.sum index c1f2550..011007d 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/PuerkitoBio/goquery v1.6.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 h1:BjkPE3785EwPhhyuFkbINB+2a1xATwk8SNDWnJiD41g= +github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5/go.mod h1:jtAfVaU/2cu1+wdSRPWE2c1N2qeAA3K4RH9pYgqwets= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/hillu/go-archive-zip-crypto v0.0.0-20200712202847-bd5cf365dd44 h1:seZZ+imS3iv6i0op0cqgt9K6BETRkXwUV9e8inrF+ws= github.com/hillu/go-archive-zip-crypto v0.0.0-20200712202847-bd5cf365dd44/go.mod h1:xUc/S/HgVuP6lhDvD0Wqtu/Npwx93VSXzbdyEDbqBb8= diff --git a/module_config/netscan_windows.go b/module_config/netscan.go similarity index 75% rename from module_config/netscan_windows.go rename to module_config/netscan.go index e322c80..f607a38 100644 --- a/module_config/netscan_windows.go +++ b/module_config/netscan.go @@ -1,4 +1,4 @@ -// +build amd64 +// +build linux,windows package config diff --git a/module_config/netscan_linux.go b/module_config/netscan_linux.go deleted file mode 100644 index c6bcfc6..0000000 --- a/module_config/netscan_linux.go +++ /dev/null @@ -1,3 +0,0 @@ -package config - -import _ "github.com/spyre-project/spyre/scanner/netscan" diff --git a/scanner/netscan/netscan.go b/scanner/netscan/netscan.go index 08e046f..d3217d9 100644 --- a/scanner/netscan/netscan.go +++ b/scanner/netscan/netscan.go @@ -1,4 +1,4 @@ -// +build linux amd64 +// +build linux windows package netscan @@ -35,22 +35,25 @@ type iocFile struct { Keys []eventIOC `json:"netstat"` } -func (s *systemScanner) Name() string { return "Netstat" } +func (s *systemScanner) FriendlyName() string { return "Netstat" } +func (s *systemScanner) ShortName() string { return "netstat" } -func (s *systemScanner) Init() error { - iocFiles := config.IocFiles - if len(iocFiles) == 0 { - iocFiles = []string{"ioc.json"} - } - for _, file := range iocFiles { - var current iocFile - if err := config.ReadIOCs(file, ¤t); err != nil { - log.Error(err.Error()) +func (s *systemScanner) Init(*config.ScannerConfig) error { + /* + iocFiles := config.IocFiles + if len(iocFiles) == 0 { + iocFiles = []string{"ioc.json"} } - for _, ioc := range current.Keys { - s.iocs = append(s.iocs, ioc) + for _, file := range iocFiles { + var current iocFile + if err := config.ReadIOCs(file, ¤t); err != nil { + log.Error(err.Error()) + } + for _, ioc := range current.Keys { + s.iocs = append(s.iocs, ioc) + } } - } + */ return nil } @@ -116,8 +119,8 @@ func (s *systemScanner) Scan() error { proc_name = fmt.Sprintf("%s", e.Process.Name) pid = fmt.Sprintf("%d", e.Process.Pid) if !(stringInSlice(e.Process.Name, ioc.Pname)) { - continue - } + continue + } if nstringInSlice(e.Process.Name, ioc.NPname) { continue } @@ -168,8 +171,8 @@ func (s *systemScanner) Scan() error { proc_name = fmt.Sprintf("%s", e.Process.Name) pid = fmt.Sprintf("%d", e.Process.Pid) if !(stringInSlice(e.Process.Name, ioc.Pname)) { - continue - } + continue + } if nstringInSlice(e.Process.Name, ioc.NPname) { continue } From 0d65c2351590fd40181362dab397e3a3aef0cf22 Mon Sep 17 00:00:00 2001 From: Hilko Bengen Date: Wed, 28 Jul 2021 19:18:34 +0200 Subject: [PATCH 4/4] wip: Read IOCs from YAML --- scanner/netscan/netscan.go | 54 ++++++++++++++------------------------ 1 file changed, 19 insertions(+), 35 deletions(-) diff --git a/scanner/netscan/netscan.go b/scanner/netscan/netscan.go index d3217d9..fe262e0 100644 --- a/scanner/netscan/netscan.go +++ b/scanner/netscan/netscan.go @@ -16,44 +16,28 @@ import ( func init() { scanner.RegisterSystemScanner(&systemScanner{}) } type systemScanner struct { - iocs []eventIOC + IOCs map[string]eventIOC `yaml:"iocs"` } type eventIOC struct { - Dip []string `json:"dip"` - Sip []string `json:"sip"` - Sport []int `json:"sport"` - Dport []int `json:"dport"` - Pname []string `json:"pname"` - NPname []string `json:"notpname"` - State []string `json:"state"` - Proto string `json:"proto"` - Description string `json:"description"` -} - -type iocFile struct { - Keys []eventIOC `json:"netstat"` + Dip []string `yaml:"dip"` + Sip []string `yaml:"sip"` + Sport []int `yaml:"sport"` + Dport []int `yaml:"dport"` + Pname []string `yaml:"pname"` + NPname []string `yaml:"notpname"` + State []string `yaml:"state"` + Proto string `yaml:"proto"` } func (s *systemScanner) FriendlyName() string { return "Netstat" } func (s *systemScanner) ShortName() string { return "netstat" } -func (s *systemScanner) Init(*config.ScannerConfig) error { - /* - iocFiles := config.IocFiles - if len(iocFiles) == 0 { - iocFiles = []string{"ioc.json"} - } - for _, file := range iocFiles { - var current iocFile - if err := config.ReadIOCs(file, ¤t); err != nil { - log.Error(err.Error()) - } - for _, ioc := range current.Keys { - s.iocs = append(s.iocs, ioc) - } - } - */ +func (s *systemScanner) Init(c *config.ScannerConfig) error { + if err := c.Config.Decode(s); err != nil { + return err + } + log.Debugf("%s: Initialized %d rules", s.ShortName(), len(s.IOCs)) return nil } @@ -102,7 +86,7 @@ func (s *systemScanner) Scan() error { if err != nil { log.Debugf("Error to get UDP socks : %s", err) } - for _, ioc := range s.iocs { + for description, ioc := range s.IOCs { //netCheck(ioc.Dip, ioc.Sip, ioc.Sport, ioc.Dport, ioc.Pname, ioc.State) for _, e := range tsocks { //fmt.Printf("%v\n", e) @@ -143,10 +127,10 @@ func (s *systemScanner) Scan() error { if !(stringInSlice(state, ioc.State)) { continue } - message := fmt.Sprintf("Found netstat rule: %s on TCP %v", ioc.Description, e) + message := fmt.Sprintf("Found netstat rule: %s on TCP %v", description, e) report.AddNetstatInfo( "ioc_on_netstat", message, - "rule", ioc.Description, + "rule", description, "State", state, "ip_src", sip, "ip_dst", dip, @@ -195,10 +179,10 @@ func (s *systemScanner) Scan() error { if !(stringInSlice(state, ioc.State)) { continue } - message := fmt.Sprintf("Found netstat rule: %s on UDP %v", ioc.Description, e) + message := fmt.Sprintf("Found netstat rule: %s on UDP %v", description, e) report.AddNetstatInfo( "ioc_on_netstat", message, - "rule", ioc.Description, + "rule", description, "State", state, "ip_src", sip, "ip_dst", dip,