Esempio n. 1
0
    def generate_agent(self, delay, jitter, profile, killDate, workingHours, lostLimit):
        """
        Generate "standard API" functionality, i.e. the actual agent.ps1 that runs.
        
        This should always be sent over encrypted comms.
        """

        f = open(self.installPath + "./data/agent/agent.ps1".replace('/', os.sep))
        code = f.read()
        f.close()

        # strip out comments and blank lines
        code = helpers.strip_powershell_comments(code)
        b64DefaultPage = base64.b64encode(http.default_page())

        # patch in the delay, jitter, lost limit, and comms profile
        code = code.replace('$AgentDelay = 60', "$AgentDelay = " + str(delay))
        code = code.replace('$AgentJitter = 0', "$AgentJitter = " + str(jitter))
        code = code.replace('$Profile = "/admin/get.php,/news.asp,/login/process.jsp|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', "$Profile = \"" + str(profile) + "\"")
        code = code.replace('$LostLimit = 60', "$LostLimit = " + str(lostLimit))
        code = code.replace('$DefaultPage = ""', '$DefaultPage = "'+b64DefaultPage+'"')

        # patch in the killDate and workingHours if they're specified
        if killDate != "":
            code = code.replace('$KillDate,', "$KillDate = '" + str(killDate) + "',")
        if workingHours != "":
            code = code.replace('$WorkingHours,', "$WorkingHours = '" + str(workingHours) + "',")

        return code
Esempio n. 2
0
    def generate_agent(self, delay, jitter, profile, killDate, workingHours, lostLimit):
        """
        Generate "standard API" functionality, i.e. the actual agent.ps1 that runs.

        This should always be sent over encrypted comms.
        """
        f = open(self.installPath + "./data/agent/agent.ps1")
        code = f.read()
        f.close()

        # strip out comments and blank lines
        code = helpers.strip_powershell_comments(code)
        b64DefaultPage = base64.b64encode(http.default_page())

        # patch in the delay, jitter, lost limit, and comms profile
        code = code.replace('$AgentDelay = 60', "$AgentDelay = " + str(delay))
        code = code.replace('$AgentJitter = 0', "$AgentJitter = " + str(jitter))
        code = code.replace('$Profile = "/admin/get.php,/news.asp,/login/process.jsp|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', "$Profile = \"" + str(profile) + "\"")
        code = code.replace('$LostLimit = 60', "$LostLimit = " + str(lostLimit))
        code = code.replace('$DefaultPage = ""', '$DefaultPage = "'+b64DefaultPage+'"')

        # patch in the killDate and workingHours if they're specified
        if killDate != "":
            code = code.replace('$KillDate,', "$KillDate = '" + str(killDate) + "',")
        if workingHours != "":
            code = code.replace('$WorkingHours,', "$WorkingHours = '" + str(workingHours) + "',")

        return code
Esempio n. 3
0
    def generate_agent(self, delay, jitter, profile, killDate, workingHours, lostLimit):
        """
        Generate "standard API" functionality, i.e. the actual agent.py that runs.

        This should always be sent over encrypted comms.
        """

        f = open(self.installPath + "./data/agent/agent.py")
        code = f.read()
        f.close()

        # strip out comments and blank lines
        code = helpers.strip_python_comments(code)

        b64DefaultPage = base64.b64encode(http.default_page())

        # patch in the delay, jitter, lost limit, and comms profile
        code = code.replace('delay = 60', 'delay = %s' % (delay))
        code = code.replace('jitter = 0.0', 'jitter = %s' % (jitter))
        code = code.replace('profile = "/admin/get.php,/news.asp,/login/process.jsp|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', 'profile = "%s"' % (profile))
        code = code.replace('lostLimit = 60', 'lostLimit = %s' % (lostLimit))
        code = code.replace('defaultPage = base64.b64decode("")', 'defaultPage = base64.b64decode("%s")' % (b64DefaultPage))

        # patch in the killDate and workingHours if they're specified
        if killDate != "":
            code = code.replace('killDate = ""', 'killDate = "%s"' % (killDate))
        if workingHours != "":
            code = code.replace('workingHours = ""', 'workingHours = "%s"' % (killDate))

        return code
