Skip to content

Commit bcfb5b5

Browse files
committed
Pass 5: DCE/RPC and DCOM realism
Make the DCE/RPC bind, DCOM activation, and DCOM ref counting look like a real Windows client. - rpcrt bind: emit a second context item carrying the [MS-RPCE] BindTimeFeatureNegotiation abstract syntax (UUID 6cb71c2c-9812-4540-0300-000000000000) alongside the interface ctx. Track the main context index so transfer-syntax negotiation still picks the right one. The bind-ack validation now tolerates a negotiate_ack (Result=3) on the BTFN context and tolerates rejections of subordinate contexts in general. - dcomrt RemAddRef / RemRelease: bump cPublicRefs from 1 to 5 to match ole32's reference batching, removing the single-ref-per-call tell. - dcomrt RemoteCreateInstance ScmRequestInfo: set ClientImpLevel to 2 (RPC_C_IMP_LEVEL_IDENTIFY) instead of leaving it at the default 0 that Impacket previously emitted. - DCOMConnection.CoCreateInstanceEx: issue an IObjectExporter ServerAlive2 preflight against the resolver before invoking the activator, mirroring Windows. Failures are logged and tolerated.
1 parent 875efe1 commit bcfb5b5

2 files changed

Lines changed: 51 additions & 6 deletions

File tree

impacket/dcerpc/v5/dcomrt.py

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,15 @@ def initConnection(self):
11071107
DCOMConnection.PORTMAPS[self.__target] = self.__portmap
11081108

11091109
def CoCreateInstanceEx(self, clsid, iid):
1110+
# Real Windows clients always issue an IObjectExporter::ServerAlive2
1111+
# preflight against the resolver before invoking the activator.
1112+
# Skipping it is one of the more obvious DCOM/WMI fingerprints, so
1113+
# we mirror Windows here. Failures are tolerated; activation will
1114+
# surface its own errors.
1115+
try:
1116+
IObjectExporter(self.__portmap).ServerAlive2()
1117+
except Exception as e:
1118+
LOG.debug('ServerAlive2 preflight failed (continuing): %s' % e)
11101119
scm = IRemoteSCMActivator(self.__portmap)
11111120
iInterface = scm.RemoteCreateInstance(clsid, iid)
11121121
self.initTimer()
@@ -1418,7 +1427,10 @@ def RemAddRef(self):
14181427
request['cInterfaceRefs'] = 1
14191428
element = REMINTERFACEREF()
14201429
element['ipid'] = self.get_iPid()
1421-
element['cPublicRefs'] = 1
1430+
# Windows COM runtime adds references in larger increments to avoid
1431+
# round-tripping per ref. 5 matches the reference batch size used by
1432+
# ole32.dll on current Windows builds.
1433+
element['cPublicRefs'] = 5
14221434
request['InterfaceRefs'].append(element)
14231435
resp = self.request(request, IID_IRemUnknown, self.get_ipidRemUnknown())
14241436
return resp
@@ -1430,7 +1442,8 @@ def RemRelease(self):
14301442
request['cInterfaceRefs'] = 1
14311443
element = REMINTERFACEREF()
14321444
element['ipid'] = self.get_iPid()
1433-
element['cPublicRefs'] = 1
1445+
# Match the AddRef batch so the server's refcount lands at 0.
1446+
element['cPublicRefs'] = 5
14341447
request['InterfaceRefs'].append(element)
14351448
resp = self.request(request, IID_IRemUnknown, self.get_ipidRemUnknown())
14361449
DCOMConnection.delOid(self.get_target(), self.get_oid())
@@ -1705,7 +1718,9 @@ def RemoteGetClassObject(self, clsId, iid):
17051718
# ScmRequestInfo
17061719
scmInfo = ScmRequestInfoData()
17071720
scmInfo['pdwReserved'] = NULL
1708-
#scmInfo['remoteRequest']['ClientImpLevel'] = 2
1721+
# 2 == RPC_C_IMP_LEVEL_IDENTIFY. Real Windows clients always populate
1722+
# this; leaving it at 0 is an Impacket-specific tell.
1723+
scmInfo['remoteRequest']['ClientImpLevel'] = 2
17091724
scmInfo['remoteRequest']['cRequestedProtseqs'] = 1
17101725
scmInfo['remoteRequest']['pRequestedProtseqs'].append(7)
17111726

