Get the Security Descriptor of a Windows Service With WMI

Reason for Project:

A while back I wrote a little utility to temporarily turn off the Microsoft Security Essentials Anti-Virus / Anti-Malware service.

In January of 2013 I discovered that my application no longer worked due to an Access denied error. I started to investigate why and discovered my application no longer had the “Rights” to shut down the service even though it was “Run As Admin”.

In order to understand what rights I or an application I wrote would have over a service I needed to see what Users and  Rights were assigned to the service. That would also explain why I no longer could control the service thru the built in Service Controller.

Back story:

Beginning with Windows Vista we now have Service isolation, Service Hardening and the ability to assign a SID to the service to help secure it more. You also need to secure the registry where the information about your service is listed so it can not be changed by unauthorized users or code.

The service can be set up with “Default permissions” or set with “Special permissions”, effectively locking it down to help keep users or malicious code from messing with it.

This is just one of many articles on the subject. Services Hardening in Windows Vista here.

So services just keep getting better protection to help keep malicious code from stopping or compromising a service.

The Code:

For several months I have been looking into finding a way to get the security descriptor from a service but no sample code could be found at the time. I asked the question on Code Project but did not get an answer.

The .Net framework  has a built in way to get at the security descriptor for the Registry,Folder, and Files but after going thru the Object browser (.Net 3.5) for some time I could not find a way to do it without creating the needed wrapper classes like was done for the other items.

So I decided to try to Platform Invoke with  the “QueryServiceObjectSecurity” function here.

After several attempts and asking another question on Code Project and not getting an answer I kept digging  and am now close but not quite there yet on getting it to work.

Now that I have a working model I can see what I am missing and possibly get it to work.

In the mean time while still researching how to do it I stumbled across a question that was asked on Stack Overflow here titled “Getting Win32_Service security descriptor using VBScript”. That question gave me a starting point to try VB script. Further research landed me on the MSDN page where I think it was originally taken from. here. WMI Security Descriptor Objects.

After seeing that, I was able to work out the following VB Script to get the Security Descriptor for all services.

VB Script:

'Tested Script Still works with the values commented out. 'SE_OWNER_DEFAULTED = &h1 'SE_GROUP_DEFAULTED = &h2 'SE_DACL_PRESENT = &h4 'SE_DACL_DEFAULTED = &h8 'ACCESS_ALLOWED_ACE_TYPE = &h0 'ACCESS_DENIED_ACE_TYPE = &h1 strComputer = "." Set objWMIService = GetObject("winmgmts:" _ & "{impersonationLevel=impersonate, (Security)}!\\" & strComputer & "\root\cimv2") ' Get an instance of Win32_SecurityDescriptorHelper Set objHelper = GetObject( _ "winmgmts:root\cimv2:Win32_SecurityDescriptorHelper" ) Set colServices = objWMIService.ExecQuery _ ("Select * from Win32_Service") For Each objService in colServices Wscript.Echo "Name: " & objService.Name ' Get security descriptor for Service Return = objService.GetSecurityDescriptor( objSD ) If ( return <> 0 ) Then WScript.Echo "Could not get security descriptor: " & Return wscript.Quit Return End If If ( return = 1 ) Then WScript.Echo "The request is not supported: " & Return wscript.Quit Return End If If ( return = 2 ) Then WScript.Echo "The user did not have the necessary access: " & Return wscript.Quit Return End If If ( return = 8 ) Then WScript.Echo "Interactive process: " & Return wscript.Quit Return End If If ( return = 9 ) Then WScript.Echo "The name specified was not valid: " & Return wscript.Quit Return End If If ( return = 21 ) Then WScript.Echo "The parameters passed to the service are not valid: " & Return wscript.Quit Return End If ' Convert Service security descriptor from ' Win32_SecurityDescriptor format to SDDL format Return = objHelper.Win32SDToSDDL( objSD,SDDLstring ) If ( Return <> 0 ) Then WScript.Echo "Could not convert to SDDL: " & Return WScript.Quit Return End If WScript.Echo SDDLstring WScript.Echo "" WScript.Echo "" Next

This script requires the “Security” Keyword and to be run as Admin for it to work.

That little script took several hours to work out once I got a clue on what needed to go into it.

For the most part it is self explanatory.

The part I had trouble understanding was the Object, “objSD”.

As it turn out that is the Security Descriptor itself. That was the out Object from the call to “GetSecurityDescriptor” it was also an In Object to “Win32SDToSDDL”. I did not totally understand this until I completed the VB.Net version of this coming up next.

The script version can be modified to just check one service name or, used “as is” , to get all of them.


The VB.Net version was a little tougher. I first tried to work it out by starting out with the code generated from my GUI WMI Code Creator but that wasn’t working.

I could probably count on one hand the number of times in the last fifteen years that I needed to “Invoke” a method in a WMI class in order to get the information I needed. So I have to relearn how every time.

Here is the Final Project:


And here is what it looks like after formatting by hand in the textbox.


As you can see you get the Owner, Group, DACL, and the SACL.

