Windows to run a PE file relies on reading the header. The PE header describes how it should be loaded in memory, which dependencies has and where is the entry point. And what about .Net Assembly? The entry point is somewhere in the IL code. Direct execution of the assembly using the entry points in the intermediate code would cause an error. This is because the intermediate code should not be executed first but the runtime will load the intermediate code and execute it.
In previous versions of Windows, execution is passed to an entry point where the boot code is located. The startup code, is a native code and uses an unmanaged CLR API to start the .NET runtime within the current process and launch the real program that is the IL code. This could be a good strategy to achieve the result. The final aim is: to run the assemply directly in memory, then the dll must have the assembly some where in memory, any command line parameters and a reference to the memory area that contains them.
So I need to create a post-exploitation module that performs the following steps:
- Spawn a process to host CLR (meterpreter)
- Reflectively Load HostCLR dll (meterpreter)
- Copy the assembly into the spawned process memory area (meterpreter)
- Copy the parameters into the spawned process memory area (meterpreter)
- Read assembly and parameters (dll)
- Execute the assembly (dll)
To start the Host process, metasploit provides Process.execute which has the following signature:
Process.execute (path, arguments = nil, opts = nil)
The interesting part is the ops parameter:
By setting Channelized to true, I can read the assembly output for free with the call
Once the Host process is created, Metasploit provides some functions for interacting with the memory of a remote process:
The inject_dll_into_process function copies binary passed as an argument to a read-write-exec memory area and returns its address and an offset of the dll's entry point.
exploit_mem, offset = inject_dll_into_process (process, library_path)
The memory.allocate function allocates memory by setting the required protection mode. In this case I will write the parameters and the assembly in the allocated memory area, for none of these two elements I need the memory to be executable so I will set RW.
I decided to organize the memory area dedicated to parameters and assemblies as follows:
- 1024 bytes for the parameters
- 1M for the assembly
assembly_mem = process.memory.allocate (1025024, PAGE_READWRITE)
The third method allows to write data to a specified memory address.
process.memory.write (assembly_mem, params + File.read (exe_path))
Now I have the memory address of dll, the offset to the entry point, the memory address fo both the parameters and the assembly to be executed. Considering the function
Thread.create (entry, parameter = nil, suspended = false)
I can use the memory address of dll plus the offset as a value for the entry parameter and the address the parameter and assembly memory area as the parameter parameter value.
process.thread.create (exploit_mem + offset, assembly_mem)
This results in a call to the entry point and an LPVOID pointer as input parameter.
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved)
It's all I need to recover the parameters to be passed to the assembly and the assembly itself.
ReadProcessMemory(GetCurrentProcess(), lpPayload, allData, RAW_ASSEMBLY_LENGTH + RAW_AGRS_LENGTH, &readed);
About the assemblies to be executed, it is important to note that the signature of the Main method must match with the parameters that have been set in the module, for example:
- If the property ARGUMENTS is set to "antani sblinda destra" the main method should be "static void main (string  args)"
- If the property ARGUMENTS is set to "" the main method should be "static void main ()"
Chaining with SharpGen
A few days ago I read a blog post from @cobb_io where he presented an interesting tool for inline compilation of .net assemblies. By default execute-assembly module looks for assemblies in $(metasploit-framework-home)/data/execute-assembly but it is also possible to change this behavior by setting the property ASSEMBLYPATH for example by pointing it to the SharpGen Output folder in this way I can compile the assemblies and execute them directly with the module.