From 19a27b522f1cc4c871effe9ee5e40582e5466292 Mon Sep 17 00:00:00 2001 From: Hardik Manocha Date: Wed, 17 Aug 2022 03:12:14 +0530 Subject: [PATCH] Added WMI AV Support --- .gitignore | 5 +- Makefile | 26 ++++---- README.md | 14 +++- cmd/EDRHunt/main.go | 29 +++++++- pkg/edrRecon/collect.go | 6 +- pkg/edrRecon/edrRecon_test.go | 10 +++ pkg/edrRecon/edrdata.go | 9 +++ pkg/edrRecon/wmi_windows.go | 113 ++++++++++++++++++++++++++++++++ pkg/resources/edrRecon.go | 12 ++++ pkg/resources/scan_edr.go | 3 + pkg/resources/systemdata.go | 13 ++-- pkg/scanners/scan_eset.go | 29 ++++++++ pkg/scanners/scan_mcafee.go | 2 + pkg/scanners/scan_qualys.go | 29 ++++++++ pkg/scanners/scan_trendmicro.go | 29 ++++++++ pkg/scanners/scanner.go | 3 + pkg/util/wmi/wmi.go | 54 +++++++++++++++ 17 files changed, 362 insertions(+), 24 deletions(-) create mode 100644 pkg/edrRecon/wmi_windows.go create mode 100644 pkg/scanners/scan_eset.go create mode 100644 pkg/scanners/scan_qualys.go create mode 100644 pkg/scanners/scan_trendmicro.go create mode 100644 pkg/util/wmi/wmi.go diff --git a/.gitignore b/.gitignore index 4ae2c95..20c2f11 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -dist -EDRHunt.exe +dist +EDRHunt.exe +.vscode diff --git a/Makefile b/Makefile index b5603e8..a27b338 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,14 @@ -all: build - -build: - go build -ldflags="-w -s" -o EDRHunt.exe github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt -garble-build: - garble -literals build -ldflags="-w -s" -o EDRHunt.exe github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt -local: - go build -ldflags="-w -s" -o EDRHunt.exe github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt -run: - go run -ldflags="-w -s" github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt all -drivers: - go run -ldflags="-w -s" github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt -d +all: build + +build: + go build -ldflags="-w -s" -o EDRHunt.exe github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt +garble-build: + garble -literals build -ldflags="-w -s" -o EDRHunt.exe github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt +local: + go build -ldflags="-w -s" -o EDRHunt.exe github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt +run: + go run -ldflags="-w -s" github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt all +drivers: + go run -ldflags="-w -s" github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt -d +avwmi: + go run -ldflags="-w -s" github.com/FourCoreLabs/EDRHunt/cmd/EDRHunt -w diff --git a/README.md b/README.md index 29d9881..8945c01 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![goreleaser](https://github.com/FourCoreLabs/EDRHunt/actions/workflows/goreleaser.yml/badge.svg)](https://github.com/FourCoreLabs/EDRHunt/actions/workflows/goreleaser.yml) -EDRHunt scans Windows services, drivers, processes, registry for installed EDRs (Endpoint Detection And Response). Read more about EDRHunt [here](https://www.fourcore.vision/blogs/red-team-adventure-windows-endpoints-edr-edrhunt). +EDRHunt scans Windows services, drivers, processes, registry, wmi for installed EDRs (Endpoint Detection And Response). Read more about EDRHunt [here](https://www.fourcore.vision/blogs/red-team-adventure-windows-endpoints-edr-edrhunt). [![asciicast](https://asciinema.org/a/P8i99w9mI497qUPTNbdwYWcwQ.svg)](https://asciinema.org/a/P8i99w9mI497qUPTNbdwYWcwQ) @@ -30,7 +30,7 @@ Detected EDR: Kaspersky Security ``` $ .\EDRHunt.exe all Running in user mode, escalate to admin for more details. -Scanning processes, services, drivers, and registry... +Scanning processes, services, drivers, wmi, and registry... [PROCESSES] Suspicious Process Name: MsMpEng.exe @@ -131,6 +131,13 @@ $ .\EDRHunt.exe -d $ .\EDRHunt.exe -r ``` + +- Find WMI Repository keys matching EDR keywords + +``` +$ .\EDRHunt.exe -w +``` + ## Detections EDR Detections Currently Available @@ -145,6 +152,9 @@ EDR Detections Currently Available - SentinelOne - FireEye - Elastic EDR +- Qualys EDR +- Trend Micro EDR +- ESET EDR More to be added soon. diff --git a/cmd/EDRHunt/main.go b/cmd/EDRHunt/main.go index 4e4db96..b6df596 100644 --- a/cmd/EDRHunt/main.go +++ b/cmd/EDRHunt/main.go @@ -16,8 +16,9 @@ var ( processes bool services bool registry bool + avwmi bool all bool - versionStr string = "1.3.1" + versionStr string = "1.4.0" versionCheck bool ) @@ -46,7 +47,8 @@ func edrCommand(cmd *cobra.Command, args []string) { drivers = true services = true registry = true - fmt.Println("Scanning processes, services, drivers, and registry...") + avwmi = true + fmt.Println("Scanning processes, services, drivers, wmi, and registry...") } if processes { @@ -67,6 +69,12 @@ func edrCommand(cmd *cobra.Command, args []string) { printServices(summary) fmt.Println() } + if avwmi { + fmt.Println("[WMI-REPO]") + summary, _ := edrRecon.CheckAVWmiRepo() + printAVWmi(summary) + fmt.Println() + } if registry { fmt.Println("[REGISTRY]") summary, _ := edrRecon.CheckRegistry(context.Background()) @@ -100,7 +108,7 @@ func allCommand(cmd *cobra.Command, args []string) { var rootCmd = &cobra.Command{ Use: "EDRHunt", Short: "scans EDR/AV", - Long: `EDRHunt scans and finds the installed EDR/AV by scanning services, processes, registry, and drivers.`, + Long: `EDRHunt scans and finds the installed EDR/AV by scanning services, processes, registry, wmi, and drivers.`, Run: edrCommand, } @@ -125,6 +133,20 @@ var allCmd = &cobra.Command{ Run: allCommand, } +func printAVWmi(summary []resources.AVWmiMetaData) { + for _, antivirus := range summary { + fmt.Printf("Suspicious Product Name: %s\n", antivirus.ProductName) + fmt.Printf("Suspicious Product GUID: %s\n", antivirus.ProductGUID) + fmt.Printf("Path to Suspicious Product Exe: %s\n", antivirus.PathToProductExe) + fmt.Printf("Suspicious Product Exe Metadata: \t%s\n", edrRecon.FileMetaDataParser(antivirus.ProductExeMetaData)) + fmt.Printf("Path to Suspicious Reporting Exe: %s\n", antivirus.PathToReportingExe) + fmt.Printf("Suspicious Reporting Exe Metadata: \t%s\n", edrRecon.FileMetaDataParser(antivirus.ReportingExeMetaData)) + fmt.Printf("Suspicious Product State: %d\n", antivirus.ProductState) + fmt.Printf("Matched Keyword: %s\n", antivirus.ScanMatch) + fmt.Println() + } +} + func printProcess(summary []resources.ProcessMetaData) { for _, process := range summary { fmt.Printf("Suspicious Process Name: %s\n", process.ProcessName) @@ -184,6 +206,7 @@ func init() { rootCmd.PersistentFlags().BoolVarP(&processes, "processes", "p", processes, "Scan installed processes") rootCmd.PersistentFlags().BoolVarP(&services, "services", "s", services, "Scan installed services") rootCmd.PersistentFlags().BoolVarP(®istry, "registry", "r", registry, "Scan installed registry") + rootCmd.PersistentFlags().BoolVarP(&avwmi, "avwmi", "w", avwmi, "Scan installed AntiVirus Providers") rootCmd.PersistentFlags().BoolVarP(&versionCheck, "version", "v", versionCheck, "Output version information and exit") rootCmd.AddCommand(versionCmd) diff --git a/pkg/edrRecon/collect.go b/pkg/edrRecon/collect.go index e18e116..671f316 100644 --- a/pkg/edrRecon/collect.go +++ b/pkg/edrRecon/collect.go @@ -7,7 +7,7 @@ import ( "github.com/FourCoreLabs/EDRHunt/pkg/resources" ) -// GetSystemData collects the parsed list of processes, services, drivers and registry keys to be used for EDR heuristics. +// GetSystemData collects the parsed list of processes, services, drivers, wmi and registry keys to be used for EDR heuristics. func GetSystemData(ctx context.Context) (resources.SystemData, error) { var err error var systemData resources.SystemData @@ -32,5 +32,9 @@ func GetSystemData(ctx context.Context) (resources.SystemData, error) { return systemData, fmt.Errorf("failed to check drivers: %w", err) } + systemData.AVProviders, err = CheckAVWmiRepo() + if err != nil { + return systemData, fmt.Errorf("failed to check wmi repo: %w", err) + } return systemData, nil } diff --git a/pkg/edrRecon/edrRecon_test.go b/pkg/edrRecon/edrRecon_test.go index ac38dfc..25f7471 100644 --- a/pkg/edrRecon/edrRecon_test.go +++ b/pkg/edrRecon/edrRecon_test.go @@ -55,6 +55,16 @@ func TestCheckProcesses(t *testing.T) { } } +func TestCheckAVWMI(t *testing.T) { + summary, _ := CheckAVWmiRepo() + for _, av := range summary { + fmt.Println(av) + } + // if err.Error() != "" { + // fmt.Println("error", err) + // } +} + func TestGetFileMetaData(t *testing.T) { fileMetaData, err := GetFileMetaData(`C:\Users\hardi\AnyDesk.exe`) if err != nil { diff --git a/pkg/edrRecon/edrdata.go b/pkg/edrRecon/edrdata.go index 5a867bf..5505896 100644 --- a/pkg/edrRecon/edrdata.go +++ b/pkg/edrRecon/edrdata.go @@ -289,6 +289,15 @@ var EdrList = []string{ "elastic-endpoint.exe", "elastic-endpoint-driver", "ElasticEndpoint", + "ecmd.exe", + "ekrn.exe", + "ESET", + "mcupdate.exe", + "ProtectedModuleHost.exe", + "dsa.exe", + "Notifier.exe", + "qualys", + "qualysagent.exe", } var ReconList = []string{ diff --git a/pkg/edrRecon/wmi_windows.go b/pkg/edrRecon/wmi_windows.go new file mode 100644 index 0000000..354544d --- /dev/null +++ b/pkg/edrRecon/wmi_windows.go @@ -0,0 +1,113 @@ +package edrRecon + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/FourCoreLabs/EDRHunt/pkg/resources" + "github.com/hashicorp/go-multierror" + "github.com/yusufpapurcu/wmi" +) + +type AntiVirusProduct struct { + DisplayName string + InstanceGuid string + PathToSignedProductExe string + PathToSignedReportingExe string + ProductState uint32 +} + +type AvResult struct { + AvProduct []AntiVirusProduct + Err error +} + +const ( + namespace = "root\\SecurityCenter2" + class = "AntiVirusProduct" + wmiErr = "wmi query timed out" +) + +func CheckAVWmiRepo() ([]resources.AVWmiMetaData, error) { + var ( + avList []AntiVirusProduct + multiErr error + summary []resources.AVWmiMetaData = make([]resources.AVWmiMetaData, 0) + err error + ) + + avList, err = GetAVwithWMI() + if err != nil { + return summary, err + } + for _, av := range avList { + if av.DisplayName == "" { + continue + } + output, err := AnalyzeAVProduct(av) + if err != nil { + multiErr = multierror.Append(multiErr, err) + continue + } + + if len(output.ScanMatch) > 0 { + summary = append(summary, output) + } + + } + return summary, multiErr +} + +func AnalyzeAVProduct(av AntiVirusProduct) (resources.AVWmiMetaData, error) { + analysis := resources.AVWmiMetaData{ + ProductName: av.DisplayName, + ProductGUID: av.InstanceGuid, + PathToProductExe: av.PathToSignedProductExe, + PathToReportingExe: av.PathToSignedReportingExe, + ProductState: av.ProductState, + } + + if analysis.PathToProductExe != "" { + analysis.ProductExeMetaData, _ = GetFileMetaData(analysis.PathToProductExe) + } + + if analysis.PathToReportingExe != "" { + analysis.ReportingExeMetaData, _ = GetFileMetaData(analysis.PathToReportingExe) + } + + for _, edr := range EdrList { + if strings.Contains( + strings.ToLower(fmt.Sprint(analysis)), + strings.ToLower(edr)) { + analysis.ScanMatch = append(analysis.ScanMatch, edr) + } + } + + return analysis, nil +} + +func GetAVwithWMI() ([]AntiVirusProduct, error) { + result := make(chan AvResult, 1) + go func() { + result <- WMIQuery() + }() + select { + case <-time.After(6 * time.Second): + return nil, errors.New(wmiErr) + case result := <-result: + return result.AvProduct, result.Err + } + +} + +func WMIQuery() AvResult { + var avResults []AntiVirusProduct + query := wmi.CreateQuery(&avResults, "", class) + err := wmi.QueryNamespace(query, &avResults, namespace) + return AvResult{ + AvProduct: avResults, + Err: err, + } +} diff --git a/pkg/resources/edrRecon.go b/pkg/resources/edrRecon.go index 43482c0..0c66a2a 100644 --- a/pkg/resources/edrRecon.go +++ b/pkg/resources/edrRecon.go @@ -5,6 +5,7 @@ type Recon interface { CheckServices() ([]ServiceMetaData, error) CheckDrivers() ([]DriverMetaData, error) CheckRegistry() (RegistryMetaData, error) + CheckAVWmiRepo() ([]AVWmiMetaData, error) CheckDirectory() (string, error) } @@ -32,6 +33,17 @@ type ServiceMetaData struct { ScanMatch []string } +type AVWmiMetaData struct { + ProductName string + ProductGUID string + PathToProductExe string + ProductExeMetaData FileMetaData + PathToReportingExe string + ReportingExeMetaData FileMetaData + ProductState uint32 + ScanMatch []string +} + type ProcessMetaData struct { ProcessName string ProcessPath string diff --git a/pkg/resources/scan_edr.go b/pkg/resources/scan_edr.go index 0298400..01861ca 100644 --- a/pkg/resources/scan_edr.go +++ b/pkg/resources/scan_edr.go @@ -19,4 +19,7 @@ var ( SentinelOneEDR EDRType = "sentinel_one" FireEyeEDR EDRType = "fireeye" ElasticAgentEDR EDRType = "elastic_agent" + QualysEDR EDRType = "qualys" + TrendMicroEDR EDRType = "trend_micro" + ESETEDR EDRType = "eset" ) diff --git a/pkg/resources/systemdata.go b/pkg/resources/systemdata.go index ccff4b5..39d7ab8 100644 --- a/pkg/resources/systemdata.go +++ b/pkg/resources/systemdata.go @@ -5,10 +5,11 @@ import ( ) type SystemData struct { - Processes []ProcessMetaData - Registry RegistryMetaData - Services []ServiceMetaData - Drivers []DriverMetaData + Processes []ProcessMetaData + Registry RegistryMetaData + Services []ServiceMetaData + Drivers []DriverMetaData + AVProviders []AVWmiMetaData } // CountMatchesAll collects all the scanned matches of suspicious names and checks for passed keywords in the matches. @@ -30,6 +31,10 @@ func (s *SystemData) CountMatchesAll(keywords ...[]string) (int, bool) { scanMatchList = append(scanMatchList, v.ScanMatch...) } + for _, v := range s.AVProviders { + scanMatchList = append(scanMatchList, v.ScanMatch...) + } + scanMatchList = append(scanMatchList, s.Registry.ScanMatch...) var totalLen int diff --git a/pkg/scanners/scan_eset.go b/pkg/scanners/scan_eset.go new file mode 100644 index 0000000..967dd2c --- /dev/null +++ b/pkg/scanners/scan_eset.go @@ -0,0 +1,29 @@ +package scanners + +import "github.com/FourCoreLabs/EDRHunt/pkg/resources" + +type ESETEDRDetection struct{} + +func (w *ESETEDRDetection) Name() string { + return "ESET Endpoint Security" +} + +func (w *ESETEDRDetection) Type() resources.EDRType { + return resources.ESETEDR +} + +var ESETHeuristic = []string{ + "ESET", + "ESET Endpoint Security", + "ecmd", + "ekrn", +} + +func (w *ESETEDRDetection) Detect(data resources.SystemData) (resources.EDRType, bool) { + _, ok := data.CountMatchesAll(ESETHeuristic) + if !ok { + return "", false + } + + return resources.QualysEDR, true +} diff --git a/pkg/scanners/scan_mcafee.go b/pkg/scanners/scan_mcafee.go index a211f28..97a1d1c 100644 --- a/pkg/scanners/scan_mcafee.go +++ b/pkg/scanners/scan_mcafee.go @@ -14,6 +14,8 @@ func (w *McafeeDetection) Type() resources.EDRType { var McafeeHeuristic = []string{ "Mcafee\\", + "mcupdate.exe", + "ProtectedModuleHost.exe", "McAfeeAgent\\", "APPolicyName", "EPPolicyName", diff --git a/pkg/scanners/scan_qualys.go b/pkg/scanners/scan_qualys.go new file mode 100644 index 0000000..5321402 --- /dev/null +++ b/pkg/scanners/scan_qualys.go @@ -0,0 +1,29 @@ +package scanners + +import "github.com/FourCoreLabs/EDRHunt/pkg/resources" + +type QualysDetection struct{} + +func (w *QualysDetection) Name() string { + return "Qualys Cloud Agent EDR" +} + +func (w *QualysDetection) Type() resources.EDRType { + return resources.QualysEDR +} + +var QualysHeuristic = []string{ + "Qualys", + "QualysAgent.exe", + "qualysagent.exe", + "qualys", +} + +func (w *QualysDetection) Detect(data resources.SystemData) (resources.EDRType, bool) { + _, ok := data.CountMatchesAll(QualysHeuristic) + if !ok { + return "", false + } + + return resources.QualysEDR, true +} diff --git a/pkg/scanners/scan_trendmicro.go b/pkg/scanners/scan_trendmicro.go new file mode 100644 index 0000000..2f826d1 --- /dev/null +++ b/pkg/scanners/scan_trendmicro.go @@ -0,0 +1,29 @@ +package scanners + +import "github.com/FourCoreLabs/EDRHunt/pkg/resources" + +type TrendMicroDetection struct{} + +func (w *TrendMicroDetection) Name() string { + return "Trend Micro Deep Security" +} + +func (w *TrendMicroDetection) Type() resources.EDRType { + return resources.TrendMicroEDR +} + +var TrendMicroHeuristic = []string{ + "Deep Security Agent\\", + "dsa", + "Notifier", + "Trend Micro", +} + +func (w *TrendMicroDetection) Detect(data resources.SystemData) (resources.EDRType, bool) { + _, ok := data.CountMatchesAll(TrendMicroHeuristic) + if !ok { + return "", false + } + + return resources.QualysEDR, true +} diff --git a/pkg/scanners/scanner.go b/pkg/scanners/scanner.go index 96163d3..d301061 100644 --- a/pkg/scanners/scanner.go +++ b/pkg/scanners/scanner.go @@ -14,5 +14,8 @@ var ( &SentinelOneDetection{}, &WinDefenderDetection{}, &ElasticAgentDetection{}, + &ESETEDRDetection{}, + &QualysDetection{}, + &TrendMicroDetection{}, } ) diff --git a/pkg/util/wmi/wmi.go b/pkg/util/wmi/wmi.go new file mode 100644 index 0000000..048f133 --- /dev/null +++ b/pkg/util/wmi/wmi.go @@ -0,0 +1,54 @@ +package main + +import ( + "errors" + "time" + + "github.com/yusufpapurcu/wmi" +) + +type AntiVirusProduct struct { + DisplayName string + InstanceGuid string + PathToSignedProductExe string + PathToSignedReportingExe string + ProductState uint32 +} + +type AvResult struct { + AvProduct []AntiVirusProduct + Err error +} + +func WMIQuery() AvResult { + var avResults []AntiVirusProduct + query := wmi.CreateQuery(&avResults, "", "AntiVirusProduct") + err := wmi.QueryNamespace(query, &avResults, "root\\SecurityCenter2") + return AvResult{ + AvProduct: avResults, + Err: err, + } +} + +func GetAVwithWMI() ([]AntiVirusProduct, error) { + result := make(chan AvResult, 1) + go func() { + result <- WMIQuery() + }() + select { + case <-time.After(10 * time.Second): + return nil, errors.New("wmi query timed out") + case result := <-result: + return result.AvProduct, result.Err + } +} + +func main() { + query, queryErr := GetAVwithWMI() + if queryErr != nil { + panic(queryErr) + } + for i, v := range query { + println(i+1, v.DisplayName, v.InstanceGuid, v.PathToSignedProductExe, v.PathToSignedReportingExe, v.ProductState) + } +}