Esempio n. 4
0
    def process_post(self, port, clientIP, sessionID, resource, postData):
        """
        Process a POST request.
        """

        # check to make sure this IP is allowed
        if not self.is_ip_allowed(clientIP):
            dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" on the blacklist/not on the whitelist.", sender="Agents")
            return (200, http.default_page())

        # check if requested resource in is session URIs for any agent profiles in the database
        if (self.is_uri_present(resource)):

            # if the sessionID doesn't exist in the database
            if not self.is_agent_present(sessionID):

                # alert everyone to an irregularity
                dispatcher.send("[!] Agent "+str(sessionID)+" posted results but isn't in the database!", sender="Agents")
                return (404, "")

            # if the ID is currently in the database, process the results
            else:

                # extract the agent's session key
                sessionKey = self.agents[sessionID][0]

                try:
                    # verify, decrypt and depad the packet
                    packet = encryption.aes_decrypt_and_verify(sessionKey, postData)

                    # update the client's last seen time
                    self.update_agent_lastseen(sessionID)

                    # process the packet and extract necessary data
                    #   [(responseName, counter, length, data), ...]
                    responsePackets = packets.parse_result_packets(packet)

                    counter = responsePackets[-1][1]

                    # validate the counter in the packet in the setcode.replace
                    if counter and packets.validate_counter(counter):
                        
                        for responsePacket in responsePackets:
                            (responseName, counter, length, data) = responsePacket
                            # process the agent's response
                            self.handle_agent_response(sessionID, responseName, data)

                        # signal that this agent returned results
                        name = self.get_agent_name(sessionID)
                        dispatcher.send("[*] Agent "+str(name)+" returned results.", sender="Agents")

                        # return a 200/valid
                        return (200, "")

                    else:
                        dispatcher.send("[!] Invalid counter value from "+str(sessionID), sender="Agents")
                        return (404, "")

                except Exception as e:
                    dispatcher.send("[!] Error processing result packet from "+str(sessionID), sender="Agents")
                    return (404, "")


        # step 3 of negotiation -> client posts public key
        elif resource.lstrip("/").split("?")[0] == self.stage1:

            if self.args and self.args.debug:
                dispatcher.send("[*] Agent "+str(sessionID)+" from "+str(clientIP)+" posted to public key URI", sender="Agents")

            # get the staging key for the given listener, keyed by port
            #   results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,lost_limit
            stagingKey = self.listeners.get_staging_information(port=port)[3]

            # decrypt the agent's public key
            message = encryption.aes_decrypt(stagingKey, postData)

            # strip non-printable characters
            message = ''.join(filter(lambda x:x in string.printable, message))

            # client posts RSA key
            if (len(message) < 400) or (not message.endswith("</RSAKeyValue>")):
                dispatcher.send("[!] Invalid key post format from "+str(sessionID), sender="Agents")
            else:
                # convert the RSA key from the stupid PowerShell export format
                rsaKey = encryption.rsa_xml_to_key(message)

                if(rsaKey):

                    if self.args and self.args.debug:
                        dispatcher.send("[*] Agent "+str(sessionID)+" from "+str(clientIP)+" posted valid RSA key", sender="Agents")

                    # get the epoch time to send to the client
                    epoch = packets.get_counter()

                    # get the staging key for the given listener, keyed by port
                    #   results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit
                    config = self.listeners.get_staging_information(port=port)
                    delay = config[4]
                    jitter = config[5]
                    profile = config[6]
                    killDate = config[7]
                    workingHours = config[8]
                    lostLimit = config[11]

                    # add the agent to the database now that it's "checked in"
                    self.add_agent(sessionID, clientIP, delay, jitter, profile, killDate, workingHours,lostLimit)

                    # step 4 of negotiation -> return epoch+aes_session_key
                    clientSessionKey = self.get_agent_session_key(sessionID)
                    data = str(epoch)+clientSessionKey
                    data = data.encode('ascii','ignore')

                    encryptedMsg = encryption.rsa_encrypt(rsaKey, data)

                    # return a 200/valid and encrypted stage to the agent
                    return (200, encryptedMsg)

                else:
                    dispatcher.send("[!] Agent "+str(sessionID)+" returned an invalid public key!", sender="Agents")
                    return (404, "")


        # step 5 of negotiation -> client posts sysinfo and requests agent
        elif resource.lstrip("/").split("?")[0] == self.stage2:

            if self.is_agent_present(sessionID):

                # if this is a hop.php relay
                if "?" in resource:
                    parts = resource.split("?")
                    if len(parts) == 2:
                        decoded = helpers.decode_base64(parts[1])

                        # get the staging key for the given listener, keyed by port
                        #   results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,lost_limit 
                        config = self.listeners.get_staging_information(host=decoded)

                else:
                    config = self.listeners.get_staging_information(port=port)
                
                delay = config[4]
                jitter = config[5]
                profile = config[6]
                killDate = config[7]
                workingHours = config[8]
                lostLimit = config[11]

                # get the session key for the agent
                sessionKey = self.agents[sessionID][0]

                try:
                    # decrypt and parse the agent's sysinfo checkin
                    data = encryption.aes_decrypt(sessionKey, postData)

                    parts = data.split("|")

                    if len(parts) < 10:
                        dispatcher.send("[!] Agent "+str(sessionID)+" posted invalid sysinfo checkin format", sender="Agents")
                        # remove the agent from the cache/database
                        self.remove_agent(sessionID)
                        return (404, "")

                    listener = parts[0].encode('ascii','ignore')
                    domainname = parts[1].encode('ascii','ignore')
                    username = parts[2].encode('ascii','ignore')
                    hostname = parts[3].encode('ascii','ignore')
                    external_ip = clientIP.encode('ascii','ignore')
                    internal_ip = parts[4].encode('ascii','ignore')
                    os_details = parts[5].encode('ascii','ignore')
                    high_integrity = parts[6].encode('ascii','ignore')
                    process_name = parts[7].encode('ascii','ignore')
                    process_id = parts[8].encode('ascii','ignore')
                    ps_version = parts[9].encode('ascii','ignore')

                    if high_integrity == "True":
                        high_integrity = 1
                    else:
                        high_integrity = 0

                except:
                    # remove the agent from the cache/database
                    self.remove_agent(sessionID)
                    return (404, "")                    

                # let everyone know an agent got stage2
                if self.args and self.args.debug:
                    dispatcher.send("[*] Sending agent (stage 2) to "+str(sessionID)+" at "+clientIP, sender="Agents")

                # step 6 of negotiation -> server sends patched agent.ps1
                agentCode = self.stagers.generate_agent(delay, jitter, profile, killDate,workingHours,lostLimit)

                username = str(domainname)+"\\"+str(username)

                # update the agent with this new information
                self.update_agent_sysinfo(sessionID, listener=listener, internal_ip=internal_ip, username=username, hostname=hostname, os_details=os_details, high_integrity=high_integrity, process_name=process_name, process_id=process_id, ps_version=ps_version)

                # encrypt the agent and send it back
                encryptedAgent = encryption.aes_encrypt(sessionKey, agentCode)

                # signal everyone that this agent is now active
                dispatcher.send("[+] Initial agent "+str(sessionID)+" from "+str(clientIP) + " now active", sender="Agents")
                output =  "[+] Agent " + str(sessionID) + " now active:\n"

                # set basic initial information to display for the agent
                agent = self.mainMenu.agents.get_agent(sessionID)

                keys = ["ID", "sessionID", "listener", "name", "delay", "jitter","external_ip", "internal_ip", "username", "high_integrity", "process_name", "process_id", "hostname", "os_details", "session_key", "checkin_time", "lastseen_time", "parent", "children", "servers", "uris", "old_uris", "user_agent", "headers", "functions", "kill_date", "working_hours", "ps_version", "lost_limit"]

                agentInfo = dict(zip(keys, agent))

                for key in agentInfo:
                    if key != "functions":
                        output += "  %s\t%s\n" % ('{0: <16}'.format(key), messages.wrap_string(agentInfo[key], width=70))

                # save the initial sysinfo information in the agent log
                self.save_agent_log(sessionID, output + "\n")

                return(200, encryptedAgent)

            else:
                dispatcher.send("[!] Agent "+str(sessionID)+" posted sysinfo without initial checkin", sender="Agents")
                return (404, "")

        # default behavior, 404
        else:
            return (404, "")
