$Logfile = "c:\$(Get-Content env:COMPUTERNAME).log" Function LogWrite { Param ([string]$logstring) Add-content $Logfile -value $logstring } # This is a sample code for the "External Scanner" feature of Metadefender V4.8.0 written in powershell script # # This script is for illustrative puposes only and is not guaranteed to work under production conditions. # # The script is intended to recognize results suspicious as False Positive in the following way: # The script checks the scan results of the current file, if the file is flagged as infected by only one engine , the file's hash is then # sent to Metadefender cloud. # Metadefender cloud's results are then analysed : # In case the file is flagged as infected in Metadefender cloud by ONLY the same one engine which flagged the file in Metadefender core # OR if the file is found to be clean by Metadefender cloud, the file will be copied to a $false_positive folder for later investigation, # and verdict will be "Suspicious" (2). and threat_found will be 'Suspected False Positive'. # If the file is flagged by any other engine on Metadefender cloud then the verdict will be "Infected" (1) and threat_found will be "Infected - Probably NOT False Positive". # If the file is not flagged by any local engine the script returns the verdict "No Threat Detected" (0). # # input: # 1. It is your responsibility to create and populate the system context variable %false_positive% with a valid folder name before running the script # 2. It is your responsibility to create and populate the system context variable %apikey% with your valid Metadefender cloud license key. # 3. The script accepts the currently scanned file location as its last command line argument, and stores it in the variable $current_file_path # 4. The script expects to find the scan results json on STDIN. it is read into the variable $scan_results # # output: # 1. The script will write its verdict (based on results from Metadefender cloud) to a result JSON and write it to the STDOUT # 2. The script has three possible verdicts: # if only the same engine (or no engine at all) flag the file as "Suspicious" (2) the script will copy the file to the folder $false_positive for later investigation # if another cloud engine flagged the file as a threat the verdict will be "Infected" (1) # if no local engine flags the file as infected the verdict will be "clean" (0) # 3. The script has 6 possible return values: # "0" - Success # "1" - Input Json Parse error # "2" - Copy error # "3" - file path of currently scanned file is invalid # "4" - the destination path of "false positive" is invalid. # "5" - call to Metadefender hash lookup failed # "6" - hash not found on Metadefender cloud LogWrite "--------------------------------------------------------------------------------------------------------------------" $date_time = Get-Date | Out-String LogWrite $date_time #get values from environment $false_positive = (get-item env:false_positive).Value LogWrite "false_positive = $false_positive" $apikey = (get-item env:apikey).Value LogWrite "apikey = $apikey" #get current file path from the last argumane on the command line $current_file_path = $args[$args.Count-1] LogWrite "current_file_path = $current_file_path" #initializations $retval = 0 $out_result = @{} $out_result.scan_result_i = 0 #Suspicious $out_result.threat_found = 'No Threats Found' $out_result.def_time = (New-TimeSpan -Start "1970-01-01" -End (Get-Date -UFormat "%Y-%m-%d").ToString().Substring(0,10)).TotalMilliseconds #proceed only if destination is valid if ($false_positive | Test-Path) { LogWrite "in the first if path tested True" #convert json from stdin to object try { $input_var=[Console]::In.ReadLine(); LogWrite "stdin input = $input_var" $scan_results = $input_var | Out-String | ConvertFrom-Json -ErrorAction Stop } catch { $retval = 1 #json parse error LogWrite "retval = $retval" } #if all fine till here check scan results if ($retval -eq 0 ){ #add display name from input structure to the destination path $display_name = $scan_results.file_info.display_name if ($display_name){ $false_positive += "\$display_name" } if ([System.IO.File]::Exists($current_file_path)) { LogWrite "current_file_path (input file) exists..." # check if how many engines flagged the file as blocked $core_engines_detected_threat = 0 # simulate local infection found #$scan_results.scan_results.scan_all_result_i = 1 if ($scan_results.scan_results.scan_all_result_i -eq 1) { # loop results and get engine name - keep ForEach ($core_engine_property in $scan_results.scan_results.scan_details.psobject.properties){ $core_engine = $core_engine_property.Value # simulate local infection found as "Avira" #if ($core_engine_property.Name -eq "Avira") { # $core_engine.scan_result_i = 1 #} if ($core_engine.scan_result_i -eq 1){ $core_engine_name = $core_engine_property.Name $core_engines_detected_threat += 1 } } } LogWrite "Number of engines = $core_engines_detected_threat" LogWrite "Engine detected = $core_engine_name" #continue only if ONE engine flagged the file as threat then check with Metadefender.com if ($core_engines_detected_threat -eq 1){ # invoke Metadefender cloud with hash lookup $uri = "https://api.metadefender.com/v2/hash/" $hash = $scan_results.file_info.md5 $uri += $hash $headers = @{} $headers.apikey = $apikey try { $result = Invoke-RestMethod -Uri $uri -Method Get -Headers $headers } catch { $err_result = $_.Exception.Response.GetResponseStream() $reader = New-Object System.IO.StreamReader($err_result) $reader.BaseStream.Position = 0 $reader.DiscardBufferedData() $err_body = $reader.ReadToEnd() $retval = 5 LogWrite "retval = $retval" } if ($result -match "Not Found") { $retval = 6 LogWrite "retval = $retval" } # simulate cloud infection found #$result.scan_results.scan_all_result_i = 1 if ($retval -eq 0){ $cloud_engines_detected_threat = 0 if ($result.scan_results.scan_all_result_i -eq 1){ # loop through scandetails until we find scan_details.current_engine.scan_result_i = 1 keep name ForEach ($cloud_engine_property in $result.scan_results.scan_details.psobject.properties){ $cloud_engine = $cloud_engine_property.Value # simulate cloud infection found by Avira engine #if ($cloud_engine_property.name -eq "Avira") { # $cloud_engine.scan_all_result_i = 1 #} if ($cloud_engine.scan_result_i -eq 1){ $cloud_engines_detected_threat += 1 $cloud_engine_name = $cloud_engine_property.name $cloud_def_time = $cloud_engine.def_time $cloud_scan_result_i = $cloud_engine.scan_result_i $cloud_threat_found = $cloud_engine.threat_found } } } LogWrite "Number of cloud engines detected = $cloud_engines_detected_threat" if (!$cloud_engine_name) {$cloud_engine_name = "no cloud engines flagged this file"} LogWrite "Engine detected is $cloud_engine_name" if (($cloud_engines_detected_threat -eq 0) -OR (($cloud_engines_detected_threat -eq 1) -AND ($core_engine_name -eq $cloud_engine_name))){ try { $out_result.scan_result_i = 2 #Suspicious $out_result.threat_found = 'Suspected False Positive' LogWrite "copying false positive FROM $current_file_path TO $false_positive" Copy-Item $current_file_path $false_positive -ErrorAction Stop; } catch { $retval = 2 # Copy error LogWrite "retval = $retval" } } else { $out_result.scan_result_i = 1 #Infected $out_result.threat_found = 'Infected - Probably NOT False Positive' } } } } else { $retval = 3 #currently scanned file path invalid LogWrite "retval = $retval" } #retval == 0 $out_result.scan_result_i = 0 #Clean LogWrite "retval = $retval" } } else { $retval = 4 #invalid destination LogWrite "retval = $retval" } #write JSON to STDOUT $json_out = ConvertTo-Json $out_result [Console]::Out.WriteLine($json_out) exit $retval