Public/Invoke-OffsetYaraScan.ps1
|
function Invoke-OffsetYaraScan { <# .SYNOPSIS Scans a file with the YARA engine and returns matches with their byte offsets. .DESCRIPTION Runs the YARA command-line engine against a file using one or more rule files and returns one record per matched string (rule, string id, byte offset, matched data), or one record per matched rule when strings are not requested. The offsets feed directly into Invoke-OffsetInspect for context. This complements the AMSI/Defender detection-boundary view with analyst-authored signatures and needs no antivirus installed - only the YARA engine (for example `winget install VirusTotal.YARA`). Cross-platform where YARA is present. .PARAMETER FilePath The file to scan. .PARAMETER RulePath One or more YARA rule files. .PARAMETER YaraPath Optional explicit path to the YARA executable (otherwise resolved from PATH / winget). .PARAMETER NoStrings Report matched rules only, without per-string offsets (faster). .PARAMETER TimeoutSeconds Maximum seconds to wait for the scan (default 60). .EXAMPLE Invoke-OffsetYaraScan .\sample.bin -RulePath .\rules\malware.yar .EXAMPLE Invoke-OffsetYaraScan .\sample.bin -RulePath .\rules.yar | ForEach-Object { Invoke-OffsetInspect $_.File $_.Offset -ContextLines 2 } #> [CmdletBinding()] param( [Parameter(Mandatory = $true, Position = 0)] [ValidateNotNullOrEmpty()] [string]$FilePath, [Parameter(Mandatory = $true)] [ValidateNotNullOrEmpty()] [string[]]$RulePath, [string]$YaraPath, [switch]$NoStrings, [ValidateRange(1, 3600)] [int]$TimeoutSeconds = 60 ) $resolvedFile = (Resolve-Path -LiteralPath $FilePath -ErrorAction Stop).Path $resolvedRules = @(foreach ($rule in $RulePath) { (Resolve-Path -LiteralPath $rule -ErrorAction Stop).Path }) $yaraExecutable = Resolve-OIYaraExecutable -YaraPath $YaraPath $arguments = New-Object 'System.Collections.Generic.List[string]' if (-not $NoStrings) { $arguments.Add('-s') } foreach ($rule in $resolvedRules) { $arguments.Add($rule) } $arguments.Add($resolvedFile) # WinPS 5.1's .NET Framework ProcessStartInfo has no ArgumentList, so build a # single quoted argument string that is safe on both editions. $quoted = @($arguments | ForEach-Object { if ($_ -match '[\s"]') { '"' + ($_ -replace '"', '\"') + '"' } else { $_ } }) $startInfo = New-Object System.Diagnostics.ProcessStartInfo $startInfo.FileName = $yaraExecutable $startInfo.Arguments = ($quoted -join ' ') $startInfo.RedirectStandardOutput = $true $startInfo.RedirectStandardError = $true $startInfo.UseShellExecute = $false $startInfo.CreateNoWindow = $true $process = $null try { $process = [System.Diagnostics.Process]::Start($startInfo) $stdoutTask = $process.StandardOutput.ReadToEndAsync() $stderrTask = $process.StandardError.ReadToEndAsync() if (-not $process.WaitForExit($TimeoutSeconds * 1000)) { try { $process.Kill() } catch { Write-Verbose "YARA process kill failed: $($_.Exception.Message)" } throw "YARA scan timed out after $TimeoutSeconds seconds." } $stdout = $stdoutTask.Result $stderr = $stderrTask.Result $exitCode = $process.ExitCode } finally { if ($null -ne $process) { $process.Dispose() } } if ($exitCode -ne 0 -and -not [string]::IsNullOrWhiteSpace($stderr)) { throw "YARA reported an error (exit $exitCode): $($stderr.Trim())" } return (ConvertFrom-OIYaraOutput -Output $stdout -FilePath $resolvedFile) } |