Esempio n. 5
0
    def process_get(self, port, clientIP, sessionID, resource):
        """
        Process a GET request.
        """

        # check to make sure this IP is allowed
        if not self.is_ip_allowed(clientIP):
            dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" on the blacklist/not on the whitelist.", sender="Agents")
            return (200, http.default_page())

        # see if the requested resource is in our valid task URI list
        if (self.is_uri_present(resource)):

            # if no session ID was supplied
            if not sessionID or sessionID == "":
                dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" with no session ID.", sender="Agents")
                # return a 404 error code and no resource
                return (404, "")

            # if the sessionID doesn't exist in the cache
            # TODO: put this code before the URI present? ...
            if not self.is_agent_present(sessionID):
                dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" with invalid session ID.", sender="Agents")
                return (404, "")

            # if the ID is currently in the cache, see if there's tasking for the agent
            else:
                
                # update the client's last seen time
                self.update_agent_lastseen(sessionID)

                # retrieve all agent taskings from the cache
                taskings = self.get_agent_tasks(sessionID)

                if taskings and taskings != []:

                    allTaskPackets = ""

                    # build tasking packets for everything we have
                    for tasking in taskings:
                        taskName, taskData = tasking
                        
                        # if there is tasking, build a tasking packet
                        taskPacket = packets.build_task_packet(taskName, taskData)
                        
                        allTaskPackets += taskPacket

                    # get the session key for the agent
                    sessionKey = self.agents[sessionID][0]

                    # encrypt the tasking packets with the agent's session key
                    encryptedData = encryption.aes_encrypt_then_mac(sessionKey, allTaskPackets)

                    return (200, encryptedData)

                # if no tasking for the agent
                else:
                    # just return the default page
                    return (200, http.default_page())

        # step 1 of negotiation -> client requests stage1 (stager.ps1)
        elif resource.lstrip("/").split("?")[0] == self.stage0:
            # return 200/valid and the initial stage code

            if self.args and self.args.debug:
                dispatcher.send("[*] Sending stager (stage 1) to "+str(clientIP), sender="Agents")

            # get the staging information for the given listener, keyed by port
            #   results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,istener_type,redirect_target,lost_limit
            config = self.listeners.get_staging_information(port=port)
            host = config[0]
            stagingkey = config[3]
            profile = config[6]
            stage = None

            # if we have a pivot or hop listener, use that config information instead for the stager
            if "?" in resource:
                parts = resource.split("?")
                if len(parts) == 2:
                    decoded = helpers.decode_base64(parts[1])

                    # http://server:port for a pivot listener
                    if decoded.count("/") == 2:
                        host = decoded
                    else:
                        # otherwise we have a http://server:port/hop.php listener
                        stage = self.stagers.generate_stager_hop(decoded, stagingkey, profile)

            if not stage:
                # generate the stage with appropriately patched information
                stage = self.stagers.generate_stager(host, stagingkey)

            # step 2 of negotiation -> return stager.ps1 (stage 1)
            return (200, stage)

        # default response
        else:
            # otherwise return the default page
            return (200, http.default_page())