Lets start with where the program starts Filling in the combo box.

I wanted to test two different ways to see how they worked.

The first is getting the list by using the “System.ServiceProcess” class.

Remember you have to add that “reference” before you can “Imports” it or use it in your code.

Private Sub FillServiceNames() 'Use ServiceProcess class to fill in the names Try Dim svcs() As ServiceController = ServiceController.GetServices() Dim svcCtlr As ServiceController For Each svcCtlr In svcs cbServiceNames.Items.Add(svcCtlr.ServiceName) count = count + 1 Next lblCount.Text = "Services Found: " & count.ToString Catch ex As Exception MsgBox(ex.Message) End Try End Sub

It is pretty straight forward.

Next is using WMI to fill the Combo Box.

Private Sub FillComboBox() ' Use WMI to fill in the Names Try Dim searcher As New ManagementObjectSearcher( _ "Root\CIMV2", _ "SELECT * FROM Win32_Service") For Each queryObj As ManagementObject In searcher.Get() cbServiceNames.Items.Add(queryObj("Name")) count = count + 1 Next lblCount.Text = "Services Found: " & count.ToString Catch ex As Exception MsgBox(ex.Message) End Try End Sub

They both make a call to get a list of services and then do a for each loop and then fill the Combo Box with the service name.

That was the easy part.

When I first couldn’t figure out the proper way to invoke a method using trial and error, I tried to use the “Strongly Typed” classes generated by “MgmtClassGen.exe” to get a better idea of what they were wanting, but I was still getting errors and could not figure out some of the required parameters. After several hours of that I tried the (Original) WMI Code creator. It has a section that generates code that works with methods. The code produced by that was not working and there was one parameter that I just could not figure out what it was supposed to be.

My next Idea was to turn to a program called “WMI Delphi Code Creator” found here.

I was given a link to it after I posted my article on my GUI WMI Code Creator, which does not do methods.

That program was able to produce code using methods like the (Original) WMI Code Creator but it was in C#, so I had to convert it to VB.Net.

So those that want a C# version may want to check it out.

(Side Note on that program, if you don’t want it calling home for updates then uncheck that option in the options section. Also they have a install and standalone version)

After generating the two required methods to get the security descriptor and to transform it from a Win32SD to a SDDL string form I dropped the code into the project and started piecing it together.

After several hours of working with the code and reading more on it and fixing several items that used the same names in both generated methods, the code finally went all of the way thru and output to the text box.

I was so happy that it stopped crashing that at first I didn’t understand the results that were returned.

There was no SDDL String returned and I had two different error codes retuned that was not listed in the normal WMI return Codes. The first one returned from the “GetSecurityDescriptor” method was this, 2147943714 (Converted)0x80070522.

As it turns out it is a privilege not held error from the system not WMI.

So back to the “Strongly Typed” classes to see if I could figure out what I was missing. I found this in the Function “GetSecurityDescriptor”

Dim EnablePrivileges As Boolean = PrivateLateBoundObject.Scope.Options.EnablePrivileges PrivateLateBoundObject.Scope.Options.EnablePrivileges = true

That gave me the answer.

So without further ado, here is the final code.

Try Dim strbldr As New StringBuilder Dim svcName As String If cbServiceNames.SelectedIndex = Nothing Then MsgBox("No Service Name Was Selected") Exit Sub Else svcName = cbServiceNames.SelectedItem.ToString End If Dim ComputerName As String = "localhost" Dim Scope As ManagementScope If Not ComputerName.Equals("localhost", StringComparison.OrdinalIgnoreCase) Then Dim Conn As New ConnectionOptions() Conn.Username = "" Conn.Password = "" Conn.Authority = "ntlmdomain:DOMAIN" Scope = New ManagementScope([String].Format("\\{0}\root\CIMV2", ComputerName), Conn) Else Scope = New ManagementScope([String].Format("\\{0}\root\CIMV2", ComputerName), Nothing) Scope.Options.EnablePrivileges = True 'Error returned without EnablePrivleges 2147943714 (Converted)0x80070522 End If Scope.Connect() Dim Options As New ObjectGetOptions() 'Dim Path As New ManagementPath("Win32_Service.Name=""AdobeARMservice""") Dim Path As New ManagementPath("Win32_Service.Name=" & "'" & svcName & "'") Dim ClassInstance As New ManagementObject(Scope, Path, Options) Dim inParams As ManagementBaseObject = ClassInstance.GetMethodParameters("GetSecurityDescriptor") Dim outParams As ManagementBaseObject = ClassInstance.InvokeMethod("GetSecurityDescriptor", inParams, Nothing) Dim operrHex As String = String.Format("0x{0:X2}", outParams("ReturnValue")) Select Case outParams("ReturnValue") Case 0 ' No Problem continue on. Case 2 MsgBox("Error Code 2" & vbNewLine & "The user does not have access to the requested information.") Exit Sub Case 8 MsgBox("Error Code 8" & vbNewLine & "Unknown failure.") Exit Sub Case 9 MsgBox("Error Code 9" & vbNewLine & "The user does not have adequate privileges.") Exit Sub Case 21 MsgBox("Error Code 21" & vbNewLine & "The specified parameter is invalid") Exit Sub Case Else MsgBox("Error Not Listed" & vbNewLine _ & "Error Code" & vbNewLine _ & outParams("ReturnValue").ToString _ & vbNewLine & "Hex:" _ & vbNewLine & operrHex) Exit Sub End Select '******************** 'Start the conversion '******************** Dim Path2 As New ManagementPath("Win32_SecurityDescriptorHelper") Dim ClassInstance2 As New ManagementClass(Path2) Dim inParams2 As ManagementBaseObject = ClassInstance2.GetMethodParameters("Win32SDToSDDL") inParams2("Descriptor") = outParams("Descriptor") Dim outParams2 As ManagementBaseObject = ClassInstance2.InvokeMethod("Win32SDToSDDL", inParams2, Nothing) strbldr.AppendLine("Security Descriptor in SDDL Format") strbldr.AppendLine() strbldr.AppendLine("Service Name:" & svcName) strbldr.AppendLine() Dim opstr As String If outParams2("SDDL") = Nothing Then opstr = "SDDL String is nothing" Else opstr = outParams2("SDDL") End If strbldr.AppendLine(opstr) tbOutput.Text = strbldr.ToString Catch ex As Exception MsgBox(ex.Message) End Try End Sub

