Recently I push some updates to the project after an exchange of ideas on the possibility of creating a PR towards the Metasploit master branch.
The most significant are:
Added the ability to inject HostingCLR into an existing process via the PID parameter Added the ability to specify the process to be created instead of notepad.exe Added parameter to enable / disable Amsi bypass Refactoring of the code to comply with Metasploit best practices
Added functionality to detect the CLR necessary for the assembly in order to load the correct one, thus also supporting .Net 3.5 assemblies. Added verification of the CLR already loaded in the process, if already loaded a new one is not instantiated. Amsi Bypass using AmsiScanBuffer patching technique.
In the first part I focused more on the ruby code because the implementation of the HostingCLR dll was almost the same as presented by Etor Madiv in his original project. In this second part we will instead focus on improving the dll in order to be feasible for a PR on the Metasploit main branch.
How to find witch CLR version is needed
To be able to load the correct CLR version we need to know witch version the assembly requires. Furthermore, the verification must be able to be performed on a byte array.
The first thing that comes up in mind is to see if it is possible to find a signature inside the byte array to determine the version. A Windows executable, EXE or DLL, must conform to a file format called PE. A standard Windows PE file is divided into sections:
- MS-DOS header
- PE header
- optional header
- Native Image Section (.data, .rdata, .rsrc, .text)
These are the standard sections of a typical Windows executable. The C/C++ compiler allows you to add your custom sections to the PE file using a #pragma compiler directive.
What about the CLR? Metadata and IL code find space in an extension of the COFF/PE format.
The CLR data part contains metadata and IL code, both determine how the program will be executed. Compilers for the CLR must issue both the CLR header and the data information in the generated PE file, otherwise the resulting PE file will not be executed in the CLR. The CLR header holds a number of relevant details required by the runtime, like Runtime, MetaData directory and Entry point token.
So now we know that it is possible to extract the version. Opening a .Net assembly with HxD we can see how the version changes by rebuilding with different Target Frameworks and determining signatures for the search.
CLR v4.0.30319 - 76 34 2E 30 2E 33 30 33 31 39 CLR v2.0.50727 - 76 32 2E 30 2E 35 30 37 32 37
Load CLR only if needed
Another interesting idea is the possibility to load the CLR only if necessary.
For example we could think of using for the PROCESS powershell.exe parameter that we know to be a .Net process or to locate the pid of a .Net process, and go to load the assembly directly using the CLR already available.
This can be easily done by going to enumerate the runtimes loaded through EnumerateLoadedRuntime
If the required runtime is already loaded, use it without creating a new one.
Starting from the version of the Framework 4.8 Anti Malware Scan Interface is also integrated into the CLR, this means that the Assembly.Load call is subject to scanning by Amsi.
For the Amsi bypass I opted for AmsiScanBuffer Patching tencique.