Esempio n. 6
0
    def process_post(self, port, clientIP, sessionID, resource, postData):
        """
        Process a POST request.
        """

        # check to make sure this IP is allowed
        if not self.is_ip_allowed(clientIP):
            dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" on the blacklist/not on the whitelist.", sender="Agents")
            return (200, http.default_page())

        # check if requested resource in is session URIs for any agent profiles in the database
        if (self.is_uri_present(resource)):

            # if the sessionID doesn't exist in the database
            if not self.is_agent_present(sessionID):

                # alert everyone to an irregularity
                dispatcher.send("[!] Agent "+str(sessionID)+" posted results but isn't in the database!", sender="Agents")
                return (404, "")

            # if the ID is currently in the database, process the results
            else:

                # extract the agent's session key
                sessionKey = self.agents[sessionID][0]

                try:
                    # verify, decrypt and depad the packet
                    packet = encryption.aes_decrypt_and_verify(sessionKey, postData)

                    # update the client's last seen time
                    self.update_agent_lastseen(sessionID)

                    # process the packet and extract necessary data
                    #   [(responseName, counter, length, data), ...]
                    responsePackets = packets.parse_result_packets(packet)

                    counter = responsePackets[-1][1]

                    # validate the counter in the packet in the setcode.replace
                    if counter and packets.validate_counter(counter):
                        
                        for responsePacket in responsePackets:
                            (responseName, counter, length, data) = responsePacket
                            # process the agent's response
                            self.handle_agent_response(sessionID, responseName, data)

                        # signal that this agent returned results
                        name = self.get_agent_name(sessionID)
                        dispatcher.send("[*] Agent "+str(name)+" returned results.", sender="Agents")

                        # return a 200/valid
                        return (200, "")

                    else:
                        dispatcher.send("[!] Invalid counter value from "+str(sessionID), sender="Agents")
                        return (404, "")

                except Exception as e:
                    dispatcher.send("[!] Error processing result packet from "+str(sessionID), sender="Agents")
                    return (404, "")


        # step 3 of negotiation -> client posts public key
        elif resource.lstrip("/").split("?")[0] == self.stage1:

            if self.args and self.args.debug:
                dispatcher.send("[*] Agent "+str(sessionID)+" from "+str(clientIP)+" posted to public key URI", sender="Agents")

            # get the staging key for the given listener, keyed by port
            #   results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,lost_limit
            stagingKey = self.listeners.get_staging_information(port=port)[3]

            # decrypt the agent's public key
            message = encryption.aes_decrypt(stagingKey, postData)

            # strip non-printable characters
            message = ''.join(filter(lambda x:x in string.printable, message))

            # client posts RSA key
            if (len(message) < 400) or (not message.endswith("</RSAKeyValue>")):
                dispatcher.send("[!] Invalid key post format from "+str(sessionID), sender="Agents")
            else:
                # convert the RSA key from the stupid PowerShell export format
                rsaKey = encryption.rsa_xml_to_key(message)

                if(rsaKey):

                    if self.args and self.args.debug:
                        dispatcher.send("[*] Agent "+str(sessionID)+" from "+str(clientIP)+" posted valid RSA key", sender="Agents")

                    # get the epoch time to send to the client
                    epoch = packets.get_counter()

                    # get the staging key for the given listener, keyed by port
                    #   results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,listener_type,redirect_target,default_lost_limit
                    config = self.listeners.get_staging_information(port=port)
                    delay = config[4]
                    jitter = config[5]
                    profile = config[6]
                    killDate = config[7]
                    workingHours = config[8]
                    lostLimit = config[11]

                    # add the agent to the database now that it's "checked in"
                    self.add_agent(sessionID, clientIP, delay, jitter, profile, killDate, workingHours,lostLimit)

                    # step 4 of negotiation -> return epoch+aes_session_key
                    clientSessionKey = self.get_agent_session_key(sessionID)
                    data = str(epoch)+clientSessionKey
                    data = data.encode('ascii','ignore')

                    encryptedMsg = encryption.rsa_encrypt(rsaKey, data)

                    # return a 200/valid and encrypted stage to the agent
                    return (200, encryptedMsg)

                else:
                    dispatcher.send("[!] Agent "+str(sessionID)+" returned an invalid public key!", sender="Agents")
                    return (404, "")


        # step 5 of negotiation -> client posts sysinfo and requests agent
        elif resource.lstrip("/").split("?")[0] == self.stage2:

            if self.is_agent_present(sessionID):

                # if this is a hop.php relay
                if "?" in resource:
                    parts = resource.split("?")
                    if len(parts) == 2:
                        decoded = helpers.decode_base64(parts[1])

                        # get the staging key for the given listener, keyed by port
                        #   results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,lost_limit 
                        config = self.listeners.get_staging_information(host=decoded)

                else:
                    config = self.listeners.get_staging_information(port=port)
                
                delay = config[4]
                jitter = config[5]
                profile = config[6]
                killDate = config[7]
                workingHours = config[8]
                lostLimit = config[11]

                # get the session key for the agent
                sessionKey = self.agents[sessionID][0]

                try:
                    # decrypt and parse the agent's sysinfo checkin
                    data = encryption.aes_decrypt(sessionKey, postData)

                    parts = data.split("|")

                    if len(parts) < 10:
                        dispatcher.send("[!] Agent "+str(sessionID)+" posted invalid sysinfo checkin format", sender="Agents")
                        # remove the agent from the cache/database
                        self.remove_agent(sessionID)
                        return (404, "")

                    listener = parts[0].encode('ascii','ignore')
                    domainname = parts[1].encode('ascii','ignore')
                    username = parts[2].encode('ascii','ignore')
                    hostname = parts[3].encode('ascii','ignore')
                    external_ip = clientIP.encode('ascii','ignore')
                    internal_ip = parts[4].encode('ascii','ignore')
                    os_details = parts[5].encode('ascii','ignore')
                    high_integrity = parts[6].encode('ascii','ignore')
                    process_name = parts[7].encode('ascii','ignore')
                    process_id = parts[8].encode('ascii','ignore')
                    ps_version = parts[9].encode('ascii','ignore')

                    if high_integrity == "True":
                        high_integrity = 1
                    else:
                        high_integrity = 0

                except:
                    # remove the agent from the cache/database
                    self.remove_agent(sessionID)
                    return (404, "")                    

                # let everyone know an agent got stage2
                if self.args and self.args.debug:
                    dispatcher.send("[*] Sending agent (stage 2) to "+str(sessionID)+" at "+clientIP, sender="Agents")

                # step 6 of negotiation -> server sends patched agent.ps1
                agentCode = self.stagers.generate_agent(delay, jitter, profile, killDate,workingHours,lostLimit)

                username = str(domainname)+"\\"+str(username)

                # update the agent with this new information
                self.update_agent_sysinfo(sessionID, listener=listener, internal_ip=internal_ip, username=username, hostname=hostname, os_details=os_details, high_integrity=high_integrity, process_name=process_name, process_id=process_id, ps_version=ps_version)

                # encrypt the agent and send it back
                encryptedAgent = encryption.aes_encrypt(sessionKey, agentCode)

                # signal everyone that this agent is now active
                dispatcher.send("[+] Initial agent "+str(sessionID)+" from "+str(clientIP) + " now active", sender="Agents")
                output =  "[+] Agent " + str(sessionID) + " now active:\n"

                # set basic initial information to display for the agent
                agent = self.mainMenu.agents.get_agent(sessionID)

                keys = ["ID", "sessionID", "listener", "name", "delay", "jitter","external_ip", "internal_ip", "username", "high_integrity", "process_name", "process_id", "hostname", "os_details", "session_key", "checkin_time", "lastseen_time", "parent", "children", "servers", "uris", "old_uris", "user_agent", "headers", "functions", "kill_date", "working_hours", "ps_version", "lost_limit"]

                agentInfo = dict(zip(keys, agent))

                for key in agentInfo:
                    if key != "functions":
                        output += "  %s\t%s\n" % ('{0: <16}'.format(key), messages.wrap_string(agentInfo[key], width=70))

                # save the initial sysinfo information in the agent log
                self.save_agent_log(sessionID, output + "\n")

                return(200, encryptedAgent)

            else:
                dispatcher.send("[!] Agent "+str(sessionID)+" posted sysinfo without initial checkin", sender="Agents")
                return (404, "")

        # default behavior, 404
        else:
            return (404, "")
