CIMplant Part 2: A Deeper Look into the Creation
In the second part of our CIMplant series we'll take a deeper dive into the code of CIMplant and go over some of the more interesting aspects of it. If you haven't read the first Cimplant blog post on the intro and some simple detections, now would be a great time to check it out.
Let's start off with the reasoning for going with CIM/MI vs WMI as the default connection process. One of the main differences between CIMplant and it's counterpart WMImplant regarding protocols is that CIMplant uses CIM/MI as a default which connects over WSMan (WinRM) and has, generally, massive time improvements over WMI. WMI connects over DCOM which is generally slower and may have some firewall issues. CIM and WMI can be used interchangeably throughout this post, the only difference will be the time the program takes to receive the data. In order to get a better understanding of the difference between CIM and WMI, check out this Defrag This blog post or this post on Microsoft DevBlogs. It will prove itself useful, even though it's PowerShell oriented.
An example of the time difference is shown below where we're just grabbing basic system info. Originally when testing I was seeing variations around 20-30 seconds when using WMI whereas CIM was steadily retrieving info at about 0-3 seconds. Using the latest version of CIMplant shows vast time improvement on my test machines (~3 seconds for WMI and ~0 seconds for CIM).
We decided to go with CIM as the default due to it being more of a standard going forward and will hopefully work with more systems seamlessly. Currently the program flow is: execution with flags -> connect to either CIM or WMI depending on flag -> fallback to the other option if errors -> execute command/parse data -> exit.
To make everything easier, we decided to create a class called Planter that will house all data necessary for the application to function. This class gets instantiated with the basic connection information: targeted system, username, password, and domain. On top of those, we wanted to create two more classes called Connector and Commander. Connector is related to the connection of WMI or CIM, while Commander is related to the commands passed. These are then instantiated and added to the Planter class which is then passed through the rest of the application. The mind map below might clarify the Planter class a bit.
The Planter class allowed easy access to all objects and connection sessions. Next, the Commander instance is created using the flags sent to the program and parsed for any missing values. I decided to overload the Commander class to, not only allow access to an empty constructor, but also take the passed command and check for null values, as shown in the image below. If any functions require the same method (moreso, the same WMI class such as the Win32Shutdown) we set the method here, otherwise we set it later in the application with the command passed and reflectively call it.
We then connect to the remote system and then pass that WMI or CIM session object variable to the Planter object. Below is the code used for the CIM connection.
This checks for the password flag sent when running CIMplant, which is a good indicator the user wants to use alternate credentials. It then attempts to start a new CIM session and saves that variable to return to the main program execution flow. If there are no errors up to this point, CIMplant uses reflection to dynamically call the method and pass the required arguments to the method (the Planter object). Below is the code which dynamically calls the method depending on the type of successful connection we obtained previously (either CIM or WMI).
Once the reflection setup is finished, the specific method passed is called. For code reuse, we decided to have every command be a method and return the raw data queried using the WMI/CIM command. This way we can alter the data as needed and use previously-written methods to gather data for new methods if needed. The screenshot below outlines the basic_info method (output can be seen in the first screenshot of the post).
This basic_info command, along with most other CIM commands, uses Windows Management Instrumentation Query Language (WQL) to query the CIM session for several different system info values. Those values are printed to the screen for the user to see and the raw data is returned (but unused in most normal data retrieving operations).
When researching the limitations of WMI, it was evident that WMI/CIM alone wouldn't be able to complete every task of CIMplant. The good side of it is that a majority of functions don't require any other language to execute, it was all done purely through WMI. At some point, if it's needed, I may add in a "--no-ps" flag that will check the command passed before executing to ensure if the operator does not want PowerShell to run, no PowerShell is run.
Unfortunately, as far as I've found out, there's no way to download/upload a file, enable/disable WinRM, or obtain the results from a remote PowerShell script without the help of a secondary language (like PowerShell). I spent several days trying everything from using the copy command remotely to looking into creating my own WMI provider. The provider will more than likely be my next larger addition to the tool (along with several bug fixes I'm sure) but you'll just have to wait and see :).
- Will Schroeder and his implementation of SharpWMI that helped me get going. https://github.com/GhostPack/SharpWMI