Dénes Olivér Óvári, Detection and Response Architect
CSIS Security Group A/S
A three-part blog series focusing on the Digital Forensics and Incident Response aspect of the compilers shipped with the Windows operating system and their usage from PowerShell.
The example shown in the previous post uses the Add-Type
cmdlet (implemented in Microsoft.PowerShell.Commands.Utility.dll
) to create an instance of a class from C# source code. A method of the instance is then callable from PowerShell to create a message box.
Using Add-Type
is not a requirement though, as we can see in a malicious PowerShell script, described by Xavier Mertens [1].
The sample attempts to perform process injection. The necessary functions from Kernel32.dll
are wrapped into a class made in C#. Another piece of PowerShell code is used to produce a .NET assembly (Figure 1), which in fact relies on the very same methods Add-Type
would call if it was used instead.
Ultimately, the assembly is instantiated as an object, and attempts to inject a binary payload.
As mentioned previously, PowerShell depends on external compiler executables if it needs to deal with objects implemented in other programming languages.
These compilers are stand-alone command line tools made to create binary files from source code according to the received parameters. A robust (though maybe somewhat old-fashioned) way of passing data to and from a subprocesses is done via temporary files, and it is just what PowerShell’s implementation does.
A class called TempFileCollection
is used to keep track of some of the files created in the %TEMP%
folder, and to eventually delete all of them once they are no longer needed.
The class generates a random 8-character alphanumeric string for each "collection" and uses that as a name for each of the files it contains, only the file extensions differ (Figure 2).
.NET Framework version 4.7.2. and higher includes measures to protect temporary files created by elevated processes. For this feature to work, the "collection" must have its own subdirectory, if the process using it is elevated. TempFileCollection
uses the same random string as the name of this subdirectory: %TEMP%/xrplyns2
for example.
PowerShell creates the following files:
Extension |
Description |
|
Contains the source code itself |
|
Contains the command line passed to the compiler |
|
|
PowerShell then executes one of the compilers, for instance, csc.exe
to create a DLL from the dropped source code. During the linking phase, csc.exe
itself executes cvtres.exe
and creates two additional temporary files to pass data.
If the compilation is successful, the linked assembly is loaded from the newly created DLL file and is ready for use.
There is a simple "type cache", which means that Add-Type
will not compile the same piece of source code again, as long as a type created with it still exists in the particular PowerShell session.
The JScript implementation is different. Although there is a working jsc.exe
binary alongside the two other compilers, it is never executed by .NET when compiling JScript. Only cvtres.exe
is required. jsc.exe
in fact simply wraps the same Microsoft.JScript.JSInProcCompiler
which Add-Type
uses too.
As we have seen above, quite a lot of file operations are done behind the scenes when assemblies are created using System.CodeDom.Compiler
.
There are plenty of parameters to choose from when this namespace is used directly. GenerateInMemory
seems particularly interesting from a DFIR viewpoint. It is in fact used by the sample shown previously too: it is set to true
(Figure 1). According to the documentation, the value of this property (unsurprisingly) controls whether the output of the compiler should be generated "in the memory".
This might infer that there won’t be any files created on the storage, but regardless of the setting, and if external compilers are needed to be called during the process, TempFileCollection
will generate its files, and the output of the compilation will nevertheless appear in the temporary folder for a short time.
.NET Core does not include compilers in its base installation, hence the behaviour described previously won’t be observed in PowerShell versions 6 or 7 either.
However, at the time of writing, PowerShell 5.1 still ships by default as a part of Windows, and its files remain even if the newer version gets installed. They run side-by-side.
It should also be emphasised, that being parts of the of the .NET Framework, csc.exe
, vbc.exe
and jsc.exe
could be used without involving PowerShell at all!
Thanks to my colleague, Conor Kelly for his review.
"Jscript, Microsoft, PowerShell, Windows are trademarks of the Microsoft group of companies."
[1] | X. Mertens, New Tool to Add to Your LOLBAS List: cvtres.exe, 2021. |