Skip to content

Conducting AMSI Scans

cobbr edited this page Sep 22, 2017 · 1 revision

PSAmsi is flexible and can be used in a variety of ways.

PSAmsi exposes the [PSAmsiScanner] class for conducting simple AMSI scans.

Here's a couple of examples of conducting AMSI scans with [PSAmsiScanner]:

PS > $Scanner = [PSAmsiScanner]::new()
PS > $Scanner.GetPSAmsiScanResult('test')
False
PS > $Scanner.GetPSAmsiScanResult([Uri]::new('https://github.com/PowerShellMafia/PowerSploit/raw/master/Exfiltration/Invoke-Mimikatz.ps1'))
True

Everything that can be accomplished with the [PSAmsiScanner] class can also be done using PowerShell cmdlets exposed by PSAmsi that wrap [PSAmsiScanner]'s functionality.

For example:

PS > $Scanner = New-PSAmsiScanner
PS > Get-PSAmsiScanResult -ScriptString 'test' -PSAmsiScanner $Scanner
False
PS > Get-PSAmsiScanResult -ScriptUri [Uri]::new('https://github.com/PowerShellMafia/PowerSploit/raw/master/Exfiltration/Invoke-Mimikatz.ps1') -PSAmsiScanner $Scanner
True

Like all functions that accept a Script as input in PSAmsi, the Get-PSAmsiScanResult function accepts either a String, Path, Uri, or ScriptBlock using the -ScriptString, -ScriptPath, -ScriptUri, or -ScriptBlock parameters.

You also do not need to create the [PSAmsiScanner] before calling Get-PSAmsiScanResult, it will create one for you if it is not provided:

PS > Get-PSAmsiScanResult -ScriptString 'test'
False
PS > Get-PSAmsiScanResult -ScriptPath '.\EvilScript.ps1'
True
PS > Get-PSAmsiScanResult -ScriptBlock { 'test' }
False
PS > Get-PSAmsiScanResult -ScriptUri 'http://example.com/EvilScript.ps1'
True

Get-PSAmsiScanResult also accepts pipeline input for bulk scanning!

PS > @({ 'test1' }, { 'test2'}, {'test3'}) | Get-PSAmsiScanResult
False
False
False

Caching Scan results

The [PSAmsiScanner] class maintains a cache of scan results, and it will return the cached result if it is asked to scan a repeated request. This is mainly done to avoid generating unnecessary AMSI alerts.

For example, the following will only generate one AMSI alert:

PS > $PSAmsiScanner = New-PSAmsiScanner
PS > Get-PSAmsiScanResult -ScriptUri 'http://example.com/EvilScript.ps1' # This generates an AMSI alert
True
PS > Get-PSAmsiScanResult -ScriptUri 'http://example.com/EvilScript.ps1' # This does NOT generate an AMSI alert, the cached result is returned
True

Keep in mind that this only works if you are using the same PSAmsiScanner, the following generates two AMSI alerts:

PS > Get-PSAmsiScanResult -ScriptUri 'http://example.com/EvilScript.ps1' # This generates an AMSI alert
True
PS > Get-PSAmsiScanResult -ScriptUri 'http://example.com/EvilScript.ps1' # This also generates an AMSI alert, because a new PSAmsiScanner is created and used
True

The pipeline is safe, and utilizes caching

# This only generates a single AMSI alert, as the PSAmsiScanner is re-used.
PS > @([uri]::new('http://example.com/EvilScript.ps1'), [uri]::new('http://example.com/EvilScript.ps1')) | Get-PSAmsiScanResult
True
True

This caching is really handy for offensive operations. But if you are trying to maintain an up-to-date list of strings flagged by AMSI over a long period of time, signatures could change and caching could cause problems.

PSAmsi allows for disabling the ScanCache in a couple of ways. You can create a new PSAmsiScanner that has caching disabled:

PS > $Scanner = New-PSAmsiScanner -DisableCache

You can also turn it off on an existing PSAmsiScanner:

PS > $Scanner.CacheEnabled = $False

An interesting alernative for the use case of long-term analysis of constanly updating AMSI singatures is to reset the ScanCache periodically, instead of disabling it. This allows us to maintain the efficiency of using a cache, but can periodically reset it to learn of new signatures.

This can be accomplished with the PowerShell cmdlet:

PS > Reset-PSAmsiScanCache -PSAmsiScanner $Scanner

Or by simply setting the ScanCache to an empty Hashtable:

PS > $Scanner.ScanCache =  @{}

Credit - PSAmsi uses PSReflect written by Matt Graeber to call the export AMSI.dll functions in memory.