I Am My Own Executable
The bypass presented in the previous section
disabled AMSI by setting a registry key,
which is very different than the approach we used to disable AMSI from PowerShell.
For PowerShell, we focused on causing an error
with AMSI-related information
or modifying the AMSI APIs to return an error.
In this section, we’ll perform a simple trick to obtain a similar result.
While we cannot locate
any of the structures to interact with the Win32 APIs from Jscript,
we know that AMSI requires AMSI.DLL.
If we could prevent AMSI.DLL from loading or load our own version of it,
we could force the AMSI implementation in wscript.exe
to produce an error and abort.
While it seems logical to attempt to simply overwrite AMSI.DLL,
we must have administrative permissions
to overwrite anything in the Windows System32 directory.
We could, however, perform a DLL hijacking attack
by exploiting the DLL search order.
To determine if this is possible,
we’ll use WinDbg to inspect the AMSI.DLL loading process.
To do this, we’ll once again launch the wscript.exe process
and open the unmodified DotNetToJscript shellcode runner Jscript file.
Once WinDbg has launched wscript.exe
and a bare minimum of modules,
it breaks the execution flow.
Listing the loaded modules with lm
and searching for a module named amsi
reveals that AMSI.DLL has not yet loaded.
At this point, we need to determine what, exactly, is loading AMSI.DLL.
To determine this, we must stop WinDbg as soon as this DLL is loaded.
One way to accomplish this
is to instruct the debugger to catch the load of the DLL in WinDbg.
We can do this with the sxe command,
along with the ld subcommand
to detect when a module is loaded by supplying the name as an argument.
Based on the output, AMSI.DLL is loaded,
and the lm command correctly displays it as in the process.
Next, we need to locate the code responsible for loading AMSI.DLL.
A DLL is typically loaded through the Win32 LoadLibrary
or LoadLibraryEx APIs
so we must look for that function to determine what function invoked it.
We are searching for the callstack or the backtrace,
which is the list of called functions that led to the current execution point.
We can list this with the the k command.
The output reveals the call to LoadLibraryExW,
which loaded AMSI.DLL
along with its calling function COleScript::Initialize.
We can unassemble the function
in the callstack to inspect the arguments supplied
to LoadLibraryExW:
According to the LoadLibraryExW function prototype,
the first argument is the name of the DLL to load.
The name of the DLL is “amsi.dll”, listed without a full path.
This is significant considering the DLL search order.
When a full path is not provided,
the folder of the launched application is searched first.
If we copy wscript.exe to a writable location
and place a custom version of AMSI.DLL in the same folder,
this could open up an attack vector.
However, LoadLibraryExW can accept additional arguments
and the third argument modifies the function’s default behavior.
In this case, R8, the third argument, is set to 0x800.
This is equivalent to the enum LOAD_LIBRARY_SEARCH_SYSTEM32,
which forces the function to search in the C:\Windows\System32 directory first.
This prevents a DLL hijacking attack.
Security researcher James Forshaw discovered an interesting way around this.
Instead of trying to hijack the DLL loading,
James suggests renaming wscript.exe to amsi.dll and executing it.
There are two important things to note about this approach.
First, if a process named “amsi.dll” tries to load a DLL of the same name,
LoadLibraryExW
will report that it’s already in memory and abort the load to improve efficiency.
Obviously, any subsequent attempts to use the AMSI APIs will fail,
causing AMSI itself to fail and be disabled,
leaving us with an AMSI bypass.
The second important thing to note is that double-clicking
or running a file with a .dll extension
will fail since DLLs are normally loaded, not executed.
This behavior is actually caused by the Win32 ShellExecute API,
which is used by cmd.exe.
However, if we instead use the CreateProcess Win32 API,
the file extension is ignored
and the file header would be parsed to determine if it is a valid executable.
We cannot directly call this API,
but we can use the Exec method of the WScript.Shell object
since it’s just a wrapper for it.
Implementing this AMSI bypass requires a few new actions.
When the Jscript is executed,
it will copy wscript.exe to a writable
and executable folder, naming it “amsi.dll”.
Then, it will execute this copy
while supplying the original Jscript file as in the previous bypass.
We check for the existence of AMSI.dll with try and catch statements
to determine if the Jscript file is being executed for the first or the second time.
In the try statement, we first detect if the copied executable already exists
through the FileExists method of the FileSystemObject object.
If it does, we execute the
DotNetToJscript-generated shellcode runner.
If it does not yet exist, we trigger an exception
and the code in the catch section is executed.
Here, we use the CopyFile method
to copy wscript.exe into the Windows\Tasks folder
and name it “AMSI.DLL”.
Next, we use the Exec method to execute the copied version of wscript.exe
and again process it as a Jscript file, just as we did in the last section.
When we execute the combined Jscript file,
we obtain a reverse Meterpreter shell but something unexpected happens.
An antivirus alert pops up.
In this case, the reverse shell launched, indicating that we bypassed AMSI,
but Windows Defender detected a new process
named “amsi.dll” and flagged our code.
In this case, we had a working shell for a brief period of time,
but it was killed as soon as Windows Defender flagged it.
We can work around this by immediately migrating the process,
which will keep our migrated shell alive.
Alternatively, we could use a shellcode runner
that performs process injection or hollowing.
Although we have lost the element of stealth by triggering Windows Defender,
this bypass will work against all antivirus vendors that support AMSI
and some products may not even detect the “amsi.dll” process.