Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# SECUREAUTH LABS. Copyright 2020 SecureAuth Corporation. All rights reserved. 

2# 

3# This software is provided under under a slightly modified version 

4# of the Apache Software License. See the accompanying LICENSE file 

5# for more information. 

6# 

7# Authors: 

8# Arseniy Sharoglazov <mohemiv@gmail.com> / Positive Technologies (https://www.ptsecurity.com/) 

9# Based on @agsolino and @_dirkjan code 

10# 

11 

12from struct import unpack 

13 

14from impacket import LOG 

15from impacket.examples.ntlmrelayx.clients import ProtocolClient 

16from impacket.nt_errors import STATUS_SUCCESS, STATUS_ACCESS_DENIED 

17from impacket.ntlm import NTLMAuthChallenge 

18from impacket.spnego import SPNEGO_NegTokenResp 

19 

20from impacket.dcerpc.v5 import transport, rpcrt, epm, tsch 

21from impacket.dcerpc.v5.ndr import NDRCALL 

22from impacket.dcerpc.v5.rpcrt import DCERPC_v5, MSRPCBind, CtxItem, MSRPCHeader, SEC_TRAILER, MSRPCBindAck, \ 

23 MSRPCRespHeader, MSRPCBindNak, DCERPCException, RPC_C_AUTHN_WINNT, RPC_C_AUTHN_LEVEL_CONNECT, \ 

24 rpc_status_codes, rpc_provider_reason 

25 

26PROTOCOL_CLIENT_CLASS = "RPCRelayClient" 

27 

28class RPCRelayClientException(Exception): 

29 pass 

30 

31class MYDCERPC_v5(DCERPC_v5): 

32 def __init__(self, transport): 

33 DCERPC_v5.__init__(self, transport) 

34 

35 def sendBindType1(self, iface_uuid, auth_data): 

36 bind = MSRPCBind() 

37 

38 item = CtxItem() 

39 item['AbstractSyntax'] = iface_uuid 

40 item['TransferSyntax'] = self.transfer_syntax 

41 item['ContextID'] = 0 

42 item['TransItems'] = 1 

43 bind.addCtxItem(item) 

44 

45 packet = MSRPCHeader() 

46 packet['type'] = rpcrt.MSRPC_BIND 

47 packet['pduData'] = bind.getData() 

48 packet['call_id'] = 0 

49 

50 sec_trailer = SEC_TRAILER() 

51 sec_trailer['auth_type'] = RPC_C_AUTHN_WINNT 

52 sec_trailer['auth_level'] = RPC_C_AUTHN_LEVEL_CONNECT 

53 sec_trailer['auth_ctx_id'] = 79231 

54 

55 pad = (4 - (len(packet.get_packet()) % 4)) % 4 

56 if pad != 0: 

57 packet['pduData'] += b'\xFF' * pad 

58 sec_trailer['auth_pad_len'] = pad 

59 

60 packet['sec_trailer'] = sec_trailer 

61 packet['auth_data'] = auth_data 

62 

63 self._transport.send(packet.get_packet()) 

64 

65 s = self._transport.recv() 

66 

67 if s != 0: 

68 resp = MSRPCHeader(s) 

69 else: 

70 return 0 #mmm why not None? 

71 

72 if resp['type'] == rpcrt.MSRPC_BINDACK or resp['type'] == rpcrt.MSRPC_ALTERCTX_R: 

73 bindResp = MSRPCBindAck(resp.getData()) 

74 elif resp['type'] == rpcrt.MSRPC_BINDNAK or resp['type'] == rpcrt.MSRPC_FAULT: 

75 if resp['type'] == rpcrt.MSRPC_FAULT: 

76 resp = MSRPCRespHeader(resp.getData()) 

77 status_code = unpack('<L', resp['pduData'][:4])[0] 

78 else: 

79 resp = MSRPCBindNak(resp['pduData']) 

80 status_code = resp['RejectedReason'] 

81 if status_code in rpc_status_codes: 

82 raise DCERPCException(error_code = status_code) 

83 elif status_code in rpc_provider_reason: 

84 raise DCERPCException("Bind context rejected: %s" % rpc_provider_reason[status_code]) 

85 else: 

86 raise DCERPCException('Unknown DCE RPC fault status code: %.8x' % status_code) 

87 else: 

88 raise DCERPCException('Unknown DCE RPC packet type received: %d' % resp['type']) 

89 

90 self.set_max_tfrag(bindResp['max_rfrag']) 

91 

92 return bindResp 

93 

94 def sendBindType3(self, auth_data): 

95 sec_trailer = SEC_TRAILER() 

96 sec_trailer['auth_type'] = RPC_C_AUTHN_WINNT 

97 sec_trailer['auth_level'] = RPC_C_AUTHN_LEVEL_CONNECT 

98 sec_trailer['auth_ctx_id'] = 79231 

99 

100 auth3 = MSRPCHeader() 

101 auth3['type'] = rpcrt.MSRPC_AUTH3 

102 

