Remote PowerShell Commands With WinRM Disabled And Windows PowerShell
NOTE: All of the above is not endorsed by my employer and is provided as is.
I have an annoying problem at work which is still in the process of being resolved. Due to security baselines, WinRM is disabled in our environment at work. Most people would say “update the resume and leave” but I am for the current moment committed to working through it. I kept picking away at the problem, trying to figure out how to get around WinRM being disabled without breaking the policies established by our security team. Now some may argue that the method I use breaks the spirit of the rule, but I like to play in the field of technicalities - and if you wish to utilize this own technique it is up to you and is not endorsed or warranted.
So how do you perform remote PowerShell commands on Windows PowerShell WITHOUT using winrm? Simple - use WMI. At this point you are probably thinking to yourself “I know you can use WMI to invoke commands - but fat lot that does us considering the output that WMI returns contains no useful information.” I know this thought - this was where I was for a bit until I decided to hook up a few more things together to be able to perform my duties.
I know WMI is NOT preferred and PSScriptAnalyzer will return warnings instructing you to use CIM instead of WMI. However, just like WinRM is blocked in my work environment so is CIM. So WMI is what I have to use. With that in mind lets begin by looking at the output from a WMI command.
The above output is what is returned from WMI when a remote process is started. Not too much helpful information - the best thing it returns is the ProcessID. But since things like get-process is even blocked - this becomes useless as monitoring for the process can’t even be done from a remote computer at this time. So how can this useless bit of information be turned around to be something useful? By combining WMI, Named Network Pipes, CLIXML, and Base64 strings instead of the above output we can get output that looks like PowerShell object output - and the reason it looks like that is because it IS PowerShell object output. This function takes into account multiple other things I learned the last few years and combines them all into one simple little script which allows me to perform my job until WinRM is enabled in my work environment. The resulting command is Invoke-POwmi (Invoke Powershell Over WMI) and is in a module which is currently being worked on.
So how does this exactly work? The logic workflow of this script looks like below:
A scriptblock is created. Inside of this scriptblock there are a few things that happen when it is executed.
A support function is defined which converts objects to Base64 strings.
A named pipe output stream is created.
The new named pipe waits for a connection to be established to it before continuing.
After a connection is established, a streamwriter is attached to the named pipe
The script block passed to the function is executed and the results from it are stored temporarily as a variable
The variable/object is converted toCLIXML and then the CLIXML is converted to a base64 string and then written to the named pipe using the streamwriter.
The streamwriter and named pipe is then closed and disposed.
Outside of the scriptblock, the replace command is called to replace elements of the script block with data passed in to function. This right now is not the most secure method - since it could potentially allow for script injection misuse. I am working on something in a branch right now to try to mitigate this - but for now it does what I need it to do.
The function then converts the scriptblock described above to a base64 string. Doing this allows for complex scriptblocks to be passed through to the remote system in invoke-wmimethod through the argumentlist parameter.
The encoded command is then passed through to the Invoke-WmiMethod command as shown below
Invoke-wmiMethod -computername $computer -class win32_process -name create -argumentlist “powershell.exe -encodedcommand $($encodedcommand)” -credential $credential
This is where the magic starts. A new named pipe is created locally with the same name as the remote pipe and connects to the computer the wmi command is run against. Invoke-wmimethod creates a powershell process on the remote computer which then invokes the scriptblock which was passed through the encodedcommand parameter. The data from the remote process is then written as a base64 string to the named pipe, transferred to the local computer, and once back on the local computer the data is converted from Base64 back to CLIXML and then finally back to a PS object.
Now there are a few things which need to be said - yes the function does return back an object but actions taken on the object directly do not work on the remote machine. I am playing around with a few things to maybe inject that sort of functionality into the script but nothing yet on Github regarding this.
So now you know WHY it works - how does the code itself work if you wanted to use it.
Currently the project is up on GitHub as an early working version of the script. I am currently using this in production at work - and decided to work on the code outside of work hours so I could share it. Documentation isn’t fully written yet but there is currently only basic functionality built in and only one way to use it at this point. Because of that this is not ready yet to be uploaded to the PowerShell gallery, but if you want to use an early version of this feel free to download it at the link above. Basic documentation is provided below.
Invoke-POWmi -pipename “pipe” -scriptblock $sb -computername $server -credential (get-credential)
Each of the parameters are listed below:
Pipename: The name of the network named pipe to connect to. You can create multiple named pipes with multiple names - DO NOT CREATE MULTIPLE NAMED PIPES WITH THE SAME NAME. If nothing is specified in this parameter a named pipe is created with a unique GUID.
Scriptblock: A powershell scriptblock. An example scriptblock is defined as below:
$sb = {
get-process
}
Now what is nice, is since the command is converted to Base64 before execution, this script block can be as complex as you want it. You unfortunately cannot pass parameters to it remotely and will need to define the entire script block ahead of time - but the tradeoff of not being able to run things remotely or not being able to use parameters as normal is an easy one to make.
Computername: The DNS name or IP address of the server to connect to
Credential: credential to run the wmi command as. Again - right now this needs to be specified and over time hopefully can do something for using the credential of the running shell instead of having to do specific creds. Currently only credentials from the same domain work as well - cross domain credentials do not work either.
I will be updating this over time with some updated/new functionality. Later this week I will also be uploading a video showing this tool in action.
Until then I have embedded a video that I took earlier which shows the command quickly in action.