Refactor AutoRun detection to use Scurvy library, enhance suspicious entry checks, and update UI rendering for detailed info
This commit is contained in:
@@ -6,7 +6,7 @@ import (
|
||||
. "rmm-hunter/internal/suspicious"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/sys/windows/registry"
|
||||
"github.com/Kraken-OffSec/Scurvy/core/autoruns"
|
||||
)
|
||||
|
||||
func Detect() []AutoRun {
|
||||
@@ -14,158 +14,79 @@ func Detect() []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`,
|
||||
}
|
||||
// Use Scurvy to enumerate autoruns from multiple sources
|
||||
autoRuns := autoruns.GetAllAutoruns()
|
||||
fmt.Printf(" [>] Dispositioning %d AutoRun Entries\n", len(autoRuns))
|
||||
|
||||
// Check both HKLM and HKCU
|
||||
roots := []registry.Key{registry.LOCAL_MACHINE, registry.CURRENT_USER}
|
||||
rootNames := []string{"HKLM", "HKCU"}
|
||||
for _, ar := range autoRuns {
|
||||
// Map Scurvy autorun to our Suspicious.AutoRun struct
|
||||
sar := AutoRun{
|
||||
Type: ar.Type,
|
||||
Location: ar.Location,
|
||||
ImagePath: ar.ImagePath,
|
||||
ImageName: ar.ImageName,
|
||||
Arguments: ar.Arguments,
|
||||
MD5: ar.MD5,
|
||||
SHA1: ar.SHA1,
|
||||
SHA256: ar.SHA256,
|
||||
Entry: ar.Entry,
|
||||
LaunchString: ar.LaunchString,
|
||||
}
|
||||
|
||||
fmt.Printf(" [>] Dispositioning %d AutoRun Keys\n", len(autorunKeys)*len(roots))
|
||||
|
||||
totalEntries := 0
|
||||
for i, root := range roots {
|
||||
for _, keyPath := range autorunKeys {
|
||||
entries := checkAutoRunKey(root, keyPath, rootNames[i])
|
||||
totalEntries += len(entries)
|
||||
suspiciousAutoRuns = append(suspiciousAutoRuns, entries...)
|
||||
if isSuspiciousAutoRunEntry(sar) {
|
||||
fmt.Printf(" [?] Found %s | %s | %s\n", sar.Location, sar.Entry, sar.ImagePath)
|
||||
suspiciousAutoRuns = append(suspiciousAutoRuns, sar)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// isSuspiciousAutoRunEntry determines if an autorun looks like an RMM by
|
||||
// checking image path/name, location, entry and launch string against
|
||||
// common RMM indicators and suspicious image suffixes. It also flags
|
||||
// suspicious installation paths.
|
||||
func isSuspiciousAutoRunEntry(ar AutoRun) bool {
|
||||
// Prepare lowercase fields for matching
|
||||
fields := []string{
|
||||
strings.ToLower(ar.ImageName),
|
||||
strings.ToLower(ar.ImagePath),
|
||||
strings.ToLower(ar.Location),
|
||||
strings.ToLower(ar.LaunchString),
|
||||
strings.ToLower(ar.Entry),
|
||||
}
|
||||
|
||||
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
|
||||
// Match against known RMM names/keywords
|
||||
for _, rmm := range common.CommonRMMs {
|
||||
rmmLower := strings.ToLower(rmm)
|
||||
if strings.Contains(nameLower, rmmLower) || strings.Contains(commandLower, rmmLower) {
|
||||
r := strings.ToLower(rmm)
|
||||
for _, f := range fields {
|
||||
if strings.Contains(f, r) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Match against common suspicious image suffix/patterns (path or name)
|
||||
imgPathLower := strings.ToLower(ar.ImagePath)
|
||||
imgNameLower := strings.ToLower(ar.ImageName)
|
||||
for _, suf := range common.CommonImageSuffixes {
|
||||
s := strings.ToLower(suf)
|
||||
if strings.Contains(imgPathLower, s) || strings.Contains(imgNameLower, s) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Check against common Suspicious executable patterns
|
||||
for _, imageEnd := range common.CommonImageSuffixes {
|
||||
imageEndLower := strings.ToLower(imageEnd)
|
||||
if strings.Contains(commandLower, imageEndLower) {
|
||||
return true
|
||||
}
|
||||
// Suspicious installation paths
|
||||
if suspicious, _ := common.AnalyzeExecutablePath(ar.ImagePath); suspicious {
|
||||
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) {
|
||||
// Consider launch string as a command line too
|
||||
if ar.LaunchString != "" {
|
||||
if suspicious, _ := common.AnalyzeExecutablePath(ar.LaunchString); suspicious {
|
||||
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, ""
|
||||
}
|
||||
|
||||
@@ -6,11 +6,14 @@ 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("Type: %s", ar.Type)
|
||||
t.Logf("Entry: %s", ar.Entry)
|
||||
t.Logf("Location: %s", ar.Location)
|
||||
t.Logf("Enabled: %t", ar.Enabled)
|
||||
t.Logf("Description: %s", ar.Description)
|
||||
t.Logf("Image: %s", ar.ImagePath)
|
||||
t.Logf("Args: %s", ar.Arguments)
|
||||
t.Logf("MD5: %s", ar.MD5)
|
||||
t.Logf("SHA1: %s", ar.SHA1)
|
||||
t.Logf("SHA256: %s", ar.SHA256)
|
||||
t.Logf("-----")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user