github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/psexec.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 # PSEXEC like functionality example using RemComSvc (https://github.com/kavika13/RemCom) 9 # 10 # Author: 11 # beto (@agsolino) 12 # 13 # Reference for: 14 # DCE/RPC and SMB. 15 16 import sys 17 import os 18 import cmd 19 import logging 20 from threading import Thread, Lock 21 import argparse 22 import random 23 import string 24 import time 25 from six import PY3 26 27 from impacket.examples import logger 28 from impacket import version, smb 29 from impacket.smbconnection import SMBConnection 30 from impacket.dcerpc.v5 import transport 31 from impacket.structure import Structure 32 from impacket.examples import remcomsvc, serviceinstall 33 34 35 class RemComMessage(Structure): 36 structure = ( 37 ('Command','4096s=""'), 38 ('WorkingDir','260s=""'), 39 ('Priority','<L=0x20'), 40 ('ProcessID','<L=0x01'), 41 ('Machine','260s=""'), 42 ('NoWait','<L=0'), 43 ) 44 45 class RemComResponse(Structure): 46 structure = ( 47 ('ErrorCode','<L=0'), 48 ('ReturnCode','<L=0'), 49 ) 50 51 RemComSTDOUT = "RemCom_stdout" 52 RemComSTDIN = "RemCom_stdin" 53 RemComSTDERR = "RemCom_stderr" 54 55 lock = Lock() 56 57 class PSEXEC: 58 def __init__(self, command, path, exeFile, copyFile, port=445, 59 username='', password='', domain='', hashes=None, aesKey=None, doKerberos=False, kdcHost=None, serviceName=None): 60 self.__username = username 61 self.__password = password 62 self.__port = port 63 self.__command = command 64 self.__path = path 65 self.__domain = domain 66 self.__lmhash = '' 67 self.__nthash = '' 68 self.__aesKey = aesKey 69 self.__exeFile = exeFile 70 self.__copyFile = copyFile 71 self.__doKerberos = doKerberos 72 self.__kdcHost = kdcHost 73 self.__serviceName = serviceName 74 if hashes is not None: 75 self.__lmhash, self.__nthash = hashes.split(':') 76 77 def run(self, remoteName, remoteHost): 78 79 stringbinding = r'ncacn_np:%s[\pipe\svcctl]' % remoteName 80 logging.debug('StringBinding %s'%stringbinding) 81 rpctransport = transport.DCERPCTransportFactory(stringbinding) 82 rpctransport.set_dport(self.__port) 83 rpctransport.setRemoteHost(remoteHost) 84 85 if hasattr(rpctransport, 'set_credentials'): 86 # This method exists only for selected protocol sequences. 87 rpctransport.set_credentials(self.__username, self.__password, self.__domain, self.__lmhash, 88 self.__nthash, self.__aesKey) 89 90 rpctransport.set_kerberos(self.__doKerberos, self.__kdcHost) 91 self.doStuff(rpctransport) 92 93 def openPipe(self, s, tid, pipe, accessMask): 94 pipeReady = False 95 tries = 50 96 while pipeReady is False and tries > 0: 97 try: 98 s.waitNamedPipe(tid,pipe) 99 pipeReady = True 100 except: 101 tries -= 1 102 time.sleep(2) 103 pass 104 105 if tries == 0: 106 raise Exception('Pipe not ready, aborting') 107 108 fid = s.openFile(tid,pipe,accessMask, creationOption = 0x40, fileAttributes = 0x80) 109 110 return fid 111 112 def doStuff(self, rpctransport): 113 114 dce = rpctransport.get_dce_rpc() 115 try: 116 dce.connect() 117 except Exception as e: 118 if logging.getLogger().level == logging.DEBUG: 119 import traceback 120 traceback.print_exc() 121 logging.critical(str(e)) 122 sys.exit(1) 123 124 global dialect 125 dialect = rpctransport.get_smb_connection().getDialect() 126 127 try: 128 unInstalled = False 129 s = rpctransport.get_smb_connection() 130 131 # We don't wanna deal with timeouts from now on. 132 s.setTimeout(100000) 133 if self.__exeFile is None: 134 installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), remcomsvc.RemComSvc(), self.__serviceName) 135 else: 136 try: 137 f = open(self.__exeFile) 138 except Exception as e: 139 logging.critical(str(e)) 140 sys.exit(1) 141 installService = serviceinstall.ServiceInstall(rpctransport.get_smb_connection(), f) 142 143 if installService.install() is False: 144 return 145 146 if self.__exeFile is not None: 147 f.close() 148 149 # Check if we need to copy a file for execution 150 if self.__copyFile is not None: 151 installService.copy_file(self.__copyFile, installService.getShare(), os.path.basename(self.__copyFile)) 152 # And we change the command to be executed to this filename 153 self.__command = os.path.basename(self.__copyFile) + ' ' + self.__command 154 155 tid = s.connectTree('IPC$') 156 fid_main = self.openPipe(s,tid,r'\RemCom_communicaton',0x12019f) 157 158 packet = RemComMessage() 159 pid = os.getpid() 160 161 packet['Machine'] = ''.join([random.choice(string.ascii_letters) for _ in range(4)]) 162 if self.__path is not None: 163 packet['WorkingDir'] = self.__path 164 packet['Command'] = self.__command 165 packet['ProcessID'] = pid 166 167 s.writeNamedPipe(tid, fid_main, packet.getData()) 168 169 # Here we'll store the command we type so we don't print it back ;) 170 # ( I know.. globals are nasty :P ) 171 global LastDataSent 172 LastDataSent = '' 173 174 # Create the pipes threads 175 stdin_pipe = RemoteStdInPipe(rpctransport, 176 r'\%s%s%d' % (RemComSTDIN, packet['Machine'], packet['ProcessID']), 177 smb.FILE_WRITE_DATA | smb.FILE_APPEND_DATA, installService.getShare()) 178 stdin_pipe.start() 179 stdout_pipe = RemoteStdOutPipe(rpctransport, 180 r'\%s%s%d' % (RemComSTDOUT, packet['Machine'], packet['ProcessID']), 181 smb.FILE_READ_DATA) 182 stdout_pipe.start() 183 stderr_pipe = RemoteStdErrPipe(rpctransport, 184 r'\%s%s%d' % (RemComSTDERR, packet['Machine'], packet['ProcessID']), 185 smb.FILE_READ_DATA) 186 stderr_pipe.start() 187 188 # And we stay here till the end 189 ans = s.readNamedPipe(tid,fid_main,8) 190 191 if len(ans): 192 retCode = RemComResponse(ans) 193 logging.info("Process %s finished with ErrorCode: %d, ReturnCode: %d" % ( 194 self.__command, retCode['ErrorCode'], retCode['ReturnCode'])) 195 installService.uninstall() 196 if self.__copyFile is not None: 197 # We copied a file for execution, let's remove it 198 s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile)) 199 unInstalled = True 200 sys.exit(retCode['ErrorCode']) 201 202 except SystemExit: 203 raise 204 except Exception as e: 205 if logging.getLogger().level == logging.DEBUG: 206 import traceback 207 traceback.print_exc() 208 logging.debug(str(e)) 209 if unInstalled is False: 210 installService.uninstall() 211 if self.__copyFile is not None: 212 s.deleteFile(installService.getShare(), os.path.basename(self.__copyFile)) 213 sys.stdout.flush() 214 sys.exit(1) 215 216 class Pipes(Thread): 217 def __init__(self, transport, pipe, permissions, share=None): 218 Thread.__init__(self) 219 self.server = 0 220 self.transport = transport 221 self.credentials = transport.get_credentials() 222 self.tid = 0 223 self.fid = 0 224 self.share = share 225 self.port = transport.get_dport() 226 self.pipe = pipe 227 self.permissions = permissions 228 self.daemon = True 229 230 def connectPipe(self): 231 try: 232 lock.acquire() 233 global dialect 234 #self.server = SMBConnection('*SMBSERVER', self.transport.get_smb_connection().getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT) 235 self.server = SMBConnection(self.transport.get_smb_connection().getRemoteName(), self.transport.get_smb_connection().getRemoteHost(), 236 sess_port=self.port, preferredDialect=dialect) 237 user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials 238 if self.transport.get_kerberos() is True: 239 self.server.kerberosLogin(user, passwd, domain, lm, nt, aesKey, kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS) 240 else: 241 self.server.login(user, passwd, domain, lm, nt) 242 lock.release() 243 self.tid = self.server.connectTree('IPC$') 244 245 self.server.waitNamedPipe(self.tid, self.pipe) 246 self.fid = self.server.openFile(self.tid,self.pipe,self.permissions, creationOption = 0x40, fileAttributes = 0x80) 247 self.server.setTimeout(1000000) 248 except: 249 if logging.getLogger().level == logging.DEBUG: 250 import traceback 251 traceback.print_exc() 252 logging.error("Something wen't wrong connecting the pipes(%s), try again" % self.__class__) 253 254 255 class RemoteStdOutPipe(Pipes): 256 def __init__(self, transport, pipe, permisssions): 257 Pipes.__init__(self, transport, pipe, permisssions) 258 259 def run(self): 260 self.connectPipe() 261 while True: 262 try: 263 ans = self.server.readFile(self.tid,self.fid, 0, 1024) 264 except: 265 pass 266 else: 267 try: 268 global LastDataSent 269 if ans != LastDataSent: 270 sys.stdout.write(ans.decode('cp437')) 271 sys.stdout.flush() 272 else: 273 # Don't echo what I sent, and clear it up 274 LastDataSent = '' 275 # Just in case this got out of sync, i'm cleaning it up if there are more than 10 chars, 276 # it will give false positives tho.. we should find a better way to handle this. 277 if LastDataSent > 10: 278 LastDataSent = '' 279 except: 280 pass 281 282 class RemoteStdErrPipe(Pipes): 283 def __init__(self, transport, pipe, permisssions): 284 Pipes.__init__(self, transport, pipe, permisssions) 285 286 def run(self): 287 self.connectPipe() 288 while True: 289 try: 290 ans = self.server.readFile(self.tid,self.fid, 0, 1024) 291 except: 292 pass 293 else: 294 try: 295 sys.stderr.write(str(ans)) 296 sys.stderr.flush() 297 except: 298 pass 299 300 class RemoteShell(cmd.Cmd): 301 def __init__(self, server, port, credentials, tid, fid, share, transport): 302 cmd.Cmd.__init__(self, False) 303 self.prompt = '\x08' 304 self.server = server 305 self.transferClient = None 306 self.tid = tid 307 self.fid = fid 308 self.credentials = credentials 309 self.share = share 310 self.port = port 311 self.transport = transport 312 self.intro = '[!] Press help for extra shell commands' 313 314 def connect_transferClient(self): 315 #self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port = self.port, preferredDialect = SMB_DIALECT) 316 self.transferClient = SMBConnection('*SMBSERVER', self.server.getRemoteHost(), sess_port=self.port, 317 preferredDialect=dialect) 318 user, passwd, domain, lm, nt, aesKey, TGT, TGS = self.credentials 319 if self.transport.get_kerberos() is True: 320 self.transferClient.kerberosLogin(user, passwd, domain, lm, nt, aesKey, 321 kdcHost=self.transport.get_kdcHost(), TGT=TGT, TGS=TGS) 322 else: 323 self.transferClient.login(user, passwd, domain, lm, nt) 324 325 def do_help(self, line): 326 print(""" 327 lcd {path} - changes the current local directory to {path} 328 exit - terminates the server process (and this session) 329 put {src_file, dst_path} - uploads a local file to the dst_path RELATIVE to the connected share (%s) 330 get {file} - downloads pathname RELATIVE to the connected share (%s) to the current local dir 331 ! {cmd} - executes a local shell cmd 332 """ % (self.share, self.share)) 333 self.send_data('\r\n', False) 334 335 def do_shell(self, s): 336 os.system(s) 337 self.send_data('\r\n') 338 339 def do_get(self, src_path): 340 try: 341 if self.transferClient is None: 342 self.connect_transferClient() 343 344 import ntpath 345 filename = ntpath.basename(src_path) 346 fh = open(filename,'wb') 347 logging.info("Downloading %s\\%s" % (self.share, src_path)) 348 self.transferClient.getFile(self.share, src_path, fh.write) 349 fh.close() 350 except Exception as e: 351 logging.critical(str(e)) 352 pass 353 354 self.send_data('\r\n') 355 356 def do_put(self, s): 357 try: 358 if self.transferClient is None: 359 self.connect_transferClient() 360 params = s.split(' ') 361 if len(params) > 1: 362 src_path = params[0] 363 dst_path = params[1] 364 elif len(params) == 1: 365 src_path = params[0] 366 dst_path = '/' 367 368 src_file = os.path.basename(src_path) 369 fh = open(src_path, 'rb') 370 f = dst_path + '/' + src_file 371 pathname = f.replace('/','\\') 372 logging.info("Uploading %s to %s\\%s" % (src_file, self.share, dst_path)) 373 if PY3: 374 self.transferClient.putFile(self.share, pathname, fh.read) 375 else: 376 self.transferClient.putFile(self.share, pathname.decode(sys.stdin.encoding), fh.read) 377 fh.close() 378 except Exception as e: 379 logging.error(str(e)) 380 pass 381 382 self.send_data('\r\n') 383 384 def do_lcd(self, s): 385 if s == '': 386 print(os.getcwd()) 387 else: 388 os.chdir(s) 389 self.send_data('\r\n') 390 391 def emptyline(self): 392 self.send_data('\r\n') 393 return 394 395 def default(self, line): 396 if PY3: 397 self.send_data(line.encode('cp437')+b'\r\n') 398 else: 399 self.send_data(line.decode(sys.stdin.encoding).encode('cp437')+'\r\n') 400 401 def send_data(self, data, hideOutput = True): 402 if hideOutput is True: 403 global LastDataSent 404 LastDataSent = data 405 else: 406 LastDataSent = '' 407 self.server.writeFile(self.tid, self.fid, data) 408 409 class RemoteStdInPipe(Pipes): 410 def __init__(self, transport, pipe, permisssions, share=None): 411 self.shell = None 412 Pipes.__init__(self, transport, pipe, permisssions, share) 413 414 def run(self): 415 self.connectPipe() 416 self.shell = RemoteShell(self.server, self.port, self.credentials, self.tid, self.fid, self.share, self.transport) 417 self.shell.cmdloop() 418 419 # Process command-line arguments. 420 if __name__ == '__main__': 421 # Init the example's logger theme 422 logger.init() 423 print(version.BANNER) 424 425 parser = argparse.ArgumentParser(add_help = True, description = "PSEXEC like functionality example using RemComSvc.") 426 427 parser.add_argument('target', action='store', help='[[domain/]username[:password]@]<targetName or address>') 428 parser.add_argument('command', nargs='*', default = ' ', help='command (or arguments if -c is used) to execute at ' 429 'the target (w/o path) - (default:cmd.exe)') 430 parser.add_argument('-c', action='store',metavar = "pathname", help='copy the filename for later execution, ' 431 'arguments are passed in the command option') 432 parser.add_argument('-path', action='store', help='path of the command to execute') 433 parser.add_argument('-file', action='store', help="alternative RemCom binary (be sure it doesn't require CRT)") 434 parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON') 435 436 group = parser.add_argument_group('authentication') 437 438 group.add_argument('-hashes', action="store", metavar = "LMHASH:NTHASH", help='NTLM hashes, format is LMHASH:NTHASH') 439 group.add_argument('-no-pass', action="store_true", help='don\'t ask for password (useful for -k)') 440 group.add_argument('-k', action="store_true", help='Use Kerberos authentication. Grabs credentials from ccache file ' 441 '(KRB5CCNAME) based on target parameters. If valid credentials cannot be found, it will use the ' 442 'ones specified in the command line') 443 group.add_argument('-aesKey', action="store", metavar = "hex key", help='AES key to use for Kerberos Authentication ' 444 '(128 or 256 bits)') 445 446 group = parser.add_argument_group('connection') 447 448 group.add_argument('-dc-ip', action='store', metavar="ip address", 449 help='IP Address of the domain controller. If omitted it will use the domain part (FQDN) specified in ' 450 'the target parameter') 451 group.add_argument('-target-ip', action='store', metavar="ip address", 452 help='IP Address of the target machine. If omitted it will use whatever was specified as target. ' 453 'This is useful when target is the NetBIOS name and you cannot resolve it') 454 group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port", 455 help='Destination port to connect to SMB Server') 456 group.add_argument('-service-name', action='store', metavar="service name", default = '', help='This will be the name of the service') 457 458 if len(sys.argv)==1: 459 parser.print_help() 460 sys.exit(1) 461 462 options = parser.parse_args() 463 464 if options.debug is True: 465 logging.getLogger().setLevel(logging.DEBUG) 466 else: 467 logging.getLogger().setLevel(logging.INFO) 468 469 import re 470 471 domain, username, password, remoteName = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match( 472 options.target).groups('') 473 474 #In case the password contains '@' 475 if '@' in remoteName: 476 password = password + '@' + remoteName.rpartition('@')[0] 477 remoteName = remoteName.rpartition('@')[2] 478 479 if domain is None: 480 domain = '' 481 482 if options.target_ip is None: 483 options.target_ip = remoteName 484 485 if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: 486 from getpass import getpass 487 password = getpass("Password:") 488 489 if options.aesKey is not None: 490 options.k = True 491 492 command = ' '.join(options.command) 493 if command == ' ': 494 command = 'cmd.exe' 495 496 executer = PSEXEC(command, options.path, options.file, options.c, int(options.port), username, password, domain, options.hashes, 497 options.aesKey, options.k, options.dc_ip, options.service_name) 498 executer.run(remoteName, options.target_ip)