fortra / impacket

Impacket is a collection of Python classes for working with network protocols.
https://www.coresecurity.com
Other
13.49k stars 3.57k forks source link

Function GetSecurityDescriptor of StdRegProv fails #387

Open MrAnde7son opened 6 years ago

MrAnde7son commented 6 years ago

I was trying to query the security descriptor of a registry key from a remote machine using WMI interface (I'm not using winreg since Remote Registry service might be off), but I get 2147749917 as the return value, indicating WBEM_E_UNEXPECTED error. Digging a little more in the wmi.py, I figured there is an error when trying to parse the security descriptor returned by the remote machine (meaning I do get a response, but it does not parses correctly). In the process of parsing it, the following error is returned:

<type 'tuple'>: ("Character 'u' field is not NUL-NUL terminated: '\x15\x00\x00\x02'", 'When unpacking field \'Character | u | ...)

The remote machine is running Windows 10 Pro, and it was also tested on Windows Server 2012 Datacenter. Here's the code I'm using

import socket
import logging
from impacket.dcerpc.v5.dcomrt import DCOMConnection, NULL
from impacket.dcerpc.v5.dcom import wmi
from impacket.examples import logger

address = '10.1.1.1'
username = 'Administrator'
password = 'Password'
domain = 'contoso.com'
lmhash = ''
nthash = ''
aesKey = None
namespace = '//./root/cimv2'

HKEY_LOCAL_MACHINE = 2147483650

logger.init()
logging.getLogger().setLevel(logging.DEBUG)
try:
    dcom = DCOMConnection(address, username, password, domain, lmhash, nthash, aesKey, oxidResolver=False, doKerberos=False)
    iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
    iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
    iWbemServices = iWbemLevel1Login.NTLMLogin(namespace, NULL, NULL)
except socket.error, e:
    logging.error("Couldn't connect %s. Error: %s" % (address, str(e)))
    exit(0)

try:
    classObject, _ = iWbemServices.GetObject('StdRegProv')
    descriptor = classObject.GetSecurityDescriptor(HKEY_LOCAL_MACHINE, 'SAM\\SAM\x00')
    print descriptor
except Exception, e:
    logging.error(str(e))
finally:
    iWbemServices.RemRelease()
    dcom.disconnect()

Any clue?

Thanks.

asolino commented 6 years ago

Hey @MrAnde7son

Yup.. bug in the DCOM class returned by the call (in particular ENCODING UNITS). Debugging and analysis needed. Welcome to [MS-WMIO]. Probably the security descriptor has a type that is not yet supported or wrongly parsed.

asolino commented 6 years ago

Confirmed.. this is because we still don't support CIM-TYPE-OBJECT as one of the attributes received by an ExecMethod call.

asolino commented 6 years ago

Ok.. git pull first, give it a try and play with it.. it's just the first pass.. It allows to read data.. Sending objects as part of a WMI call is still not supported (maybe you can crack it ;) ). Example code, based on yours:

import socket
import logging
from impacket.dcerpc.v5.dcomrt import DCOMConnection, NULL
from impacket.dcerpc.v5.dcom import wmi
from impacket.examples import logger

address = '10.1.1.1'
username = 'Administrator'
password = 'Password'
domain = 'contoso.com'
lmhash = ''
nthash = ''
aesKey = None
namespace = '//./root/cimv2'

HKEY_LOCAL_MACHINE = 2147483650

logger.init()
logging.getLogger().setLevel(logging.DEBUG)
try:
    dcom = DCOMConnection(address, username, password, domain, lmhash, nthash, aesKey, oxidResolver=False, doKerberos=False)
    iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
    iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
    iWbemServices = iWbemLevel1Login.NTLMLogin(namespace, NULL, NULL)
except socket.error, e:
    logging.error("Couldn't connect %s. Error: %s" % (address, str(e)))
    exit(0)

try:
    classObject, _ = iWbemServices.GetObject('StdRegProv')
    retVals = classObject.GetSecurityDescriptor(HKEY_LOCAL_MACHINE, 'SAM\\SAM\x00')
    print "ReturnValue: %d" % retVals.ReturnValue
    print "Descriptor Info:"
    retVals.Descriptor.printInformation()
    print "Owner Info:"
    retVals.Descriptor.Owner.printInformation()
    print "Accessing Descriptor.Owner.SIDString: %s" % retVals.Descriptor.Owner.SIDString
except Exception, e:
    logging.error(str(e))
finally:
    iWbemServices.RemRelease()
    dcom.disconnect()

It would be great if you can check with other source (e.g. Powershell) if the data received is right.

MrAnde7son commented 6 years ago

Awesome @asolino, Thanks!

It would be great if you can check with other source (e.g. Powershell) if the data received is right.

Confirmed with Powershell, works like charm.

I will try to implement the support for sending objects.

asolino commented 6 years ago

Updated example code, showing how to get into the CIM_ARRAY_OBJECTs:

import socket
import logging
from impacket.dcerpc.v5.dcomrt import DCOMConnection, NULL
from impacket.dcerpc.v5.dcom import wmi
from impacket.examples import logger

address = '10.1.1.1'
username = 'Administrator'
password = 'Password'
domain = 'contoso.com'
lmhash = ''
nthash = ''
aesKey = None
namespace = '//./root/cimv2'

HKEY_LOCAL_MACHINE = 2147483650

logger.init()
logging.getLogger().setLevel(logging.DEBUG)
try:
    dcom = DCOMConnection(address, username, password, domain, lmhash, nthash, aesKey, oxidResolver=False, doKerberos=False)
    iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
    iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
    iWbemServices = iWbemLevel1Login.NTLMLogin(namespace, NULL, NULL)
except socket.error, e:
    logging.error("Couldn't connect %s. Error: %s" % (address, str(e)))
    exit(0)

try:
    classObject, _ = iWbemServices.GetObject('StdRegProv')
    retVals = classObject.GetSecurityDescriptor(HKEY_LOCAL_MACHINE, 'SOFTWARE\x00')
    print "ReturnValue: %d" % retVals.ReturnValue
    print "Descriptor Info:"
    retVals.Descriptor.printInformation()
    print "Group Info:"
    retVals.Descriptor.Group.printInformation()
    print "Owner Info:"
    retVals.Descriptor.Owner.printInformation()
    print "Accessing Descriptor.Owner.SIDString: %s" % retVals.Descriptor.Owner.SIDString
    print "DACLS: "
    for dacl in retVals.Descriptor.DACL:
        dacl.printInformation()
        dacl.Trustee.printInformation()

except Exception, e:
    logging.error(str(e))
finally:
    iWbemServices.RemRelease()
    dcom.disconnect()
MrAnde7son commented 6 years ago

Added the same thing on my side and was going to commit :) Now I'm trying to figure out how to send objects back so I can use SetSecurityDescriptor function.

MrAnde7son commented 6 years ago

Hi @asolino , When trying to use SetSecurityDescriptor I get E_FAIL error indicating an "Unspecified error". As 'Descriptor' parameter of the function, I'm using the one I received calling GetSecurityDescriptor, and I verified that the type is right (__SecurityDescriptor). tested the same with PowerShell, and it works. Any idea what to look for? The returned error does not suggest what's wrong, and I didn't find any good reference of using this function.

asolino commented 6 years ago

Hey @MrAnde7son

Yup.. you get E_FAIL because, when the arguments of the function your are executed are marshaled, CIM_TYPE_OBJECT are not processed :).

