I recently came across a situation where some offensive powershell tools which I’ve used quite often, are now eventually being flagged as malicious. In this case it was Windows Defender, although AMSI (AntiMalware Scan Interface) is agnostic of antimalware product so it could easily be something different.

In this situation, there’s a number of options available. I could use something like Invoke-Obfuscation to play around with functions/strings etc, I could do this manually using trial and error or I could use one of the AMSI bypasses that are available. Sometimes though, it would be just nice to know exactly what triggered it, which is why I wrote AMSITrigger.

AMSITrigger will identify all of the malicious strings in a powershell file, by repeatedly making calls to AMSI using AMSIScanBuffer, line by line. On receiving an AMSI_RESULT_DETECTED response code, the line will then be scrutinised to identify the individual triggers.

V3 Update:

In order to speed up the scanning process, AMSITrigger breaks a file up into smaller chunks. Being able to ignore whole chunks, reduces calls to AMSIScanBuffer but presents a few problems that can result in false-negatives ie. A clean result where you expected a trigger.

The first problem is where the signature length exceeds the chunk size. To cater for this I've included a ChunkSize parameter, which should be increased if you're getting an unexpected result.

The second problem is where a signature spans 2 consecutive chunks, which is more likely when you use smaller chunk sizes. To cater for this, I've added a maxSignatureLength parameter. This will assume that the final maxSignatureLength bytes in a chunk could be the start of a truncated signature, and will prepend the next chunk with those bytes.

V2 Update:

I’ve had to change the logic slightly so instead of scanning line by line, I’m now using data chunks. This has made trigger identification more accurate with only a slight tradeoff in execution time.

I’ve also added an extra parameter to load the powershell directly from a URL, for 2 reasons: It’s not always possible to match your lab environment with your target environment. Maybe your signatures are not sync’d up or maybe its difficult matching the EDRs. Also - you’ll probably be using fileless execution so there’s no need to drop payloads unecessarily to disk as this may cause them to be removed by the EDR.


     _    __  __ ____ ___ _____     _
    / \  |  \/  / ___|_ _|_   _| __(_) __ _  __ _  ___ _ __
   / _ \ | |\/| \___ \| |  | || '__| |/ _` |/ _` |/ _ \ '__|
  / ___ \| |  | |___) | |  | || |  | | (_| | (_| |  __/ |
 /_/   \_\_|  |_|____/___| |_||_|  |_|\__, |\__, |\___|_|
                                      |___/ |___/    v3


  -i, --inputfile=VALUE      Filename
  -u, --url=VALUE            URL eg.
  -f, --format=VALUE         Output Format:
                               1 - Only show Triggers
                               2 - Show Triggers with line numbers
                               3 - Show Triggers inline with code
                               4 - Show AMSI calls (xmas tree mode)
  -d, --debug                Show debug info
  -m, --maxsiglength=VALUE   maximum signature Length to cater for,
  -c, --chunksize=VALUE      Chunk size to send to AMSIScanBuffer,
  -h, -?, --help             Show Help


Example 1 - Show Triggers in Invoke-Phant0m.ps1 using URL


Example 2 - Show Triggers in Invoke-Mimikatz.ps1 with Line Numbers


Example 3 - Show Triggers inline with code


If you’re curious to see how AMSITrigger works, run in Xmas Tree mode (-f=4) which will display exactly what is being sent to AMSI. Check out the GitHub Repo for code.


AMSITrigger currently only works against Powershell files. I will look at extending this to VBScript, Java etc


Calling AMSI Scan functions: Thanks to S3cur3Th1sSh1t for details on multi-line signatures.