103 # pad (4 bytes): Can be set to any arbitrary value when set and MUST be 

104 # ignored on receipt. The pad field MUST be immediately followed by a 

105 # sec_trailer structure whose layout, location, and alignment are as 

106 # specified in section 2.2.2.11 

107 auth3['pduData'] = b' ' 

108 auth3['sec_trailer'] = sec_trailer 

109 auth3['auth_data'] = auth_data 

110 auth3['call_id'] = 0 

111 

112 self._transport.send(auth3.get_packet(), forceWriteAndx = 1) 

113 

114class DummyOp(NDRCALL): 

115 opnum = 255 

116 structure = ( 

117 ) 

118 

119class RPCRelayClient(ProtocolClient): 

120 PLUGIN_NAME = "RPC" 

121 

122 def __init__(self, serverConfig, target, targetPort=None, extendedSecurity=True): 

123 ProtocolClient.__init__(self, serverConfig, target, targetPort, extendedSecurity) 

124 

125 # TODO: support relaying RPC to different endpoints (e.g. DCOM, SpoolSS) 

126 # TODO: create a single LOG interface for ntlmrelayx to provide a user info which message/error to which thread belongs 

127 self.endpoint = serverConfig.rpc_mode 

128 

129 if self.endpoint == "TSCH": 

130 self.endpoint_uuid = tsch.MSRPC_UUID_TSCHS 

131 else: 

132 raise NotImplementedError("Not implemented!") 

133 

134 if self.serverConfig.rpc_use_smb: 

135 if self.endpoint == "TSCH": 

136 self.stringbinding = "ncacn_np:%s[\\pipe\\atsvc]" % target.netloc 

137 else: 

138 raise NotImplementedError("Not implemented!") 

139 else: 

140 LOG.debug("Connecting to ncacn_ip_tcp:%s[135] to determine %s stringbinding" % (target.netloc, self.endpoint)) 

141 self.stringbinding = epm.hept_map(target.netloc, self.endpoint_uuid, protocol='ncacn_ip_tcp') 

142 

143 LOG.debug("%s stringbinding is %s" % (self.endpoint, self.stringbinding)) 

144 

145 def initConnection(self): 

146 rpctransport = transport.DCERPCTransportFactory(self.stringbinding) 

147 

148 if self.serverConfig.rpc_use_smb: 

149 LOG.info("Authenticating to smb://%s:%d with creds provided in cmdline" % (self.target.netloc, self.serverConfig.rpc_smb_port)) 

150 rpctransport.set_credentials(self.serverConfig.smbuser, self.serverConfig.smbpass, self.serverConfig.smbdomain, \ 

151 self.serverConfig.smblmhash, self.serverConfig.smbnthash) 

152 rpctransport.set_dport(self.serverConfig.rpc_smb_port) 

153 

154 self.session = MYDCERPC_v5(rpctransport) 

155 self.session.set_auth_level(RPC_C_AUTHN_LEVEL_CONNECT) 

156 self.session.connect() 

157 

158 if self.serverConfig.rpc_use_smb: 

159 LOG.info("Authentication to smb://%s:%d succeeded" % (self.target.netloc, self.serverConfig.rpc_smb_port)) 

160 

161 return True 

162 

163 def sendNegotiate(self, auth_data): 

164 bindResp = self.session.sendBindType1(self.endpoint_uuid, auth_data) 

165 

166 challenge = NTLMAuthChallenge() 

167 challenge.fromString(bindResp['auth_data']) 

168 

169 return challenge 

170 

171 def sendAuth(self, authenticateMessageBlob, serverChallenge=None): 

172 if unpack('B', authenticateMessageBlob[:1])[0] == SPNEGO_NegTokenResp.SPNEGO_NEG_TOKEN_RESP: 

173 respToken2 = SPNEGO_NegTokenResp(authenticateMessageBlob) 

174 auth_data = respToken2['ResponseToken'] 

175 else: 

176 auth_data = authenticateMessageBlob 

177 

178 self.session.sendBindType3(auth_data) 

179 

180 try: 

181 req = DummyOp() 

182 self.session.request(req) 

183 except DCERPCException as e: 

184 if 'nca_s_op_rng_error' in str(e) or 'RPC_E_INVALID_HEADER' in str(e): 

185 return None, STATUS_SUCCESS 

186 elif 'rpc_s_access_denied' in str(e): 

187 return None, STATUS_ACCESS_DENIED 

188 else: 

189 LOG.info("Unexpected rpc code received from %s: %s" % (self.stringbinding, str(e))) 

190 return None, STATUS_ACCESS_DENIED 

191 

192 def killConnection(self): 

193 if self.session is not None: 

194 self.session.get_rpc_transport().disconnect() 

195 self.session = None 

196 

197 def keepAlive(self): 

198 try: 

199 req = DummyOp() 

200 self.session.request(req) 

201 except DCERPCException as e: 

202 if 'nca_s_op_rng_error' not in str(e) or 'RPC_E_INVALID_HEADER' not in str(e): 

203 raise