This is similar to what happened when receiving objects (the unmarshal part), but, in the opposite way.

asolino commented 6 years ago

Btw.. WMI involves a lot of meta-programming. It might be good for you to understand first that for every CIM object received, a class is created on the fly with its methods and properties.

Then, when you call any WMI method (for example GetSecurityDescriptor) this method is called that marshals all the parameters and finally calls WMI IWbemServices::ExecMethod. How to marshal/unmarshal is detailed in [MS-WMIO] and [MS-WMI].

MrAnde7son commented 6 years ago

will do, thanks!

MrAnde7son commented 6 years ago

Hey @asolino , I had some free time, so I decided to get back and make the SetSecurityDescriptor function to work. According to [MS_WMIO], I understand that in order to process CIM_TYPE_OBJECT parameter, I should use ENCODED_UNIT structure. I tried to follow the same concept of your implementation for CIM_TYPE_STRING\CIM_TYPE_DATETIME\CIM_TYPE_REFERENCE parameters, and added the following code at line:

elif pType == CIM_TYPE_ENUM.CIM_TYPE_OBJECT.value:
    # For now we just pack None
    #
    #valueTable += '\x00'*4
    valueTable += pack('<L', curHeapPtr)
    instanceHeap += str(inArg.encodingUnit)
    curHeapPtr = len(instanceHeap)

However, I still get the same E_FAIL error. Isn't ENCODED_UNIT is the correct form for marshaled CIM_TYPE_OBJECT parameters? Your help will be much appreciated!