From e79b68eac8e4b40279d231ed161c2b6908fe7c79 Mon Sep 17 00:00:00 2001 From: codeyourweb Date: Sat, 24 Jan 2026 12:54:08 +0100 Subject: [PATCH 1/6] Enhance scanning pipeline with concurrency improvements and detailed logging --- event_forwarding.go | 8 ++++- finder.go | 14 +++++--- main.go | 9 +++-- scanner_pipeline.go | 82 ++++++++++++++++++++++++++++++++++----------- 4 files changed, 85 insertions(+), 28 deletions(-) diff --git a/event_forwarding.go b/event_forwarding.go index 8c38c31..d9c442e 100644 --- a/event_forwarding.go +++ b/event_forwarding.go @@ -24,6 +24,7 @@ type EventForwarder struct { currentFilePath string lastRotation time.Time fileMutex sync.Mutex + wg sync.WaitGroup } // FastFinderEvent represents an event to be forwarded @@ -133,6 +134,7 @@ func InitializeEventForwarding(config *ForwardingConfig) error { httpClient: httpClient, } + eventForwarder.wg.Add(1) // Start the forwarding goroutine go eventForwarder.forwardingLoop() @@ -243,6 +245,7 @@ func (ef *EventForwarder) shouldForwardEvent(eventType, severity string) bool { // forwardingLoop runs the periodic event forwarding func (ef *EventForwarder) forwardingLoop() { + defer ef.wg.Done() ticker := time.NewTicker(time.Duration(ef.config.FlushTime) * time.Second) defer ticker.Stop() @@ -466,11 +469,14 @@ func (ef *EventForwarder) cleanOldFiles() { // StopEventForwarding stops the event forwarding system func StopEventForwarding() { if eventForwarder != nil { + close(eventForwarder.stopChannel) + eventForwarder.wg.Wait() + // Close current file if open if eventForwarder.currentFile != nil { eventForwarder.currentFile.Close() + eventForwarder.currentFile = nil } - close(eventForwarder.stopChannel) eventForwarder = nil } } diff --git a/finder.go b/finder.go index c71d2f5..1e6b5a1 100644 --- a/finder.go +++ b/finder.go @@ -68,7 +68,6 @@ func FindInFilesContent(files *[]string, patterns []string, rules *yara.Rules, h // handle file content and checksum match for _, m := range CheckFileChecksumAndContent(path, b, hashList, patterns) { if !Contains(matchingFiles, m) { - LogMessage(LOG_ALERT, "(ALERT)", "File content match on:", path) matchingFiles = append(matchingFiles, m) } } @@ -117,7 +116,6 @@ func FindInFilesContent(files *[]string, patterns []string, rules *yara.Rules, h // handle file content and checksum match for each file in the archive for _, m := range CheckFileChecksumAndContent(path, body, hashList, patterns) { if !Contains(matchingFiles, m) { - LogMessage(LOG_ALERT, "(ALERT)", "File content match on:", path) matchingFiles = append(matchingFiles, m) } } @@ -186,10 +184,16 @@ func checkForChecksum(path string, content []byte, hashList []string) (matchingF // checkForStringPattern check if file content matches any specified pattern func checkForStringPattern(path string, content []byte, patterns []string) (matchingFiles []string) { LogMessage(LOG_VERBOSE, "(SCAN)", "Checking grep patterns in", path) + contentStr := string(content) + lines := strings.Split(contentStr, "\n") + for _, expression := range patterns { - if strings.Contains(string(content), expression) { - LogMessage(LOG_ALERT, "(ALERT)", "Grep match:", expression, "in", path) - matchingFiles = append(matchingFiles, path) + for i, line := range lines { + if strings.Contains(line, expression) { + LogMessage(LOG_ALERT, "(ALERT)", "Grep match:", expression, "in", path, "at line", fmt.Sprintf("%d:", i+1), strings.TrimSpace(line)) + matchingFiles = append(matchingFiles, path) + break + } } } return matchingFiles diff --git a/main.go b/main.go index be4c40c..0f9c771 100644 --- a/main.go +++ b/main.go @@ -266,6 +266,11 @@ func MainFastfinderRoutine(config Configuration, pConfigPath string, pNoAdvUI bo // Wait for matches collection to complete <-matchesDone + // Update stats + totalFilesScanned += int(pipeline.GetFilesScanned()) + totalErrorsEncountered += int(pipeline.GetErrorsEncountered()) + totalMatchesFound += len(matchingFiles) + // listing and copy matching files LogMessage(LOG_INFO, "(INFO)", "scan finished in", basePath) if len(matchingFiles) > 0 { @@ -297,8 +302,8 @@ func MainFastfinderRoutine(config Configuration, pConfigPath string, pNoAdvUI bo StopEventForwarding() } - LogMessage(LOG_INFO, "(INFO)", fmt.Sprintf("Scan completed in %v", scanDuration)) - LogMessage(LOG_INFO, "(INFO)", fmt.Sprintf("Files scanned: %d, Matches found: %d, Errors: %d", + LogMessage(LOG_ALERT, "(INFO)", fmt.Sprintf("Scan completed in %v", scanDuration)) + LogMessage(LOG_ALERT, "(INFO)", fmt.Sprintf("Files scanned: %d, Matches found: %d, Errors: %d", totalFilesScanned, totalMatchesFound, totalErrorsEncountered)) ExitProgram(0, !UIactive) diff --git a/scanner_pipeline.go b/scanner_pipeline.go index 0a640e0..e2b68e5 100644 --- a/scanner_pipeline.go +++ b/scanner_pipeline.go @@ -13,12 +13,15 @@ import ( // ScannerPipeline manages concurrent file enumeration and scanning type ScannerPipeline struct { - fileChan chan string - matchesChan chan string - errChan chan error - wg sync.WaitGroup - enumerationDone chan bool - scanningDone chan bool + fileChan chan string + matchesChan chan string + errChan chan error + wg sync.WaitGroup + enumerationDone chan bool + scanningDone chan bool + filesScanned int64 + errorsEncountered int64 + statsMutex sync.Mutex } // NewScannerPipeline creates a new scanner pipeline @@ -38,7 +41,7 @@ func (sp *ScannerPipeline) StartEnumeration(paths []string, excludedPaths []stri go func() { defer sp.wg.Done() for _, path := range paths { - enumerateFilesStreaming(path, excludedPaths, sp.fileChan) + sp.enumerateFiles(path, excludedPaths) } close(sp.fileChan) sp.enumerationDone <- true @@ -83,6 +86,20 @@ func (sp *ScannerPipeline) GetErrors() <-chan error { return sp.errChan } +// GetFilesScanned returns the number of files scanned +func (sp *ScannerPipeline) GetFilesScanned() int64 { + sp.statsMutex.Lock() + defer sp.statsMutex.Unlock() + return sp.filesScanned +} + +// GetErrorsEncountered returns the number of errors encountered +func (sp *ScannerPipeline) GetErrorsEncountered() int64 { + sp.statsMutex.Lock() + defer sp.statsMutex.Unlock() + return sp.errorsEncountered +} + // Wait waits for enumeration to complete func (sp *ScannerPipeline) WaitEnumeration() { <-sp.enumerationDone @@ -100,37 +117,44 @@ func (sp *ScannerPipeline) WaitAll() { close(sp.errChan) } -// enumerateFilesStreaming enumerates files and sends them through a channel using parallel workers -func enumerateFilesStreaming(path string, excludedPaths []string, fileChan chan string) { +// enumerateFiles enumerates files and sends them through a channel using parallel workers +func (sp *ScannerPipeline) enumerateFiles(path string, excludedPaths []string) { const numWorkers = 8 // Number of parallel workers for directory enumeration dirQueue := make(chan string, 1000) // Queue of directories to process - var wg sync.WaitGroup + var workerWg sync.WaitGroup // WaitGroup for workers + var taskWg sync.WaitGroup // WaitGroup for scanning tasks var dirCountMutex sync.Mutex dirCount := int64(0) // Launch worker goroutines for i := 0; i < numWorkers; i++ { - wg.Add(1) + workerWg.Add(1) go func() { - defer wg.Done() + defer workerWg.Done() for dirPath := range dirQueue { - enumerateDirectoryWorker(dirPath, excludedPaths, fileChan, dirQueue, &wg, &dirCount, &dirCountMutex) + sp.enumerateDirectoryWorker(dirPath, excludedPaths, dirQueue, &taskWg, &dirCount, &dirCountMutex) + taskWg.Done() // Mark this directory task as completed } }() } // Queue the root directory - wg.Add(1) + taskWg.Add(1) dirQueue <- path + // Watcher routine to close queue when all tasks are done + go func() { + taskWg.Wait() + close(dirQueue) + }() + // Wait for all workers to finish - wg.Wait() - close(dirQueue) + workerWg.Wait() } // enumerateDirectoryWorker processes a single directory and queues its subdirectories -func enumerateDirectoryWorker(dirPath string, excludedPaths []string, fileChan chan string, dirQueue chan string, wg *sync.WaitGroup, dirCount *int64, mutex *sync.Mutex) { +func (sp *ScannerPipeline) enumerateDirectoryWorker(dirPath string, excludedPaths []string, dirQueue chan string, wg *sync.WaitGroup, dirCount *int64, mutex *sync.Mutex) { // Update directory count mutex.Lock() *dirCount++ @@ -142,6 +166,9 @@ func enumerateDirectoryWorker(dirPath string, excludedPaths []string, fileChan c entries, err := os.ReadDir(dirPath) if err != nil { LogMessage(LOG_ERROR, "(ERROR)", err) + sp.statsMutex.Lock() + sp.errorsEncountered++ + sp.statsMutex.Unlock() return } @@ -165,10 +192,12 @@ func enumerateDirectoryWorker(dirPath string, excludedPaths []string, fileChan c if entry.IsDir() { // Queue subdirectory for processing by a worker wg.Add(1) - dirQueue <- fullPath + go func(p string) { + dirQueue <- p + }(fullPath) } else { // Send file to the channel - fileChan <- fullPath + sp.fileChan <- fullPath } } } @@ -184,6 +213,10 @@ func (sp *ScannerPipeline) scanFiles( contentDependsOnPath bool) { for filePath := range sp.fileChan { + sp.statsMutex.Lock() + sp.filesScanned++ + sp.statsMutex.Unlock() + // Check path patterns first if they exist pathMatches := false if len(pathPatterns) > 0 { @@ -211,6 +244,9 @@ func (sp *ScannerPipeline) scanFiles( b, err := os.ReadFile(filePath) if err != nil { LogMessage(LOG_ERROR, "(ERROR)", "Unable to read file", filePath) + sp.statsMutex.Lock() + sp.errorsEncountered++ + sp.statsMutex.Unlock() continue } @@ -222,7 +258,6 @@ func (sp *ScannerPipeline) scanFiles( // Check checksum and grep patterns for _, m := range CheckFileChecksumAndContent(filePath, b, hashList, effectivePatterns) { - LogMessage(LOG_ALERT, "(ALERT)", "File content match on:", filePath) sp.matchesChan <- m } @@ -232,6 +267,9 @@ func (sp *ScannerPipeline) scanFiles( yaraResult, err := PerformYaraScan(&b, rules) if err != nil { LogMessage(LOG_ERROR, "(ERROR)", "Error performing yara scan on", filePath, err) + sp.statsMutex.Lock() + sp.errorsEncountered++ + sp.statsMutex.Unlock() continue } @@ -256,6 +294,10 @@ func (sp *ScannerPipeline) scanFiles( // scanFilesPathOnly scans only path patterns func (sp *ScannerPipeline) scanFilesPathOnly(pathPatterns []*regexp2.Regexp) { for filePath := range sp.fileChan { + sp.statsMutex.Lock() + sp.filesScanned++ + sp.statsMutex.Unlock() + for _, pattern := range pathPatterns { if match, _ := pattern.MatchString(filePath); match { LogMessage(LOG_ALERT, "(ALERT)", "File path match on:", filePath) From 507337785863354c3383121f4523f9528065a798 Mon Sep 17 00:00:00 2001 From: codeyourweb Date: Sat, 24 Jan 2026 13:53:22 +0100 Subject: [PATCH 2/6] Remove unused ConfigurationObject struct and related UnmarshalYAML function; update YAML decoding in getConfiguration method --- configuration.go | 24 +++--------------------- main.go | 2 +- 2 files changed, 4 insertions(+), 22 deletions(-) diff --git a/configuration.go b/configuration.go index 959d193..5b229ea 100644 --- a/configuration.go +++ b/configuration.go @@ -13,12 +13,6 @@ import ( "gopkg.in/yaml.v3" ) -type ConfigurationObject struct { - Line int - - Configuration -} - type Configuration struct { Input Input `yaml:"input"` Options Options `yaml:"options"` @@ -60,17 +54,6 @@ type AdvancedParameters struct { CleanMemoryIfFileGreaterThanSize int `yaml:"cleanMemoryIfFileGreaterThanSize"` } -func (i *ConfigurationObject) UnmarshalYAML(value *yaml.Node) error { - err := value.Decode(&i.Configuration) - if err != nil { - return err - } - - i.Line = value.Line - - return nil -} - func (c *Configuration) getConfiguration(configFile string) *Configuration { var yamlContent []byte var err error @@ -107,15 +90,14 @@ func (c *Configuration) getConfiguration(configFile string) *Configuration { yamlContent = RC4Cipher(yamlContent, BUILDER_RC4_KEY) } - var o ConfigurationObject - err = yaml.Unmarshal(yamlContent, &o) + decoder := yaml.NewDecoder(bytes.NewReader(yamlContent)) + decoder.KnownFields(true) + err = decoder.Decode(c) if err != nil { LogFatal(fmt.Sprintf("%s - %v", configFile, err)) } - *c = o.Configuration - // check for specific user configuration params inconsistencies if len(c.Input.Path) == 0 || (len(c.Input.Content.Grep) == 0 && len(c.Input.Content.Yara) == 0 && len(c.Input.Content.Checksum) == 0) { c.Options.ContentMatchDependsOnPathMatch = false diff --git a/main.go b/main.go index 0f9c771..8e9518f 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,7 @@ import ( "github.com/hillu/go-yara/v4" ) -const FASTFINDER_VERSION = "3.0.0" +const FASTFINDER_VERSION = "3.6.0" const YARA_VERSION = "4.5.5" const BUILDER_RC4_KEY = ">Õ°ªKb{¡§ÌB$lMÕ±9l.tòÑ馨¿" From 4650ae88f3521991839821bedf2ee2a9dc392dc2 Mon Sep 17 00:00:00 2001 From: codeyourweb Date: Sat, 24 Jan 2026 13:55:00 +0100 Subject: [PATCH 3/6] Readme update --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 713a1a5..ec112de 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,8 @@ [![Go Version](https://img.shields.io/badge/Go-1.24+-00ADD8?style=flat-square&logo=go)](https://golang.org) [![License](https://img.shields.io/github/license/codeyourweb/fastfinder?style=flat-square)](LICENSE) [![Release](https://img.shields.io/github/v/release/codeyourweb/fastfinder?style=flat-square)](https://github.com/codeyourweb/fastfinder/releases) -[![Build Status](https://img.shields.io/github/actions/workflow/status/codeyourweb/fastfinder/go_build_windows.yml?style=flat-square&label=Windows)](https://github.com/codeyourweb/fastfinder/actions) -[![Build Status](https://img.shields.io/github/actions/workflow/status/codeyourweb/fastfinder/go_build_linux.yml?style=flat-square&label=Linux)](https://github.com/codeyourweb/fastfinder/actions) +[![Build Status](https://img.shields.io/github/actions/workflow/status/codeyourweb/fastfinder/go_build_windows_amd64.yml?style=flat-square&label=Windows)](https://github.com/codeyourweb/fastfinder/actions) +[![Build Status](https://img.shields.io/github/actions/workflow/status/codeyourweb/fastfinder/go_build_linux_amd64.yml?style=flat-square&label=Linux)](https://github.com/codeyourweb/fastfinder/actions) [![Platform Support](https://img.shields.io/badge/Platform-Windows%20%7C%20Linux-brightgreen?style=flat-square)](#installation) ## ✨ Overview From 1495890de1bbd0c2509078677aea143fa255cec8 Mon Sep 17 00:00:00 2001 From: codeyourweb Date: Sat, 24 Jan 2026 14:03:33 +0100 Subject: [PATCH 4/6] Enhance TestConfigurationEmpty to handle subprocess execution for empty configuration --- config_integration_test.go | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/config_integration_test.go b/config_integration_test.go index fe20115..0b0c647 100644 --- a/config_integration_test.go +++ b/config_integration_test.go @@ -2,6 +2,7 @@ package main import ( "os" + "os/exec" "path/filepath" "testing" ) @@ -108,14 +109,26 @@ func TestConfigurationMissingRequired(t *testing.T) { // TestConfigurationEmpty tests handling of empty configuration func TestConfigurationEmpty(t *testing.T) { + if os.Getenv("TEST_CONFIGURATION_EMPTY_SUBPROCESS") == "1" { + tmpFile := os.Getenv("TEST_CONFIGURATION_EMPTY_FILE") + var config Configuration + config.getConfiguration(tmpFile) + return + } + tmpFile := filepath.Join(t.TempDir(), "empty_config.yml") os.WriteFile(tmpFile, []byte(""), 0644) - var config Configuration - config.getConfiguration(tmpFile) + cmd := exec.Command(os.Args[0], "-test.run=TestConfigurationEmpty") + cmd.Env = append(os.Environ(), "TEST_CONFIGURATION_EMPTY_SUBPROCESS=1", "TEST_CONFIGURATION_EMPTY_FILE="+tmpFile) + err := cmd.Run() - // Verify no panic occurred - t.Log("Empty configuration handled gracefully") + // We expect an exit error here because LogFatal calls os.Exit(1) + if e, ok := err.(*exec.ExitError); ok && !e.Success() { + t.Log("Empty configuration triggered exit as expected") + return + } + t.Fatalf("process ran with err %v, want exit status 1", err) } // TestConfigurationYARAWithRC4 tests YARA section with RC4 encryption From fcced1f4dc28a74e70d1896addf8e7a258bdd56e Mon Sep 17 00:00:00 2001 From: codeyourweb Date: Sat, 24 Jan 2026 14:26:55 +0100 Subject: [PATCH 5/6] Add memory scanning capabilities and related event forwarding functions --- README.md | 1 + configuration.go | 2 +- event_forwarding.go | 24 +++++++++++++ finder.go | 9 ++++- main.go | 2 +- process_scanner.go | 18 +++++++--- process_scanner_test.go | 77 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 125 insertions(+), 8 deletions(-) create mode 100644 process_scanner_test.go diff --git a/README.md b/README.md index ec112de..4286912 100644 --- a/README.md +++ b/README.md @@ -148,6 +148,7 @@ options: findInRemovableDrives: true # enumerate removable drive content findInNetworkDrives: true # enumerate network drive content findInCDRomDrives: true # enumerate physical CD-ROM and mounted iso / vhd... + findInMemory: true # check for results in processes memory output: copyMatchingFiles: true # create a copy of every matching file base64Files: true # base64 matched content before copy diff --git a/configuration.go b/configuration.go index 5b229ea..81720cb 100644 --- a/configuration.go +++ b/configuration.go @@ -39,7 +39,7 @@ type Options struct { FindInRemovableDrives bool `yaml:"findInRemovableDrives"` FindInNetworkDrives bool `yaml:"findInNetworkDrives"` FindInCDRomDrives bool `yaml:"findInCDRomDrives"` - ScanMemory bool `yaml:"scanMemory"` + FindInMemory bool `yaml:"findInMemory"` } type Output struct { diff --git a/event_forwarding.go b/event_forwarding.go index d9c442e..9e4a2a4 100644 --- a/event_forwarding.go +++ b/event_forwarding.go @@ -192,6 +192,30 @@ func ForwardAlertEvent(ruleName, filePath string, fileSize int64, fileHash strin ForwardEvent("alert", "high", fmt.Sprintf("YARA rule match: %s in %s", ruleName, filePath), metadata) } +// ForwardGrepMatchEvent forwards a Grep match event +func ForwardGrepMatchEvent(pattern, filePath string, fileSize int64, metadata map[string]string) { + if metadata == nil { + metadata = make(map[string]string) + } + metadata["grep_pattern"] = pattern + metadata["file_path"] = filePath + metadata["file_size"] = fmt.Sprintf("%d", fileSize) + + ForwardEvent("alert", "high", fmt.Sprintf("Grep match: %s in %s", pattern, filePath), metadata) +} + +// ForwardChecksumMatchEvent forwards a Checksum match event +func ForwardChecksumMatchEvent(checksum, filePath string, fileSize int64, metadata map[string]string) { + if metadata == nil { + metadata = make(map[string]string) + } + metadata["checksum"] = checksum + metadata["file_path"] = filePath + metadata["file_size"] = fmt.Sprintf("%d", fileSize) + + ForwardEvent("alert", "high", fmt.Sprintf("Checksum match: %s in %s", checksum, filePath), metadata) +} + // ForwardScanCompleteEvent forwards scan completion statistics func ForwardScanCompleteEvent(filesScanned, matchesFound, errorsEncountered int, duration time.Duration) { if eventForwarder == nil { diff --git a/finder.go b/finder.go index 1e6b5a1..6735f97 100644 --- a/finder.go +++ b/finder.go @@ -174,6 +174,7 @@ func checkForChecksum(path string, content []byte, hashList []string) (matchingF for _, c := range hashs { if Contains(hashList, c) && !Contains(matchingFiles, path) { LogMessage(LOG_ALERT, "(ALERT)", "Checksum match:", c, "in", path) + ForwardChecksumMatchEvent(c, path, int64(len(content)), nil) matchingFiles = append(matchingFiles, path) } } @@ -190,7 +191,13 @@ func checkForStringPattern(path string, content []byte, patterns []string) (matc for _, expression := range patterns { for i, line := range lines { if strings.Contains(line, expression) { - LogMessage(LOG_ALERT, "(ALERT)", "Grep match:", expression, "in", path, "at line", fmt.Sprintf("%d:", i+1), strings.TrimSpace(line)) + LogMessage(LOG_ALERT, "(ALERT)", "Grep match:", expression, "in", path, "at line", fmt.Sprintf("%d", i+1)) + + metadata := map[string]string{ + "line_number": fmt.Sprintf("%d", i+1), + } + ForwardGrepMatchEvent(expression, path, int64(len(content)), metadata) + matchingFiles = append(matchingFiles, path) break } diff --git a/main.go b/main.go index 8e9518f..85c4b30 100644 --- a/main.go +++ b/main.go @@ -150,7 +150,7 @@ func MainFastfinderRoutine(config Configuration, pConfigPath string, pNoAdvUI bo } // Memory Scan - if config.Options.ScanMemory { + if config.Options.FindInMemory { ScanMemory(config, rules) } diff --git a/process_scanner.go b/process_scanner.go index 473d20a..6f4568f 100644 --- a/process_scanner.go +++ b/process_scanner.go @@ -22,15 +22,22 @@ func ScanMemory(config Configuration, rules *yara.Rules) { procs := ListProcesses() LogMessage(LOG_INFO, "(INFO)", fmt.Sprintf("Found %d running processes", len(procs))) + ScanProcesses(procs, config, rules) + + LogMessage(LOG_INFO, "(INFO)", "Memory scan finished") +} + +// ScanProcesses scans a list of processes for patterns and YARA rules +func ScanProcesses(procs []ProcessInformation, config Configuration, rules *yara.Rules) int { + totalMatches := 0 for _, proc := range procs { LogMessage(LOG_VERBOSE, "(MEMORY)", "Scanning process:", proc.ProcessName, fmt.Sprintf("(PID: %d)", proc.PID)) // Check memory content with Grep if len(config.Input.Content.Grep) > 0 { + // checkForStringPattern already handles logging and alerting matches := checkForStringPattern(fmt.Sprintf("MEMORY:%s:%d", proc.ProcessName, proc.PID), proc.MemoryDump, config.Input.Content.Grep) - for _, m := range matches { - LogMessage(LOG_ALERT, "(ALERT)", "Memory Grep match:", m) - } + totalMatches += len(matches) } // Check memory content with YARA @@ -38,6 +45,8 @@ func ScanMemory(config Configuration, rules *yara.Rules) { matchs, err := PerformYaraScan(&proc.MemoryDump, rules) if err != nil { LogMessage(LOG_ERROR, "(ERROR)", "Memory YARA scan failed for", proc.ProcessName, err) + } else { + totalMatches += len(matchs) } for i := 0; i < len(matchs); i++ { @@ -61,6 +70,5 @@ func ScanMemory(config Configuration, rules *yara.Rules) { proc.MemoryDump = nil debug.FreeOSMemory() } - - LogMessage(LOG_INFO, "(INFO)", "Memory scan finished") + return totalMatches } diff --git a/process_scanner_test.go b/process_scanner_test.go new file mode 100644 index 0000000..dac944e --- /dev/null +++ b/process_scanner_test.go @@ -0,0 +1,77 @@ +package main + +import ( + "testing" + + "github.com/hillu/go-yara/v4" +) + +func TestScanProcessesGrep(t *testing.T) { + // Setup + config := Configuration{} + config.Input.Content.Grep = []string{"bad_string"} + + proc := ProcessInformation{ + PID: 1234, + ProcessName: "test_process.exe", + MemoryDump: []byte("This is a memory dump containing a bad_string here."), + } + procs := []ProcessInformation{proc} + + // Execute + matches := ScanProcesses(procs, config, nil) + + // Verify + if matches != 1 { + t.Errorf("Expected 1 match, got %d", matches) + } +} + +func TestScanProcessesNoMatch(t *testing.T) { + // Setup + config := Configuration{} + config.Input.Content.Grep = []string{"missing_string"} + + proc := ProcessInformation{ + PID: 1234, + ProcessName: "test_process.exe", + MemoryDump: []byte("This is a memory dump containing a bad_string here."), + } + procs := []ProcessInformation{proc} + + // Execute + matches := ScanProcesses(procs, config, nil) + + // Verify + if matches != 0 { + t.Errorf("Expected 0 matches, got %d", matches) + } +} + +func TestScanProcessesYara(t *testing.T) { + c, err := yara.NewCompiler() + if err != nil { + t.Skip("YARA compiler not available: ", err) + return + } + err = c.AddString(`rule test { strings: $a = "yara_match" condition: $a }`, "test") + if err != nil { + t.Fatal("Failed to compile yara rule:", err) + } + rules, err := c.GetRules() + if err != nil { + t.Fatal("Failed to get rules:", err) + } + + config := Configuration{} + proc := ProcessInformation{ + PID: 1234, + ProcessName: "test_process.exe", + MemoryDump: []byte("Before yara_match After"), + } + + matches := ScanProcesses([]ProcessInformation{proc}, config, rules) + if matches != 1 { + t.Errorf("Expected 1 YARA match, got %d", matches) + } +} From cac0f16801e0746f98cae686788c43f6b8a907ce Mon Sep 17 00:00:00 2001 From: codeyourweb Date: Sat, 24 Jan 2026 14:46:28 +0100 Subject: [PATCH 6/6] Increase default logging verbosity and testing --- configuration.go | 3 ++- examples/CISA-AA21-259A/CISA-AA21-259A.yaml | 2 +- examples/React2Shell/react2shell.yaml | 2 +- examples/example_configuration_api_triage.yaml | 2 +- examples/example_configuration_distant.yaml | 2 +- examples/example_configuration_docker_triage.yaml | 2 +- examples/example_configuration_linux.yaml | 2 +- examples/example_configuration_windows.yaml | 2 +- examples/linux-fontonlake/eset_fontonlake_linux.yaml | 2 +- examples/log4j_vuln_checker/config-log4j_vuln_checker.yaml | 1 + examples/proxyshell/drophell.yaml | 2 +- logger.go | 2 +- main.go | 2 +- 13 files changed, 14 insertions(+), 12 deletions(-) diff --git a/configuration.go b/configuration.go index 81720cb..d6d9afd 100644 --- a/configuration.go +++ b/configuration.go @@ -185,5 +185,6 @@ func (c *Configuration) getConfiguration(configFile string) *Configuration { } } + LogMessage(LOG_INFO, "Configuration loaded") return c -} +} \ No newline at end of file diff --git a/examples/CISA-AA21-259A/CISA-AA21-259A.yaml b/examples/CISA-AA21-259A/CISA-AA21-259A.yaml index f9f6271..185cb94 100644 --- a/examples/CISA-AA21-259A/CISA-AA21-259A.yaml +++ b/examples/CISA-AA21-259A/CISA-AA21-259A.yaml @@ -29,7 +29,7 @@ options: findInRemovableDrives: false findInNetworkDrives: false findInCDRomDrives: false - scanMemory: false + findInMemory: false output: copyMatchingFiles: false base64Files: false diff --git a/examples/React2Shell/react2shell.yaml b/examples/React2Shell/react2shell.yaml index 7161319..159471a 100644 --- a/examples/React2Shell/react2shell.yaml +++ b/examples/React2Shell/react2shell.yaml @@ -24,7 +24,7 @@ options: findInRemovableDrives: true findInNetworkDrives: true findInCDRomDrives: true - scanMemory: true + findInMemory: true output: copyMatchingFiles: false base64Files: false diff --git a/examples/example_configuration_api_triage.yaml b/examples/example_configuration_api_triage.yaml index c10c5ea..23aa677 100644 --- a/examples/example_configuration_api_triage.yaml +++ b/examples/example_configuration_api_triage.yaml @@ -11,7 +11,7 @@ options: findInRemovableDrives: false findInNetworkDrives: false findInCDRomDrives: false - scanMemory: false + findInMemory: false output: copyMatchingFiles: false base64Files: false diff --git a/examples/example_configuration_distant.yaml b/examples/example_configuration_distant.yaml index b006561..5ee3650 100644 --- a/examples/example_configuration_distant.yaml +++ b/examples/example_configuration_distant.yaml @@ -12,7 +12,7 @@ options: findInRemovableDrives: false findInNetworkDrives: false findInCDRomDrives: false - scanMemory: false + findInMemory: false output: copyMatchingFiles: false base64Files: false diff --git a/examples/example_configuration_docker_triage.yaml b/examples/example_configuration_docker_triage.yaml index 1e41b9b..54746f4 100644 --- a/examples/example_configuration_docker_triage.yaml +++ b/examples/example_configuration_docker_triage.yaml @@ -38,7 +38,7 @@ options: findInRemovableDrives: false findInNetworkDrives: false findInCDRomDrives: false - scanMemory: false + findInMemory: false output: copyMatchingFiles: true base64Files: true diff --git a/examples/example_configuration_linux.yaml b/examples/example_configuration_linux.yaml index 7493ae0..51fb831 100644 --- a/examples/example_configuration_linux.yaml +++ b/examples/example_configuration_linux.yaml @@ -14,7 +14,7 @@ options: findInRemovableDrives: false findInNetworkDrives: false findInCDRomDrives: false - scanMemory: true + findInMemory: true output: copyMatchingFiles: false base64Files: false diff --git a/examples/example_configuration_windows.yaml b/examples/example_configuration_windows.yaml index 8cbbde8..3ffb65f 100644 --- a/examples/example_configuration_windows.yaml +++ b/examples/example_configuration_windows.yaml @@ -20,7 +20,7 @@ options: findInRemovableDrives: false findInNetworkDrives: false findInCDRomDrives: false - scanMemory: true + findInMemory: true output: copyMatchingFiles: false base64Files: false diff --git a/examples/linux-fontonlake/eset_fontonlake_linux.yaml b/examples/linux-fontonlake/eset_fontonlake_linux.yaml index 69d3d7c..a34114b 100644 --- a/examples/linux-fontonlake/eset_fontonlake_linux.yaml +++ b/examples/linux-fontonlake/eset_fontonlake_linux.yaml @@ -48,7 +48,7 @@ options: findInRemovableDrives: false findInNetworkDrives: false findInCDRomDrives: false - scanMemory: false + findInMemory: false output: copyMatchingFiles: false base64Files: false diff --git a/examples/log4j_vuln_checker/config-log4j_vuln_checker.yaml b/examples/log4j_vuln_checker/config-log4j_vuln_checker.yaml index 7f2b8cd..5a94835 100644 --- a/examples/log4j_vuln_checker/config-log4j_vuln_checker.yaml +++ b/examples/log4j_vuln_checker/config-log4j_vuln_checker.yaml @@ -50,6 +50,7 @@ options: findInRemovableDrives: false findInNetworkDrives: false findInCDRomDrives: false + findInMemory: true output: copyMatchingFiles: false base64Files: false diff --git a/examples/proxyshell/drophell.yaml b/examples/proxyshell/drophell.yaml index bffc083..5130da9 100644 --- a/examples/proxyshell/drophell.yaml +++ b/examples/proxyshell/drophell.yaml @@ -25,7 +25,7 @@ options: findInRemovableDrives: false findInNetworkDrives: false findInCDRomDrives: false - scanMemory: true + findInMemory: true output: copyMatchingFiles: false base64Files: false diff --git a/logger.go b/logger.go index bd413d5..b7e2b66 100644 --- a/logger.go +++ b/logger.go @@ -17,7 +17,7 @@ const ( LOG_VERBOSE = 5 // Full verbosity (all messages) ) -var loggingVerbosity int = 3 +var loggingVerbosity int = 4 var loggingPath string = "" var loggingFile *os.File var unitTesting bool diff --git a/main.go b/main.go index 85c4b30..386528b 100644 --- a/main.go +++ b/main.go @@ -33,7 +33,7 @@ func main() { pConfigPath := parser.String("c", "configuration", &argparse.Options{Required: false, Default: "", Help: "Fastfind configuration file"}) pSfxPath := parser.String("b", "build", &argparse.Options{Required: false, Help: "Output a standalone package with configuration and rules in a single binary"}) pSilentMode := parser.Flag("s", "silent", &argparse.Options{Required: false, Help: "Silent mode - run without any visible window or console"}) - pLogVerbosity := parser.Int("v", "verbosity", &argparse.Options{Required: false, Default: 3, Help: "File log verbosity \n\t\t\t\t | 1: Only alerts\n\t\t\t\t | 2: Alerts and warnings\n\t\t\t\t | 3: Alerts,warnings and errors\n\t\t\t\t | 4: Alerts,warnings,errors and I/O operations\n\t\t\t\t | 5: Full verbosity)\n\t\t\t\t"}) + pLogVerbosity := parser.Int("v", "verbosity", &argparse.Options{Required: false, Default: 4, Help: "File log verbosity \n\t\t\t\t | 1: Only alerts\n\t\t\t\t | 2: Alerts and warnings\n\t\t\t\t | 3: Alerts,warnings and errors\n\t\t\t\t | 4: Alerts,warnings,errors and I/O operations\n\t\t\t\t | 5: Full verbosity)\n\t\t\t\t"}) pTriage := parser.Flag("t", "triage", &argparse.Options{Required: false, Default: false, Help: "Triage mode (infinite run - scan every new file in the input path directories)"}) pRootPath := parser.String("r", "root", &argparse.Options{Required: false, Default: "", Help: "Scan root path (override drive enumeration to scan specific directory)"})