Skip to content

Commit 85a03c6

Browse files
authored
Enhance attack logging for ntlmrelayx (#2032)
* Adding ID to each client. Logging it when relay succeeds. Showin it in 'socks' command * Avoid crashing ntlmrelayx when 'socks' command has an invalid filter * Showing relayed connection information when running attacks in context of relay * Showing scheme in attacks logging. Fixing table printing in 'socks' command * Add whitespace to standardize * Code cleanup. Set 'target' and 'relay_client' as optional parameters in ProtocolAttack (backwards compatibilty) * Format identity filter
1 parent 8426ec9 commit 85a03c6

File tree

15 files changed

+118
-40
lines changed

15 files changed

+118
-40
lines changed

examples/ntlmrelayx.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ def printTable(items, header):
8383

8484
# Print header
8585
print(outputFormat.format(*header))
86-
print(' '.join(['-' * itemLen for itemLen in colLen]))
86+
print(' '.join(['-' * max(itemLen, 3) for itemLen in colLen]))
8787

8888
# And now the rows
8989
for row in items:
@@ -112,7 +112,7 @@ def do_socks(self, line):
112112
- admin : true or false
113113
'''
114114

115-
headers = ["Protocol", "Target", "Username", "AdminStatus", "Port"]
115+
headers = ["Protocol", "Target", "Username", "AdminStatus", "Port", "ID"]
116116
url = "http://{}/ntlmrelayx/api/v1.0/relays".format(self.api_address)
117117
try:
118118
proxy_handler = ProxyHandler({})
@@ -135,7 +135,8 @@ def do_socks(self, line):
135135
elif(_filter=='admin'):
136136
_filter=3
137137
else:
138-
logging.info('Expect : target / username / admin = value')
138+
logging.info('Expect : target / username / admin = value')
139+
return
139140
_items=[]
140141
for i in items:
141142
if(_value.lower() in i[_filter].lower()):

impacket/examples/logger.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import logging
1818
import sys
1919
from impacket import version
20+
from impacket.examples.ntlmrelayx.utils.identity_log import IdentityFilter
2021

2122
# This module can be used by scripts using the Impacket library
2223
# in order to configure the root logger to output events
@@ -30,7 +31,7 @@ class ImpacketFormatter(logging.Formatter):
3031
Prefixing logged messages through the custom attribute 'bullet'.
3132
'''
3233
def __init__(self):
33-
logging.Formatter.__init__(self,'%(bullet)s %(message)s', None)
34+
logging.Formatter.__init__(self,'%(bullet)s %(identity)s%(message)s', None)
3435

3536
def format(self, record):
3637
if record.levelno == logging.INFO:
@@ -49,7 +50,7 @@ class ImpacketFormatterTimeStamp(ImpacketFormatter):
4950
Prefixing logged messages through the custom attribute 'bullet'.
5051
'''
5152
def __init__(self):
52-
logging.Formatter.__init__(self,'[%(asctime)-15s] %(bullet)s %(message)s', None)
53+
logging.Formatter.__init__(self,'[%(asctime)-15s] %(bullet)s %(identity)s%(message)s', None)
5354

5455
def formatTime(self, record, datefmt=None):
5556
return ImpacketFormatter.formatTime(self, record, datefmt="%Y-%m-%d %H:%M:%S")
@@ -63,6 +64,8 @@ def init(ts=False, debug=False):
6364
else:
6465
handler.setFormatter(ImpacketFormatter())
6566

67+
handler.addFilter(IdentityFilter())
68+
6669
logging.getLogger().addHandler(handler)
6770

6871
if debug is True:
@@ -71,4 +74,4 @@ def init(ts=False, debug=False):
7174
logging.debug(version.getInstallationPath())
7275
else:
7376
logging.getLogger().setLevel(logging.INFO)
74-
logging.getLogger('impacket.smbserver').setLevel(logging.ERROR)
77+
logging.getLogger('impacket.smbserver').setLevel(logging.ERROR)

impacket/examples/ntlmrelayx/attacks/__init__.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
from impacket import LOG
2222
from threading import Thread
2323

24+
from impacket.examples.ntlmrelayx.utils.identity_log import identity_context
25+
2426
PROTOCOL_ATTACKS = {}
2527

2628
# Base class for Protocol Attacks for different protocols (SMB, MSSQL, etc)
@@ -31,9 +33,27 @@
3133
# PROTOCOL_ATTACK_CLASSES = ["<name of the class for the plugin>", "<another class>"]
3234
# These classes must have the attribute PLUGIN_NAMES which is a list of protocol names
3335
# that will be matched later with the relay targets (e.g. SMB, LDAP, etc)
36+
37+
def _wrap_run_with_identity(run_func):
38+
def _wrapped(self, *a, **k):
39+
if self.target is not None and self.relay_client is not None:
40+
connection_identifier = '%s://%s/%s@%s [%s]' % (self.target.scheme, self.domain, self.username, self.target.hostname, self.relay_client.client_id)
41+
with identity_context(connection_identifier):
42+
return run_func(self, *a, **k)
43+
else:
44+
return run_func(self, *a, **k)
45+
return _wrapped
46+
3447
class ProtocolAttack(Thread):
3548
PLUGIN_NAMES = ['PROTOCOL']
36-
def __init__(self, config, client, username):
49+
50+
def __init_subclass__(cls, **kwargs):
51+
super().__init_subclass__(**kwargs)
52+
# If subclass defines its own run(), wrap it
53+
if 'run' in cls.__dict__:
54+
cls.run = _wrap_run_with_identity(cls.run)
55+
56+
def __init__(self, config, client, username, target=None, relay_client=None):
3757
Thread.__init__(self)
3858
# Set threads as daemon
3959
self.daemon = True
@@ -43,6 +63,9 @@ def __init__(self, config, client, username):
4363
self.username = username.split('/')[1]
4464
# But we also store the domain for later use
4565
self.domain = username.split('/')[0]
66+
# --
67+
self.target = target
68+
self.relay_client = relay_client
4669

4770
def run(self):
4871
raise RuntimeError('Virtual Function')

impacket/examples/ntlmrelayx/attacks/ldapattack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,10 @@ class LDAPAttack(ProtocolAttack):
116116
GENERIC_EXECUTE = 0x00020004
117117
GENERIC_ALL = 0x000F01FF
118118

119-
def __init__(self, config, LDAPClient, username):
119+
def __init__(self, config, LDAPClient, username, target=None, relay_client=None):
120120
self.computerName = '' if not config.addcomputer else config.addcomputer[0]
121121
self.computerPassword = '' if not config.addcomputer or len(config.addcomputer) < 2 else config.addcomputer[1]
122-
ProtocolAttack.__init__(self, config, LDAPClient, username)
122+
ProtocolAttack.__init__(self, config, LDAPClient, username, target, relay_client)
123123
if self.config.interactive:
124124
# Launch locally listening interactive shell.
125125
self.tcp_shell = TcpShell()

impacket/examples/ntlmrelayx/attacks/mssqlattack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@
2626

2727
class MSSQLAttack(ProtocolAttack):
2828
PLUGIN_NAMES = ["MSSQL"]
29-
def __init__(self, config, MSSQLclient, username):
30-
ProtocolAttack.__init__(self, config, MSSQLclient, username)
29+
def __init__(self, config, MSSQLclient, username, target=None, relay_client=None):
30+
ProtocolAttack.__init__(self, config, MSSQLclient, username, target, relay_client)
3131
if self.config.interactive:
3232
# Launch locally listening interactive shell.
3333
self.tcp_shell = TcpShell()

impacket/examples/ntlmrelayx/attacks/rpcattack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -172,8 +172,8 @@ def _run(self):
172172
class RPCAttack(ProtocolAttack, TSCHRPCAttack):
173173
PLUGIN_NAMES = ["RPC"]
174174

175-
def __init__(self, config, dce, username):
176-
ProtocolAttack.__init__(self, config, dce, username)
175+
def __init__(self, config, dce, username, target=None, relay_client=None):
176+
ProtocolAttack.__init__(self, config, dce, username, target, relay_client)
177177
self.dce = dce
178178
self.rpctransport = dce.get_rpc_transport()
179179
self.stringbinding = self.rpctransport.get_stringbinding()

impacket/examples/ntlmrelayx/attacks/smbattack.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ class SMBAttack(ProtocolAttack):
4040
shell if the -i option is specified.
4141
"""
4242
PLUGIN_NAMES = ["SMB"]
43-
def __init__(self, config, SMBClient, username):
44-
ProtocolAttack.__init__(self, config, SMBClient, username)
43+
def __init__(self, config, SMBClient, username, target=None, relay_client=None):
44+
ProtocolAttack.__init__(self, config, SMBClient, username, target, relay_client)
4545
if isinstance(SMBClient, smb.SMB) or isinstance(SMBClient, smb3.SMB3):
4646
self.__SMBConnection = SMBConnection(existingConnection=SMBClient)
4747
else:

impacket/examples/ntlmrelayx/clients/__init__.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#
1818
import os, sys
1919
from importlib.resources import files
20+
from threading import Lock
2021
from impacket import LOG
2122

2223
PROTOCOL_CLIENTS = {}
@@ -26,6 +27,8 @@
2627
# writing a plugin for protocol clients:
2728
# PROTOCOL_CLIENT_CLASS = "<name of the class for the plugin>"
2829
# PLUGIN_NAME must be the protocol name that will be matched later with the relay targets (e.g. SMB, LDAP, etc)
30+
client_idx = 0
31+
lock = Lock()
2932
class ProtocolClient:
3033
PLUGIN_NAME = 'PROTOCOL'
3134
def __init__(self, serverConfig, target, targetPort, extendedSecurity=True):
@@ -95,6 +98,13 @@ def isAdmin(self):
9598
# By default, raise exception
9699
raise RuntimeError('Virtual Function')
97100

101+
def setClientId(self):
102+
with lock:
103+
global client_idx
104+
client_idx += 1
105+
self.client_id = client_idx
106+
107+
98108
clients_path = files('impacket.examples.ntlmrelayx').joinpath('clients')
99109
for file in [f.name for f in clients_path.iterdir() if f.is_file()]:
100110
if file.find('__') >= 0 or file.endswith('.py') is False:

impacket/examples/ntlmrelayx/servers/httprelayserver.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -464,7 +464,8 @@ def do_relay(self, messageType, token, proxy, content = None):
464464
self.do_AUTHHEAD(b'NTLM', proxy=proxy)
465465
else:
466466
# Relay worked, do whatever we want here...
467-
LOG.info("(HTTP): Authenticating connection from %s@%s against %s://%s SUCCEED" % (self.authUser, self.client_address[0], self.target.scheme, self.target.netloc))
467+
self.client.setClientId()
468+
LOG.info("(HTTP): Authenticating connection from %s@%s against %s://%s SUCCEED [%s]" % (self.authUser, self.client_address[0], self.target.scheme, self.target.netloc, self.client.client_id))
468469

469470
ntlm_hash_data = outputToJohnFormat(self.challengeMessage['challenge'],
470471
authenticateMessage['user_name'],
@@ -530,7 +531,7 @@ def do_attack(self):
530531
if self.target.scheme.upper() in self.server.config.attacks:
531532
# We have an attack.. go for it
532533
clientThread = self.server.config.attacks[self.target.scheme.upper()](self.server.config, self.client.session,
533-
self.authUser)
534+
self.authUser, self.target, self.client)
534535
clientThread.start()
535536
else:
536537
LOG.error('(HTTP): No attack configured for %s' % self.target.scheme.upper())

impacket/examples/ntlmrelayx/servers/rawrelayserver.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,15 +113,15 @@ def handle(self):
113113
# Relay worked, do whatever we want here...
114114
self.request.sendall(struct.pack('h', 1))
115115
self.request.sendall(struct.pack('?', True))
116-
116+
self.client.setClientId()
117117
if authenticateMessage['flags'] & ntlm.NTLMSSP_NEGOTIATE_UNICODE:
118-
LOG.info("(RAW): Authenticating connection from %s/%s@%s against %s://%s SUCCEED" % (
118+
LOG.info("(RAW): Authenticating connection from %s/%s@%s against %s://%s SUCCEED [%s]" % (
119119
authenticateMessage['domain_name'].decode('utf-16le'), authenticateMessage['user_name'].decode('utf-16le'),
120-
self.client_address[0], self.target.scheme, self.target.netloc))
120+
self.client_address[0], self.target.scheme, self.target.netloc, self.client.client_id))
121121
else:
122-
LOG.info("(RAW): Authenticating connection from %s/%s@%s against %s://%s SUCCEED" % (
122+
LOG.info("(RAW): Authenticating connection from %s/%s@%s against %s://%s SUCCEED [%s]" % (
123123
authenticateMessage['domain_name'].decode('ascii'), authenticateMessage['user_name'].decode('ascii'),
124-
self.client_address[0], self.target.scheme, self.target.netloc))
124+
self.client_address[0], self.target.scheme, self.target.netloc, self.client.client_id))
125125

126126
ntlm_hash_data = outputToJohnFormat(self.challengeMessage['challenge'],
127127
authenticateMessage['user_name'],
@@ -194,7 +194,7 @@ def do_attack(self):
194194
if self.target.scheme.upper() in self.server.config.attacks:
195195
# We have an attack.. go for it
196196
clientThread = self.server.config.attacks[self.target.scheme.upper()](self.server.config, self.client.session,
197-
self.authUser)
197+
self.authUser, self.target, self.client)
198198
clientThread.start()
199199
else:
200200
LOG.error('(RAW): No attack configured for %s' % self.target.scheme.upper())

0 commit comments

Comments
 (0)