From 10b1bb7ed65ada8721d6e63a7b6986258d5fb28c Mon Sep 17 00:00:00 2001 From: Evan Hosinski Date: Fri, 10 Oct 2025 15:35:17 -0400 Subject: [PATCH] Implement initial detection and data structures for suspicious artifacts --- cmd/root.go | 109 ++ go.mod | 30 + go.sum | 63 + internal/pkg/hunt/detect/autorun/autorun.go | 170 +++ .../pkg/hunt/detect/autorun/autorun_test.go | 16 + internal/pkg/hunt/detect/binaries/binaries.go | 94 ++ .../pkg/hunt/detect/binaries/binaries_test.go | 12 + internal/pkg/hunt/detect/common/common.go | 1031 +++++++++++++++++ internal/pkg/hunt/detect/common/functions.go | 71 ++ .../pkg/hunt/detect/connections/outbound.go | 192 +++ .../hunt/detect/connections/outbound_test.go | 17 + .../pkg/hunt/detect/directory/directories.go | 36 + .../hunt/detect/directory/directories_test.go | 12 + .../pkg/hunt/detect/processes/processes.go | 75 ++ .../hunt/detect/processes/processes_test.go | 15 + .../pkg/hunt/detect/scheduledTasks/tasks.go | 54 + .../hunt/detect/scheduledTasks/tasks_test.go | 22 + internal/pkg/hunt/detect/services/services.go | 149 +++ .../pkg/hunt/detect/services/services_test.go | 28 + internal/pkg/hunt/eliminate/eliminate.go | 1 + internal/pkg/hunter/hunter.go | 55 + internal/pkg/options.go | 5 + internal/pkg/writer/html.go | 1 + internal/pkg/writer/json.go | 1 + internal/suspicious/rmm.go | 114 ++ main.go | 9 + 26 files changed, 2382 insertions(+) create mode 100644 cmd/root.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/pkg/hunt/detect/autorun/autorun.go create mode 100644 internal/pkg/hunt/detect/autorun/autorun_test.go create mode 100644 internal/pkg/hunt/detect/binaries/binaries.go create mode 100644 internal/pkg/hunt/detect/binaries/binaries_test.go create mode 100644 internal/pkg/hunt/detect/common/common.go create mode 100644 internal/pkg/hunt/detect/common/functions.go create mode 100644 internal/pkg/hunt/detect/connections/outbound.go create mode 100644 internal/pkg/hunt/detect/connections/outbound_test.go create mode 100644 internal/pkg/hunt/detect/directory/directories.go create mode 100644 internal/pkg/hunt/detect/directory/directories_test.go create mode 100644 internal/pkg/hunt/detect/processes/processes.go create mode 100644 internal/pkg/hunt/detect/processes/processes_test.go create mode 100644 internal/pkg/hunt/detect/scheduledTasks/tasks.go create mode 100644 internal/pkg/hunt/detect/scheduledTasks/tasks_test.go create mode 100644 internal/pkg/hunt/detect/services/services.go create mode 100644 internal/pkg/hunt/detect/services/services_test.go create mode 100644 internal/pkg/hunt/eliminate/eliminate.go create mode 100644 internal/pkg/hunter/hunter.go create mode 100644 internal/pkg/options.go create mode 100644 internal/pkg/writer/html.go create mode 100644 internal/pkg/writer/json.go create mode 100644 internal/suspicious/rmm.go create mode 100644 main.go diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..c8a4b4f --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,109 @@ +package cmd + +import ( + "fmt" + "os" + "rmm-hunter/internal/pkg" + "rmm-hunter/internal/pkg/hunter" + + "github.com/spf13/cobra" +) + +var ( + excludeRMMs []string + inputFile string + outputFile string +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "rmm-hunter", + Short: "RMM-Hunter - Detect and eliminate Remote Monitoring and Management software", + Long: `RMM-Hunter is a tool designed to detect and eliminate Remote Monitoring +and Management (RMM) software on Windows systems. It can hunt for suspicious +processes, services, binaries, and network connections associated with RMM tools.`, + Version: "1.0.0", +} + +// huntCmd represents the hunt command +var huntCmd = &cobra.Command{ + Use: "hunt", + Short: "Hunt for RMM software on the system", + Long: `Hunt mode scans the system for signs of RMM software including: +- Suspicious processes +- Services +- Binaries and executables +- Network connections +- Scheduled tasks +- Registry entries`, + Run: func(cmd *cobra.Command, args []string) { + fmt.Println("Starting RMM Hunt...") + runHunt() + }, +} + +// eliminateCmd represents the eliminate command +var eliminateCmd = &cobra.Command{ + Use: "eliminate", + Short: "Eliminate Sus software based on hunt results", + Long: `Eliminate mode removes detected Sus software from the system. +Requires a JSON input file containing hunt results to determine what to remove.`, + Run: func(cmd *cobra.Command, args []string) { + if inputFile == "" { + fmt.Println("Error: --input flag is required for eliminate command") + os.Exit(1) + } + + fmt.Printf("Starting Sus Elimination using input file: %s\n", inputFile) + // TODO: Call eliminate.Eliminate() function + runEliminate() + }, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Add subcommands + rootCmd.AddCommand(huntCmd) + rootCmd.AddCommand(eliminateCmd) + + // Global flags + rootCmd.PersistentFlags().StringSliceVar(&excludeRMMs, "exclude", []string{}, + "Comma-separated list of Sus names to exclude from detection (optional)") + + // Hunt command flags + huntCmd.Flags().StringSliceVar(&excludeRMMs, "exclude", []string{}, + "Comma-separated list of Sus names to exclude from hunt") + huntCmd.Flags().StringVarP(&outputFile, "output", "o", "suspicious-hunter.json", + "Output file to write hunt results (optional) Default: suspicious-hunter.json") + + // Eliminate command flags + eliminateCmd.Flags().StringVarP(&inputFile, "input", "i", "", + "JSON input file containing hunt results (required)") + eliminateCmd.MarkFlagRequired("input") +} + +func runHunt() { + fmt.Println("Starting Sus Hunt...") + if len(excludeRMMs) > 0 { + fmt.Printf("Excluding RMMs: %v\n", excludeRMMs) + } + + hunter.Start(pkg.RunOptions{ + ExcludeRMMs: excludeRMMs, + }) + +} + +func runEliminate() { + // TODO: Implement eliminate functionality + fmt.Println("Eliminate functionality not yet implemented") + fmt.Printf("Input file: %s\n", inputFile) + fmt.Printf("Excluded RMMs: %v\n", excludeRMMs) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..aec74e1 --- /dev/null +++ b/go.mod @@ -0,0 +1,30 @@ +module rmm-hunter + +go 1.24.7 + +require ( + github.com/Kraken-OffSec/Scurvy v0.0.0-20251010192328-967933276439 + github.com/spf13/cobra v1.10.1 + golang.org/x/sys v0.29.0 +) + +require ( + github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a // indirect + github.com/alwindoss/morse v1.0.1 // indirect + github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 // indirect + github.com/cakturk/go-netstat v0.0.0-20200220111822-e5b49efee7a5 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect + github.com/ecies/go/v2 v2.0.10 // indirect + github.com/elastic/go-sysinfo v1.15.1 // indirect + github.com/elastic/go-windows v1.0.2 // indirect + github.com/ethereum/go-ethereum v1.14.12 // indirect + github.com/go-ole/go-ole v1.3.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/prometheus/procfs v0.15.1 // indirect + github.com/ryanuber/columnize v2.1.2+incompatible // indirect + github.com/spf13/pflag v1.0.9 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/text v0.21.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + howett.net/plist v1.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5612973 --- /dev/null +++ b/go.sum @@ -0,0 +1,63 @@ +github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a h1:4c0nc0krv8eh7gD809n+swLaCuFyHpxdrxwx0ZmHvBw= +github.com/Binject/debug v0.0.0-20230508195519-26db73212a7a/go.mod h1:QzgxDLY/qdKlvnbnb65eqTedhvQPbaSP2NqIbcuKvsQ= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010174510-8091571ab864 h1:zYVI4GRNB7wjLtorhpnPLP8v8w5T3axCpCtNDKI2LOs= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010174510-8091571ab864/go.mod h1:hljxQLLV5S60GVVG51+u3r1agCjZ45x8jd2WiJxy0wQ= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010183749-8ab59cb85591 h1:APveZhhJVm6tFcpldhMLxln4JR1V3Aw1xegt0SKGybg= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010183749-8ab59cb85591/go.mod h1:hljxQLLV5S60GVVG51+u3r1agCjZ45x8jd2WiJxy0wQ= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010184333-fb8a7710cf48 h1:nyCMY/8w7IsmduLZspdBuCmWutMUY6lzn5DCKVmQGt0= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010184333-fb8a7710cf48/go.mod h1:hljxQLLV5S60GVVG51+u3r1agCjZ45x8jd2WiJxy0wQ= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010184534-de2a2a349253 h1:ZfFDU6Kp9mFlEb0OZniWQR1E3w3Okr9gK2HlRb9lN6E= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010184534-de2a2a349253/go.mod h1:hljxQLLV5S60GVVG51+u3r1agCjZ45x8jd2WiJxy0wQ= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010185212-88db5cc88bc0 h1:g01ZBGUyvJXSWvxs7SVPTtqv3ruhbFsgsRGxCM2yYoY= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010185212-88db5cc88bc0/go.mod h1:hljxQLLV5S60GVVG51+u3r1agCjZ45x8jd2WiJxy0wQ= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010190109-3a4c7586120a h1:Z4cjdwk5DupnEg/F2dv4DPutwSEmDq7WWe565FjZrtQ= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010190109-3a4c7586120a/go.mod h1:hljxQLLV5S60GVVG51+u3r1agCjZ45x8jd2WiJxy0wQ= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010190312-0ad88c5f3f2f h1:XJ9IudxrEjAhodOLCTaWCIxWdj0fIa+JOdzfd1nST9k= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010190312-0ad88c5f3f2f/go.mod h1:hljxQLLV5S60GVVG51+u3r1agCjZ45x8jd2WiJxy0wQ= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010192328-967933276439 h1:n/B4+1K6vpKX34iISUKHzEKEND53PmxePHrtsy693Jo= +github.com/Kraken-OffSec/Scurvy v0.0.0-20251010192328-967933276439/go.mod h1:hljxQLLV5S60GVVG51+u3r1agCjZ45x8jd2WiJxy0wQ= +github.com/alwindoss/morse v1.0.1 h1:PkUh5m1UHMcZ1Upvl7CmSIBMxdEBejWoQ4rQQtgJsCQ= +github.com/alwindoss/morse v1.0.1/go.mod h1:qAqJOep3jEpIpiLgqSGgLk5Zh4BZKsyzMQHuAwVPMXc= +github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4 h1:cIAK2NNf2yafdgpFRNJrgZMwvy61BEVpGoHc2n4/yWs= +github.com/awgh/rawreader v0.0.0-20200626064944-56820a9c6da4/go.mod h1:SalMPBCab3yuID8nIhLfzwoBV+lBRyaC7NhuN8qL8xE= +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/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= +github.com/ecies/go/v2 v2.0.10 h1:AaLxGio0MLLbvWur4rKnLzw+K9zI+wMScIDAtqCqOtU= +github.com/ecies/go/v2 v2.0.10/go.mod h1:N73OyuR6tuKznit2LhXjrZ0XAQ234uKbzYz8pEPYzlI= +github.com/elastic/go-sysinfo v1.15.1 h1:zBmTnFEXxIQ3iwcQuk7MzaUotmKRp3OabbbWM8TdzIQ= +github.com/elastic/go-sysinfo v1.15.1/go.mod h1:jPSuTgXG+dhhh0GKIyI2Cso+w5lPJ5PvVqKlL8LV/Hk= +github.com/elastic/go-windows v1.0.2 h1:yoLLsAsV5cfg9FLhZ9EXZ2n2sQFKeDYrHenkcivY4vI= +github.com/elastic/go-windows v1.0.2/go.mod h1:bGcDpBzXgYSqM0Gx3DM4+UxFj300SZLixie9u9ixLM8= +github.com/ethereum/go-ethereum v1.14.12 h1:8hl57x77HSUo+cXExrURjU/w1VhL+ShCTJrTwcCQSe4= +github.com/ethereum/go-ethereum v1.14.12/go.mod h1:RAC2gVMWJ6FkxSPESfbshrcKpIokgQKsVKmAuqdekDY= +github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= +github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= +github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v2.1.2+incompatible h1:C89EOx/XBWwIXl8wm8OPJBd7kPF25UfsK2X7Ph/zCAk= +github.com/ryanuber/columnize v2.1.2+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9 h1:9exaQaMOCwffKiiiYk6/BndUBv+iRViNW+4lEMi0PvY= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= +golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v1 v1.0.0-20140924161607-9f9df34309c0/go.mod h1:WDnlLJ4WF5VGsH/HVa3CI79GS0ol3YnhVnKP89i0kNg= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +howett.net/plist v1.0.1 h1:37GdZ8tP09Q35o9ych3ehygcsL+HqKSwzctveSlarvM= +howett.net/plist v1.0.1/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g= diff --git a/internal/pkg/hunt/detect/autorun/autorun.go b/internal/pkg/hunt/detect/autorun/autorun.go new file mode 100644 index 0000000..77b9bd7 --- /dev/null +++ b/internal/pkg/hunt/detect/autorun/autorun.go @@ -0,0 +1,170 @@ +package autorun + +import ( + "fmt" + "rmm-hunter/internal/pkg/hunt/detect/common" + . "rmm-hunter/internal/suspicious" + "strings" + + "golang.org/x/sys/windows/registry" +) + +func Detect() []AutoRun { + var suspiciousAutoRuns []AutoRun + + fmt.Printf("[*] Enumerating AutoRun Applications\n") + + // Check common autorun registry locations + autorunKeys := []string{ + `SOFTWARE\Microsoft\Windows\CurrentVersion\Run`, + `SOFTWARE\Microsoft\Windows\CurrentVersion\RunOnce`, + `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Run`, + `SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\RunOnce`, + `SOFTWARE\Microsoft\Windows\CurrentVersion\RunServices`, + `SOFTWARE\Microsoft\Windows\CurrentVersion\RunServicesOnce`, + `SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\Explorer\Run`, + `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Userinit`, + `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon\Shell`, + `SOFTWARE\Microsoft\Active Setup\Installed Components`, + } + + // Check both HKLM and HKCU + roots := []registry.Key{registry.LOCAL_MACHINE, registry.CURRENT_USER} + rootNames := []string{"HKLM", "HKCU"} + + totalEntries := 0 + for i, root := range roots { + for _, keyPath := range autorunKeys { + entries := checkAutoRunKey(root, keyPath, rootNames[i]) + totalEntries += len(entries) + suspiciousAutoRuns = append(suspiciousAutoRuns, entries...) + } + } + + fmt.Printf(" [>] Dispositioning %d AutoRun Entries\n", totalEntries) + fmt.Printf("[+] Found %d Suspicious AutoRun Applications\n", len(suspiciousAutoRuns)) + + return suspiciousAutoRuns +} + +func checkAutoRunKey(root registry.Key, keyPath, rootName string) []AutoRun { + var autoRuns []AutoRun + + key, err := registry.OpenKey(root, keyPath, registry.QUERY_VALUE) + if err != nil { + return autoRuns + } + defer key.Close() + + valueNames, err := key.ReadValueNames(-1) + if err != nil { + return autoRuns + } + + for _, valueName := range valueNames { + value, _, err := key.GetStringValue(valueName) + if err != nil { + continue + } + + // Check if this autorun entry matches any known Suspicious patterns + if isSuspiciousAutoRun(valueName, value) { + // Analyze the executable path for additional suspicious indicators + isPathSuspicious, pathReason := analyzeExecutablePath(value) + description := extractDescription(value) + if isPathSuspicious { + description += fmt.Sprintf(" [%s]", pathReason) + } + + fmt.Printf(" [?] Found %s\\%s: %s = %s\n", rootName, keyPath, valueName, value) + autoRuns = append(autoRuns, AutoRun{ + Name: valueName, + Command: value, + Location: fmt.Sprintf("%s\\%s", rootName, keyPath), + Enabled: true, + Description: description, + }) + } + } + + return autoRuns +} + +func isSuspiciousAutoRun(name, command string) bool { + // Convert to lowercase for case-insensitive comparison + nameLower := strings.ToLower(name) + commandLower := strings.ToLower(command) + + // Check against known Suspicious names + for _, rmm := range common.CommonRMMs { + rmmLower := strings.ToLower(rmm) + if strings.Contains(nameLower, rmmLower) || strings.Contains(commandLower, rmmLower) { + return true + } + } + + // Check against common Suspicious executable patterns + for _, imageEnd := range common.CommonImageEnds { + imageEndLower := strings.ToLower(imageEnd) + if strings.Contains(commandLower, imageEndLower) { + return true + } + } + + // Additional suspicious patterns + suspiciousPatterns := []string{ + "remote", "control", "assist", "support", "vnc", "rdp", "teamview", + "anydesk", "logmein", "screenconnect", "splashtop", "ultravnc", + } + + for _, pattern := range suspiciousPatterns { + if strings.Contains(nameLower, pattern) || strings.Contains(commandLower, pattern) { + return true + } + } + + return false +} + +func extractDescription(command string) string { + // Extract just the executable name from the command + parts := strings.Fields(command) + if len(parts) > 0 { + return parts[0] + } + return command +} + +func analyzeExecutablePath(command string) (bool, string) { + // Extract executable path from command + var execPath string + if strings.HasPrefix(command, "\"") { + // Handle quoted paths + endQuote := strings.Index(command[1:], "\"") + if endQuote != -1 { + execPath = command[1 : endQuote+1] + } + } else { + // Handle unquoted paths + parts := strings.Fields(command) + if len(parts) > 0 { + execPath = parts[0] + } + } + + // Check for suspicious installation paths + suspiciousPaths := []string{ + "\\temp\\", "\\tmp\\", "\\appdata\\local\\temp\\", + "\\users\\public\\", "\\programdata\\", + "\\windows\\temp\\", "\\%temp%\\", + } + + execPathLower := strings.ToLower(execPath) + for _, suspPath := range suspiciousPaths { + if strings.Contains(execPathLower, suspPath) { + return true, fmt.Sprintf("Suspicious installation path: %s", suspPath) + } + } + + return false, "" +} diff --git a/internal/pkg/hunt/detect/autorun/autorun_test.go b/internal/pkg/hunt/detect/autorun/autorun_test.go new file mode 100644 index 0000000..dfd2e8c --- /dev/null +++ b/internal/pkg/hunt/detect/autorun/autorun_test.go @@ -0,0 +1,16 @@ +package autorun + +import "testing" + +func TestAutoRun(t *testing.T) { + autoruns := Detect() + for _, ar := range autoruns { + t.Logf("-----") + t.Logf("Name: %s", ar.Name) + t.Logf("Command: %s", ar.Command) + t.Logf("Location: %s", ar.Location) + t.Logf("Enabled: %t", ar.Enabled) + t.Logf("Description: %s", ar.Description) + t.Logf("-----") + } +} diff --git a/internal/pkg/hunt/detect/binaries/binaries.go b/internal/pkg/hunt/detect/binaries/binaries.go new file mode 100644 index 0000000..bf3e828 --- /dev/null +++ b/internal/pkg/hunt/detect/binaries/binaries.go @@ -0,0 +1,94 @@ +package binaries + +import ( + "fmt" + "os" + "path/filepath" + "rmm-hunter/internal/pkg/hunt/detect/common" + "strings" + "sync" +) + +func Detect() []string { + var foundBinaries []string + var mu sync.Mutex + var wg sync.WaitGroup + + fmt.Printf("[*] Enumerating Suspicious Binaries\n") + + // Define search directories + searchDirs := []string{ + os.Getenv("APPDATA"), + `C:\ProgramData\`, + `C:\Program Files\`, + `C:\Program Files (x86)\`, + `C:\Downloads\`, + } + + fmt.Printf(" [>] Dispositioning %d Directories\n", len(searchDirs)) + + // Channel to collect results + resultChan := make(chan string, 100) + + // Start goroutines for each directory + for _, dir := range searchDirs { + if dir == "" { + continue // Skip if environment variable is empty + } + + wg.Add(1) + go func(searchDir string) { + defer wg.Done() + searchDirectory(searchDir, resultChan) + }(dir) + } + + // Goroutine to close channel when all searches complete + go func() { + wg.Wait() + close(resultChan) + }() + + // Collect results + for result := range resultChan { + mu.Lock() + foundBinaries = append(foundBinaries, result) + mu.Unlock() + fmt.Printf(" [?] Found %s\n", result) + } + + fmt.Printf("[+] Found %d Suspicious Binaries\n", len(foundBinaries)) + return foundBinaries +} + +func searchDirectory(dir string, resultChan chan<- string) { + err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { + if err != nil { + // Skip directories we can't access + return nil + } + + // Skip directories, only check files + if info.IsDir() { + return nil + } + + // Check if the file path ends with any CommonImageEnds + for _, imageEnd := range common.CommonImageEnds { + // Normalize path separators and make case-insensitive comparison + normalizedPath := strings.ToLower(filepath.ToSlash(path)) + normalizedImageEnd := strings.ToLower(filepath.ToSlash(imageEnd)) + + if strings.HasSuffix(normalizedPath, normalizedImageEnd) { + resultChan <- path + break + } + } + + return nil + }) + + if err != nil { + fmt.Printf(" [-] Error searching %s: %v\n", dir, err) + } +} diff --git a/internal/pkg/hunt/detect/binaries/binaries_test.go b/internal/pkg/hunt/detect/binaries/binaries_test.go new file mode 100644 index 0000000..5a3a77c --- /dev/null +++ b/internal/pkg/hunt/detect/binaries/binaries_test.go @@ -0,0 +1,12 @@ +package binaries + +import "testing" + +func TestDetect(t *testing.T) { + binaries := Detect() + for _, binary := range binaries { + t.Logf("-----") + t.Logf("Binary: %s", binary) + t.Logf("-----") + } +} diff --git a/internal/pkg/hunt/detect/common/common.go b/internal/pkg/hunt/detect/common/common.go new file mode 100644 index 0000000..13f1c1a --- /dev/null +++ b/internal/pkg/hunt/detect/common/common.go @@ -0,0 +1,1031 @@ +package common + +var CommonDirectories = []string{ + `C:\Program Files (x86)%\mRemoteNG`, + `C:\\Program Files (x86)\Sysprogs`, + `C:\\Program Files (x86)\Sysprogs\SmarTTY`, + `C:\AlpemixSrvc`, + `C:\Downloads\SuperPuTTY`, + `C:\Program Files (x86)\Almageste\DragonDisk`, + `C:\Program Files (x86)\AnyDesk`, + `C:\Program Files (x86)\AnyViewer`, + `C:\Program Files (x86)\Atera Networks`, + `C:\Program Files (x86)\Bitvise SSH Client`, + `C:\Program Files (x86)\Bluetrait Agent`, + `C:\Program Files (x86)\DesktopCentral_Agent`, + `C:\Program Files (x86)\DesktopCentral_Agent\bin`, + `C:\Program Files (x86)\GoTo Opener`, + `C:\Program Files (x86)\GoToMyPC`, + `C:\Program Files (x86)\Google\Chrome Remote Desktop`, + `C:\Program Files (x86)\ISL Online`, + `C:\Program Files (x86)\Kaseya`, + `C:\Program Files (x86)\LANDesk`, + `C:\Program Files (x86)\OnionShare`, + `C:\Program Files (x86)\NetSarang`, + `C:\Program Files (x86)\NetSarang\xShell`, + `C:\Program Files (x86)\PJ Technologies`, + `C:\Program Files (x86)\PJ Technologies\GOVsrv`, + `C:\Program Files (x86)\Radmin Viewer 3`, + `C:\Program Files (x86)\RemotePC`, + `C:\Program Files (x86)\S3 Browser`, + `C:\Program Files (x86)\ScreenConnect Client (`, // C:\Program Files (x86)\ScreenConnect Client () + `C:\Program Files (x86)\SmartFTP Client`, + `C:\Program Files (x86)\Splashtop`, + `C:\Program Files (x86)\TeamViewer`, + `C:\Program Files (x86)\UltraViewer`, + `C:\Program Files (x86)\Xpra`, + `C:\Program Files (x86)\Yandex`, + `C:\Program Files (x86)\mRemoteNG`, + `C:\Program Files\ATERA NETWORKS`, + `C:\Program Files\ATERA NETWORKS\AteraAgent`, + `C:\Program Files\AnyDesk`, + `C:\Program Files\Bitvise SSH Server`, + `C:\Program Files\Danware Data\NetOp Packn Deploy`, + `C:\Program Files\Level`, + `C:\\Program Files\\LiteManager Pro`, + `C:\\Program Files\\LiteManager Pro \u2013 Viewer`, + `C:\Program Files\ManageEngine\ManageEngine Free Tools`, + `C:\Program Files\ManageEngine\ManageEngine Free Tools\Launcher`, + `C:\Program Files\RealVNC`, + `C:\Program Files\RealVNC\VNC Serve`, + `C:\Program Files\Remote Utilities`, + `C:\Program Files\Remote Utilities\Agent`, + `C:\Program Files\Solar-Putty-v4`, + `C:\Program Files\SolarWinds\Dameware Mini Remote Control`, + `C:\Program Files\SysAidServer`, + `C:\Program Files\TeamViewer`, + `C:\Program Files\TightVNC`, + `C:\Program Files\ZOC8`, + `C:\Program Files\uvnc bvba`, + `C:\Program Files\uvnc bvba\UltraVNC`, + `C:\ProgramData\Kaseya`, + `C:\ProgramData\Total Software Deployment`, + `C:\ProgramFiles\GoTo Machine Installer`, + `C:\ProgramFiles (x86)\GoTo Machine Installer`, + `{{APPDATA}}\Local\Google\Chrome\User Data\Default\Extensions\iodihamcpbpeioajjeobimgagajmlibd`, + `{{APPDATA}}\Local\MEGAsync`, + `{{APPDATA}}\Roaming\Mikogo`, + `{{APPDATA}}\Roaming\SyncTrayzor`, + `C:\Users\IEUser\Downloads\WinSCP-5.21.6-Portable`, + `C:\Users\USERNAME\AppData\Roaming\Insync`, + `C:\Users\USERNAME\AppData\Roaming\Insync\App`, + `C:\Windows\Action1`, + `C:\Windows\SysWOW64\rserver30`, + `C:\Windows\SysWOW64\rserver30\FamItrfc`, + `C:\Windows\SysWOW64\rserver30\FamItrf2`, + `C:\Windows\dwrcs`, + `C:\ProgramData\AMMYY`, +} + +var CommonRMMs = []string{ + "GoToRemote", + "LogMeIn", + "ScreenConnect", + "Screen Connect", + "RustDesk", + "Rust Desk", + "TeamViewer", + "Team Viewer", +} + +var CommonImageEnds = []string{ + "\\9380CC75B872221A7425D7503565B67580407F60", + "\\AEMAgent.exe", + "\\AMMYY_Admin.exe", + "\\ARDAgent.app", + "\\AcronisCyberProtectConnectAgent.exe", + "\\AcronisCyberProtectConnectQuickAssist*.exe", + "\\AeroAdmin.exe", + "\\AgentSetup-*.exe", + "\\Agent_*_RW.exe", + "\\AgentlessRC.exe", + "\\ArcUI.exe", + "\\AweSun.exe", + "\\BASEClient.exe", + "\\BASupApp.exe", + "\\BASupAppElev.exe", + "\\BASupAppSrvc.exe", + "\\BASupSrvc.exe", + "\\BASupSrvcCnfg.exe", + "\\BASupSysInf.exe", + "\\BASupTSHelper.exe", + "\\Beinsync*.exe", + "\\G2RDesktopConsole-x64.msi", + "\\MobaXterm_installer_12.1.msi", + "\\SecureCRT.EXE", + "\\ZA_Access.exe", + "\\kitty.exe", + "\\ngrok.zip", + "\\nxplayer.exe", + "\\puttytray.exe", + "\\AlpemixService.exe", + "\\Radmin.exe", + "\\ScreenConnect.ClientService.exe", + "\\UltraViewer_Desktop.exe", + "\\AMMYY_Admin.exe", + "\\ExtraPuTTY-0.30-2016-01-28-installer.exe", + "\\Insync.exe", + "\\rserver3.exe", + "\\mstsc.exe", + "\\CBBackupPlan.exe", + "\\CagService.exe", + "\\Cloud.Backup.RM.Service.exe", + "\\Cloud.Backup.Scheduler.exe", + "\\CloudRaCmd.exe", + "\\CloudRaSd.exe", + "\\CloudRaService.exe", + "\\CloudRaUtilities.exe", + "\\ComodoRemoteControl.exe", + "\\Connect.Backdrop.cloud*.exe", + "\\Connect.exe", + "\\ConnectAppSetup*.exe", + "\\ConnectDetector.exe", + "\\ConnectShellSetup*.exe", + "\\ConnectWiseControl*.exe", + "\\CrossLoopConnect.exe", + "\\DSGuest.exe", + "\\DameWare Mini Remote Control*.exe", + "\\DameWare Remote Support.exe", + "\\DeskRollUA.exe", + "\\Deskroll.exe", + "\\Domotz Pro Desktop App Setup*.exe", + "\\Domotz Pro Desktop App.exe", + "\\ERAAgent.exe", + "\\EricomConnectRemoteHost*.exe", + "\\FastViewer.exe", + "\\FixMeit Client.exe", + "\\FixMeit Expert Setup.exe", + "\\FixMeit Unattended Access Setup.exe", + "\\FixMeitClient*.exe", + "\\GetScreen.exe", + "\\GoTo Assist Opener.exe", + "\\GotoHTTP*.exe", + "\\GotoHTTP_x64.exe", + "\\GovAgentInstallHelper.exe", + "\\GovAgentx64.exe", + "\\GovReachClient.exe", + "\\HelpuManager.exe", + "\\HelpuUpdater.exe", + "\\I'm InTouch Go Installer.exe", + "\\ISLLight.exe", + "\\ISLLightClient.exe", + "\\ITSMAgent.exe", + "\\ITSMService.exe", + "\\ITarianRemoteAccessSetup.exe", + "\\Idrive.File-Transfer", + "\\ImperoClientSVC.exe", + "\\ImperoInit.exe", + "\\InstallShield Setup.exe", + "\\InstantHousecall.exe", + "\\ItsmRsp.exe", + "\\IvantiRemoteControl.exe", + "'\\JumpCloud*.exe '", + "\\KHelpDesk.exe", + "\\Kabuto.App.Runner.exe", + "\\Kabuto.Installer.exe", + "\\Kabuto.Service.Runner.exe", + "\\KabutoSetup.exe", + "\\LANDeskPortalManager.exe", + "\\MEAgentHelper.exe", + "\\ManageEngine_Remote_Access_Plus.exe", + "\\ManualLauncher.exe", + "\\Microsoft Remote Desktop", + "\\MonitoringAgent.exe", + "\\NTRsupportPro_EN.exe", + "\\Netop Ondemand.exe", + "\\NinjaRMMAgenPatcher.exe", + "\\NinjaRMMAgent.exe", + "\\OOSysAgent.exe", + "\\OTPowerShell.exe", + "\\OTService.exe", + "\\OnionShare-win*.msi", + "\\Online Backup.exe", + "\\OrayRemoteService.exe", + "\\OrayRemoteShell.exe", + "\\PAExec-*.exe", + "\\PCIVIDEO.EXE", + "\\PCMonitorManager.exe", + "\\Pilixo_Installer*.exe", + "\\QQProtect.exe", + "\\RAccess.exe", + "\\RDConsole.exe", + "\\RDPCheck.exe", + "\\RDPConf.exe", + "\\RDPWInst.exe", + "\\RDesktop.exe", + "\\RHost.exe", + "\\ROMFUSClient.exe", + "\\ROMServer.exe", + "\\RViewer.exe", + "\\Remote Desktop.exe", + "\\Remote Workforce Client.exe", + "\\RemotePC.exe", + "\\RemotePCService.exe", + "\\RmmService.exe", + "\\RocketRemoteDesktop_Setup.exe", + "\\SMPCSetup.exe", + "\\SRManager.exe", + "\\SRServer.exe", + "\\ScreenMeet.Support.exe", + "\\ScreenMeetSupport.exe", + "\\SensoClient.exe", + "\\SensoService.exe", + "\\ServiceProxyLocalSys.exe", + "\\Site24x7PluginAgent.exe", + "\\Site24x7WindowsAgentTrayIcon.exe", + "\\SolarWinds-Dameware-DRS*.exe", + "\\SolarWinds-Dameware-MRC*.exe", + "\\Sorillus Launcher.exe", + "\\Sorillus-Launcher*.exe", + "\\SplashtopSOS.exe", + "\\Splashtop_Streamer_Windows*.exe", + "\\Syncro.App.Runner.exe", + "\\Syncro.Installer.exe", + "\\Syncro.Overmind.Service.exe", + "\\Syncro.Service.exe", + "\\SyncroLive.Agent.exe", + "\\SyncroLive.Service.exe", + "\\TPowerShell.exe", + "\\TSClient.exe", + "\\TakeControl.exe", + "\\TaniumCX.exe", + "\\TaniumClient.exe", + "\\TaniumExecWrapper.exe", + "\\TaniumFileInfo.exe", + "\\TeamTaskManager.exe", + "\\TiClientCore.exe", + "\\TiClientHelper*.exe", + "\\TiExpertCore.exe", + "\\TiExpertStandalone.exe", + "\\TightVNCViewerPortable*.exe", + "\\ToDesk_Service.exe", + "\\ToDesk_Setup.exe", + "\\UltraVNC*.exe", + "\\UltraViewer_Desktop.exe", + "\\UltraViewer_Service.exe", + "\\UltraViewer_setup*", + "\\WinVNCStub.exe", + "\\XSightService.exe", + "\\ZMAgent.exe", + "\\Zaservice.exe", + "\\ZohoMeeting.exe", + "\\ZohoURSService.exe", + "\\Zohours.exe", + "\\aa_v*.exe", + "\\aadg.exe", + "\\accessserver*.exe", + "\\accessserver.exe", + "\\addigy-*.pkg", + "\\aeroadmin.exe", + "\\agent32.exe", + "\\agent64.exe", + "\\agent_setup_5.exe", + "\\agentu.exe", + "\\alitask.exe", + "\\apc_host.exe", + "\\atera_agent.exe", + "\\ateraagent.exe", + "\\auvik.agent.exe", + "\\auvik.engine.exe", + "\\aweray_remote*.exe", + "\\awhost32.exe", + "\\awrem32.exe", + "\\basupsrvc.exe", + "\\basupsrvcupdate.exe", + "\\basuptshelper.exe", + "\\beamyourscreen-host.exe", + "\\beamyourscreen.exe", + "\\bomgar-pac-*.exe", + "\\bomgar-pac.exe", + "\\bomgar-rdp.exe", + "\\bomgar-scc-*.exe", + "\\bomgar-scc.exe", + "\\Duplicati.Server.exe", + "\\cbb.exe", + "\\client32.exe", + "\\clientmrinit.exe", + "\\cloudflared.exe", + "\\connectwise*.exe", + "\\connectwisechat-customer.exe", + "\\connectwisecontrol.client.exe", + "\\crossloopservice.exe", + "\\csexec.exe", + "\\ctes.exe", + "\\cteshostsvc.exe", + "\\ctespersitence.exe", + "\\ctiserv.exe", + "\\dcagentregister.exe", + "\\dcagentservice.exe", + "\\dd.exe", + "\\ddsystem.exe", + "\\desktopnow.exe", + "\\distant-desktop.exe", + "\\dntus*.exe", + "\\domotz*.exe", + "\\domotz-windows*.exe", + "\\domotz.exe", + "\\domotz_bash.exe", + "\\dwagent.exe", + "\\dwagsvc.exe", + "\\dwrcs.exe", + "\\echoserver*.exe", + "\\echoware.dll", + "\\ehorus standalone.exe", + "\\ehorus_agent.exe", + "\\einstaller.exe", + "\\era.exe", + "\\eratool.exe", + "\\ericomconnnectconfigurationtool.exe", + "\\ezHelpManager.exe", + "\\ezhelp*.exe", + "\\ezhelpclient.exe", + "\\ezhelpclientmanager.exe", + "\\fastclient.exe", + "\\fastmaster.exe", + "\\fixmeitclient.exe", + "\\fleetdeck_agent.exe", + "\\fleetdeck_agent_svc.exe", + "\\fleetdeck_commander_launcher.exe", + "\\fleetdeck_commander_svc.exe", + "\\fleetdeck_installer.exe", + "\\g2a*.exe", + "\\getscreen.exe", + "\\gotoassist.exe", + "\\gotohttp.exe", + "\\goverrmc.exe", + "\\govsrv*.exe", + "\\gp3.exe", + "\\gp4.exe", + "\\gp5.exe", + "\\grabberEM.*msi", + "\\grabberTT*.msi", + "\\guacd.exe", + "\\helpbeam*.exe", + "\\helpu_install.exe", + "\\hsloader.exe", + "\\iadmin.exe", + "\\idrive.RemotePCAgent", + "\\ihcserver.exe", + "\\iit.exe", + "\\instanthousecall.exe", + "\\intelliadmin.exe", + "\\intouch.exe", + "\\iperius.exe", + "\\iperiusremote.exe", + "\\ir_agent.exe", + "\\islalwaysonmonitor.exe", + "\\isllight.exe", + "\\isllightservice.exe", + "\\issuser.exe", + "\\itsmagent.exe", + "\\jumpclient.exe", + "\\jumpconnect.exe", + "\\jumpdesktop.exe", + "\\jumpservice.exe", + "\\jumpupdater.exe", + "\\konea.exe", + "\\landeskagentbootstrap.exe", + "\\laplink-everywhere-setup*.exe", + "\\laplink.exe", + "\\laplinkeverywhere.exe", + "\\ldinv32.exe", + "\\ldsensors.exe", + "\\level-remote-control-ffmpeg.exe", + "\\level-windows-amd64.exe", + "\\level.exe", + "\\llrcservice.exe", + "\\lmi_rescue.exe", + "\\lmnoipserver.exe", + "\\ltsvc.exe", + "\\ltsvcmon.exe", + "\\lttray.exe", + "\\mRemoteNG-Installer-*.msi", + "\\mRemoteNG.exe", + "\\meshagent*.exe", + "\\meshcentral*.exe", + "\\mgntsvc.exe", + "\\mikogo-service.exe", + "\\mikogo-starter.exe", + "\\mikogo.exe", + "\\mikogolauncher.exe", + "\\mionet.exe", + "\\mionetmanager.exe", + "\\mstsc.exe", + "\\mwcliun.exe", + "\\mygreenpc.exe", + "\\myivomanager.exe", + "\\myivomgr.exe", + "\\nateon*.exe", + "\\nateon.exe", + "\\nateonmain.exe", + "\\neturo*.exe", + "\\neturo.exe", + "\\netviewer*.exe", + "\\netviewer.exe", + "\\ngrok.exe", + "\\ngstw32.exe", + "\\nhostsvc.exe", + "\\nhstw32.exe", + "\\ninjarmm-cli.exe", + "\\ninjarmmagent.exe", + "\\nldrw32.exe", + "\\nomachine*.exe", + "\\ntrntservice.exe", + "\\nvClient.exe", + "\\nxd.exe", + "\\nxservice*.ese", + "\\ocsinventory.exe", + "\\ocsservice.exe", + "\\oo-syspectr*.exe", + "\\p9agent*.exe", + "\\paexec.exe", + "\\parallelsaccess-*.exe", + "\\pcaquickconnect.exe", + "\\pcicfgui.exe", + "\\pcictlui.exe", + "\\pcmonitorsrv.exe", + "\\pcnmgr.exe", + "\\pcstarter.exe", + "\\pcvisit-easysupport.exe", + "\\pcvisit.exe", + "\\pcvisit_client.exe", + "\\pcvisit_service_client.exe", + "\\pdq-connect*.exe", + "\\pocketcloud*.exe", + "\\pocketcloudservice.exe", + "\\pocketcontroller.exe", + "\\prl_deskctl_agent.exe", + "\\prl_deskctl_wizard.exe", + "\\prl_pm_service.exe", + "\\psexec.exe", + "\\psexecsvc.exe", + "\\pstlaunch.exe", + "\\ptdskclient.exe", + "\\ptdskhost.exe", + "\\qq.exe", + "\\qqpcmgr.exe", + "\\quickassist.exe", + "\\raautoup.exe", + "\\rapid7_agent_core.exe", + "\\rapid7_endpoint_broker.exe", + "\\rcengmgru.exe", + "\\rcmgrsvc.exe", + "\\rcstartsupport.exe", + "\\rd.exe", + "\\rdp.exe", + "\\rdp2tcp.py", + "\\remcom.exe", + "\\remcomsvc.exe", + "\\remcos*.exe", + "\\remobo.exe", + "\\remobo_client.exe", + "\\remobo_tracker.exe", + "\\remote access.exe", + "\\remote-it-installer.exe", + "\\remote.it.exe", + "\\remote_host.exe", + "\\remoteconsole.exe", + "\\remoteit.exe", + "\\remotepass-access.exe", + "\\remotepchost.exe", + "\\remotepcservice.exe", + "\\remotesupportplayeru.exe", + "\\remoteview.exe", + "\\remoting_host.exe", + "\\requires sign up", + "\\rfusclient.exe", + "\\rmserverconsolemediator.exe", + "\\romfusclient.exe", + "\\romserver.exe", + "\\romviewer.exe", + "\\routernt.exe", + "\\royalserver.exe", + "\\royalts.exe", + "\\rpaccess.exe", + "\\rpcld.exe", + "\\rpcnet.exe", + "\\rpcsuite.exe", + "\\rport.exe", + "\\rpwhostscr.exe", + "\\rudesktop*.exe", + "\\rustdesk*.exe", + "\\rustdesk.exe", + "\\rutserv.exe", + "\\rutview.exe", + "\\rv.exe", + "\\rvagent.exe", + "\\rvagtray.exe", + "\\rviewer.exe", + "\\rxstartsupport.exe", + "\\saazapsc.exe", + "\\screenconnect*.exe", + "\\screenconnect.clientservice.exe", + "\\screenconnect.windowsclient.exe", + "\\seetrolcenter.exe", + "\\seetrolclient.exe", + "\\seetrolmyservice.exe", + "\\seetrolremote.exe", + "\\seetrolsetting.exe", + "\\servereye*.exe", + "\\serverproxyservice.exe", + "\\serviceconfig.xml", + "\\showmypc*.exe", + "\\showmypc.exe", + "\\simplegatewayservice.exe", + "\\simplehelpcustomer.exe", + "\\simpleservice.exe", + "\\smpcsetup.exe", + "\\spsrv.exe", + "\\sragent.exe", + "\\srmanager.exe", + "\\srserver.exe", + "\\srservice.exe", + "\\strwinclt.exe", + "\\sunlogin*.exe", + "\\superops.exe", + "\\superopsticket.exe", + "\\support-logmeinrescue*.exe", + "\\support-logmeinrescue.exe", + "\\supporttool.exe", + "\\supremo.exe", + "\\supremohelper.exe", + "\\supremoservice.exe", + "\\supremosystem.exe", + "\\syncrosetup.exe", + "\\sysdiag.exe", + "\\tacticalrmm.exe", + "\\tailscale-*.exe", + "\\tailscale-ipn.exe", + "\\tailscaled.exe", + "\\tdp2tcp.exe", + "\\teamviewer_desktop.exe", + "\\teamviewer_service.exe", + "\\teamviewerhost", + "\\termsrv.exe", + "\\tigervnc*.exe", + "\\todesk.exe", + "\\tsircusr.exe", + "\\turbomeeting.exe", + "\\turbomeetingstarter.exe", + "\\tvnserver.exe", + "\\tvnviewer.exe", + "\\ultimate_*.exe", + "\\ultraviewer.exe", + "\\ultraviewer_desktop.exe", + "\\ultraviewer_service.exe", + "\\vncserver.exe", + "\\vncserverui.exe", + "\\vncviewer.exe", + "\\webexpcnow.exe", + "\\webrdp.exe", + "\\weezo setup*.exe", + "\\weezo.exe", + "\\weezohttpd.exe", + "\\winagent.exe", + "\\winaw32.exe", + "\\windowslauncher.exe", + "\\winvnc*.exe", + "\\winvnc.exe", + "\\winvnc4.exe", + "\\winvncsc.exe", + "\\winwvc.exe", + "\\wisshell*.exe", + "\\wmc.exe", + "\\wmc_deployer.exe", + "\\wmcsvc.exe", + "\\wysebrowser.exe", + "\\xcmd.exe", + "\\xcmdsvc.exe", + "\\xeox-agent_*.exe", + "\\xeox-agent_x64.exe", + "\\xeox-agent_x86.exe", + "\\xeox_service_windows.exe", + "\\za_connect.exe", + "\\zabbix_agent*.exe", + "\\zaservice.exe", + "\\zero-powershell.exe", + "\\zerotier*.exe", + "\\zerotier*.msi", + "\\zohotray.exe", +} + +var CommonDNS = []string{ + "*.ezhelp.co.kr", + "*.remobo.en.softonic.com", + "*.action1.com", + "*.zohoassist.jp", + "*-dms.zoho.com.cn", + "*.teamviewer.com", + "*.superopsbeta.com", + "*.realvnc.com/en/connect/download/vnc", + "*.devtunnels.ms", + "*.agentreporting.atera.com", + "*.51.255.19.178", + "*.level.io", + "*.skyfex.com", + "*.mremoteng.org", + "*.gotohttp.com", + "*.guacamole.apache.org", + "*.zohoassist.com.cn", + "*.getgo.com", + "*.logmeinrescue.com", + "*.nate.com", + "*.spytech-web.com", + "*.rview.com", + "*.relay.splashtop.com", + "*.logmeinrescue.eu", + "*.opti-tune.com", + "*.hostedrmm.com", + "*.*mdmsupport.comodo.com", + "*.barracudamsp.com", + "*.cloudbackup.management", + "*.server-eye.de", + "*.todesktop.com", + "*.atled.syspectr.com", + "*.agent-api.atera.com", + "*.domotz.com", + "*.beamyourscreen.com", + "*.fleetdeck.io", + "*.cacerts.thawte.com", + "*.anysupport.net", + "*.basecamp.com", + "*.remotedesktop.com", + "*.*content.rview.com", + "*.auth.api.remote.it", + "*.eset.com/me/business/remote-management/remote-administrator/", + "*.136.243.104.235", + "*.ultraviewer.net", + "*.ultraviewer.net", + "*.cfcdn.pdq.com", + "*.getscreen.me", + "*.ninjaone.com", + "*.pilixo.com", + "*.kabutoservices.com", + "*.grtmprod.addigy.com", + "*.techinline.net", + "*.system-monitor.com", + "*.itsm-us1.comodo.com", + "*.jumpto.me", + "*.GoToMyPC.com", + "*.goverlan.com", + "*.upload_data.qq.com", + "*.rustdesk.com", + "*.imperosoftware.com", + "*.pubsub.pubnub.com", + "*.gatherplace.com", + "*.syncromsp.com", + "*.desktop.qq.com", + "*.meshcentral.com", + "*.beyondtrustcloud.com", + "*.ld.aurelius.host", + "*.beinsync.com", + "*.helpbeam.software.informer.com", + "*.*remotedesktop-pa.googleapis.com", + "*.deploy01.kaseya.com", + "*.servably.com", + "*.*cell-1.domotz.com", + "*.gw.remotix.com", + "*.au.pcmag.com/utilities/21470/webex-pcnow", + "*.rel.tunnels.api.visualstudio.com", + "*.site24x7.com/msp", + "*.tailscale.com", + "*.agents.level.io", + "*.ehorus.com", + "*.myivo-server.software.informer.com", + "*.*system-monitor.com", + "*.web.rustdesk.com", + "*.gotoassist.com", + "*.sophosupd.com", + "*.systemmonitor.us", + "*.desktopcentral.manageengine.cn", + "*.tailscale.com", + "*.remoteaccess.itarian.com", + "*.islonline.net", + "*.downloads.io", + "*.gateway.zohoassist.com", + "*.iperius-rs.com", + "*.nomachine.com", + "*.136.243.104.242", + "*.rport.io", + "*.fortra.com", + "*.plus*.site24x7.com", + "*.crosstecsoftware.com/remotecontrol", + "*.kaseya.net", + "*.rmansys.ru", + "*.connect.acronis.com", + "*.superops.ai", + "*.pulseway.com", + "*.download.pilixo.com", + "*.tele-desk.com", + "*.logicnow.com", + "*.www.remotepc.com", + "*.client.teamviewer.com", + "*.deskroll.com", + "*.serv.superopsalpha.com", + "*.builds.level.io", + "*.parallels.com/products/ras/try", + "*.adobeconnect.com", + "*.zoho.com", + "*.gatherplace.com", + "*.weezo.en.softonic.com", + "*.appcdn.atera.com", + "*.radmin.com", + "*.nanosystems.it", + "*.plus*.site24x7.eu", + "*.*cloudbackup.management", + "*.*setme.net", + "*.getscreen.me", + "*.bomgarcloud.com", + "*.plus*.site24x7.cn", + "*.remotedesktop.google.com", + "*.ngrok.com", + "*.jumpdesktop.com", + "*.*soti.net", + "*.senso.cloud", + "*.ps.pndsn.com", + "*.cloud.tanium.com", + "*.*systemmonitor.eu.com", + "*.repairshopr.com", + "*.01com.com/imintouch-remote-pc-desktop", + "*.dameware.com", + "*.le.laplink.com", + "*.endpoint.ingress.rapid7.com", + "*.ninjaone.com", + "*.analytics.insight.rapid7.com", + "*.iperiusremote.com", + "*.beamyourscreen.com", + "*.instanthousecall.net", + "*.screenconnect.com", + "*.remotepc.com", + "*.fastviewer.com", + "*.gatherplace.net", + "*.logmeininc.com", + "*.mspbackups.com", + "*.tanium.com/products/tanium-deploy", + "*.suspicious.barracudamsp.com", + "*.*remotedesktop.google.com", + "*.weezo.me", + "*.pcvisit.de", + "*.remotepass.com", + "*.manageengine.com/remote-monitoring-management/", + "*.iperiusremote.com", + "*.mikogo4.com", + "*.wen.laplink.com/product/laplink-gold", + "*.crossloop.en.softonic.com", + "*.sophosupd.net", + "*.ericom.com", + "*.internetid.ru", + "*.plus*.site24x7.in", + "*.plus*.site24x7.net.au", + "*.netop.com", + "*.beanywhere.en.uptodown.com/windows", + "*.*search.namequery.com", + "*.netreo.com", + "*.kickidler.com", + "*.kabuto.io", + "*.supremocontrol.com", + "*.atera-agent-heartbeat.servicebus.windows.net", + "*.fleetdeck.io", + "*.emcosoftware.com", + "*.deskday.ai", + "*.domotz.co", + "*.encapto.com", + "*.one.comodo.com", + "*.app.kabuto.io", + "*.*managedsupport.kaseya.net", + "*.kabuto.io", + "*.splashtop.com", + "*.logmein.com", + "*.client-api.aweray.com", + "*.01com.com", + "*.logmein-gateway.com", + "*.*systemmonitor.co.uk", + "*.goto.com", + "*.bomgarcloud.com", + "*.sunlogin.oray.com", + "*.parallels.com", + "*.systemmonitor.co.uk", + "*.online.level.io", + "*.router15.teamviewer.com", + "*.mdmsupport.comodo.com", + "*.showmypc.com", + "*.beinsync.net", + "*.agentreportingstore.blob.core.windows.net", + "*.community.sophos.com/on-premise-endpoint/f/sophos-endpoint-software/5725/sophos-remote-management-system", + "*.splashtop.com", + "*.fastsupport.com", + "*.superopsalpha.com", + "*.pubsub.atera.com", + "*.real-time-collaboration.com", + "*.dms.zoho.com.eu", + "*.gotohttp.com", + "*.app.pdq.com", + "*.nchuser.com", + "*.khelpdesk.com.br", + "*.cloudberrylab.com", + "*.*server.absolute.com", + "*.*cc.centrastage.net", + "*.electric.ai", + "*.GetScreen.me", + "*.github.com/Mikej81/WebRDP", + "*.msp360.com", + "*.getalphacontrol.com", + "*.screenmeet.com", + "*.portal.ehorus.com", + "*.logmein.eu", + "*.global.rel.tunnels.api.visualstudio.com", + "*.auvik.com", + "*.ps.atera.com", + "*.senso.cloud", + "*.msp360.com", + "*.www.quest.com/kace/", + "*.itsupport247.net", + "*.atera.pubnubapi.com", + "*.pcvisit.de", + "*.*ammyy.com", + "*.remotepc.com", + "*.support.services.microsoft.com", + "*.ivanticloud.com", + "*.client.oray.net", + "*.swi-tc.com", + "*.*systemmonitor.us", + "*.zoho.eu", + "*.panorama9.com", + "*.learn.microsoft.com/en-us/azure/developer/dev-tunnels/overview", + "*.live.screenconnect.com", + "*.anydesk.com", + "*.distantdesktop.com", + "*.itsupport247.net", + "*.bluetrait.io", + "*.remotecall.com", + "*.level.io", + "*.systemmonitor.eu.com", + "*.litemanager.com", + "*.agenthb.atera.com", + "*.app.atera.com", + "*.api.netreo.com", + "*.cloud.tanium.com", + "*.aomeisoftware.com", + "*.ammyy.com", + "*.netsupportmanager.com", + "*.litemanager.com", + "*.mikogo.com", + "*.instanthousecall.com", + "*.agents.addigy.com", + "*.136.243.18.122", + "*.remote.it", + "*.syncromsp.com", + "*.51.255.19.179", + "*.optitune.us", + "*.geo.netsupportsoftware.com", + "*.weezo.net", + "*.aeroadmin.com", + "*.*set.me", + "*.zoho.in", + "*.instanthousecall.com", + "*.pilixo.com", + "*.cloudflare.com/products/tunnel/", + "*.api.remote.it", + "*.startsupport.com", + "*.changes.panorama9.com", + "*.cognito-idp.us-west-2.amazonaws.com", + "*.my.kickidler.com", + "*.islonline.com", + "*.ultravnc.com", + "*.mdt.qq.com", + "*.connect.backdrop.cloud", + "*.beanywhere.com", + "*.neturo.uplus.co.kr", + "*.todesk.com", + "*.superops.ai", + "*.dwservice.net", + "*.alpemix.com", + "*.acceo.com/turbomeeting/", + "*.github.com/V-E-O/rdp2tcp", + "*.seetrol.co.kr", + "*.showmypc.com", + "*.assist.zoho.com", + "*.downloads.zohocdn.com", + "*.ocsinventory-ng.org", + "*.imperosoftware.com/impero-connect/", + "*.iperius.com", + "*.connectwise.com", + "*.dms.zoho.com", + "*.helpu.co.kr", + "*.anyplace-control.com", + "*.zerotier.com", + "*.weezo.net", + "*.sorillus.com", + "*.helpme.net", + "*.download.cnet.com/Net-Viewer/3000-2370_4-10034828.html", + "*.a1-backend-packages.s3.amazonaws.com", + "*.kabutoservices.com", + "*.assist.jumpcloud.com", + "*.kace.com", + "*.helpu.co.kr", + "*.rudesktop.ru", + "*.content.rview.com", + "*.systemmanager.ru/dntu.en/rdp_view.htm", + "*.syncroapi.com", + "*.control.connectwise.com", + "*.mikogo.com", + "*.api.splashtop.eu", + "*.datto.com/au/products/suspicious/", + "*.simple-help.com", + "*.gotoassist.me", + "*.repairtechsolutions.com/kabuto/", + "*.ivanti.com", + "*.litemanager.ru", + "*.api.jumpcloud.com", + "*.islonline.com", + "*.logicnow.com", + "*.anyviewer.com", + "*.supremocontrol.com", + "*.charon.netreo.net", + "*.logmeinrescue.com", + "*.zabbix.com", + "*.centuriontech.com", + "*.fixme.it", + "*.suspicious.datto.com", + "*.relay-[a-f0-9]{8}.net.anydesk.com:443", + "*.intelliadmin.com/remote-control", + "*.ivanti.com", + "*.app.syspectr.com", + "*.royalapps.com", + "*.ninjarmm.com", + "*.sorillus.com", + "*.donkz.nl", + "*.*remote.management", + "*.api.splashtop.com", + "*.gotoassist.at", + "*.todesk.com", + "*.*mygreenpc.com", + "*.servicedesk.itarian.com", + "*.zoho.com.cn", + "*.runsmart.io", + "*.jumpto.me", + "*.zohoassist.com", + "*.ivanti.com/", + "*.downloads.zohodl.com.cn", + "*.qq-messenger.en.softonic.com", + "*.docs.tacticalrmm.com", + "*.cmdm.comodo.com", + "*.sophos.com", + "*.247ithelp.com", + "*.jumpdesktop.com", + "*.boot.net.anydesk.com", + "*.auth*.aeroadmin.com", + "*.zoho.com/assist/", + "*.zoho.com.au", + "*.ezhelp.co.kr", + "*.rudesktop.ru", + "*.netsupportmanager.com", + "*.systemmonitor.us.cdn.cloudflare.net", + "*.fixme.it", + "*.desktopcentral.manageengine.com", + "*.trusted.panorama9.com", + "*.secure.instanthousecall.com", + "*.desktopcentral.manageengine.com.eu", + "*.n-able.com", + "*.crossloop.com", + "*.remote.management", + "*.attachments.servably.com", + "*.taf.teamviewer.com", + "*.remoteutilities.com", + "*.n-able.com", + "*.beanywhere.com", + "*.tailscale.io", + "*.tightvnc.com", + "*.xeox.com", + "*.soti.net/products/soti-pocket-controller", + "*.fastviewer.com", + "*.wangwang.taobao.com", + "*.agents*-cloud.acronis.com", + "*.deskroll.com", + "*.activation.netreo.net", + "*.packagesstore.blob.core.windows.net", + "*.everywhere.laplink.com", + "*.spyanywhere.com", + "*.github.com/stascorp/rdpwrap", + "*.teknopars.com", + "*.asapi*.aweray.net", + "*.auvik.com", + "*.intelliadmin.com", + "*.resources.ninjarmm.com", + "*.app.deskday.ai", + "*.scrn.mt", + "*.zerotier.com", + "*.prod.addigy.com", + "*.*signalserver.xyz", + "*.remotecall.com", + "*.my.auvik.com", + "*.cloud.acronis.com", + "*.connectwise.com", + "*.xeox.com", + "*.login.tailscale.com", + "*.naverisk.com", + "*.ntrsupport.com", + "*.kaseya.com", + "*.desktopstreaming.com", + "*.bluetrait.io", +} diff --git a/internal/pkg/hunt/detect/common/functions.go b/internal/pkg/hunt/detect/common/functions.go new file mode 100644 index 0000000..ad03854 --- /dev/null +++ b/internal/pkg/hunt/detect/common/functions.go @@ -0,0 +1,71 @@ +package common + +import ( + "fmt" + "strings" +) + +func AnalyzeExecutablePath(command string) (bool, string) { + // Extract executable path from command + var execPath string + if strings.HasPrefix(command, "\"") { + // Handle quoted paths + endQuote := strings.Index(command[1:], "\"") + if endQuote != -1 { + execPath = command[1 : endQuote+1] + } + } else { + // Handle unquoted paths + parts := strings.Fields(command) + if len(parts) > 0 { + execPath = parts[0] + } + } + + // Check for suspicious installation paths + suspiciousPaths := []string{ + "\\temp\\", "\\tmp\\", "\\appdata\\local\\temp\\", + "\\users\\public\\", "\\programdata\\", + "\\windows\\temp\\", "\\%temp%\\", + } + + execPathLower := strings.ToLower(execPath) + + // Check for suspicious installation paths + suspiciousPaths = []string{ + "\\temp\\", "\\tmp\\", "\\appdata\\local\\temp\\", + "\\users\\public\\", "\\programdata\\", + "\\windows\\temp\\", "\\%temp%\\", + } + + for _, suspPath := range suspiciousPaths { + if strings.Contains(execPathLower, suspPath) { + // Check for trusted publishers/companies + trustedPublishers := []string{ + "\\microsoft\\", + "\\adobe\\", + "\\google\\", + "\\intel\\", + "\\nvidia\\", + "\\oracle\\", + "\\citrix\\", + "\\vmware\\", + // Add more trusted publishers as needed + } + + isTrusted := false + for _, publisher := range trustedPublishers { + if strings.Contains(execPathLower, publisher) { + isTrusted = true + break + } + } + + if !isTrusted { + return true, fmt.Sprintf("Suspicious installation path: %s", suspPath) + } + } + } + + return false, "" +} diff --git a/internal/pkg/hunt/detect/connections/outbound.go b/internal/pkg/hunt/detect/connections/outbound.go new file mode 100644 index 0000000..250d1c3 --- /dev/null +++ b/internal/pkg/hunt/detect/connections/outbound.go @@ -0,0 +1,192 @@ +package connections + +import ( + "bufio" + "fmt" + "net" + "os/exec" + "regexp" + "rmm-hunter/internal/pkg/hunt/detect/common" + . "rmm-hunter/internal/suspicious" + "strings" +) + +func DetectOutboundConnections() []NetworkConnection { + var connections []NetworkConnection + + fmt.Printf("[*] Enumerating Outbound Connections...\n") + + // Get active connections via netstat + netstatConnections := getNetstatConnections() + connections = append(connections, netstatConnections...) + + // Get DNS cache entries for hostname resolution + dnsCache := getDNSCache() + + // Resolve hostnames for IP addresses + for i := range connections { + if hostname, exists := dnsCache[connections[i].RemoteAddr]; exists { + connections[i].RemoteHost = hostname + } else { + connections[i].RemoteHost = resolveHostname(connections[i].RemoteAddr) + } + } + + fmt.Printf(" [>] Dispositioning %d Outbound Connections\n", len(connections)) + + return compareConnections(connections) +} + +func compareConnections(connections []NetworkConnection) []NetworkConnection { + var suspiciousConnections []NetworkConnection + + for _, conn := range connections { + remote := conn.RemoteHost + + for _, dns := range common.CommonDNS { + if matchesDNSPattern(remote, dns) { + fmt.Printf(" [?] Found %s\n", conn.RemoteHost) + suspiciousConnections = append(suspiciousConnections, conn) + break + } + } + } + + fmt.Printf("[+] Found %d Suspicious Outbound Connections\n", len(suspiciousConnections)) + + return suspiciousConnections +} + +// matchesDNSPattern converts DNS pattern to regex and matches hostname +func matchesDNSPattern(hostname, pattern string) bool { + // Convert to lowercase for case-insensitive matching + pattern = strings.ToLower(pattern) + + // Remove leading dot if present + if strings.HasPrefix(pattern, ".") { + pattern = pattern[1:] + } + + // Escape special regex characters except * and . + pattern = regexp.QuoteMeta(pattern) + + // Convert wildcards back to regex + pattern = strings.ReplaceAll(pattern, `\*`, `[^.]*`) + pattern = strings.ReplaceAll(pattern, `\.`, `\.`) + + // Anchor the pattern to match end of hostname + pattern = `(^|\.)` + pattern + `$` + + regex, err := regexp.Compile(pattern) + if err != nil { + return false + } + + return regex.MatchString(hostname) +} + +func getNetstatConnections() []NetworkConnection { + var connections []NetworkConnection + + cmd := exec.Command("netstat", "-ano") + output, err := cmd.Output() + if err != nil { + return connections + } + + scanner := bufio.NewScanner(strings.NewReader(string(output))) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.Contains(line, "TCP") && strings.Contains(line, "ESTABLISHED") { + fields := strings.Fields(line) + if len(fields) >= 5 { + localAddr := fields[1] + remoteAddr := fields[2] + state := fields[3] + pid := fields[4] + + // Filter for outbound connections (exclude localhost) + if !strings.HasPrefix(remoteAddr, "127.0.0.1") && + !strings.HasPrefix(remoteAddr, "::1") { + connections = append(connections, NetworkConnection{ + LocalAddr: localAddr, + RemoteAddr: extractIP(remoteAddr), + State: state, + PID: pid, + }) + } + } + } + } + + return connections +} + +func getDNSCache() map[string]string { + cache := make(map[string]string) + + cmd := exec.Command("ipconfig", "/displaydns") + output, err := cmd.Output() + if err != nil { + return cache + } + + lines := strings.Split(string(output), "\n") + var currentHost string + + for _, line := range lines { + line = strings.TrimSpace(line) + + if strings.Contains(line, "Record Name") { + parts := strings.Split(line, ":") + if len(parts) > 1 { + currentHost = strings.TrimSpace(parts[1]) + } + } + + if strings.Contains(line, "A (Host) Record") && currentHost != "" { + // Look for IP in next few lines + continue + } + + if currentHost != "" && net.ParseIP(strings.TrimSpace(line)) != nil { + cache[strings.TrimSpace(line)] = currentHost + currentHost = "" + } + } + + return cache +} + +func extractIP(addr string) string { + if idx := strings.LastIndex(addr, ":"); idx != -1 { + return addr[:idx] + } + return addr +} + +func resolveHostname(ip string) string { + names, err := net.LookupAddr(ip) + if err != nil || len(names) == 0 { + return ip + } + return strings.TrimSuffix(names[0], ".") +} + +// GetHTTPHostnames extracts unique hostnames from outbound connections +func GetHTTPHostnames() []string { + connections := DetectOutboundConnections() + hostnameMap := make(map[string]bool) + var hostnames []string + + for _, conn := range connections { + if conn.RemoteHost != "" && conn.RemoteHost != conn.RemoteAddr { + if !hostnameMap[conn.RemoteHost] { + hostnameMap[conn.RemoteHost] = true + hostnames = append(hostnames, conn.RemoteHost) + } + } + } + + return hostnames +} diff --git a/internal/pkg/hunt/detect/connections/outbound_test.go b/internal/pkg/hunt/detect/connections/outbound_test.go new file mode 100644 index 0000000..fc978ad --- /dev/null +++ b/internal/pkg/hunt/detect/connections/outbound_test.go @@ -0,0 +1,17 @@ +package connections + +import "testing" + +func TestDetectOutboundConnections(t *testing.T) { + conns := DetectOutboundConnections() + for _, conn := range conns { + t.Logf("-----") + t.Logf("PID: %s", conn.PID) + t.Logf("LocalAddr: %s", conn.LocalAddr) + t.Logf("RemoteAddr: %s", conn.RemoteAddr) + t.Logf("RemoteHost: %s", conn.RemoteHost) + t.Logf("State: %s", conn.State) + t.Logf("Process: %s", conn.Process) + t.Logf("-----") + } +} diff --git a/internal/pkg/hunt/detect/directory/directories.go b/internal/pkg/hunt/detect/directory/directories.go new file mode 100644 index 0000000..ec34138 --- /dev/null +++ b/internal/pkg/hunt/detect/directory/directories.go @@ -0,0 +1,36 @@ +package directory + +import ( + "fmt" + "os" + "path/filepath" + "rmm-hunter/internal/pkg/hunt/detect/common" + "strings" +) + +var appData = os.Getenv("APPDATA") + +func Detect() []string { + var suspiciousDirectories []string + + fmt.Printf("[*] Enumerating Suspicious Directories \n") + // Check for common directories + for _, dir := range common.CommonDirectories { + dir = replaceAppData(dir) + if _, err := os.Stat(dir); err == nil { + fmt.Printf(" [?] Found %s\n", dir) + suspiciousDirectories = append(suspiciousDirectories, dir) + } + } + fmt.Printf("[+] Found %d Suspicious Directories\n", len(suspiciousDirectories)) + + return suspiciousDirectories +} + +func replaceAppData(path string) string { + if strings.Contains(path, "{{APPDATA}}") { + p := strings.Replace(path, "{{APPDATA}}", "", -1) + return filepath.Join(appData, p) + } + return path +} diff --git a/internal/pkg/hunt/detect/directory/directories_test.go b/internal/pkg/hunt/detect/directory/directories_test.go new file mode 100644 index 0000000..cb4c978 --- /dev/null +++ b/internal/pkg/hunt/detect/directory/directories_test.go @@ -0,0 +1,12 @@ +package directory + +import "testing" + +func TestDetect(t *testing.T) { + directories := Detect() + for _, dir := range directories { + t.Logf("-----") + t.Logf("Directory: %s", dir) + t.Logf("-----") + } +} diff --git a/internal/pkg/hunt/detect/processes/processes.go b/internal/pkg/hunt/detect/processes/processes.go new file mode 100644 index 0000000..936e569 --- /dev/null +++ b/internal/pkg/hunt/detect/processes/processes.go @@ -0,0 +1,75 @@ +package processes + +import ( + "fmt" + "rmm-hunter/internal/pkg/hunt/detect/common" + . "rmm-hunter/internal/suspicious" + "strings" + + "github.com/Kraken-OffSec/Scurvy/core/process" +) + +func Detect() []Process { + fmt.Printf("[*] Enumerating Processes \n") + + processes, err := process.Processes() + if err != nil { + fmt.Printf("[-] Error enumerating processes: %s\n", err.Error()) + return []Process{} + } + + fmt.Printf(" [>] Dispositioning %d Processes\n", len(processes)) + + return compareProcesses(processes) +} + +func compareProcesses(processes []process.Process) []Process { + var suspiciousProcesses []Process + + for _, proc := range processes { + procName := proc.Executable() + procNameLower := strings.ToLower(procName) + + // Get full executable path if available + var fullPath string + if proc.Path() != "" { + fullPath = proc.Path() + } + + // Check against known RMMs + isRMMMatch := false + for _, rmm := range common.CommonRMMs { + rmmLower := strings.ToLower(rmm) + if strings.Contains(procNameLower, rmmLower) { + isRMMMatch = true + break + } + } + + // Check for suspicious path + isPathSuspicious := false + pathReason := "" + if fullPath != "" { + isPathSuspicious, pathReason = common.AnalyzeExecutablePath(fullPath) + } + + if isRMMMatch || isPathSuspicious { + args := "" + if isPathSuspicious { + args = fmt.Sprintf("[%s]", pathReason) + } + + fmt.Printf(" [?] Found %s\n", procName) + suspiciousProcesses = append(suspiciousProcesses, Process{ + Name: procName, + PID: proc.Pid(), + PPID: proc.PPid(), + Path: fullPath, + Args: args, + }) + } + } + + fmt.Printf("[+] Found %d Suspicious Processes\n", len(suspiciousProcesses)) + return suspiciousProcesses +} diff --git a/internal/pkg/hunt/detect/processes/processes_test.go b/internal/pkg/hunt/detect/processes/processes_test.go new file mode 100644 index 0000000..725f455 --- /dev/null +++ b/internal/pkg/hunt/detect/processes/processes_test.go @@ -0,0 +1,15 @@ +package processes + +import "testing" + +func TestDetect(t *testing.T) { + processes := Detect() + for _, proc := range processes { + t.Logf("-----") + t.Logf("Name: %s", proc.Name) + t.Logf("PID: %d", proc.PID) + t.Logf("PPID: %d", proc.PPID) + t.Logf("Path: %s", proc.Path) + t.Logf("-----") + } +} diff --git a/internal/pkg/hunt/detect/scheduledTasks/tasks.go b/internal/pkg/hunt/detect/scheduledTasks/tasks.go new file mode 100644 index 0000000..1df8f76 --- /dev/null +++ b/internal/pkg/hunt/detect/scheduledTasks/tasks.go @@ -0,0 +1,54 @@ +package scheduledTasks + +import ( + "fmt" + "rmm-hunter/internal/pkg/hunt/detect/common" + . "rmm-hunter/internal/suspicious" + "strings" + "time" + + schTasks "github.com/Kraken-OffSec/Scurvy/core/scheduledTasks" +) + +func Detect() []*ScheduledTask { + fmt.Printf("[*] Enumerating Scheduled Tasks \n") + tasks, err := schTasks.ListTasks() + if err != nil { + fmt.Printf("[-] Error enumerating scheduled tasks: %s\n", err.Error()) + return []*ScheduledTask{} + } + fmt.Printf(" [>] Dispositioning %d Scheduled Tasks\n", len(tasks)) + + return compareTasks(tasks) +} + +func compareTasks(tasks []schTasks.TaskInfo) []*ScheduledTask { + var suspiciousTasks []*ScheduledTask + + for _, task := range tasks { + for _, rmm := range common.CommonRMMs { + rmmLower := strings.ToLower(rmm) + taskNameLower := strings.ToLower(task.Name) + if strings.Contains(taskNameLower, rmmLower) { + fmt.Printf(" [?] Found %s\n", task.Name) + suspiciousTasks = append(suspiciousTasks, &ScheduledTask{ + Name: task.Name, + Author: task.Author, + LastRun: task.LastRun.Format(time.RFC3339), + NextRun: task.NextRun.Format(time.RFC3339), + LastResult: task.LastResult, + CreatedDate: task.CreationDate.Format(time.RFC3339), + State: task.State, + Path: task.Path, + Description: task.Description, + ModifiedDate: "", + Enabled: task.Enabled, + }) + break + } + } + } + + fmt.Printf("[+] Found %d Suspicious Scheduled Tasks\n", len(suspiciousTasks)) + return suspiciousTasks +} diff --git a/internal/pkg/hunt/detect/scheduledTasks/tasks_test.go b/internal/pkg/hunt/detect/scheduledTasks/tasks_test.go new file mode 100644 index 0000000..f81dc75 --- /dev/null +++ b/internal/pkg/hunt/detect/scheduledTasks/tasks_test.go @@ -0,0 +1,22 @@ +package scheduledTasks + +import "testing" + +func TestDetect(t *testing.T) { + tasks := Detect() + for _, task := range tasks { + t.Logf("-----") + t.Logf("Name: %s", task.Name) + t.Logf("Author: %s", task.Author) + t.Logf("LastRun: %s", task.LastRun) + t.Logf("NextRun: %s", task.NextRun) + t.Logf("LastResult: %s", task.LastResult) + t.Logf("CreatedDate: %s", task.CreatedDate) + t.Logf("State: %s", task.State) + t.Logf("Path: %s", task.Path) + t.Logf("Description: %s", task.Description) + t.Logf("ModifiedDate: %s", task.ModifiedDate) + t.Logf("Enabled: %t", task.Enabled) + t.Logf("-----") + } +} diff --git a/internal/pkg/hunt/detect/services/services.go b/internal/pkg/hunt/detect/services/services.go new file mode 100644 index 0000000..7c37c5a --- /dev/null +++ b/internal/pkg/hunt/detect/services/services.go @@ -0,0 +1,149 @@ +package services + +import ( + "fmt" + "rmm-hunter/internal/pkg/hunt/detect/common" + . "rmm-hunter/internal/suspicious" + "strings" + + "github.com/Kraken-OffSec/Scurvy/core/service" + "golang.org/x/sys/windows" +) + +func Detect() []*Service { + fmt.Printf("[*] Enumerating Services \n") + + scm, err := service.Connect() + if err != nil { + fmt.Printf("[-] Error getting Service Manager: %s\n", err.Error()) + return []*Service{} + } + defer windows.Close(scm.Handle) + + services, err := scm.ListServices() + if err != nil { + fmt.Printf("[-] Error enumerating services: %s\n", err.Error()) + return []*Service{} + } + + fmt.Printf(" [>] Dispositioning %d Services\n", len(services)) + + return compareServices(services, scm) +} + +func compareServices(serviceStrings []string, scm *service.Mgr) []*Service { + var suspiciousServices []*Service + + for _, serviceString := range serviceStrings { + svc, err := scm.OpenService(serviceString) + if err != nil { + fmt.Printf(" [>-] Error opening service %s: %s\n", serviceString, err.Error()) + continue + } + config, err := svc.Config() + if err != nil { + fmt.Printf(" [>-] Error getting service config %s: %s\n", serviceString, err.Error()) + continue + } + svcStartName := strings.ToLower(config.ServiceStartName) + svcDisplayName := strings.ToLower(config.DisplayName) + svcBinaryPath := strings.ToLower(config.BinaryPathName) + + // Check against known RMMs + isRMMMatch := false + for _, rmm := range common.CommonRMMs { + rmmLower := strings.ToLower(rmm) + if strings.Contains(svcDisplayName, rmmLower) || strings.Contains(svcStartName, rmmLower) || strings.Contains(svcBinaryPath, rmmLower) { + isRMMMatch = true + break + } + } + + // Check for suspicious path regardless of RMM match + isPathSuspicious, pathReason := common.AnalyzeExecutablePath(config.BinaryPathName) + + if isRMMMatch || isPathSuspicious { + description := config.Description + if isPathSuspicious { + description += fmt.Sprintf(" [%s]", pathReason) + } + + fmt.Printf(" [?] Found %s\n", config.DisplayName) + suspiciousServices = append(suspiciousServices, &Service{ + Name: serviceString, + DisplayName: config.DisplayName, + ServiceTypeRaw: config.ServiceType, + ServiceType: getServiceType(config.ServiceType), + StartTypeRaw: config.StartType, + StartType: getStartType(config.StartType), + ErrorControlRaw: config.ErrorControl, + ErrorControl: getErrorControl(config.ErrorControl), + BinaryPathName: config.BinaryPathName, + LoadOrderGroup: config.LoadOrderGroup, + TagId: config.TagId, + Dependencies: config.Dependencies, + ServiceStartName: config.ServiceStartName, + Password: config.Password, + Description: description, + SidType: config.SidType, + DelayedAutoStart: config.DelayedAutoStart, + }) + } + } + + fmt.Printf("[+] Found %d Suspicious Services\n", len(suspiciousServices)) + return suspiciousServices +} + +func getServiceType(raw uint32) string { + switch raw { + case 1: + return "KernelDriver" + case 2: + return "FileSystemDriver" + case 4: + return "Adapter" + case 8: + return "RecognizerDriver" + case 16: + return "Win32OwnProcess" + case 32: + return "Win32ShareProcess" + case 256: + return "InteractiveProcess" + default: + return "Unknown" + } +} + +func getStartType(raw uint32) string { + switch raw { + case 0: + return "Boot" + case 1: + return "System" + case 2: + return "Automatic" + case 3: + return "Manual" + case 4: + return "Disabled" + default: + return "Unknown" + } +} + +func getErrorControl(raw uint32) string { + switch raw { + case 0: + return "Ignore" + case 1: + return "Normal" + case 2: + return "Severe" + case 3: + return "Critical" + default: + return fmt.Sprintf("Unknown %d", raw) + } +} diff --git a/internal/pkg/hunt/detect/services/services_test.go b/internal/pkg/hunt/detect/services/services_test.go new file mode 100644 index 0000000..8476719 --- /dev/null +++ b/internal/pkg/hunt/detect/services/services_test.go @@ -0,0 +1,28 @@ +package services + +import "testing" + +func TestDetect(t *testing.T) { + services := Detect() + for _, svc := range services { + t.Logf("-----") + t.Logf("Name: %s", svc.Name) + t.Logf("DisplayName: %s", svc.DisplayName) + t.Logf("ServiceTypeRaw: %d", svc.ServiceTypeRaw) + t.Logf("ServiceType: %s", svc.ServiceType) + t.Logf("StartTypeRaw: %d", svc.StartTypeRaw) + t.Logf("StartType: %s", svc.StartType) + t.Logf("ErrorControlRaw: %d", svc.ErrorControlRaw) + t.Logf("ErrorControl: %s", svc.ErrorControl) + t.Logf("BinaryPathName: %s", svc.BinaryPathName) + t.Logf("LoadOrderGroup: %s", svc.LoadOrderGroup) + t.Logf("TagId: %d", svc.TagId) + t.Logf("Dependencies: %v", svc.Dependencies) + t.Logf("ServiceStartName: %s", svc.ServiceStartName) + t.Logf("Password: %s", svc.Password) + t.Logf("Description: %s", svc.Description) + t.Logf("SidType: %d", svc.SidType) + t.Logf("DelayedAutoStart: %t", svc.DelayedAutoStart) + t.Logf("-----") + } +} diff --git a/internal/pkg/hunt/eliminate/eliminate.go b/internal/pkg/hunt/eliminate/eliminate.go new file mode 100644 index 0000000..cadbced --- /dev/null +++ b/internal/pkg/hunt/eliminate/eliminate.go @@ -0,0 +1 @@ +package eliminate diff --git a/internal/pkg/hunter/hunter.go b/internal/pkg/hunter/hunter.go new file mode 100644 index 0000000..90a0124 --- /dev/null +++ b/internal/pkg/hunter/hunter.go @@ -0,0 +1,55 @@ +package hunter + +import ( + "rmm-hunter/internal/pkg" + "rmm-hunter/internal/pkg/hunt/detect/autorun" + "rmm-hunter/internal/pkg/hunt/detect/binaries" + "rmm-hunter/internal/pkg/hunt/detect/connections" + "rmm-hunter/internal/pkg/hunt/detect/directory" + "rmm-hunter/internal/pkg/hunt/detect/processes" + "rmm-hunter/internal/pkg/hunt/detect/scheduledTasks" + "rmm-hunter/internal/pkg/hunt/detect/services" + . "rmm-hunter/internal/suspicious" +) + +type Hunter struct { + Options pkg.RunOptions + Sus Suspicious +} + +func Start(options pkg.RunOptions) { + hunter := Hunter{ + Options: options, + } + hunter.run() +} + +func (h *Hunter) run() { + // Find suspicious processes + processes := processes.Detect() + h.Sus.Processes = processes + + // Find suspicious services + services := services.Detect() + h.Sus.Services = services + + // Find suspicious autoruns + autoruns := autorun.Detect() + h.Sus.AutoRuns = autoruns + + // Find suspicious outbound connections + connections := connections.DetectOutboundConnections() + h.Sus.OutboundConnections = connections + + // Find suspicious scheduled tasks + tasks := scheduledTasks.Detect() + h.Sus.ScheduledTasks = tasks + + // Find suspicious binaries + binaries := binaries.Detect() + h.Sus.Binaries = binaries + + // Find suspicious directories + directories := directory.Detect() + h.Sus.Directories = directories +} diff --git a/internal/pkg/options.go b/internal/pkg/options.go new file mode 100644 index 0000000..291ff16 --- /dev/null +++ b/internal/pkg/options.go @@ -0,0 +1,5 @@ +package pkg + +type RunOptions struct { + ExcludeRMMs []string +} diff --git a/internal/pkg/writer/html.go b/internal/pkg/writer/html.go new file mode 100644 index 0000000..41a4261 --- /dev/null +++ b/internal/pkg/writer/html.go @@ -0,0 +1 @@ +package writer diff --git a/internal/pkg/writer/json.go b/internal/pkg/writer/json.go new file mode 100644 index 0000000..41a4261 --- /dev/null +++ b/internal/pkg/writer/json.go @@ -0,0 +1 @@ +package writer diff --git a/internal/suspicious/rmm.go b/internal/suspicious/rmm.go new file mode 100644 index 0000000..0c6c8cc --- /dev/null +++ b/internal/suspicious/rmm.go @@ -0,0 +1,114 @@ +package suspicious + +/* +Suspicious +The object used to resemble the Suspicious artifacts and activities. +*/ +type Suspicious struct { + Artifacts []Artifact `json:"artifacts"` + Persistence Persistence `json:"persistence"` + RootFolder string `json:"rootFolder"` + Binaries []string `json:"binaries"` + Directories []string `json:"directories"` + Services []*Service `json:"services"` + Processes []Process `json:"processes"` + OutboundConnections []NetworkConnection `json:"outboundConnections"` + AutoRuns []AutoRun `json:"autoRuns"` + ScheduledTasks []*ScheduledTask `json:"scheduledTasks"` +} + +type NetworkConnection struct { + LocalAddr string + RemoteAddr string + RemoteHost string + State string + PID string + Process string +} + +/* +Artifact +The object used to resemble the artifacts found by the Suspicious software. +*/ +type Artifact struct { + Location string `json:"location"` + Content string `json:"content"` + SHA256 string `json:"sha256"` +} + +/* +Persistence +The object used to resemble the persistence methods used by the Suspicious software. +*/ +type Persistence struct { + AutoRuns []AutoRun `json:"autoRuns"` + ScheduledTasks []ScheduledTask `json:"scheduledTasks"` +} + +/* +AutoRun +The object used to resemble the auto run methods used by the Suspicious software. +*/ +type AutoRun struct { + Name string `json:"name"` + Command string `json:"command"` + Location string `json:"location"` + Enabled bool `json:"enabled"` + Description string `json:"description"` +} + +/* +ScheduledTask +The object used to resemble the scheduled tasks used by the Suspicious software. +*/ +type ScheduledTask struct { + Name string `json:"name"` + Author string `json:"author"` + CreatedDate string `json:"createdDate"` + ModifiedDate string `json:"modifiedDate"` + Description string `json:"description"` + State string `json:"state"` + Enabled bool `json:"enabled"` + LastResult string `json:"lastResult"` + NextRun string `json:"nextRun"` + LastRun string `json:"lastRun"` + Path string `json:"path"` +} + +/* +Process +The object used to resemble the processes used by the Suspicious software. +*/ +type Process struct { + Name string `json:"name"` + PID int `json:"pid"` + PPID int `json:"ppid"` + Parent string `json:"parent"` + Args string `json:"args"` + Created string `json:"created"` + Path string `json:"path"` +} + +/* +Service +The object used to resemble the services used by the Suspicious software. +*/ +type Service struct { + Name string `json:"name"` + DisplayName string `json:"displayName"` + ServiceTypeRaw uint32 `json:"serviceTypeRaw"` + ServiceType string `json:"serviceType"` + StartTypeRaw uint32 `json:"startTypeRaw"` + StartType string `json:"startType"` + ErrorControlRaw uint32 `json:"errorControlRaw"` + ErrorControl string `json:"errorControl"` + BinaryPathName string `json:"binaryPathName"` + LoadOrderGroup string `json:"loadOrderGroup"` + TagId uint32 `json:"tagId"` + Dependencies []string `json:"dependencies"` + ServiceStartName string `json:"serviceStartName"` + Password string `json:"password"` + Description string `json:"description"` + SidType uint32 `json:"sidType"` + DelayedAutoStart bool `json:"delayedAutoStart"` +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..ec9ba86 --- /dev/null +++ b/main.go @@ -0,0 +1,9 @@ +package main + +import ( + "rmm-hunter/cmd" +) + +func main() { + cmd.Execute() +}