Is That Your Registry Key?
In order to use a DotNetToJscript payload, we’ll need to bypass AMSI.
However, when bypassing AMSI in PowerShell,
we relied on reflection or Win32 APIs,
but these techniques are not available from Jscript.
Security researcher @Tal_Liberman discovered that Jscript tries to query
the “AmsiEnable” registry key
from the HKEY Current User hive before initializing AMSI.
If this key is set to “0”,
AMSI is not enabled for the Jscript process.
This query is performed in a function inside Jscript.dll,
which is only called when wscript.exe is started.
Let’s use WinDbg to attempt to discover the exact registry query.
We’ll open WinDbg,
navigate to File -> Open Executable…
and enter the full path of wscript.exe
along with the full path of our testing Jscript file.
With wscript.exe started,
we’ll set a breakpoint on AmsiIsEnabledByRegistry with bu:
The breakpoint is triggered and we can now track the execution of the function.
Next, we’ll unassemble the beginning of the function
to better understand the function’s layout.
The highlighted call to the Win32 RegOpenKeyExW API
opens the registry key, which is supplied as the second argument.
Due to the _fastcall calling convention, the second argument is supplied in RDX.
We can display the contents at that address with WinDbg
to identify the registry key:
This reveals the SOFTWARE\Microsoft\Windows Script\Settings
registry path.
A subsequent call to RegQueryValueExW is used to query the registry value.
The name of the registry key is also
supplied as the second argument to this API
so we can dump it in WinDbg.
We now have the full path to the registry key.
In order to bypass AMSI, we’ll create the key
and set its value to “0” with the RegWrite method,
from the WScript.Shell object.
This method accepts the full registry key,
the value content, and the value data type.
Now that the registry key is set,
let’s rerun the previous DotNetToJscript-converted shellcode runner
with the included sleep timer and invoke Frida to hook the AMSI APIs.
According to this output, AmsiScanBuffer and AmsiScanString were not invoked.
In addition, our shellcode runner
generates a reverse Meterpreter shell.
This bypass works very well!
Although this bypass was successful,
it only works if the registry key is set before the wscript.exe process is started.
Let’s enable AMSI again before we proceed.
Let’s improve our technique by implementing a check
for the AmsiEnable registry key.
If it exists, we’ll execute the shellcode runner,
zbut if it doesn’t, we’ll create it and execute the Jscript again.
Let’s unpack a few elements of this code.
First, the code is wrapped in try and catch exception handling statements.
As in many other languages, the code inside the try bracket is executed,
and if an exception occurs, the code inside the catch statement is executed.
Otherwise, exception will continue past the try and catch statements.
Inside the try statement, we call RegRead
to determine if the AmsiEnable key is already set.
If it isn’t, the throw statement along with the new Error constructor
throws a new exception.
If this happens, the code inside the catch statement is executed,
setting the AmsiEnable value,
and invoking the Run method.
The arguments for the call to the Run method are important.
First, we specify the cscript.exe executable,
which is the command-line equivalent of wscript.exe.
Next, we use -e to specify which scripting engine will execute the script.
The highlighted value is a globally unique identifier,
which when used in this manner may be understood as a registry entry under
HKLM\SOFTWARE\Classes\CLSID.
If we navigate to the registry path and locate the key with the correct GUID,
we’ll find an entry associated with Jscript and jscript.dll.
In essence, the -e option indicates that the specified script file
will be processed by jscript.dll.
The script file must be the original Jscript
and we provide this through the ScriptFullName property.
We supply an additional two arguments to the Run method
after the script file.
The first is the windows style
where “0” specifies that the window be hidden.
For the second argument, we specify “1”,
which will cause execution to wait for the script executed by the Run method
to be completed.
With this bypass in place,
we can prepend it to the DotNetToJscript generated shellcode runner.
When we run it,
we bypass AMSI and generate a reverse shell.