diff --git a/apiclient.go b/apiclient.go index edb8034..5b437e0 100644 --- a/apiclient.go +++ b/apiclient.go @@ -17,6 +17,7 @@ type DNSRecord struct { type Tool struct { Name string `json:"name"` SHA256 string `json:"sha256"` + Parent *Tool `json:"parent"` } type FileEvent struct { @@ -59,7 +60,7 @@ func (apiclient *ApiClient) sendDNSRecord(correlationId, repo, domainName, ipAdd return apiclient.sendApiRequest("POST", url, dnsRecord) } -func (apiclient *ApiClient) sendNetConnection(correlationId, repo, ipAddress, port, status string, timestamp time.Time, tool, toolChecksum string) error { +func (apiclient *ApiClient) sendNetConnection(correlationId, repo, ipAddress, port, status string, timestamp time.Time, tool Tool) error { networkConnection := &NetworkConnection{} @@ -67,20 +68,20 @@ func (apiclient *ApiClient) sendNetConnection(correlationId, repo, ipAddress, po networkConnection.Port = port networkConnection.Status = status networkConnection.TimeStamp = timestamp - networkConnection.Tool = Tool{Name: tool, SHA256: toolChecksum} + networkConnection.Tool = tool url := fmt.Sprintf("%s/github/%s/actions/jobs/%s/networkconnection", apiclient.APIURL, repo, correlationId) return apiclient.sendApiRequest("POST", url, networkConnection) } -func (apiclient *ApiClient) sendFileEvent(correlationId, repo, fileType string, timestamp time.Time, tool, toolChecksum string) error { +func (apiclient *ApiClient) sendFileEvent(correlationId, repo, fileType string, timestamp time.Time, tool Tool) error { fileEvent := &FileEvent{} fileEvent.FileType = fileType fileEvent.TimeStamp = timestamp - fileEvent.Tool = Tool{Name: tool, SHA256: toolChecksum} + fileEvent.Tool = tool url := fmt.Sprintf("%s/github/%s/actions/jobs/%s/fileevent", apiclient.APIURL, repo, correlationId) diff --git a/apiclient_test.go b/apiclient_test.go index abf3740..1b694fd 100644 --- a/apiclient_test.go +++ b/apiclient_test.go @@ -81,8 +81,7 @@ func Test_sendNetConnection(t *testing.T) { port string status string timestamp time.Time - tool string - toolChecksum string + tool Tool } apiclient := &ApiClient{Client: &http.Client{}, APIURL: agentApiBaseUrl} @@ -102,7 +101,8 @@ func Test_sendNetConnection(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := apiclient.sendNetConnection(tt.args.correlationId, tt.args.repo, tt.args.ipAddress, tt.args.port, tt.args.status, tt.args.timestamp, tt.args.tool, tt.args.toolChecksum); (err != nil) != tt.wantErr { + if err := apiclient.sendNetConnection(tt.args.correlationId, tt.args.repo, tt.args.ipAddress, + tt.args.port, tt.args.status, tt.args.timestamp, tt.args.tool); (err != nil) != tt.wantErr { t.Errorf("sendNetConnection() error = %v, wantErr %v", err, tt.wantErr) } }) diff --git a/eventhandler.go b/eventhandler.go index 1aeeb34..f26cf08 100644 --- a/eventhandler.go +++ b/eventhandler.go @@ -26,6 +26,7 @@ type EventHandler struct { ProcessMap map[string]*Process netMutex sync.RWMutex fileMutex sync.RWMutex + procMutex sync.RWMutex } var classAPrivateSubnet, classBPrivateSubnet, classCPrivateSubnet, loopBackSubnet *net.IPNet @@ -56,18 +57,27 @@ func (eventHandler *EventHandler) handleFileEvent(event *Event) { } if fileType != "" { - toolChecksum, _ := getProgramChecksum(event.Exe) - exe := filepath.Base(event.Exe) - eventHandler.ApiClient.sendFileEvent(eventHandler.CorrelationId, eventHandler.Repo, fileType, event.Timestamp, exe, toolChecksum) + tool := *eventHandler.GetToolChain(event.PPid, event.Exe) + eventHandler.ApiClient.sendFileEvent(eventHandler.CorrelationId, eventHandler.Repo, fileType, event.Timestamp, tool) eventHandler.ProcessFileMap[event.Pid] = true } } eventHandler.fileMutex.Unlock() } -func (eventHandler *EventHandler) handleProcessEvent() { +func (eventHandler *EventHandler) handleProcessEvent(event *Event) { + eventHandler.procMutex.Lock() + + _, found := eventHandler.ProcessMap[event.Pid] + + if !found { + eventHandler.ProcessMap[event.Pid] = &Process{PID: event.Pid, PPid: event.PPid, Exe: event.Exe, Arguments: event.ProcessArguments} + } + + eventHandler.procMutex.Unlock() } + func (eventHandler *EventHandler) handleNetworkEvent(event *Event) { eventHandler.netMutex.Lock() @@ -82,23 +92,18 @@ func (eventHandler *EventHandler) handleNetworkEvent(event *Event) { if !found { //writeLog(fmt.Sprintf("handleNetworkEvent %v", event)) + tool := Tool{} image := GetContainerByPid(event.Pid) - checksum := "" - exe := "" if image == "" { - if event.Exe != "" { - checksum, _ = getProgramChecksum(event.Exe) - + tool = *eventHandler.GetToolChain(event.PPid, event.Exe) } - exe = filepath.Base(event.Exe) + } else { - event.Exe = image - checksum = image - exe = image + tool = Tool{Name: image, SHA256: image} // TODO: Set container image checksum } - eventHandler.ApiClient.sendNetConnection(eventHandler.CorrelationId, eventHandler.Repo, event.IPAddress, event.Port, "", event.Timestamp, exe, checksum) + eventHandler.ApiClient.sendNetConnection(eventHandler.CorrelationId, eventHandler.Repo, event.IPAddress, event.Port, "", event.Timestamp, tool) eventHandler.ProcessConnectionMap[cacheKey] = true } } @@ -113,7 +118,7 @@ func (eventHandler *EventHandler) HandleEvent(event *Event) { case fileMonitorTag: eventHandler.handleFileEvent(event) case processMonitorTag: - eventHandler.handleProcessEvent() + eventHandler.handleProcessEvent(event) } } @@ -160,6 +165,34 @@ func getProgramChecksum(path string) (string, error) { return fmt.Sprintf("%x", h.Sum(nil)), nil } +func (eventHandler *EventHandler) GetToolChain(ppid, exe string) *Tool { + checksum, _ := getProgramChecksum(exe) + tool := Tool{Name: filepath.Base(exe), SHA256: checksum} + + // In some cases the process has already exited, so get from map first + parentProcess, found := eventHandler.ProcessMap[ppid] + + if found { + tool.Parent = eventHandler.GetToolChain(parentProcess.PPid, parentProcess.Exe) + return &tool + } + + // If not in map, may be long running, so get from OS + parentProcessId, err := getParentProcessId(ppid) + if err != nil { + return &tool + } + + path, err := getProcessExe(ppid) + if err != nil { + return &tool + } + + tool.Parent = eventHandler.GetToolChain(fmt.Sprintf("%d", parentProcessId), path) + + return &tool +} + func isPrivateIPAddress(ipAddress string) bool { if classAPrivateSubnet == nil { _, classAPrivateSubnet, _ = net.ParseCIDR(classAPrivateAddressRange) diff --git a/go.mod b/go.mod index f8e32ff..9cb9107 100644 --- a/go.mod +++ b/go.mod @@ -36,8 +36,6 @@ require ( github.com/docker/docker v20.10.9+incompatible github.com/google/go-cmp v0.5.6 // indirect github.com/mdlayher/netlink v1.1.0 // indirect - golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b // indirect golang.org/x/net v0.0.0-20210326060303-6b1517762897 // indirect - golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect ) diff --git a/go.sum b/go.sum index c9451be..f8c056b 100644 --- a/go.sum +++ b/go.sum @@ -431,8 +431,6 @@ github.com/mdlayher/netlink v0.0.0-20190409211403-11939a169225/go.mod h1:eQB3mZE github.com/mdlayher/netlink v1.0.0/go.mod h1:KxeJAFOFLG6AjpyDkQ/iIhxygIUKD+vcwqcnu43w/+M= github.com/mdlayher/netlink v1.1.0 h1:mpdLgm+brq10nI9zM1BpX1kpDbh3NLl3RSnVq6ZSkfg= github.com/mdlayher/netlink v1.1.0/go.mod h1:H4WCitaheIsdF9yOYu8CFmCgQthAPIWZmcKp9uZHgmY= -github.com/miekg/dns v1.1.25 h1:dFwPR6SfLtrSwgDcIq2bcU/gVutB4sNApq2HBdqcakg= -github.com/miekg/dns v1.1.25/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg= github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= @@ -639,14 +637,11 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b h1:7mWr3k41Qtv8XlltBkDkl8LoP3mpSgBW8BUoxtEdbXg= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -697,7 +692,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -757,8 +751,6 @@ golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -833,7 +825,6 @@ golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgw golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= diff --git a/netmon.go b/netmon.go index 83760d0..60dfa96 100644 --- a/netmon.go +++ b/netmon.go @@ -10,6 +10,8 @@ import ( "github.com/pkg/errors" ) +const Unknown = "Unknown" + type NetworkMonitor struct { CorrelationId string Repo string @@ -88,7 +90,7 @@ func (netMonitor *NetworkMonitor) handlePacket(attrs nflog.Attribute) { if isSYN { netMonitor.ApiClient.sendNetConnection(netMonitor.CorrelationId, netMonitor.Repo, - ipv4.DstIP.String(), port, netMonitor.Status, timestamp, "Unknown", "Unknown") + ipv4.DstIP.String(), port, netMonitor.Status, timestamp, Tool{Name: Unknown, SHA256: Unknown}) } } diff --git a/procmon.go b/procmon.go index a36e8a4..40ce3a1 100644 --- a/procmon.go +++ b/procmon.go @@ -24,6 +24,7 @@ type ProcessMonitor struct { type Process struct { PID string + PPid string Exe string WorkingDirectory string Arguments []string diff --git a/procmon_darwin.go b/procmon_darwin.go index c235091..38f01ee 100644 --- a/procmon_darwin.go +++ b/procmon_darwin.go @@ -3,6 +3,17 @@ package main +import ( + "fmt" +) + func (p *ProcessMonitor) MonitorProcesses(errc chan error) { writeLog("Monitor Processes called") } + +func getParentProcessId(pid string) (int, error) { + return -1, fmt.Errorf("not implemented") +} +func getProcessExe(pid string) (string, error) { + return "", fmt.Errorf("not implemented") +} diff --git a/procmon_linux.go b/procmon_linux.go index de87a75..66d8cac 100644 --- a/procmon_linux.go +++ b/procmon_linux.go @@ -11,6 +11,9 @@ import ( "github.com/elastic/go-libaudit/v2/rule" "github.com/elastic/go-libaudit/v2/rule/flags" "github.com/pkg/errors" + "io/ioutil" + "os" + "strings" ) func (p *ProcessMonitor) MonitorProcesses(errc chan error) { @@ -139,3 +142,37 @@ func (p *ProcessMonitor) receive(r *libaudit.AuditClient) error { } } + +func getParentProcessId(pid string) (int, error) { + statPath := fmt.Sprintf("/proc/%s/stat", pid) + dataBytes, err := ioutil.ReadFile(statPath) + if err != nil { + return -1, err + } + + data := string(dataBytes) + binStart := strings.IndexRune(data, '(') + 1 + binEnd := strings.IndexRune(data[binStart:], ')') + + var ppid, pgrp, sid int + var state rune + + // Move past the image name and start parsing the rest + data = data[binStart+binEnd+2:] + _, err = fmt.Sscanf(data, + "%c %d %d %d", + &state, + &ppid, + &pgrp, + &sid) + + return ppid, err +} + +func getProcessExe(pid string) (string, error) { + path, err := os.Readlink(fmt.Sprintf("/proc/%s/exe", pid)) + if err != nil { + return "", err + } + return path, nil +}