The first part of the code just checks to see if a service name was selected from the combo box. If it was it sets “svcName” to the name that was selected.

Next starts the generated code. This code is setup for Local and Remote use, I left it as is.

This part checks to see if you are connecting to WMI on the local system or on the network  if yes then it inputs the network Credentials if not then it doesn’t use that parameter thus it is set to “Nothing” versus “Conn” as above.

As I hinted to above, The whole trick to get this to work with out that error above is to use the “Scope.Options.EnablePrivileges = True” setting. Then Scope.Connect() .

Next we set up to connect to the given service name and call the “GetSecurityDescriptor” method.

This is the one of the lines that gave me so much trouble.

Dim outParams As ManagementBaseObject = ClassInstance.InvokeMethod("GetSecurityDescriptor", inParams, Nothing)

In VB Script it is.

' Get security descriptor for Service Return = objService.GetSecurityDescriptor( objSD )

What is objSD ? it is not Declared anywhere like VB.Net. But it just works in the script.

And the WMI Code Creator Output.

' Execute the method and obtain the return values. Dim outParams As ManagementBaseObject = _ classInstance.InvokeMethod("GetSecurityDescriptor", Nothing, Nothing)

I couldn’t figure out what they were wanting for a “ManagementBaseObject” when I was working with the “Strongly Typed” classes.

The next line is just there so that if you receive an error other than the normal ones in the list it will get the hex version of the Error and add that to a message box.

Next we set up the Select Case Statement using the Values that would be output.

Next we set up for the Conversion.

Now we are at the line that I had so much trouble understanding what they were wanting.

inParams2("Descriptor") = outParams("Descriptor")

In VB Script:

Return = objHelper.Win32SDToSDDL( objSD,SDDLstring )

In the WMI Code Creator you had to give a name, who knew what it was supposed to be.

' Add the input parameters. inParams("Descriptor") = InsertNameHere

The WMI Code Creator listed it as type “Object” but that was not very helpful.

By the “objSD” in the script I assumed it was a Security Descriptor Object but didn’t know that was the Object you worked with. So it is the out parameter of the first call. In this case it was called amazingly,  “outParams”.

The last parts just sets up the string builder to get the name of the selected service.

Then verifies there is actually an output for the SDDL String.

Then finally write the information out to the textbox.

Once you understand what the input and output is expecting then it makes the job easier.

Later I may still go back and see if I can complete the Platform Invoke version just to see if I can figure it out now that I know what is involved using other methods.

I hope you learned something because I did.

SC.exe Utility

To get the DACL using this utility use the command

sc sdshow ServiceName

To get the SID of a service use the command

sc.exe showsid ServiceName

Note: this command uses Undocumented API calls to create a SID from the ServiceName.

Final Notes:

These functions should only work on Vista and above.



Getting Win32_Service security descriptor using VBScript

GetSecurityDescriptor method of the Win32_Service class

Win32SDToSDDL method of the Win32_SecurityDescriptorHelper class

WMI Security Descriptor Objects

Privilege Constants (WMI)

Executing Privileged Operations

Changing Access Security on Securable Objects

WMI Return Codes

WMI Error Constants

WMI Delphi Code Creator

Service Changes for Windows Vista

Service Accounts Step-by-Step Guide (AD)

WMI .NET Scenarios

About pcsxcetrasupport3

My part time Business, I mainly do system building and system repair. Over the last several years I have been building system utility's in vb script , HTA applications and VB.Net to be able to better find the information I need to better understand the systems problems in order to get the systems repaired and back to my customers quicker.
This entry was posted in CodeProject and tagged , , . Bookmark the permalink.