# Copyright (c) 2003-2006 CORE Security Technologies # # This software is provided under under a slightly modified version # of the Apache Software License. See the accompanying LICENSE file # for more information. # # $Id: dcerpc.py,v 1.7 2006/05/23 22:26:51 gera Exp $ # import array from binascii import crc32 try: from Crypto.Cipher import ARC4 from Crypto.Hash import MD4 POW = None except Exception: try: import POW except Exception: print "WARNING: Crypto package not found. Some features will fail." from impacket import ntlm from impacket import ImpactPacket from impacket.structure import Structure,pack,unpack # MS/RPC Constants MSRPC_REQUEST = 0x00 MSRPC_RESPONSE = 0x02 MSRPC_FAULT = 0x03 MSRPC_ACK = 0x07 MSRPC_BIND = 0x0B MSRPC_BINDACK = 0x0C MSRPC_BINDNAK = 0x0D MSRPC_ALTERCTX = 0x0E MSRPC_AUTH3 = 0x10 # MS/RPC Packet Flags MSRPC_FIRSTFRAG = 0x01 MSRPC_LASTFRAG = 0x02 MSRPC_NOTAFRAG = 0x04 MSRPC_RECRESPOND= 0x08 MSRPC_NOMULTIPLEX = 0x10 MSRPC_NOTFORIDEMP = 0x20 MSRPC_NOTFORBCAST = 0x40 MSRPC_NOUUID = 0x80 #Reasons for rejection of a context element, included in bind_ack result reason rpc_provider_reason = { 0 : 'reason_not_specified', 1 : 'abstract_syntax_not_supported', 2 : 'proposed_transfer_syntaxes_not_supported', 3 : 'local_limit_exceeded' } MSRPC_CONT_RESULT_ACCEPT = 0 MSRPC_CONT_RESULT_USER_REJECT = 1 MSRPC_CONT_RESULT_PROV_REJECT = 2 #Results of a presentation context negotiation rpc_cont_def_result = { 0 : 'acceptance', 1 : 'user_rejection', 2 : 'provider_rejection' } #status codes, references: #http://msdn.microsoft.com/library/default.asp?url=/library/en-us/rpc/rpc/rpc_return_values.asp #http://msdn.microsoft.com/library/default.asp?url=/library/en-us/randz/protocol/common_return_values.asp #winerror.h #http://www.opengroup.org/onlinepubs/9629399/apdxn.htm rpc_status_codes = { 0x00000005L : 'rpc_s_access_denied', 0x00000008L : 'Authentication type not recognized', 0x000006C6L : 'rpc_x_invalid_bound', # the arrays bound are invalid 0x000006F7L : 'rpc_x_bad_stub_data', # the stub data is invalid, doesn't match with the IDL definition 0x1C010001L : 'nca_s_comm_failure', # unable to get response from server: 0x1C010002L : 'nca_s_op_rng_error', # bad operation number in call 0x1C010003L : 'nca_s_unk_if', # unknown interface 0x1C010006L : 'nca_s_wrong_boot_time', # client passed server wrong server boot time 0x1C010009L : 'nca_s_you_crashed', # a restarted server called back a client 0x1C01000BL : 'nca_s_proto_error', # someone messed up the protocol 0x1C010013L : 'nca_s_out_args_too_big ', # output args too big 0x1C010014L : 'nca_s_server_too_busy', # server is too busy to handle call 0x1C010015L : 'nca_s_fault_string_too_long', # string argument longer than declared max len 0x1C010017L : 'nca_s_unsupported_type ', # no implementation of generic operation for object 0x1C000001L : 'nca_s_fault_int_div_by_zero', 0x1C000002L : 'nca_s_fault_addr_error ', 0x1C000003L : 'nca_s_fault_fp_div_zero', 0x1C000004L : 'nca_s_fault_fp_underflow', 0x1C000005L : 'nca_s_fault_fp_overflow', 0x1C000006L : 'nca_s_fault_invalid_tag', 0x1C000007L : 'nca_s_fault_invalid_bound ', 0x1C000008L : 'nca_s_rpc_version_mismatch', 0x1C000009L : 'nca_s_unspec_reject ', 0x1C00000AL : 'nca_s_bad_actid', 0x1C00000BL : 'nca_s_who_are_you_failed', 0x1C00000CL : 'nca_s_manager_not_entered ', 0x1C00000DL : 'nca_s_fault_cancel', 0x1C00000EL : 'nca_s_fault_ill_inst', 0x1C00000FL : 'nca_s_fault_fp_error', 0x1C000010L : 'nca_s_fault_int_overflow', 0x1C000012L : 'nca_s_fault_unspec', 0x1C000013L : 'nca_s_fault_remote_comm_failure ', 0x1C000014L : 'nca_s_fault_pipe_empty ', 0x1C000015L : 'nca_s_fault_pipe_closed', 0x1C000016L : 'nca_s_fault_pipe_order ', 0x1C000017L : 'nca_s_fault_pipe_discipline', 0x1C000018L : 'nca_s_fault_pipe_comm_error', 0x1C000019L : 'nca_s_fault_pipe_memory', 0x1C00001AL : 'nca_s_fault_context_mismatch ', 0x1C00001BL : 'nca_s_fault_remote_no_memory ', 0x1C00001CL : 'nca_s_invalid_pres_context_id', 0x1C00001DL : 'nca_s_unsupported_authn_level', 0x1C00001FL : 'nca_s_invalid_checksum ', 0x1C000020L : 'nca_s_invalid_crc', 0x1C000021L : 'nca_s_fault_user_defined', 0x1C000022L : 'nca_s_fault_tx_open_failed', 0x1C000023L : 'nca_s_fault_codeset_conv_error', 0x1C000024L : 'nca_s_fault_object_not_found ', 0x1C000025L : 'nca_s_fault_no_client_stub' } class MSRPCArray: def __init__(self, id=0, len=0, size=0): self._length = len self._size = size self._id = id self._max_len = 0 self._offset = 0 self._length2 = 0 self._name = '' def set_max_len(self, n): self._max_len = n def set_offset(self, n): self._offset = n def set_length2(self, n): self._length2 = n def get_size(self): return self._size def set_name(self, n): self._name = n def get_name(self): return self._name def get_id(self): return self._id def rawData(self): return pack(' 0 return self._SIZE + var_size def contains(self, aHeader): ImpactPacket.Header.contains(self, aHeader) if self.child(): contents_size = self.child().get_size() self.set_op_num(self.child().OP_NUM) self.set_frag_len(self.get_header_size() + contents_size) self.set_alloc_hint(contents_size) class MSRPCBindNak(ImpactPacket.Header): _SIZE = 24 def __init__(self, aBuffer = None): ImpactPacket.Header.__init__(self, self._SIZE) self.set_type(MSRPC_BINDNAK) if aBuffer: self.load_header(aBuffer) def get_version(self): """ This method returns a tuple in (major, minor) form.""" return (self.get_byte(0), self.get_byte(1)) def set_version(self, version): """ This method takes a tuple in (major, minor) form.""" self.set_byte(0, version[0]) self.set_byte(1, version[1]) def get_type(self): return self.get_byte(2) def set_type(self, type): self.set_byte(2, type) def get_flags(self): return self.get_byte(3) def set_flags(self, flags): self.set_byte(3, flags) def get_representation(self): return self.get_long(4, '<') def set_representation(self, representation): self.set_long(4, representation, '<') def get_frag_len(self): return self.get_word(8, '<') def set_frag_len(self, len): self.set_word(8, len, '<') def get_auth_len(self): return self.get_word(10, '<') def set_auth_len(self, len): self.set_word(10, len, '<') def get_call_id(self): return self.get_long(12, '<') def set_call_id(self, id): self.set_long(12, id, '<') def get_reason(self): return self.get_word(16, '<') def set_reason(self, reason): self.set_word(16, reason, '<') def get_assoc_group(self): return self.get_long(20, '<') def set_assoc_group(self, id): self.set_long(20, id, '<') def get_header_size(self): return self._SIZE class DCERPC: _max_ctx = 0 def __init__(self,transport): self._transport = transport self.set_ctx_id(0) self._max_frag = None self.set_default_max_fragment_size() def set_ctx_id(self, ctx_id): self._ctx = ctx_id def connect(self): return self._transport.connect() def disconnect(self): return self._transport.disconnect() def set_max_fragment_size(self, fragment_size): # -1 is default fragment size: 0 for v5, 1300 y pico for v4 # 0 is don't fragment # other values are max fragment size if fragment_size == -1: self.set_default_max_fragment_size() else: self._max_frag = fragment_size def set_default_max_fragment_size(self): # default is 0: don'fragment. v4 will override this method self._max_frag = 0 def send(self, data): raise RuntimeError, 'virtual method. Not implemented in subclass' def recv(self): raise RuntimeError, 'virtual method. Not implemented in subclass' def alter_ctx(self, newUID, bogus_binds = ''): raise RuntimeError, 'virtual method. Not implemented in subclass' def set_credentials(self, username, password): pass def set_auth_level(self, auth_level): pass def get_idempotent(self): return 0 def set_idempotent(self, flag): pass def call(self, function, body): return self.send(DCERPC_RawCall(function, str(body))) class DCERPC_v5(DCERPC): endianness = '<' def __init__(self, transport): DCERPC.__init__(self, transport) self.__auth_level = ntlm.NTLM_AUTH_NONE self.__username = None self.__password = None def set_auth_level(self, auth_level): # auth level is ntlm.NTLM_AUTH_* self.__auth_level = auth_level def set_credentials(self, username, password): self.set_auth_level(ntlm.NTLM_AUTH_CONNECT) # self.set_auth_level(ntlm.NTLM_AUTH_CALL) # self.set_auth_level(ntlm.NTLM_AUTH_PKT_INTEGRITY) # self.set_auth_level(ntlm.NTLM_AUTH_PKT_PRIVACY) self.__username = username self.__password = password def bind(self, uuid, alter = 0, bogus_binds = 0): bind = MSRPCBind(endianness = self.endianness) syntax = '\x04\x5d\x88\x8a\xeb\x1c\xc9\x11\x9f\xe8\x08\x00\x2b\x10\x48\x60' if self.endianness == '>': syntax = unpack('LHHBB6s', *syntax) uuid = list(unpack('LHHBB6sHH', *uuid) ctx = 0 for i in range(bogus_binds): bind.set_ctx_id(self._ctx, index = ctx) bind.set_trans_num(1, index = ctx) bind.set_if_binuuid('A'*20, index = ctx) bind.set_xfer_syntax_binuuid(syntax, index = ctx) bind.set_xfer_syntax_ver(2, index = ctx) self._ctx += 1 ctx += 1 bind.set_ctx_id(self._ctx, index = ctx) bind.set_trans_num(1, index = ctx) bind.set_if_binuuid(uuid,index = ctx) bind.set_xfer_syntax_binuuid(syntax, index = ctx) bind.set_xfer_syntax_ver(2, index = ctx) bind.set_ctx_num(ctx+1) if alter: bind.set_type(MSRPC_ALTERCTX) if (self.__auth_level != ntlm.NTLM_AUTH_NONE): if (self.__username is None) or (self.__password is None): self.__username, self.__password, nth, lmh = self._transport.get_credentials() auth = ntlm.NTLMAuthNegotiate() auth['auth_level'] = self.__auth_level auth['auth_ctx_id'] = self._ctx + 79231 bind.set_auth_data(str(auth)) self._transport.send(bind.get_packet()) s = self._transport.recv() if s != 0: resp = MSRPCBindAck(s) else: return 0 #mmm why not None? if resp.get_type() == MSRPC_BINDNAK: resp = MSRPCBindNak(s) status_code = resp.get_reason() if rpc_status_codes.has_key(status_code): raise Exception(rpc_status_codes[status_code], resp) else: raise Exception('Unknown DCE RPC fault status code: %.8x' % status_code, resp) self.__max_xmit_size = resp.get_max_tfrag() if self.__auth_level != ntlm.NTLM_AUTH_NONE: authResp = ntlm.NTLMAuthChallenge(data = resp.get_auth_data().tostring()) self._ntlm_challenge = authResp['challenge'] response = ntlm.NTLMAuthChallengeResponse(self.__username,self.__password, self._ntlm_challenge) response['auth_ctx_id'] = self._ctx + 79231 response['auth_level'] = self.__auth_level if self.__auth_level in (ntlm.NTLM_AUTH_CONNECT, ntlm.NTLM_AUTH_PKT_INTEGRITY, ntlm.NTLM_AUTH_PKT_PRIVACY): if self.__password: key = ntlm.compute_nthash(self.__password) if POW: hash = POW.Digest(POW.MD4_DIGEST) else: hash = MD4.new() hash.update(key) key = hash.digest() else: key = '\x00'*16 if POW: cipher = POW.Symmetric(POW.RC4) cipher.encryptInit(key) self.cipher_encrypt = cipher.update else: cipher = ARC4.new(key) self.cipher_encrypt = cipher.encrypt if response['flags'] & ntlm.NTLMSSP_KEY_EXCHANGE: session_key = 'A'*16 # XXX Generate random session key response['session_key'] = self.cipher_encrypt(session_key) if POW: cipher = POW.Symmetric(POW.RC4) cipher.encryptInit(session_key) self.cipher_encrypt = cipher.update else: cipher = ARC4.new(session_key) self.cipher_encrypt = cipher.encrypt self.sequence = 0 auth3 = MSRPCHeader() auth3.set_type(MSRPC_AUTH3) auth3.set_auth_data(str(response)) self._transport.send(auth3.get_packet(), forceWriteAndx = 1) return resp # means packet is signed, if verifier is wrong it fails def _transport_send(self, rpc_packet, forceWriteAndx = 0, forceRecv = 0): if self.__auth_level == ntlm.NTLM_AUTH_CALL: if rpc_packet.get_type() == MSRPC_REQUEST: response = ntlm.NTLMAuthChallengeResponse(self.__username,self.__password, self._ntlm_challenge) response['auth_ctx_id'] = self._ctx + 79231 response['auth_level'] = self.__auth_level rpc_packet.set_auth_data(str(response)) if self.__auth_level in [ntlm.NTLM_AUTH_PKT_INTEGRITY, ntlm.NTLM_AUTH_PKT_PRIVACY]: verifier = ntlm.NTLMAuthVerifier() verifier['auth_level'] = self.__auth_level verifier['auth_ctx_id'] = self._ctx + 79231 verifier['data'] = ' '*12 rpc_packet.set_auth_data(str(verifier)) rpc_call = rpc_packet.child() if self.__auth_level == ntlm.NTLM_AUTH_PKT_PRIVACY: data = DCERPC_RawCall(rpc_call.OP_NUM) data.setData(self.cipher_encrypt(rpc_call.get_packet())) rpc_packet.contains(data) crc = crc32(rpc_call.get_packet()) data = pack(' self.__max_xmit_size - 32: max_frag = self.__max_xmit_size - 32 # XXX: 32 is a safe margin for auth data if self._max_frag: max_frag = min(max_frag, self._max_frag) if max_frag: packet = str(data.get_bytes().tostring()) offset = 0 rawcall = DCERPC_RawCall(data.OP_NUM) while 1: toSend = packet[offset:offset+max_frag] if not toSend: break flags = 0 if offset == 0: flags |= MSRPC_FIRSTFRAG offset += len(toSend) if offset == len(packet): flags |= MSRPC_LASTFRAG rpc.set_flags(flags) rawcall.setData(toSend) rpc.contains(rawcall) self._transport_send(rpc, forceWriteAndx = 1, forceRecv = flags & MSRPC_LASTFRAG) else: rpc.contains(data) self._transport_send(rpc) def recv(self): self.response_data = self._transport.recv() self.response_header = MSRPCRespHeader(self.response_data) off = self.response_header.get_header_size() if self.response_header.get_type() == MSRPC_FAULT and self.response_header.get_frag_len() >= off+4: status_code = unpack("