mirror of
https://github.com/lgandx/Responder.git
synced 2024-10-18 05:00:39 -07:00
593 lines
20 KiB
Python
593 lines
20 KiB
Python
# SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
|
|
#
|
|
# This software is provided under under a slightly modified version
|
|
# of the Apache Software License. See the accompanying LICENSE file
|
|
# for more information.
|
|
#
|
|
# Author: Alberto Solino (@agsolino)
|
|
#
|
|
# Description:
|
|
# Transport implementations for the DCE/RPC protocol.
|
|
#
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import binascii
|
|
import os
|
|
import re
|
|
import socket
|
|
|
|
try:
|
|
from urllib.parse import urlparse, urlunparse
|
|
except ImportError:
|
|
from urlparse import urlparse, urlunparse
|
|
|
|
from impacket import ntlm
|
|
from impacket.dcerpc.v5.rpcrt import DCERPCException, DCERPC_v5, DCERPC_v4
|
|
from impacket.dcerpc.v5.rpch import RPCProxyClient, RPCProxyClientException, RPC_OVER_HTTP_v1, RPC_OVER_HTTP_v2
|
|
from impacket.smbconnection import SMBConnection
|
|
|
|
class DCERPCStringBinding:
|
|
parser = re.compile(r'(?:([a-fA-F0-9-]{8}(?:-[a-fA-F0-9-]{4}){3}-[a-fA-F0-9-]{12})@)?' # UUID (opt.)
|
|
+'([_a-zA-Z0-9]*):' # Protocol Sequence
|
|
+'([^\[]*)' # Network Address (opt.)
|
|
+'(?:\[([^\]]*)\])?') # Endpoint and options (opt.)
|
|
|
|
def __init__(self, stringbinding):
|
|
match = DCERPCStringBinding.parser.match(stringbinding)
|
|
self.__uuid = match.group(1)
|
|
self.__ps = match.group(2)
|
|
self.__na = match.group(3)
|
|
options = match.group(4)
|
|
if options:
|
|
options = options.split(',')
|
|
|
|
self.__endpoint = options[0]
|
|
try:
|
|
self.__endpoint.index('endpoint=')
|
|
self.__endpoint = self.__endpoint[len('endpoint='):]
|
|
except:
|
|
pass
|
|
|
|
self.__options = {}
|
|
for option in options[1:]:
|
|
vv = option.split('=', 1)
|
|
self.__options[vv[0]] = vv[1] if len(vv) > 1 else ''
|
|
else:
|
|
self.__endpoint = ''
|
|
self.__options = {}
|
|
|
|
def get_uuid(self):
|
|
return self.__uuid
|
|
|
|
def get_protocol_sequence(self):
|
|
return self.__ps
|
|
|
|
def get_network_address(self):
|
|
return self.__na
|
|
|
|
def set_network_address(self, addr):
|
|
self.__na = addr
|
|
|
|
def get_endpoint(self):
|
|
return self.__endpoint
|
|
|
|
def get_options(self):
|
|
return self.__options
|
|
|
|
def get_option(self, option_name):
|
|
return self.__options[option_name]
|
|
|
|
def is_option_set(self, option_name):
|
|
return option_name in self.__options
|
|
|
|
def unset_option(self, option_name):
|
|
del self.__options[option_name]
|
|
|
|
def __str__(self):
|
|
return DCERPCStringBindingCompose(self.__uuid, self.__ps, self.__na, self.__endpoint, self.__options)
|
|
|
|
def DCERPCStringBindingCompose(uuid=None, protocol_sequence='', network_address='', endpoint='', options={}):
|
|
s = ''
|
|
if uuid:
|
|
s += uuid + '@'
|
|
s += protocol_sequence + ':'
|
|
if network_address:
|
|
s += network_address
|
|
if endpoint or options:
|
|
s += '[' + endpoint
|
|
if options:
|
|
s += ',' + ','.join([key if str(val) == '' else "=".join([key, str(val)]) for key, val in options.items()])
|
|
s += ']'
|
|
|
|
return s
|
|
|
|
def DCERPCTransportFactory(stringbinding):
|
|
sb = DCERPCStringBinding(stringbinding)
|
|
|
|
na = sb.get_network_address()
|
|
ps = sb.get_protocol_sequence()
|
|
if 'ncadg_ip_udp' == ps:
|
|
port = sb.get_endpoint()
|
|
if port:
|
|
rpctransport = UDPTransport(na, int(port))
|
|
else:
|
|
rpctransport = UDPTransport(na)
|
|
elif 'ncacn_ip_tcp' == ps:
|
|
port = sb.get_endpoint()
|
|
if port:
|
|
rpctransport = TCPTransport(na, int(port))
|
|
else:
|
|
rpctransport = TCPTransport(na)
|
|
elif 'ncacn_http' == ps:
|
|
port = sb.get_endpoint()
|
|
if port:
|
|
rpctransport = HTTPTransport(na, int(port))
|
|
else:
|
|
rpctransport = HTTPTransport(na)
|
|
elif 'ncacn_np' == ps:
|
|
named_pipe = sb.get_endpoint()
|
|
if named_pipe:
|
|
named_pipe = named_pipe[len(r'\pipe'):]
|
|
rpctransport = SMBTransport(na, filename = named_pipe)
|
|
else:
|
|
rpctransport = SMBTransport(na)
|
|
elif 'ncalocal' == ps:
|
|
named_pipe = sb.get_endpoint()
|
|
rpctransport = LOCALTransport(filename = named_pipe)
|
|
else:
|
|
raise DCERPCException("Unknown protocol sequence.")
|
|
|
|
rpctransport.set_stringbinding(sb)
|
|
return rpctransport
|
|
|
|
class DCERPCTransport:
|
|
|
|
DCERPC_class = DCERPC_v5
|
|
|
|
def __init__(self, remoteName, dstport):
|
|
self.__remoteName = remoteName
|
|
self.__remoteHost = remoteName
|
|
self.__dstport = dstport
|
|
self._stringbinding = None
|
|
self._max_send_frag = None
|
|
self._max_recv_frag = None
|
|
self._domain = ''
|
|
self._lmhash = ''
|
|
self._nthash = ''
|
|
self.__connect_timeout = None
|
|
self._doKerberos = False
|
|
self._username = ''
|
|
self._password = ''
|
|
self._domain = ''
|
|
self._aesKey = None
|
|
self._TGT = None
|
|
self._TGS = None
|
|
self._kdcHost = None
|
|
self.set_credentials('','')
|
|
# Strict host validation - off by default and currently only for
|
|
# SMBTransport
|
|
self._strict_hostname_validation = False
|
|
self._validation_allow_absent = True
|
|
self._accepted_hostname = ''
|
|
|
|
def connect(self):
|
|
raise RuntimeError('virtual function')
|
|
def send(self,data=0, forceWriteAndx = 0, forceRecv = 0):
|
|
raise RuntimeError('virtual function')
|
|
def recv(self, forceRecv = 0, count = 0):
|
|
raise RuntimeError('virtual function')
|
|
def disconnect(self):
|
|
raise RuntimeError('virtual function')
|
|
def get_socket(self):
|
|
raise RuntimeError('virtual function')
|
|
|
|
def get_connect_timeout(self):
|
|
return self.__connect_timeout
|
|
def set_connect_timeout(self, timeout):
|
|
self.__connect_timeout = timeout
|
|
|
|
def getRemoteName(self):
|
|
return self.__remoteName
|
|
|
|
def setRemoteName(self, remoteName):
|
|
"""This method only makes sense before connection for most protocols."""
|
|
self.__remoteName = remoteName
|
|
|
|
def getRemoteHost(self):
|
|
return self.__remoteHost
|
|
|
|
def setRemoteHost(self, remoteHost):
|
|
"""This method only makes sense before connection for most protocols."""
|
|
self.__remoteHost = remoteHost
|
|
|
|
def get_dport(self):
|
|
return self.__dstport
|
|
def set_dport(self, dport):
|
|
"""This method only makes sense before connection for most protocols."""
|
|
self.__dstport = dport
|
|
|
|
def get_stringbinding(self):
|
|
return self._stringbinding
|
|
|
|
def set_stringbinding(self, stringbinding):
|
|
self._stringbinding = stringbinding
|
|
|
|
def get_addr(self):
|
|
return self.getRemoteHost(), self.get_dport()
|
|
def set_addr(self, addr):
|
|
"""This method only makes sense before connection for most protocols."""
|
|
self.setRemoteHost(addr[0])
|
|
self.set_dport(addr[1])
|
|
|
|
def set_kerberos(self, flag, kdcHost = None):
|
|
self._doKerberos = flag
|
|
self._kdcHost = kdcHost
|
|
|
|
def get_kerberos(self):
|
|
return self._doKerberos
|
|
|
|
def get_kdcHost(self):
|
|
return self._kdcHost
|
|
|
|
def set_max_fragment_size(self, send_fragment_size):
|
|
# -1 is default fragment size: 0 (don't fragment)
|
|
# 0 is don't fragment
|
|
# other values are max fragment size
|
|
if send_fragment_size == -1:
|
|
self.set_default_max_fragment_size()
|
|
else:
|
|
self._max_send_frag = send_fragment_size
|
|
|
|
def set_hostname_validation(self, validate, accept_empty, hostname):
|
|
self._strict_hostname_validation = validate
|
|
self._validation_allow_absent = accept_empty
|
|
self._accepted_hostname = hostname
|
|
|
|
def set_default_max_fragment_size(self):
|
|
# default is 0: don't fragment.
|
|
# subclasses may override this method
|
|
self._max_send_frag = 0
|
|
|
|
def get_credentials(self):
|
|
return (
|
|
self._username,
|
|
self._password,
|
|
self._domain,
|
|
self._lmhash,
|
|
self._nthash,
|
|
self._aesKey,
|
|
self._TGT,
|
|
self._TGS)
|
|
|
|
def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None):
|
|
self._username = username
|
|
self._password = password
|
|
self._domain = domain
|
|
self._aesKey = aesKey
|
|
self._TGT = TGT
|
|
self._TGS = TGS
|
|
if lmhash != '' or nthash != '':
|
|
if len(lmhash) % 2:
|
|
lmhash = '0%s' % lmhash
|
|
if len(nthash) % 2:
|
|
nthash = '0%s' % nthash
|
|
try: # just in case they were converted already
|
|
self._lmhash = binascii.unhexlify(lmhash)
|
|
self._nthash = binascii.unhexlify(nthash)
|
|
except:
|
|
self._lmhash = lmhash
|
|
self._nthash = nthash
|
|
pass
|
|
|
|
def doesSupportNTLMv2(self):
|
|
# By default we'll be returning the library's default. Only on SMB Transports we might be able to know it beforehand
|
|
return ntlm.USE_NTLMv2
|
|
|
|
def get_dce_rpc(self):
|
|
return DCERPC_v5(self)
|
|
|
|
class UDPTransport(DCERPCTransport):
|
|
"Implementation of ncadg_ip_udp protocol sequence"
|
|
|
|
DCERPC_class = DCERPC_v4
|
|
|
|
def __init__(self, remoteName, dstport = 135):
|
|
DCERPCTransport.__init__(self, remoteName, dstport)
|
|
self.__socket = 0
|
|
self.set_connect_timeout(30)
|
|
self.__recv_addr = ''
|
|
|
|
def connect(self):
|
|
try:
|
|
af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_DGRAM)[0]
|
|
self.__socket = socket.socket(af, socktype, proto)
|
|
self.__socket.settimeout(self.get_connect_timeout())
|
|
except socket.error as msg:
|
|
self.__socket = None
|
|
raise DCERPCException("Could not connect: %s" % msg)
|
|
|
|
return 1
|
|
|
|
def disconnect(self):
|
|
try:
|
|
self.__socket.close()
|
|
except socket.error:
|
|
self.__socket = None
|
|
return 0
|
|
return 1
|
|
|
|
def send(self,data, forceWriteAndx = 0, forceRecv = 0):
|
|
self.__socket.sendto(data, (self.getRemoteHost(), self.get_dport()))
|
|
|
|
def recv(self, forceRecv = 0, count = 0):
|
|
buffer, self.__recv_addr = self.__socket.recvfrom(8192)
|
|
return buffer
|
|
|
|
def get_recv_addr(self):
|
|
return self.__recv_addr
|
|
|
|
def get_socket(self):
|
|
return self.__socket
|
|
|
|
class TCPTransport(DCERPCTransport):
|
|
"""Implementation of ncacn_ip_tcp protocol sequence"""
|
|
|
|
def __init__(self, remoteName, dstport = 135):
|
|
DCERPCTransport.__init__(self, remoteName, dstport)
|
|
self.__socket = 0
|
|
self.set_connect_timeout(30)
|
|
|
|
def connect(self):
|
|
af, socktype, proto, canonname, sa = socket.getaddrinfo(self.getRemoteHost(), self.get_dport(), 0, socket.SOCK_STREAM)[0]
|
|
self.__socket = socket.socket(af, socktype, proto)
|
|
try:
|
|
self.__socket.settimeout(self.get_connect_timeout())
|
|
self.__socket.connect(sa)
|
|
except socket.error as msg:
|
|
self.__socket.close()
|
|
raise DCERPCException("Could not connect: %s" % msg)
|
|
return 1
|
|
|
|
def disconnect(self):
|
|
try:
|
|
self.__socket.close()
|
|
except socket.error:
|
|
self.__socket = None
|
|
return 0
|
|
return 1
|
|
|
|
def send(self,data, forceWriteAndx = 0, forceRecv = 0):
|
|
if self._max_send_frag:
|
|
offset = 0
|
|
while 1:
|
|
toSend = data[offset:offset+self._max_send_frag]
|
|
if not toSend:
|
|
break
|
|
self.__socket.send(toSend)
|
|
offset += len(toSend)
|
|
else:
|
|
self.__socket.send(data)
|
|
|
|
def recv(self, forceRecv = 0, count = 0):
|
|
if count:
|
|
buffer = b''
|
|
while len(buffer) < count:
|
|
buffer += self.__socket.recv(count-len(buffer))
|
|
else:
|
|
buffer = self.__socket.recv(8192)
|
|
return buffer
|
|
|
|
def get_socket(self):
|
|
return self.__socket
|
|
|
|
class HTTPTransport(TCPTransport, RPCProxyClient):
|
|
"""Implementation of ncacn_http protocol sequence"""
|
|
|
|
def __init__(self, remoteName=None, dstport=593):
|
|
self._useRpcProxy = False
|
|
self._rpcProxyUrl = None
|
|
self._transport = TCPTransport
|
|
self._version = RPC_OVER_HTTP_v2
|
|
|
|
DCERPCTransport.__init__(self, remoteName, dstport)
|
|
RPCProxyClient.__init__(self, remoteName, dstport)
|
|
self.set_connect_timeout(30)
|
|
|
|
def set_credentials(self, username, password, domain='', lmhash='', nthash='', aesKey='', TGT=None, TGS=None):
|
|
return self._transport.set_credentials(self, username, password,
|
|
domain, lmhash, nthash, aesKey, TGT, TGS)
|
|
|
|
def rpc_proxy_init(self):
|
|
self._useRpcProxy = True
|
|
self._transport = RPCProxyClient
|
|
|
|
def set_rpc_proxy_url(self, url):
|
|
self.rpc_proxy_init()
|
|
self._rpcProxyUrl = urlparse(url)
|
|
|
|
def get_rpc_proxy_url(self):
|
|
return urlunparse(self._rpcProxyUrl)
|
|
|
|
def set_stringbinding(self, set_stringbinding):
|
|
DCERPCTransport.set_stringbinding(self, set_stringbinding)
|
|
|
|
if self._stringbinding.is_option_set("RpcProxy"):
|
|
self.rpc_proxy_init()
|
|
|
|
rpcproxy = self._stringbinding.get_option("RpcProxy").split(":")
|
|
|
|
if rpcproxy[1] == '443':
|
|
self.set_rpc_proxy_url('https://%s/rpc/rpcproxy.dll' % rpcproxy[0])
|
|
elif rpcproxy[1] == '80':
|
|
self.set_rpc_proxy_url('http://%s/rpc/rpcproxy.dll' % rpcproxy[0])
|
|
else:
|
|
# 2.1.2.1
|
|
# RPC over HTTP always uses port 80 for HTTP traffic and port 443 for HTTPS traffic.
|
|
# But you can use set_rpc_proxy_url method to set any URL / query you want.
|
|
raise DCERPCException("RPC Proxy port must be 80 or 443")
|
|
|
|
def connect(self):
|
|
if self._useRpcProxy == False:
|
|
# Connecting directly to the ncacn_http port
|
|
#
|
|
# Here we using RPC over HTTPv1 instead complex RPC over HTTP v2 syntax
|
|
# RPC over HTTP v2 here can be implemented in the future
|
|
self._version = RPC_OVER_HTTP_v1
|
|
|
|
TCPTransport.connect(self)
|
|
|
|
# Reading legacy server response
|
|
data = self.get_socket().recv(8192)
|
|
|
|
if data != b'ncacn_http/1.0':
|
|
raise DCERPCException("%s:%s service is not ncacn_http" % (self.__remoteName, self.__dstport))
|
|
else:
|
|
RPCProxyClient.connect(self)
|
|
|
|
def send(self, data, forceWriteAndx=0, forceRecv=0):
|
|
return self._transport.send(self, data, forceWriteAndx, forceRecv)
|
|
|
|
def recv(self, forceRecv=0, count=0):
|
|
return self._transport.recv(self, forceRecv, count)
|
|
|
|
def get_socket(self):
|
|
if self._useRpcProxy == False:
|
|
return TCPTransport.get_socket(self)
|
|
else:
|
|
raise DCERPCException("This method is not supported for RPC Proxy connections")
|
|
|
|
def disconnect(self):
|
|
return self._transport.disconnect(self)
|
|
|
|
class SMBTransport(DCERPCTransport):
|
|
"""Implementation of ncacn_np protocol sequence"""
|
|
|
|
def __init__(self, remoteName, dstport=445, filename='', username='', password='', domain='', lmhash='', nthash='',
|
|
aesKey='', TGT=None, TGS=None, remote_host='', smb_connection=0, doKerberos=False, kdcHost=None):
|
|
DCERPCTransport.__init__(self, remoteName, dstport)
|
|
self.__socket = None
|
|
self.__tid = 0
|
|
self.__filename = filename
|
|
self.__handle = 0
|
|
self.__pending_recv = 0
|
|
self.set_credentials(username, password, domain, lmhash, nthash, aesKey, TGT, TGS)
|
|
self._doKerberos = doKerberos
|
|
self._kdcHost = kdcHost
|
|
|
|
if remote_host != '':
|
|
self.setRemoteHost(remote_host)
|
|
|
|
if smb_connection == 0:
|
|
self.__existing_smb = False
|
|
else:
|
|
self.__existing_smb = True
|
|
self.set_credentials(*smb_connection.getCredentials())
|
|
|
|
self.__prefDialect = None
|
|
self.__smb_connection = smb_connection
|
|
self.set_connect_timeout(30)
|
|
|
|
def preferred_dialect(self, dialect):
|
|
self.__prefDialect = dialect
|
|
|
|
def setup_smb_connection(self):
|
|
if not self.__smb_connection:
|
|
self.__smb_connection = SMBConnection(self.getRemoteName(), self.getRemoteHost(), sess_port=self.get_dport(),
|
|
preferredDialect=self.__prefDialect, timeout=self.get_connect_timeout())
|
|
if self._strict_hostname_validation:
|
|
self.__smb_connection.setHostnameValidation(self._strict_hostname_validation, self._validation_allow_absent, self._accepted_hostname)
|
|
|
|
def connect(self):
|
|
# Check if we have a smb connection already setup
|
|
if self.__smb_connection == 0:
|
|
self.setup_smb_connection()
|
|
if self._doKerberos is False:
|
|
self.__smb_connection.login(self._username, self._password, self._domain, self._lmhash, self._nthash)
|
|
else:
|
|
self.__smb_connection.kerberosLogin(self._username, self._password, self._domain, self._lmhash,
|
|
self._nthash, self._aesKey, kdcHost=self._kdcHost, TGT=self._TGT,
|
|
TGS=self._TGS)
|
|
self.__tid = self.__smb_connection.connectTree('IPC$')
|
|
self.__handle = self.__smb_connection.openFile(self.__tid, self.__filename)
|
|
self.__socket = self.__smb_connection.getSMBServer().get_socket()
|
|
return 1
|
|
|
|
def disconnect(self):
|
|
self.__smb_connection.disconnectTree(self.__tid)
|
|
# If we created the SMB connection, we close it, otherwise
|
|
# that's up for the caller
|
|
if self.__existing_smb is False:
|
|
self.__smb_connection.logoff()
|
|
self.__smb_connection.close()
|
|
self.__smb_connection = 0
|
|
|
|
def send(self,data, forceWriteAndx = 0, forceRecv = 0):
|
|
if self._max_send_frag:
|
|
offset = 0
|
|
while 1:
|
|
toSend = data[offset:offset+self._max_send_frag]
|
|
if not toSend:
|
|
break
|
|
self.__smb_connection.writeFile(self.__tid, self.__handle, toSend, offset = offset)
|
|
offset += len(toSend)
|
|
else:
|
|
self.__smb_connection.writeFile(self.__tid, self.__handle, data)
|
|
if forceRecv:
|
|
self.__pending_recv += 1
|
|
|
|
def recv(self, forceRecv = 0, count = 0 ):
|
|
if self._max_send_frag or self.__pending_recv:
|
|
# _max_send_frag is checked because it's the same condition we checked
|
|
# to decide whether to use write_andx() or send_trans() in send() above.
|
|
if self.__pending_recv:
|
|
self.__pending_recv -= 1
|
|
return self.__smb_connection.readFile(self.__tid, self.__handle, bytesToRead = self._max_recv_frag)
|
|
else:
|
|
return self.__smb_connection.readFile(self.__tid, self.__handle)
|
|
|
|
def get_smb_connection(self):
|
|
return self.__smb_connection
|
|
|
|
def set_smb_connection(self, smb_connection):
|
|
self.__smb_connection = smb_connection
|
|
self.set_credentials(*smb_connection.getCredentials())
|
|
self.__existing_smb = True
|
|
|
|
def get_smb_server(self):
|
|
# Raw Access to the SMBServer (whatever type it is)
|
|
return self.__smb_connection.getSMBServer()
|
|
|
|
def get_socket(self):
|
|
return self.__socket
|
|
|
|
def doesSupportNTLMv2(self):
|
|
return self.__smb_connection.doesSupportNTLMv2()
|
|
|
|
class LOCALTransport(DCERPCTransport):
|
|
"""
|
|
Implementation of ncalocal protocol sequence, not the same
|
|
as ncalrpc (I'm not doing LPC just opening the local pipe)
|
|
"""
|
|
|
|
def __init__(self, filename = ''):
|
|
DCERPCTransport.__init__(self, '', 0)
|
|
self.__filename = filename
|
|
self.__handle = 0
|
|
|
|
def connect(self):
|
|
if self.__filename.upper().find('PIPE') < 0:
|
|
self.__filename = '\\PIPE\\%s' % self.__filename
|
|
self.__handle = os.open('\\\\.\\%s' % self.__filename, os.O_RDWR|os.O_BINARY)
|
|
return 1
|
|
|
|
def disconnect(self):
|
|
os.close(self.__handle)
|
|
|
|
def send(self,data, forceWriteAndx = 0, forceRecv = 0):
|
|
os.write(self.__handle, data)
|
|
|
|
def recv(self, forceRecv = 0, count = 0 ):
|
|
data = os.read(self.__handle, 65535)
|
|
return data
|