diff --git a/daemon/conman/connection.go b/daemon/conman/connection.go index b91a70160b..9c32c0f10a 100644 --- a/daemon/conman/connection.go +++ b/daemon/conman/connection.go @@ -105,7 +105,7 @@ func newConnectionImpl(nfp *netfilter.Packet, c *Connection, protoType string) ( } else if procmon.MethodIsAudit() { if aevent := audit.GetEventByPid(pid); aevent != nil { audit.Lock.RLock() - c.Process = procmon.NewProcess(pid, aevent.ProcName) + c.Process = procmon.NewProcessEmpty(pid, aevent.ProcName) c.Process.Path = aevent.ProcPath c.Process.ReadCmdline() c.Process.CWD = aevent.ProcDir @@ -117,7 +117,7 @@ func newConnectionImpl(nfp *netfilter.Packet, c *Connection, protoType string) ( c.Process.ReadEnv() c.Process.CleanPath() - procmon.EventsCache.Add(*c.Process) + procmon.EventsCache.Add(c.Process) return c, nil } log.Debug("[auditd conn] PID not found via auditd, falling back to proc") @@ -154,7 +154,7 @@ func newConnectionImpl(nfp *netfilter.Packet, c *Connection, protoType string) ( if pid == os.Getpid() { // return a Process object with our PID, to be able to exclude our own connections // (to the UI on a local socket for example) - c.Process = procmon.NewProcess(pid, "") + c.Process = procmon.NewProcessEmpty(pid, "") return c, nil } @@ -334,5 +334,6 @@ func (c *Connection) Serialize() *protocol.Connection { ProcessEnv: c.Process.Env, ProcessCwd: c.Process.CWD, ProcessChecksums: c.Process.Checksums, + ProcessTree: c.Process.Tree, } } diff --git a/daemon/procmon/activepids.go b/daemon/procmon/activepids.go index 8df86e9f7d..2d9885bca3 100644 --- a/daemon/procmon/activepids.go +++ b/daemon/procmon/activepids.go @@ -30,26 +30,22 @@ func MonitorProcEvents(stop <-chan struct{}) { if ev.IsExec() { // we don't receive the path of the process, therefore we need to discover it, // to check if the PID has replaced the PPID. - proc := NewProcess(int(ev.PID), "") - proc.GetInfo() - proc.Parent = NewProcess(int(ev.TGID), "") - proc.Parent.GetInfo() + proc := NewProcessWithParent(int(ev.PID), int(ev.TGID), "") log.Debug("[procmon exec event] %d, pid:%d tgid:%d %s, %s -> %s\n", ev.TimeStamp, ev.PID, ev.TGID, proc.Comm, proc.Path, proc.Parent.Path) - //log.Debug("[procmon exec event] %d, pid:%d tgid:%d\n", ev.TimeStamp, ev.PID, ev.TGID) - if _, needsHashUpdate, found := EventsCache.IsInStore(int(ev.PID), proc); found { - // check if this PID has replaced the PPID: - // systemd, pid:1234 -> curl, pid:1234 -> curl (i.e.: pid 1234) opens x.x.x.x:443 - // Without this, we would display that systemd is connecting to x.x.x.x:443 - // The previous pid+path will still exist as parent of the new child, in proc.Parent - if needsHashUpdate { - //log.Debug("[procmon inCache REPLACEMENT] rehashing, new: %d, %s -> inCache: %d -> %s", proc.ID, proc.Path, item.Proc.ID, item.Proc.Path) + if _, needsUpdate, found := EventsCache.IsInStore(int(ev.PID), proc); found { + if needsUpdate { EventsCache.ComputeChecksums(proc) + EventsCache.UpdateItemDetails(proc) } log.Debug("[procmon exec event inCache] %d, pid:%d tgid:%d\n", ev.TimeStamp, ev.PID, ev.TGID) continue } - EventsCache.Add(*proc) + // adding item to cache in 2 steps: + // 1. with basic information, to have it readily available + // 2. getting the rest of the process details + EventsCache.Add(proc) + EventsCache.UpdateItemDetails(proc) } else if ev.IsExit() { p, _, found := EventsCache.IsInStore(int(ev.PID), nil) if found && p.Proc.IsAlive() == false { diff --git a/daemon/procmon/cache_events.go b/daemon/procmon/cache_events.go index 081da09acc..5c2d038795 100644 --- a/daemon/procmon/cache_events.go +++ b/daemon/procmon/cache_events.go @@ -37,7 +37,8 @@ type ProcessEvent struct { // ExecEventItem represents an item of the cache type ExecEventItem struct { - Proc Process + sync.RWMutex + Proc *Process LastSeen int64 TTL int32 } @@ -56,7 +57,6 @@ type EventsStore struct { eventByPath map[string]*ExecEventItem checksums map[string]uint mu *sync.RWMutex - hashed uint64 checksumsEnabled bool } @@ -69,54 +69,50 @@ func NewEventsStore() *EventsStore { return &EventsStore{ mu: &sync.RWMutex{}, - checksums: make(map[string]uint), - eventByPID: make(map[int]*ExecEventItem), - eventByPath: make(map[string]*ExecEventItem), + checksums: make(map[string]uint, 5000), + eventByPID: make(map[int]*ExecEventItem, 5000), + eventByPath: make(map[string]*ExecEventItem, 5000), } } // Add adds a new process to cache. // If computing checksums is enabled, new checksums will be computed if needed, // or reused existing ones otherwise. -func (e *EventsStore) Add(proc Process) { +func (e *EventsStore) Add(proc *Process) { log.Debug("[cache] EventsStore.Add() %d, %s", proc.ID, proc.Path) + // add the item to cache ASAP + // then calculate the checksums if needed. + e.UpdateItem(proc) if e.GetComputeChecksums() { - e.ComputeChecksums(&proc) + e.ComputeChecksums(proc) + e.UpdateItem(proc) } - - e.updateItem(&proc) } -func (e *EventsStore) updateItem(proc *Process) { - log.Debug("[cache] updateItem() adding to events store (total: %d, hashed:%d), pid: %d, paths: %s", e.Len(), e.hashed, proc.ID, proc.Path) +// UpdateItem updates a cache item +func (e *EventsStore) UpdateItem(proc *Process) { + log.Debug("[cache] updateItem() adding to events store (total: %d), pid: %d, paths: %s", e.Len(), proc.ID, proc.Path) if proc.Path == "" { return } - - e.mu.Lock() - defer e.mu.Unlock() - ev := &ExecEventItem{ - Proc: *proc, + Proc: proc, LastSeen: time.Now().UnixNano(), } + e.mu.Lock() e.eventByPID[proc.ID] = ev e.eventByPath[proc.Path] = ev + e.mu.Unlock() } // IsInStore checks if a PID is in the store. // If the PID is in cache, we may need to update it if the PID // is reusing the PID of the parent. -func (e *EventsStore) IsInStore(key int, proc *Process) (item *ExecEventItem, needsHashUpdate bool, found bool) { - //fmt.Printf("IsInStore()\n") +func (e *EventsStore) IsInStore(key int, proc *Process) (item *ExecEventItem, needsUpdate bool, found bool) { item, found = e.IsInStoreByPID(key) if !found { return } - /*if e.checksumsEnabled && len(item.Proc.Checksums) == 0 { - log.Info("RECALCULATING STORED item: %s", item.Proc.Path) - item.Proc.ComputeChecksums(e.checksums) - }*/ log.Debug("[cache] Event found by PID: %d, %s", key, item.Proc.Path) // check if this PID has replaced the PPID: @@ -126,9 +122,8 @@ func (e *EventsStore) IsInStore(key int, proc *Process) (item *ExecEventItem, ne // The previous pid+path will still exist as parent of the new child, in proc.Parent if proc != nil && proc.Path != "" && item.Proc.Path != proc.Path { log.Debug("[event inCache, replacement] new: %d, %s -> inCache: %d -> %s", proc.ID, proc.Path, item.Proc.ID, item.Proc.Path) - //e.ComputeChecksums(proc) - e.updateItem(proc) - needsHashUpdate = true + //e.UpdateItem(proc) + needsUpdate = true } return @@ -137,19 +132,19 @@ func (e *EventsStore) IsInStore(key int, proc *Process) (item *ExecEventItem, ne // IsInStoreByPID checks if a pid exists in cache. func (e *EventsStore) IsInStoreByPID(key int) (item *ExecEventItem, found bool) { e.mu.RLock() - defer e.mu.RUnlock() item, found = e.eventByPID[key] + e.mu.RUnlock() return } // IsInStoreByPath checks if a process exists in cache by path. func (e *EventsStore) IsInStoreByPath(path string) (item *ExecEventItem, found bool) { - e.mu.RLock() - defer e.mu.RUnlock() if path == "" || path == KernelConnection { return } + e.mu.RLock() item, found = e.eventByPath[path] + e.mu.RUnlock() if found { log.Debug("[cache] event found by path: %s", path) } @@ -159,14 +154,14 @@ func (e *EventsStore) IsInStoreByPath(path string) (item *ExecEventItem, found b // Delete an item from cache func (e *EventsStore) Delete(key int) { e.mu.Lock() - defer e.mu.Unlock() delete(e.eventByPID, key) + e.mu.Unlock() } // Len returns the number of items in cache. func (e *EventsStore) Len() int { e.mu.RLock() - e.mu.RUnlock() + defer e.mu.RUnlock() return len(e.eventByPID) } @@ -190,6 +185,14 @@ func (e *EventsStore) DeleteOldItems() { } } +func (e *EventsStore) UpdateItemDetails(proc *Process) { + proc.GetParent() + proc.GetTree() + proc.ReadCwd() + proc.ReadEnv() + e.UpdateItem(proc) +} + // ------------------------------------------------------------------------- // TODO: Move to its own package. // A hashing service than runs in background, and accepts paths to hash @@ -210,13 +213,12 @@ func (e *EventsStore) ComputeChecksums(proc *Process) { // and because of this sometimes we don't receive the event of the parent. item, _, found := e.IsInStore(proc.ID, proc) if !found { - //log.Debug("cache.reuseChecksums() %d not inCache, %s", proc.ID, proc.Path) + log.Debug("cache.reuseChecksums() %d not inCache, %s", proc.ID, proc.Path) // if parent path and current path are equal, and the parent is alive, see if we have the hash of the parent path if !proc.IsChild() { - log.Debug("[cache] reuseChecksums() pid not in cache, not child of parent: %d, %s - %d", proc.ID, proc.Path, proc.Starttime) proc.ComputeChecksums(e.checksums) - e.hashed++ + log.Debug("[cache] reuseChecksums() pid not in cache, not child of parent: %d, %s - %d - %v", proc.ID, proc.Path, proc.Starttime, proc.Checksums) return } @@ -244,14 +246,16 @@ func (e *EventsStore) ComputeChecksums(proc *Process) { // pid found in cache // we should check other parameters to see if the pid is really the same process // proc//maps - if len(item.Proc.Checksums) > 0 && (item.Proc.IsAlive() && item.Proc.Path == proc.Path) { - log.Debug("[cache] reuseChecksums() cached PID alive, already hashed:%v, %s new: %s", item.Proc.Checksums, item.Proc.Path, proc.Path) + item.RLock() + checksumsNum := len(item.Proc.Checksums) + item.RUnlock() + if checksumsNum > 0 && (item.Proc.IsAlive() && item.Proc.Path == proc.Path) { + log.Debug("[cache] reuseChecksums() cached PID alive, already hashed: %v, %s new: %s", item.Proc.Checksums, item.Proc.Path, proc.Path) proc.Checksums = item.Proc.Checksums return } log.Debug("[cache] reuseChecksums() PID found inCache, computing hashes: %s new: %s - hashes: |%v<>%v|", item.Proc.Path, proc.Path, item.Proc.Checksums, proc.Checksums) proc.ComputeChecksums(e.checksums) - e.hashed++ } // AddChecksumHash adds a new hash algorithm to compute checksums @@ -270,10 +274,10 @@ func (e *EventsStore) DelChecksumHash(hash string) { e.mu.Unlock() } -// SetComputeChecksums configures if we compute checksums of processes -// They can be disabled for example if there's no rule that requires checksums. +// SetComputeChecksums configures if we compute checksums of processes. +// They will be disabled if there's no rule that requires checksums. // When enabling this functionality, some already stored process may don't have -// the checksums computed, so when enabling compute them. +// the checksums computed yet, so when enabling compute them. func (e *EventsStore) SetComputeChecksums(compute bool) { e.mu.Lock() defer e.mu.Unlock() @@ -293,7 +297,7 @@ func (e *EventsStore) SetComputeChecksums(compute bool) { } } -// DisableChecksums disables computing checksums functionality +// DisableChecksums disables computing checksums functionality. func (e *EventsStore) DisableChecksums() { e.mu.Lock() defer e.mu.Unlock() @@ -302,10 +306,10 @@ func (e *EventsStore) DisableChecksums() { } // GetComputeChecksums returns if computing checksums is enabled or not. -// Disabled -> if there're no rules with checksum field +// Disabled -> if there're no rules with checksum field. // Disabled -> if events monitors are not available. -// TODO: Disabled -> if there were n rules with checksums, but the user delete them, or -// unchecked checksums. +// Disabled -> if the user disables it globally. +// TODO: Disabled -> if there were n rules with checksums, but the user delete them. func (e *EventsStore) GetComputeChecksums() bool { e.mu.RLock() defer e.mu.RUnlock() diff --git a/daemon/procmon/details.go b/daemon/procmon/details.go index 1fc25b47f1..574941d97d 100644 --- a/daemon/procmon/details.go +++ b/daemon/procmon/details.go @@ -2,6 +2,7 @@ package procmon import ( "bufio" + "bytes" "crypto/md5" "crypto/sha1" "encoding/hex" @@ -19,10 +20,10 @@ import ( "github.com/evilsocket/opensnitch/daemon/dns" "github.com/evilsocket/opensnitch/daemon/log" "github.com/evilsocket/opensnitch/daemon/netlink" + "github.com/evilsocket/opensnitch/daemon/ui/protocol" ) var socketsRegex, _ = regexp.Compile(`socket:\[([0-9]+)\]`) -var ppidPattern = regexp.MustCompile(`PPid:\s*(\d+)`) // GetParent obtains the information of this process' parent. func (p *Process) GetParent() { @@ -31,33 +32,58 @@ func (p *Process) GetParent() { return } - data, err := ioutil.ReadFile(p.pathStatus) + // ReadFile + parse = ~40us + data, err := ioutil.ReadFile(p.pathStat) if err != nil { return } - - ppidMatches := ppidPattern.FindStringSubmatch(string(data)) - ppid := 0 - if len(ppidMatches) < 2 { + var ppid int + var state string + // https://lore.kernel.org/lkml/tog7cb$105a$1@ciao.gmane.io/T/ + parts := bytes.Split(data, []byte(")")) + data = parts[len(parts)-1] + _, err = fmt.Sscanf(string(data), "%s %d", &state, &ppid) + if err != nil || ppid == 0 { return } - ppid, err = strconv.Atoi(ppidMatches[1]) - if err != nil { - return - } - if ppid == 0 { - return + if item, found := EventsCache.IsInStoreByPID(ppid); found { + p.mu.Lock() + p.Parent = item.Proc + p.mu.Unlock() + + EventsCache.UpdateItem(p) + } else { + p.Parent = NewProcessEmpty(ppid, "") + p.Parent.ReadPath() + EventsCache.Add(p.Parent) } - p.Parent = NewProcess(ppid, "") - p.Parent.GetInfo() // get process tree - //p.Parent.GetParent() + p.Parent.GetParent() +} + +// GetTree returns all the parents of this process. +func (p *Process) GetTree() { + if len(p.Tree) > 0 { + fmt.Println("GetTree not empty:", p.Tree) + } + p.mu.Lock() + p.Tree = make([]*protocol.StringInt, 0) + for pp := p.Parent; pp != nil; pp = pp.Parent { + // add the parents in reverse order, so when we iterate over them with the rules + // the first item is the most direct parent of the process. + p.Tree = append(p.Tree, + &protocol.StringInt{ + Key: pp.Path, Value: uint32(pp.ID), + }, + ) + } + p.mu.Unlock() } // GetInfo collects information of a process. -func (p *Process) GetInfo() error { +func (p *Process) GetDetails() error { if os.Getpid() == p.ID { return nil } @@ -73,14 +99,14 @@ func (p *Process) GetInfo() error { return fmt.Errorf("Unable to get process information") } } - p.ReadCmdline() - p.ReadComm() - p.ReadCwd() - if err := p.ReadPath(); err != nil { log.Debug("GetInfo() path can't be read: %s", p.Path) return err } + p.ReadCmdline() + p.ReadComm() + p.ReadCwd() + // we need to load the env variables now, in order to be used with the rules. p.ReadEnv() @@ -119,7 +145,9 @@ func (p *Process) ReadCwd() error { if err != nil { return err } + p.mu.Lock() p.CWD = link + p.mu.Unlock() return nil } @@ -158,7 +186,7 @@ func (p *Process) ReadPath() error { if p.Path == "" { // determine if this process might be of a kernel task. if data, err := ioutil.ReadFile(p.pathMaps); err == nil && len(data) == 0 { - p.Path = "Kernel connection" + p.Path = KernelConnection p.Args = append(p.Args, p.Comm) return } @@ -197,22 +225,22 @@ func (p *Process) ReadCmdline() { if len(p.Args) > 0 { return } - if data, err := ioutil.ReadFile(p.pathCmdline); err == nil { - if len(data) == 0 { - return - } - for i, b := range data { - if b == 0x00 { - data[i] = byte(' ') - } + data, err := ioutil.ReadFile(p.pathCmdline) + if err != nil || len(data) == 0 { + return + } + // XXX: remove this loop, and split by "\x00" + for i, b := range data { + if b == 0x00 { + data[i] = byte(' ') } + } - args := strings.Split(string(data), " ") - for _, arg := range args { - arg = core.Trim(arg) - if arg != "" { - p.Args = append(p.Args, arg) - } + args := strings.Split(string(data), " ") + for _, arg := range args { + arg = core.Trim(arg) + if arg != "" { + p.Args = append(p.Args, arg) } } p.CleanArgs() @@ -222,7 +250,7 @@ func (p *Process) ReadCmdline() { // - AppImages cmdline reports the execuable launched as /proc/self/exe, // instead of the actual path to the binary. func (p *Process) CleanArgs() { - if len(p.Args) > 0 && p.Args[0] == "/proc/self/exe" { + if len(p.Args) > 0 && p.Args[0] == ProcSelfExe { p.Args[0] = p.Path } } @@ -326,7 +354,7 @@ func (p *Process) CleanPath() { // This is not useful to the user, and besides it's a generic path that can represent // to any process. // Therefore we cannot use /proc/self/exe directly, because it resolves to our own process. - if p.Path == "/proc/self/exe" { + if p.Path == ProcSelfExe { if link, err := os.Readlink(p.pathExe); err == nil { p.Path = link return @@ -417,7 +445,7 @@ func (p *Process) ComputeChecksum(algo string) { log.Debug("[hashing %s] Unable to open path: %s", algo, paths[i]) // one of the reasons to end here is when hashing AppImages - code, err := p.dumpImage() + code, err := p.DumpImage() if err != nil { log.Debug("[hashing] Unable to dump process memory: %s", err) continue @@ -433,7 +461,9 @@ func (p *Process) ComputeChecksum(algo string) { log.Debug("[hashing %s] Error copying data: %s", algo, err) continue } + p.mu.Lock() p.Checksums[algo] = hex.EncodeToString(h.Sum(nil)) + p.mu.Unlock() log.Debug("[hashing] elapsed: %v ,Hash: %s, %s\n", time.Since(start), p.Checksums[algo], paths[i]) break @@ -448,7 +478,9 @@ type MemoryMapping struct { EndAddr uint64 } -func (p *Process) dumpImage() ([]byte, error) { +// DumpImage reads the memory of the current process, and returns it +// as byte array. +func (p *Process) DumpImage() ([]byte, error) { return p.dumpFileImage(p.Path) } diff --git a/daemon/procmon/ebpf/events.go b/daemon/procmon/ebpf/events.go index fcc8e92736..e4e973a8ef 100644 --- a/daemon/procmon/ebpf/events.go +++ b/daemon/procmon/ebpf/events.go @@ -142,7 +142,7 @@ func initPerfMap(mod *elf.Module) error { } perfMapList[perfMap] = mod - eventWorkers += 4 + eventWorkers += 8 for i := 0; i < eventWorkers; i++ { go streamEventsWorker(i, perfChan, lostEvents, kernelEvents) } @@ -170,14 +170,22 @@ func streamEventsWorker(id int, chn chan []byte, lost chan uint64, kernelEvents continue } // TODO: store multiple executions with the same pid but different paths: forks, execves... - if p, needsHashUpdate, found := procmon.EventsCache.IsInStore(int(event.PID), proc); found { - if needsHashUpdate { + if item, needsUpdate, found := procmon.EventsCache.IsInStore(int(event.PID), proc); found { + if needsUpdate { + // when a process is replaced in memory, it'll be found in cache by PID, + // but the new process' details will be empty + proc.Parent = item.Proc procmon.EventsCache.ComputeChecksums(proc) + procmon.EventsCache.UpdateItemDetails(proc) } - log.Debug("[eBPF event inCache] -> %d, %v", event.PID, p.Proc.Checksums) + log.Debug("[eBPF event inCache] -> %d, %v", event.PID, item.Proc.Checksums) continue } - procmon.EventsCache.Add(*proc) + // adding item to cache in 2 steps: + // 1. with basic information, to have it readily available + // 2. getting the rest of the process details that takes more time + procmon.EventsCache.Add(proc) + procmon.EventsCache.UpdateItemDetails(proc) case EV_TYPE_SCHED_EXIT: log.Debug("[eBPF exit event] total: %d, pid: %d, ppid: %d", 0 /*execEvents.Len()*/, event.PID, event.PPID) @@ -201,7 +209,8 @@ Exit: } func event2process(event *execEvent) (proc *procmon.Process) { - proc = procmon.NewProcess(int(event.PID), byteArrayToString(event.Comm[:])) + proc = procmon.NewProcessEmpty(int(event.PID), byteArrayToString(event.Comm[:])) + proc.UID = int(event.UID) // trust process path received from kernel path := byteArrayToString(event.Filename[:]) if path != "" { @@ -211,10 +220,6 @@ func event2process(event *execEvent) (proc *procmon.Process) { return nil } } - proc.ReadCwd() - proc.ReadEnv() - proc.UID = int(event.UID) - if event.ArgsPartial == 0 { for i := 0; i < int(event.ArgsCount); i++ { proc.Args = append(proc.Args, byteArrayToString(event.Args[i][:])) @@ -223,7 +228,6 @@ func event2process(event *execEvent) (proc *procmon.Process) { } else { proc.ReadCmdline() } - proc.GetParent() log.Debug("[eBPF exec event] ppid: %d, pid: %d, %s -> %s", event.PPID, event.PID, proc.Path, proc.Args) return diff --git a/daemon/procmon/ebpf/find.go b/daemon/procmon/ebpf/find.go index b0a1a54e97..d53e8ce24d 100644 --- a/daemon/procmon/ebpf/find.go +++ b/daemon/procmon/ebpf/find.go @@ -23,14 +23,13 @@ func GetPid(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint return proc, false, nil } if findAddressInLocalAddresses(dstIP) { - // FIXME: systemd-resolved sometimes makes a TCP Fast Open connection to a DNS server (8.8.8.8 on my machine) - // and we get a packet here with **source** (not detination!!!) IP 8.8.8.8 - // Maybe it's an in-kernel response with spoofed IP because resolved's TCP Fast Open packet, nor the response. - // Another scenario when systemd-resolved or dnscrypt-proxy is used, is that every outbound connection has - // the fields swapped: + // NOTE: + // Sometimes every outbound connection has the fields swapped: // 443:public-ip -> local-ip:local-port , like if it was a response (but it's not). // Swapping connection fields helps to identify the connection + pid + process, and continue working as usual - // when systemd-resolved is being used. But we should understand why is this happenning. + // when systemd-resolved is being used. + // This seems to be the case when using conntrack to intercept outbound connections, specially for TCP. + // @see: e090833d29738274c1d171eba53e239c1c49ea7c if proc := getPidFromEbpf(proto, dstPort, dstIP, srcIP, srcPort); proc != nil { return proc, true, fmt.Errorf("[ebpf conn] FIXME: found swapping fields, systemd-resolved is that you? set DNS=x.x.x.x to your DNS server in /etc/systemd/resolved.conf to workaround this problem") @@ -41,7 +40,6 @@ func GetPid(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstPort uint if proto == "tcp" || proto == "tcp6" { if pid, uid, err := findInAlreadyEstablishedTCP(proto, srcPort, srcIP, dstIP, dstPort); err == nil { proc := procmon.NewProcess(pid, "") - proc.GetInfo() proc.UID = uid return proc, false, nil } @@ -104,7 +102,7 @@ func getPidFromEbpf(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstP if cacheItem, isInCache := ebpfCache.isInCache(k); isInCache { // should we re-read the info? // environ vars might have changed - //proc.GetInfo() + //proc.GetDetails() deleteEbpfEntry(proto, unsafe.Pointer(&key[0])) proc = &cacheItem.Proc log.Debug("[ebpf conn] in cache: %s, %d -> %s", k, proc.ID, proc.Path) @@ -158,36 +156,26 @@ func getPidFromEbpf(proto string, srcPort uint, srcIP net.IP, dstIP net.IP, dstP // the rest of the details. // TODO: get the details from kernel, with mm_struct (exe_file, fd_path, etc). func findConnProcess(value *networkEventT, connKey string) (proc *procmon.Process) { - comm := byteArrayToString(value.Comm[:]) - proc = procmon.NewProcess(int(value.Pid), comm) + // Use socket's UID. A process may have dropped privileges. // This is the UID that we've always used. - proc.UID = int(value.UID) if ev, _, found := procmon.EventsCache.IsInStore(int(value.Pid), nil); found { - // use socket's UID. See above why ^ - ev.Proc.UID = proc.UID - if proc.Path == "" { - proc.Path = ev.Proc.Path - proc.Args = ev.Proc.Args - } - if /*err == nil &&*/ ev.Proc.Path != proc.Path { - //proc.ReadCmdline() - ev.Proc.Path = proc.Path - ev.Proc.Args = proc.Args - } - proc = &ev.Proc - // XXX: update cache? - + ev.Lock() + ev.Proc.UID = int(value.UID) + ev.Unlock() + proc = ev.Proc log.Debug("[ebpf conn] not in cache, but in execEvents: %s, %d -> %s -> %s", connKey, proc.ID, proc.Path, proc.Args) - } else { - log.Debug("[ebpf conn] not in cache, NOR in execEvents: %s, %d -> %s -> %s", connKey, proc.ID, proc.Path, proc.Args) - // We'll end here if the events module has not been loaded, or if the process is not in cache. - proc.GetParent() - proc.GetInfo() - procmon.EventsCache.Add(*proc) + return } + // We'll end here if the events module has not been loaded, or if the process is not in cache. + comm := byteArrayToString(value.Comm[:]) + proc = procmon.NewProcess(int(value.Pid), comm) + proc.UID = int(value.UID) + procmon.EventsCache.Add(proc) + log.Debug("[ebpf conn] not in cache, NOR in execEvents: %s, %d -> %s -> %s", connKey, proc.ID, proc.Path, proc.Args) + return } @@ -215,8 +203,8 @@ func findInAlreadyEstablishedTCP(proto string, srcPort uint, srcIP net.IP, dstIP //returns true if addr is in the list of this machine's addresses func findAddressInLocalAddresses(addr net.IP) bool { - lock.Lock() - defer lock.Unlock() + lock.RLock() _, found := localAddresses[addr.String()] + lock.RUnlock() return found } diff --git a/daemon/procmon/parse.go b/daemon/procmon/parse.go index 15b651dcda..205f9fcb9f 100644 --- a/daemon/procmon/parse.go +++ b/daemon/procmon/parse.go @@ -94,19 +94,19 @@ func GetPIDFromINode(inode int, inodeKey string) int { // to identify a process (cmdline, name, environment variables, etc). func FindProcess(pid int, interceptUnknown bool) *Process { if interceptUnknown && pid < 0 { - return NewProcess(0, "") + return NewProcessEmpty(0, "") } if ev, _, found := EventsCache.IsInStore(pid, nil); found { - return &ev.Proc + return ev.Proc } - proc := NewProcess(pid, "") - if err := proc.GetInfo(); err != nil { + proc := NewProcessEmpty(pid, "") + if err := proc.GetDetails(); err != nil { log.Debug("[%d] FindProcess() error: %s", pid, err) return nil } - EventsCache.Add(*proc) + EventsCache.Add(proc) return proc } diff --git a/daemon/procmon/process.go b/daemon/procmon/process.go index f00eb9e49f..cb3d1384a8 100644 --- a/daemon/procmon/process.go +++ b/daemon/procmon/process.go @@ -22,6 +22,7 @@ const ( MethodEbpf = "ebpf" KernelConnection = "Kernel connection" + ProcSelfExe = "/proc/self/exe" HashMD5 = "process.hash.md5" HashSHA1 = "process.hash.sha1" @@ -64,11 +65,14 @@ type Process struct { Checksums map[string]string Env map[string]string Descriptors []*procDescriptors + Tree []*protocol.StringInt Parent *Process IOStats *procIOstats NetStats *procNetStats Statm *procStatm + mu *sync.RWMutex + // Args is the command that the user typed. It MAY contain the absolute path // of the binary: // $ curl https://... @@ -113,23 +117,21 @@ type Process struct { UID int } -// NewProcess returns a new Process structure. -func NewProcess(pid int, comm string) *Process { - +// NewProcessEmpty returns a new Process struct with no details. +func NewProcessEmpty(pid int, comm string) *Process { p := &Process{ + mu: &sync.RWMutex{}, Starttime: time.Now().UnixNano(), ID: pid, Comm: comm, Args: make([]string, 0), Env: make(map[string]string), + Tree: make([]*protocol.StringInt, 0), IOStats: &procIOstats{}, NetStats: &procNetStats{}, Statm: &procStatm{}, Checksums: make(map[string]string), } - if pid <= 0 { - return p - } p.pathProc = fmt.Sprint("/proc/", p.ID) p.pathExe = fmt.Sprint(p.pathProc, "/exe") p.pathCwd = fmt.Sprint(p.pathProc, "/cwd") @@ -148,6 +150,32 @@ func NewProcess(pid int, comm string) *Process { return p } +// NewProcess returns a new Process structure. +func NewProcess(pid int, comm string) *Process { + p := NewProcessEmpty(pid, comm) + if pid <= 0 { + return p + } + p.GetDetails() + p.GetParent() + p.GetTree() + + return p +} + +// NewProcessWithParent returns a new Process structure. +func NewProcessWithParent(pid, ppid int, comm string) *Process { + p := NewProcessEmpty(pid, comm) + if pid <= 0 { + return p + } + p.PPID = ppid + p.GetDetails() + p.Parent = NewProcess(ppid, comm) + + return p +} + //Serialize transforms a Process object to gRPC protocol object func (p *Process) Serialize() *protocol.Process { ioStats := p.IOStats @@ -160,19 +188,20 @@ func (p *Process) Serialize() *protocol.Process { } return &protocol.Process{ - Pid: uint64(p.ID), - Ppid: uint64(p.PPID), - Uid: uint64(p.UID), - Comm: p.Comm, - Path: p.Path, - Args: p.Args, - Env: p.Env, - Cwd: p.CWD, - Checksums: p.Checksums, - IoReads: uint64(ioStats.RChar), - IoWrites: uint64(ioStats.WChar), - NetReads: netStats.ReadBytes, - NetWrites: netStats.WriteBytes, + Pid: uint64(p.ID), + Ppid: uint64(p.PPID), + Uid: uint64(p.UID), + Comm: p.Comm, + Path: p.Path, + Args: p.Args, + Env: p.Env, + Cwd: p.CWD, + Checksums: p.Checksums, + IoReads: uint64(ioStats.RChar), + IoWrites: uint64(ioStats.WChar), + NetReads: netStats.ReadBytes, + NetWrites: netStats.WriteBytes, + ProcessTree: p.Tree, } } diff --git a/daemon/procmon/process_test.go b/daemon/procmon/process_test.go index dd9b2a8941..2528e564b3 100644 --- a/daemon/procmon/process_test.go +++ b/daemon/procmon/process_test.go @@ -7,7 +7,7 @@ import ( var ( myPid = os.Getpid() - proc = NewProcess(myPid, "fakeComm") + proc = NewProcessEmpty(myPid, "fakeComm") ) func TestNewProcess(t *testing.T) { diff --git a/daemon/ui/notifications.go b/daemon/ui/notifications.go index aade00184f..9ad6ec5401 100644 --- a/daemon/ui/notifications.go +++ b/daemon/ui/notifications.go @@ -60,13 +60,18 @@ func (c *Client) getClientConfig() *protocol.ClientConfig { } func (c *Client) monitorProcessDetails(pid int, stream protocol.UI_NotificationsClient, notification *protocol.Notification) { - p := procmon.NewProcess(pid, "") + p := &procmon.Process{} item, found := procmon.EventsCache.IsInStoreByPID(pid) if found { - p = &item.Proc + newProc := *item.Proc + p = &newProc + if len(p.Tree) == 0 { + p.GetParent() + p.GetTree() + } + } else { + p = procmon.NewProcess(pid, "") } - item.Proc.GetParent() - item.Proc.GetInfo() ticker := time.NewTicker(2 * time.Second) for { diff --git a/proto/ui.proto b/proto/ui.proto index cdada51816..19871dbe5f 100644 --- a/proto/ui.proto +++ b/proto/ui.proto @@ -105,6 +105,11 @@ message PingReply { uint64 id = 1; } +message StringInt { + string key = 1; + uint32 value = 2; +} + message Process { uint64 pid = 1; uint64 ppid = 2; @@ -119,6 +124,7 @@ message Process { uint64 io_writes = 11; uint64 net_reads = 12; uint64 net_writes = 13; + repeated StringInt process_tree = 14; } message Connection { @@ -135,6 +141,7 @@ message Connection { repeated string process_args = 11; map process_env = 12; map process_checksums = 13; + repeated StringInt process_tree = 14; } message Operator { diff --git a/ui/opensnitch/dialogs/processdetails.py b/ui/opensnitch/dialogs/processdetails.py index 6c73ef0176..09928debb7 100644 --- a/ui/opensnitch/dialogs/processdetails.py +++ b/ui/opensnitch/dialogs/processdetails.py @@ -166,10 +166,12 @@ def _reset(self): self.comboPids.clear() self.labelProcName.setText(QtCore.QCoreApplication.translate("proc_details", "loading...")) self.labelProcArgs.setText(QtCore.QCoreApplication.translate("proc_details", "loading...")) + self.labelProcPath.setText(QtCore.QCoreApplication.translate("proc_details", "loading...")) self.labelProcIcon.clear() self.labelStatm.setText("") self.labelCwd.setText("") self.labelChecksums.setText("") + self.labelParent.setText("") for tidx in range(0, len(self.TABS)): self.TABS[tidx]['text'].setPlainText("") @@ -246,8 +248,21 @@ def _load_data(self, data): self.labelProcName.setText("" + self._app_name + "") self.labelProcName.setToolTip("" + self._app_name + "") + if 'Tree' in proc: + proc['Tree'].reverse() + self.labelParent.setText( + "Parent(s): " + " 🡆 ".join( + path['key'] for path in proc['Tree'] + ) + ) + else: + self.labelParent.setText("") + if proc['Path'] not in proc['Args']: - proc['Args'].insert(0, "({0}) ".format(proc['Path'])) + self.labelProcPath.setVisible(True) + self.labelProcPath.setText("({0})".format(proc['Path'])) + else: + self.labelProcPath.setVisible(False) if 'Checksums' in proc: checksums = proc['Checksums'] diff --git a/ui/opensnitch/dialogs/prompt.py b/ui/opensnitch/dialogs/prompt.py index a44afc1d6e..7ea2e4cc31 100644 --- a/ui/opensnitch/dialogs/prompt.py +++ b/ui/opensnitch/dialogs/prompt.py @@ -105,10 +105,17 @@ def __init__(self, parent=None, appicon=None): self.checkDstIP.clicked.connect(self._button_clicked) self.checkDstPort.clicked.connect(self._button_clicked) self.checkUserID.clicked.connect(self._button_clicked) + self.cmdInfo.clicked.connect(self._cb_cmdinfo_clicked) + self.cmdBack.clicked.connect(self._cb_cmdback_clicked) self.allowIcon = Icons.new(self, "emblem-default") denyIcon = Icons.new(self, "emblem-important") rejectIcon = Icons.new(self, "window-close") + backIcon = Icons.new(self, "go-previous") + infoIcon = Icons.new(self, "info") + + self.cmdInfo.setIcon(infoIcon) + self.cmdBack.setIcon(backIcon) self._default_action = self._cfg.getInt(self._cfg.DEFAULT_ACTION_KEY) @@ -190,6 +197,7 @@ def _check_advanced_toggled(self, state): self.checkSum.setVisible(self._con.process_checksums[Config.OPERAND_PROCESS_HASH_MD5] != "" and state) self.checksumLabel_2.setVisible(self._con.process_checksums[Config.OPERAND_PROCESS_HASH_MD5] != "" and state) self.checksumLabel.setVisible(self._con.process_checksums[Config.OPERAND_PROCESS_HASH_MD5] != "" and state) + self.stackedWidget.setCurrentIndex(1) self._ischeckAdvanceded = state self.adjust_size() @@ -198,6 +206,14 @@ def _check_advanced_toggled(self, state): def _button_clicked(self): self._stop_countdown() + def _cb_cmdinfo_clicked(self): + self.stackedWidget.setCurrentIndex(0) + self._stop_countdown() + + def _cb_cmdback_clicked(self): + self.stackedWidget.setCurrentIndex(1) + self._stop_countdown() + def _set_elide_text(self, widget, text, max_size=132): if len(text) > max_size: text = text[:max_size] + "..." @@ -251,9 +267,16 @@ def _timeout_worker(self): @QtCore.pyqtSlot() def on_connection_prompt_triggered(self): + self.stackedWidget.setCurrentIndex(1) + # FIXME: scrolling to the top in _render_details doesn't seem to work, + # so do it here until wi figure out why. + xpos = self.connDetails.verticalScrollBar().minimum() + self.connDetails.verticalScrollBar().setValue(xpos) self._render_connection(self._con) if self._tick > 0: self.show() + # render details after displaying the details. + self._render_details(self._con) @QtCore.pyqtSlot() def on_tick_triggered(self): @@ -435,6 +458,51 @@ def _render_connection(self, con): self.setFixedSize(self.size()) + self._post_popup_plugins(con) + + def _render_details(self, con): + tree = "" + space = " " + spaces = " " + indicator = "" + + con.process_tree.reverse() + for path in con.process_tree: + tree = "{0}

│{1}\t{2}{3}{4}

".format(tree, path.value, spaces, indicator, path.key) + spaces += " " * 4 + indicator = "\_ " + + # XXX: table element doesn't work? + details = """{0} {1}:{2} -> {3}:{4} +

+Path:{5}{6}
+Cmdline: {7}
+CWD:{8}{9}
+MD5:{10}{11}
+UID:{12}{13}
+PID:{14}{15}
+
+Process tree:
+{16} +
+

Environment variables:

+{17} +""".format( + con.protocol.upper(), + con.src_port, con.src_ip, con.dst_ip, con.dst_port, + space * 6, con.process_path, + " ".join(con.process_args), + space * 6, con.process_cwd, + space * 7, con.process_checksums[Config.OPERAND_PROCESS_HASH_MD5], + space * 9, con.user_id, + space * 9, con.process_id, + tree, + "".join('

{}={}

'.format(key, value) for key, value in con.process_env.items()) +) + + self.connDetails.document().clear() + self.connDetails.document().setHtml(details) + # https://gis.stackexchange.com/questions/86398/how-to-disable-the-escape-key-for-a-dialog def keyPressEvent(self, event): if not event.key() == QtCore.Qt.Key_Escape: diff --git a/ui/opensnitch/dialogs/ruleseditor.py b/ui/opensnitch/dialogs/ruleseditor.py index 27a828ff0d..243b66e822 100644 --- a/ui/opensnitch/dialogs/ruleseditor.py +++ b/ui/opensnitch/dialogs/ruleseditor.py @@ -87,6 +87,7 @@ def __init__(self, parent=None, _rule=None, appicon=None): self.dstListIPsCheck.toggled.connect(self._cb_dstiplists_check_toggled) self.dstListNetsCheck.toggled.connect(self._cb_dstnetlists_check_toggled) self.uidCombo.currentIndexChanged.connect(self._cb_uid_combo_changed) + self.md5Check.toggled.connect(self._cb_md5check_toggled) self._users_list = pwd.getpwall() @@ -225,6 +226,9 @@ def _cb_dstnetlists_check_toggled(self, state): def _cb_uid_combo_changed(self, index): self.uidCombo.setCurrentText(str(self._users_list[index][self.PW_UID])) + def _cb_md5check_toggled(self, state): + self.md5Line.setEnabled(state) + def _set_status_error(self, msg): self.statusLabel.setStyleSheet('color: red') self.statusLabel.setText(msg) diff --git a/ui/opensnitch/res/process_details.ui b/ui/opensnitch/res/process_details.ui index bb48b191dc..aa0055a1df 100644 --- a/ui/opensnitch/res/process_details.ui +++ b/ui/opensnitch/res/process_details.ui @@ -6,8 +6,8 @@ 0 0 - 731 - 478 + 829 + 556 @@ -58,6 +58,13 @@ + + + + TextLabel + + + @@ -90,7 +97,20 @@ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse @@ -99,11 +119,14 @@ CWD: loading... + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + true - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse @@ -114,8 +137,11 @@ mem stats: loading... + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + - Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse @@ -269,7 +295,7 @@ - .. + ../../../../../../.designer/backup../../../../../../.designer/backup true @@ -283,7 +309,7 @@ - .. + ../../../../../../.designer/backup../../../../../../.designer/backup diff --git a/ui/opensnitch/res/prompt.ui b/ui/opensnitch/res/prompt.ui index cd1bf2a762..2e32982a50 100644 --- a/ui/opensnitch/res/prompt.ui +++ b/ui/opensnitch/res/prompt.ui @@ -2,15 +2,12 @@ Dialog - - Qt::NonModal - 0 0 - 520 - 317 + 570 + 343 @@ -19,40 +16,15 @@ 0 - - - 0 - 200 - - - - - true - - - opensnitch-qt - - - - :/pics/icon-white.png - :/pics/icon.png:/pics/icon-white.png + Dialog - - false - - - - 2 - - - QLayout::SetMinAndMaxSize - + 5 - 5 + 3 5 @@ -60,671 +32,18 @@ 5 - - - - QFormLayout::ExpandingFieldsGrow - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - 0 - - - - - - - - 0 - 0 - - - - - 64 - 64 - - - - - 64 - 64 - - - - - - - :/pics/icon.png - - - true - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - - - - - - - - - - - - - - 0 - - - 5 - - - - - - 0 - 0 - - - - - 16 - 75 - true - true - - - - Chromium Web Browser - - - Qt::PlainText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 10 - 50 - true - false - true - true - - - - <html><head/><body><p>/opt/google/chrome/bin/chrome --something abc --more-long def --for-word-wrapping</p></body></html> - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - (/path/to/bin/chromium) - - - Qt::PlainText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 10 - true - - - - (/path/to/bin/chromium) - - - Qt::PlainText - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - - - - - 0 - 0 - - - - Qt::Horizontal - - - - - - - - 0 - 0 - - - - - 520 - 40 - - - - Chromium Web Browser wants to connect to www.evilsocket.net on tcp port 443. And maybe to www.goodsocket.net on port 344 - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - 5 - - - Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - + + 3 + + + - 6 - - - 3 - - - - - - 0 - 0 - - - - - 10 - 75 - true - true - - - - Destination IP - - - - - - - - - - - - - - - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 10 - true - - - - TextLabel - - - Qt::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 0 - 0 - - - - - 10 - 75 - true - true - - - - Dst Port - - - - - - - Qt::Horizontal - - - - 20 - 20 - - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 10 - true - - - - TextLabel - - - Qt::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 10 - true - - - - - - - Qt::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 10 - true - - - - TextLabel - - - Qt::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 10 - true - - - - TextLabel - - - Qt::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 0 - 0 - - - - - 10 - 75 - true - true - - - - User ID - - - - - - - - 0 - 0 - - - - - 10 - 75 - true - true - - - - Process ID - - - - - - - - 0 - 0 - - - - - 10 - 75 - true - true - - - - <html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html> - - - - - - - - 0 - 0 - - - - 0 - - - - - - - - - - - - - - - 0 - 0 - - - - - 90 - 0 - - - - - 10 - true - - - - TextLabel - - - Qt::PlainText - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - 0 - 0 - - - - - 10 - 75 - true - true - - - - Source IP - - - - - - - - 10 - 75 - true - true - - - - Checksum - - - - - - - - 10 - true - - - - TextLabel - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - - - - - - - - - - - 0 - 0 - - - - - - - - - - - QLayout::SetMinimumSize + 4 - + 0 0 @@ -775,6 +94,12 @@ + + + 0 + 0 + + 97 @@ -834,6 +159,12 @@ + + + 0 + 0 + + 97 @@ -854,7 +185,7 @@ - ../../../../../../.designer/backup../../../../../../.designer/backup + ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup QToolButton::MenuButtonPopup @@ -866,6 +197,12 @@ + + + 0 + 0 + + 97 @@ -883,14 +220,14 @@ - ../../../../../../.designer/backup../../../../../../.designer/backup + ../../../../../../../../../../.designer/backup../../../../../../../../../../.designer/backup - + 0 0 @@ -920,21 +257,794 @@ + + + + 1 + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 0 + + + + + + + + 0 + 0 + + + + + + + + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + 2 + + + 2 + + + 2 + + + 2 + + + 0 + + + + + 4 + + + + + + + + 0 + 0 + + + + + 64 + 64 + + + + + 64 + 64 + + + + + + + :/pics/icon.png + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 0 + 0 + + + + + + + + + + + + + 5 + + + 5 + + + 2 + + + + + + 0 + 0 + + + + + 16 + 75 + true + true + + + + Chromium Web Browser + + + Qt::PlainText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + + + + + + + true + + + + + + + + 0 + 0 + + + + + 10 + 50 + true + false + true + true + + + + <html><head/><body><p>/opt/google/chrome/bin/chrome --something abc --more-long def --for-word-wrapping</p></body></html> + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + (/path/to/bin/chromium) + + + Qt::PlainText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + + 10 + true + + + + (/path/to/bin/chromium) + + + Qt::PlainText + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + 0 + 0 + + + + Qt::Horizontal + + + + + + + 6 + + + 3 + + + + + + 0 + 0 + + + + + 90 + 0 + + + + + 10 + true + + + + TextLabel + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + true + + + + User ID + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + true + + + + Dst Port + + + + + + + + 0 + 0 + + + + + 90 + 0 + + + + + 10 + true + + + + TextLabel + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 10 + true + + + + TextLabel + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + + + + + + 0 + 0 + + + + + 90 + 0 + + + + + 10 + true + + + + + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + true + + + + Process ID + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + true + + + + Destination IP + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + true + + + + Source IP + + + + + + + + + + + + + + + + + + + + + + 0 + 0 + + + + + 10 + 75 + true + true + + + + <html><head/><body><p><span style=" font-weight:600;">Executed from</span></p></body></html> + + + + + + + + 0 + 0 + + + + + 90 + 0 + + + + + 10 + true + + + + TextLabel + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 0 + 0 + + + + 0 + + + + + + + + 0 + 0 + + + + + 90 + 0 + + + + + 10 + true + + + + TextLabel + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + 10 + 75 + true + true + + + + Checksum + + + + + + + + + + + + + + Qt::Horizontal + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 90 + 0 + + + + + 10 + true + + + + TextLabel + + + Qt::PlainText + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + + + 0 + 0 + + + + Chromium Web Browser wants to connect to www.evilsocket.net on tcp port 443. And maybe to www.goodsocket.net on port 344 + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + true + + + 5 + + + Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + checkAdvanced + allowButton actionButton durationCombo whatCombo - whatIPCombo - checkUserID checkDstIP + checkSum + connDetails + checkUserID checkDstPort + whatIPCombo + cmdBack - + +