@@ -1868,7 +1883,9 @@ def RemoteCreateInstance(self, clsId, iid):
18681883
# ScmRequestInfo
18691884
scmInfo = ScmRequestInfoData()
18701885
scmInfo['pdwReserved'] = NULL
1871-
#scmInfo['remoteRequest']['ClientImpLevel'] = 2
1886+
# 2 == RPC_C_IMP_LEVEL_IDENTIFY. Real Windows clients always populate
1887+
# this; leaving it at 0 is an Impacket-specific tell.
1888+
scmInfo['remoteRequest']['ClientImpLevel'] = 2
18721889
scmInfo['remoteRequest']['cRequestedProtseqs'] = 1
18731890
scmInfo['remoteRequest']['pRequestedProtseqs'].append(7)
18741891

impacket/dcerpc/v5/rpcrt.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,15 @@
139139
rpc_cont_def_result = {
140140
0 : 'acceptance',
141141
1 : 'user_rejection',
142-
2 : 'provider_rejection'
142+
2 : 'provider_rejection',
143+
3 : 'negotiate_ack',
143144
}
144145

146+
# [MS-RPCE] 3.3.1.5.3 BindTimeFeatureNegotiation. The abstract syntax UUID
147+
# encodes a feature bitmask in its low bytes. 0x03 = SecurityContextMultiplexing
148+
# | KeepConnectionOnOrphan, which is what current Windows clients advertise.
149+
BIND_TIME_FEATURE_NEGOTIATION_UUID = uuidtup_to_bin(('6cb71c2c-9812-4540-0300-000000000000', '1.0'))
150+
145151
#status codes, references:
146152
#https://docs.microsoft.com/windows/desktop/Rpc/rpc-return-values
147153
#https://msdn.microsoft.com/library/default.asp?url=/library/en-us/randz/protocol/common_return_values.asp
@@ -1549,6 +1555,17 @@ def bind(self, iface_uuid, alter = 0, bogus_binds = 0, transfer_syntax = ('8a885
15491555
item['ContextID'] = ctx
15501556
item['TransItems'] = 1
15511557
bind.addCtxItem(item)
1558+
self._bind_main_ctx = ctx
1559+
1560+
# Bind Time Feature Negotiation. Windows always sends a second
1561+
# context item advertising BTFN; servers that don't understand it
1562+
# respond with negotiate_ack rather than rejecting the bind.
1563+
btfn_item = CtxItem()
1564+
btfn_item['AbstractSyntax'] = BIND_TIME_FEATURE_NEGOTIATION_UUID
1565+
btfn_item['TransferSyntax'] = uuidtup_to_bin(transfer_syntax)
1566+
btfn_item['ContextID'] = ctx + 1
1567+
btfn_item['TransItems'] = 1
1568+
bind.addCtxItem(btfn_item)
15521569

15531570
packet = MSRPCHeader()
15541571
packet['type'] = MSRPC_BIND
@@ -1618,10 +1635,20 @@ def bind(self, iface_uuid, alter = 0, bogus_binds = 0, transfer_syntax = ('8a885
16181635
else:
16191636
raise DCERPCException('Unknown DCE RPC packet type received: %d' % resp['type'])
16201637

1638+
# The 1-based ContextID of the interface we actually care about.
1639+
main_ctx_index = getattr(self, '_bind_main_ctx', bogus_binds) + 1
16211640
# check ack results for each context, except for the bogus ones
16221641
for ctx in range(bogus_binds+1,bindResp['ctx_num']+1):
16231642
ctxItems = bindResp.getCtxItem(ctx)
1643+
# Result 3 = negotiate_ack, sent by servers acknowledging a
1644+
# Bind Time Feature Negotiation context. Don't treat as fatal.
1645+
if ctxItems['Result'] == 3:
1646+
continue
16241647
if ctxItems['Result'] != 0:
1648+
if ctx != main_ctx_index:
1649+
# Subordinate contexts (e.g. extra transfer syntaxes)
1650+
# may legitimately be rejected by the server.
1651+
continue
16251652
msg = "Bind context %d rejected: " % ctx
16261653
msg += rpc_cont_def_result.get(ctxItems['Result'], 'Unknown DCE RPC context result code: %.4x' % ctxItems['Result'])
16271654
msg += "; "
@@ -1632,7 +1659,8 @@ def bind(self, iface_uuid, alter = 0, bogus_binds = 0, transfer_syntax = ('8a885
16321659
raise DCERPCException(msg)
16331660

16341661
# Save the transfer syntax for later use
1635-
self.transfer_syntax = ctxItems['TransferSyntax']
1662+
if ctx == main_ctx_index:
1663+
self.transfer_syntax = ctxItems['TransferSyntax']
16361664

16371665
# The received transmit size becomes the client's receive size, and the received receive size becomes the client's transmit size.
16381666
self.__max_xmit_size = bindResp['max_rfrag']

0 commit comments

Comments
 (0)