github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/wmiexec.py (about) 1 #!/usr/bin/env python 2 # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved. 3 # 4 # This software is provided under under a slightly modified version 5 # of the Apache Software License. See the accompanying LICENSE file 6 # for more information. 7 # 8 # A similar approach to smbexec but executing commands through WMI. 9 # Main advantage here is it runs under the user (has to be Admin) 10 # account, not SYSTEM, plus, it doesn't generate noisy messages 11 # in the event log that smbexec.py does when creating a service. 12 # Drawback is it needs DCOM, hence, I have to be able to access 13 # DCOM ports at the target machine. 14 # 15 # Author: 16 # beto (@agsolino) 17 # 18 # Reference for: 19 # DCOM 20 # 21 from __future__ import division 22 from __future__ import print_function 23 import sys 24 import os 25 import cmd 26 import argparse 27 import time 28 import logging 29 import ntpath 30 31 from impacket.examples import logger 32 from impacket import version 33 from impacket.smbconnection import SMBConnection, SMB_DIALECT, SMB2_DIALECT_002, SMB2_DIALECT_21 34 from impacket.dcerpc.v5.dcomrt import DCOMConnection 35 from impacket.dcerpc.v5.dcom import wmi 36 from impacket.dcerpc.v5.dtypes import NULL 37 from six import PY2 38 39 OUTPUT_FILENAME = '__' + str(time.time()) 40 CODEC = sys.stdout.encoding 41 42 class WMIEXEC: 43 def __init__(self, command='', username='', password='', domain='', hashes=None, aesKey=None, share=None, 44 noOutput=False, doKerberos=False, kdcHost=None): 45 self.__command = command 46 self.__username = username 47 self.__password = password 48 self.__domain = domain 49 self.__lmhash = '' 50 self.__nthash = '' 51 self.__aesKey = aesKey 52 self.__share = share 53 self.__noOutput = noOutput 54 self.__doKerberos = doKerberos 55 self.__kdcHost = kdcHost 56 self.shell = None 57 if hashes is not None: 58 self.__lmhash, self.__nthash = hashes.split(':') 59 60 def run(self, addr): 61 if self.__noOutput is False: 62 smbConnection = SMBConnection(addr, addr) 63 if self.__doKerberos is False: 64 smbConnection.login(self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash) 65 else: 66 smbConnection.kerberosLogin(self.__username, self.__password, self.__domain, self.__lmhash, 67 self.__nthash, self.__aesKey, kdcHost=self.__kdcHost) 68 69 dialect = smbConnection.getDialect() 70 if dialect == SMB_DIALECT: 71 logging.info("SMBv1 dialect used") 72 elif dialect == SMB2_DIALECT_002: 73 logging.info("SMBv2.0 dialect used") 74 elif dialect == SMB2_DIALECT_21: 75 logging.info("SMBv2.1 dialect used") 76 else: 77 logging.info("SMBv3.0 dialect used") 78 else: 79 smbConnection = None 80 81 dcom = DCOMConnection(addr, self.__username, self.__password, self.__domain, self.__lmhash, self.__nthash, 82 self.__aesKey, oxidResolver=True, doKerberos=self.__doKerberos, kdcHost=self.__kdcHost) 83 try: 84 iInterface = dcom.CoCreateInstanceEx(wmi.CLSID_WbemLevel1Login,wmi.IID_IWbemLevel1Login) 85 iWbemLevel1Login = wmi.IWbemLevel1Login(iInterface) 86 iWbemServices= iWbemLevel1Login.NTLMLogin('//./root/cimv2', NULL, NULL) 87 iWbemLevel1Login.RemRelease() 88 89 win32Process,_ = iWbemServices.GetObject('Win32_Process') 90 91 self.shell = RemoteShell(self.__share, win32Process, smbConnection) 92 if self.__command != ' ': 93 self.shell.onecmd(self.__command) 94 else: 95 self.shell.cmdloop() 96 except (Exception, KeyboardInterrupt) as e: 97 if logging.getLogger().level == logging.DEBUG: 98 import traceback 99 traceback.print_exc() 100 logging.error(str(e)) 101 if smbConnection is not None: 102 smbConnection.logoff() 103 dcom.disconnect() 104 sys.stdout.flush() 105 sys.exit(1) 106 107 if smbConnection is not None: 108 smbConnection.logoff() 109 dcom.disconnect() 110 111 class RemoteShell(cmd.Cmd): 112 def __init__(self, share, win32Process, smbConnection): 113 cmd.Cmd.__init__(self) 114 self.__share = share 115 self.__output = '\\' + OUTPUT_FILENAME 116 self.__outputBuffer = str('') 117 self.__shell = 'cmd.exe /Q /c ' 118 self.__win32Process = win32Process 119 self.__transferClient = smbConnection 120 self.__pwd = str('C:\\') 121 self.__noOutput = False 122 self.intro = '[!] Launching semi-interactive shell - Careful what you execute\n[!] Press help for extra shell commands' 123 124 # We don't wanna deal with timeouts from now on. 125 if self.__transferClient is not None: 126 self.__transferClient.setTimeout(100000) 127 self.do_cd('\\') 128 else: 129 self.__noOutput = True 130 131 def do_shell(self, s): 132 os.system(s) 133 134 def do_help(self, line): 135 print(""" 136 lcd {path} - changes the current local directory to {path} 137 exit - terminates the server process (and this session) 138 put {src_file, dst_path} - uploads a local file to the dst_path (dst_path = default current directory) 139 get {file} - downloads pathname to the current local dir 140 ! {cmd} - executes a local shell cmd 141 """) 142 143 def do_lcd(self, s): 144 if s == '': 145 print(os.getcwd()) 146 else: 147 try: 148 os.chdir(s) 149 except Exception as e: 150 logging.error(str(e)) 151 152 def do_get(self, src_path): 153 154 try: 155 import ntpath 156 newPath = ntpath.normpath(ntpath.join(self.__pwd, src_path)) 157 drive, tail = ntpath.splitdrive(newPath) 158 filename = ntpath.basename(tail) 159 fh = open(filename,'wb') 160 logging.info("Downloading %s\\%s" % (drive, tail)) 161 self.__transferClient.getFile(drive[:-1]+'$', tail, fh.write) 162 fh.close() 163 164 except Exception as e: 165 logging.error(str(e)) 166 167 if os.path.exists(filename): 168 os.remove(filename) 169 170 171 172 def do_put(self, s): 173 try: 174 params = s.split(' ') 175 if len(params) > 1: 176 src_path = params[0] 177 dst_path = params[1] 178 elif len(params) == 1: 179 src_path = params[0] 180 dst_path = '' 181 182 src_file = os.path.basename(src_path) 183 fh = open(src_path, 'rb') 184 dst_path = dst_path.replace('/','\\') 185 import ntpath 186 pathname = ntpath.join(ntpath.join(self.__pwd,dst_path), src_file) 187 drive, tail = ntpath.splitdrive(pathname) 188 logging.info("Uploading %s to %s" % (src_file, pathname)) 189 self.__transferClient.putFile(drive[:-1]+'$', tail, fh.read) 190 fh.close() 191 except Exception as e: 192 logging.critical(str(e)) 193 pass 194 195 def do_exit(self, s): 196 return True 197 198 def emptyline(self): 199 return False 200 201 def do_cd(self, s): 202 self.execute_remote('cd ' + s) 203 if len(self.__outputBuffer.strip('\r\n')) > 0: 204 print(self.__outputBuffer) 205 self.__outputBuffer = '' 206 else: 207 if PY2: 208 self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s.decode(sys.stdin.encoding))) 209 else: 210 self.__pwd = ntpath.normpath(ntpath.join(self.__pwd, s)) 211 self.execute_remote('cd ') 212 self.__pwd = self.__outputBuffer.strip('\r\n') 213 self.prompt = (self.__pwd + '>') 214 self.__outputBuffer = '' 215 216 def default(self, line): 217 # Let's try to guess if the user is trying to change drive 218 if len(line) == 2 and line[1] == ':': 219 # Execute the command and see if the drive is valid 220 self.execute_remote(line) 221 if len(self.__outputBuffer.strip('\r\n')) > 0: 222 # Something went wrong 223 print(self.__outputBuffer) 224 self.__outputBuffer = '' 225 else: 226 # Drive valid, now we should get the current path 227 self.__pwd = line 228 self.execute_remote('cd ') 229 self.__pwd = self.__outputBuffer.strip('\r\n').encode(CODEC) 230 self.prompt = (self.__pwd + b'>') 231 self.__outputBuffer = '' 232 else: 233 if line != '': 234 self.send_data(line) 235 236 def get_output(self): 237 def output_callback(data): 238 try: 239 self.__outputBuffer += data.decode(CODEC) 240 except UnicodeDecodeError: 241 logging.error('Decoding error detected, consider running chcp.com at the target,\nmap the result with ' 242 'https://docs.python.org/2.4/lib/standard-encodings.html\nand then execute wmiexec.py ' 243 'again with -codec and the corresponding codec') 244 self.__outputBuffer += data.decode(CODEC, errors='replace') 245 246 if self.__noOutput is True: 247 self.__outputBuffer = '' 248 return 249 250 while True: 251 try: 252 self.__transferClient.getFile(self.__share, self.__output, output_callback) 253 break 254 except Exception as e: 255 if str(e).find('STATUS_SHARING_VIOLATION') >=0: 256 # Output not finished, let's wait 257 time.sleep(1) 258 pass 259 elif str(e).find('Broken') >= 0: 260 # The SMB Connection might have timed out, let's try reconnecting 261 logging.debug('Connection broken, trying to recreate it') 262 self.__transferClient.reconnect() 263 return self.get_output() 264 self.__transferClient.deleteFile(self.__share, self.__output) 265 266 def execute_remote(self, data): 267 command = self.__shell + data 268 if self.__noOutput is False: 269 command += ' 1> ' + '\\\\127.0.0.1\\%s' % self.__share + self.__output + ' 2>&1' 270 if PY2: 271 self.__win32Process.Create(command.decode(sys.stdin.encoding), self.__pwd, None) 272 else: 273 self.__win32Process.Create(command, self.__pwd, None) 274 self.get_output() 275 276 def send_data(self, data): 277 self.execute_remote(data) 278 print(self.__outputBuffer) 279 self.__outputBuffer = '' 280 281 class AuthFileSyntaxError(Exception): 282 283 '''raised by load_smbclient_auth_file if it encounters a syntax error 284 while loading the smbclient-style authentication file.''' 285 286 def __init__(self, path, lineno, reason): 287 self.path=path 288 self.lineno=lineno 289 self.reason=reason 290 291 def __str__(self): 292 return 'Syntax error in auth file %s line %d: %s' % ( 293 self.path, self.lineno, self.reason ) 294 295 def load_smbclient_auth_file(path): 296 297 '''Load credentials from an smbclient-style authentication file (used by 298 smbclient, mount.cifs and others). returns (domain, username, password) 299 or raises AuthFileSyntaxError or any I/O exceptions.''' 300 301 lineno=0 302 domain=None 303 username=None 304 password=None 305 for line in open(path): 306 lineno+=1 307 308 line = line.strip() 309 310 if line.startswith('#') or line=='': 311 continue 312 313 parts = line.split('=',1) 314 if len(parts) != 2: 315 raise AuthFileSyntaxError(path, lineno, 'No "=" present in line') 316 317 (k,v) = (parts[0].strip(), parts[1].strip()) 318 319 if k=='username': 320 username=v 321 elif k=='password': 322 password=v 323 elif k=='domain': 324 domain=v 325 else: 326 raise AuthFileSyntaxError(path, lineno, 'Unknown option %s' % repr(k)) 327 328 return (domain, username, password) 329 330 # Process command-line arguments. 331 if __name__ == '__main__': 332 # Init the example's logger theme 333 logger.init() 334 print(version.BANNER) 335 336 parser = argparse.ArgumentParser(add_help = True, description = "Executes a semi-interactive shell using Windows " 337 "Management Instrumentation.") 338 parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>') 339 parser.add_argument('-share', action='store', default = 'ADMIN$', help='share where the output will be grabbed from ' 340 '(default ADMIN$)') 341 parser.add_argument('-nooutput', action='store_true', default = False, help='whether or not to print the output ' 342 '(no SMB connection created)') 343 parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 344 parser.add_argument('-codec', action='store', help='Sets encoding used (codec) from the target\'s output (default ' 345 '"%s"). If errors are detected, run chcp.com at the target, ' 346 'map the result with ' 347 'https://docs.python.org/2.4/lib/standard-encodings.html and then execute wmiexec.py ' 348 'again with -codec and the corresponding codec ' % CODEC) 349 350 parser.add_argument('command', nargs='*', default = ' ', help='command to execute at the target. If empty it will ' 351 'launch a semi-interactive shell') 352 353 group = parser.add_argument_group('authentication') 354 355 group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 356 group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 357 group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' 358 '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ' 359 'ones specified in the command line') 360 group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication ' 361 '(128 or 256 bits)') 362 group.add_argument('-dc-ip', action='store',metavar = "ip address", help='IP Address of the domain controller. If ' 363 'ommited it use the domain part (FQDN) specified in the target parameter') 364 group.add_argument('-A', action="store", metavar = "authfile", help="smbclient/mount.cifs-style authentication file. " 365 "See smbclient man page's -A option.") 366 367 if len(sys.argv)==1: 368 parser.print_help() 369 sys.exit(1) 370 371 options = parser.parse_args() 372 373 if options.codec is not None: 374 CODEC = options.codec 375 else: 376 if CODEC is None: 377 CODEC = 'UTF-8' 378 379 if ' '.join(options.command) == ' ' and options.nooutput is True: 380 logging.error("-nooutput switch and interactive shell not supported") 381 sys.exit(1) 382 383 if options.debug is True: 384 logging.getLogger().setLevel(logging.DEBUG) 385 else: 386 logging.getLogger().setLevel(logging.INFO) 387 388 import re 389 390 domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match( 391 options.target).groups('') 392 393 #In case the password contains '@' 394 if '@' in address: 395 password = password + '@' + address.rpartition('@')[0] 396 address = address.rpartition('@')[2] 397 398 try: 399 if options.A is not None: 400 (domain, username, password) = load_smbclient_auth_file(options.A) 401 logging.debug('loaded smbclient auth file: domain=%s, username=%s, password=%s' % (repr(domain), repr(username), repr(password))) 402 403 if domain is None: 404 domain = '' 405 406 if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: 407 from getpass import getpass 408 password = getpass("Password:") 409 410 if options.aesKey is not None: 411 options.k = True 412 413 executer = WMIEXEC(' '.join(options.command), username, password, domain, options.hashes, options.aesKey, 414 options.share, options.nooutput, options.k, options.dc_ip) 415 executer.run(address) 416 except KeyboardInterrupt as e: 417 logging.error(str(e)) 418 except Exception as e: 419 if logging.getLogger().level == logging.DEBUG: 420 import traceback 421 traceback.print_exc() 422 logging.error(str(e)) 423 sys.exit(1) 424 425 sys.exit(0)