Implement initial detection and data structures for suspicious artifacts

This commit is contained in:
Evan Hosinski
2025-10-10 15:35:17 -04:00
commit 10b1bb7ed6
26 changed files with 2382 additions and 0 deletions
+170
View File
@@ -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, ""
}
@@ -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("-----")
}
}
@@ -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)
}
}
@@ -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("-----")
}
}
File diff suppressed because it is too large Load Diff
@@ -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, ""
}
@@ -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
}
@@ -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("-----")
}
}
@@ -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
}
@@ -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("-----")
}
}
@@ -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
}
@@ -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("-----")
}
}
@@ -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
}
@@ -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("-----")
}
}
@@ -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)
}
}
@@ -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("-----")
}
}
+1
View File
@@ -0,0 +1 @@
package eliminate
+55
View File
@@ -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
}
+5
View File
@@ -0,0 +1,5 @@
package pkg
type RunOptions struct {
ExcludeRMMs []string
}
+1
View File
@@ -0,0 +1 @@
package writer
+1
View File
@@ -0,0 +1 @@
package writer
+114
View File
@@ -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"`
}