Esempio n. 7
0
    def process_get(self, port, clientIP, sessionID, resource):
        """
        Process a GET request.
        """

        # check to make sure this IP is allowed
        if not self.is_ip_allowed(clientIP):
            dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" on the blacklist/not on the whitelist.", sender="Agents")
            return (200, http.default_page())

        # see if the requested resource is in our valid task URI list
        if (self.is_uri_present(resource)):

            # if no session ID was supplied
            if not sessionID or sessionID == "":
                dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" with no session ID.", sender="Agents")
                # return a 404 error code and no resource
                return (404, "")

            # if the sessionID doesn't exist in the cache
            # TODO: put this code before the URI present? ...
            if not self.is_agent_present(sessionID):
                dispatcher.send("[!] "+str(resource)+" requested by "+str(clientIP)+" with invalid session ID.", sender="Agents")
                return (404, "")

            # if the ID is currently in the cache, see if there's tasking for the agent
            else:
                
                # update the client's last seen time
                self.update_agent_lastseen(sessionID)

                # retrieve all agent taskings from the cache
                taskings = self.get_agent_tasks(sessionID)

                if taskings and taskings != []:

                    allTaskPackets = ""

                    # build tasking packets for everything we have
                    for tasking in taskings:
                        taskName, taskData = tasking
                        
                        # if there is tasking, build a tasking packet
                        taskPacket = packets.build_task_packet(taskName, taskData)
                        
                        allTaskPackets += taskPacket

                    # get the session key for the agent
                    sessionKey = self.agents[sessionID][0]

                    # encrypt the tasking packets with the agent's session key
                    encryptedData = encryption.aes_encrypt_then_mac(sessionKey, allTaskPackets)

                    return (200, encryptedData)

                # if no tasking for the agent
                else:
                    # just return the default page
                    return (200, http.default_page())

        # step 1 of negotiation -> client requests stage1 (stager.ps1)
        elif resource.lstrip("/").split("?")[0] == self.stage0:
            # return 200/valid and the initial stage code

            if self.args and self.args.debug:
                dispatcher.send("[*] Sending stager (stage 1) to "+str(clientIP), sender="Agents")

            # get the staging information for the given listener, keyed by port
            #   results: host,port,cert_path,staging_key,default_delay,default_jitter,default_profile,kill_date,working_hours,istener_type,redirect_target,lost_limit
            config = self.listeners.get_staging_information(port=port)
            host = config[0]
            stagingkey = config[3]
            profile = config[6]
            stage = None

            # if we have a pivot or hop listener, use that config information instead for the stager
            if "?" in resource:
                parts = resource.split("?")
                if len(parts) == 2:
                    decoded = helpers.decode_base64(parts[1])

                    # http://server:port for a pivot listener
                    if decoded.count("/") == 2:
                        host = decoded
                    else:
                        # otherwise we have a http://server:port/hop.php listener
                        stage = self.stagers.generate_stager_hop(decoded, stagingkey, profile)

            if not stage:
                # generate the stage with appropriately patched information
                stage = self.stagers.generate_stager(host, stagingkey)

            # step 2 of negotiation -> return stager.ps1 (stage 1)
            return (200, stage)

        # default response
        else:
            # otherwise return the default page
            return (200, http.default_page())