Introduction
Whilst writing a PowerShell Packer, I had a quick look into ConvertTo-SecureString and quickly remembered it is a feature in Invoke-Obfuscation . Looking into this as a method of obfuscation then led me to PowerShell Obfuscation using SecureString .
This seems to be a trend now, but what I wanted was to use it in an Environmental Keying scenario.
But first, what is it...
About SecureString
SecureString , as far as I can tell, is an AES encrypted string which aims to help users mask credentials. To see how secure SecureString
is, Microsoft have a How Secure is SecureString explanation.
Lets have a look at an example:
In the above, Get-WmiObject
is used to query a remote computer. It then failed, and a new credential was created and used; this allowed access.
Small Proof-of-Concept
As an example, here is how data would be encrypted:
Get a key: For now, an array of 0 -> 31 will do.
Encrypt the data with ConvertTo-SecureString
Copy ConvertFrom-SecureString -Key $key (ConvertTo-SecureString "Get-Date" -AsPlainText -Force)
This will produce something like:
Copy 76492d1116743f0423413b16050a5345MgB8AG8AVQBaAGwAUgBpAEkAZQAzAHYAOQBIAEEAdgBkADMAVABqAGkANwBvAEEAPQA9AHwAMwAwADAAMAAwADIANwBlADIANgBjAGUAOAA1ADgAZABiADMAMQBhAGMAMgA0ADAAMQA1AGUAZQA0ADkAYQA5ADEANgA4ADIANABjADMAYwAxADMAOABkADkAOABiADUAYgA3ADMAMwBlAGYAZgAzADcAMAAxAGEAOABjADgAYQA=
To then decrypt it, the following command can be used:
Copy (New -Object System . Net . NetworkCredential ( "" , (ConvertTo - SecureString - key $key $encrypted) ) ) . Password
Which looks like this:
Quite simple.
Using SecureString for Keying
Now lets take a quick look at how this can be used with keying... Honestly, its quite simple. Assume the payload to run is Get-Date
, and the keying string is:
Copy $env:USERDNSDOMAIN\$env:USERNAME
In this case, it would be:
To join the string:
Copy ( -join($env:USERDNSDOMAIN, '\' ,$env:USERNAME )) .ToLower ()
To convert this to a byte array:
Copy [ system . Text . Encoding ]::UTF8 .GetBytes ((( - join($env:USERDNSDOMAIN,'\',$env:USERNAME)).ToLower()))
This now has one issue, it is not of length 32. Which, again, is easy to fix with PadRight()
:
Copy [ system . Text . Encoding ]::UTF8 .GetBytes ((( - join($env:USERDNSDOMAIN,'\',$env:USERNAME)).PadRight( 32 , 0 ).ToLower()))
Wrapping this up:
Copy $key = [system.Text.Encoding]::UTF8.GetBytes(((-join($env:USERDNSDOMAIN,'\',$env:USERNAME)).PadRight(32,0).ToLower()))
ConvertFrom-SecureString -Key $key (ConvertTo-SecureString "Get-Date" -AsPlainText -Force)
In my case, it produces:
Copy 76492d1116743f0423413b16050a5345MgB8AFYAMQBhAEYARABsAGYAaQBjAEgAeABSAEYAQQAvAE8AQgBwADMAVABDAHcAPQA9AHwAMwA3ADgAOQA4ADYANgBmAGIANwBlADUAZgA0ADMAMQBjADUANQA4ADkAYQBjADQAMgBjAGMAMwBiAGQANABjADMAOQA=
Here is an example of it all running:
Now that it works, lets automate it.
Automating it
I was unable to find a good way to do this natively in Linux, and I didn't want to do it on Windows because of Invoke-Obfuscation, and I tend to work from Linux 99% of the time anyway.
Here is the script I threw together which relies on PowerShell for Linux:
Copy import subprocess
def get_encrypted_payload (payload : str , password : str ) -> str :
base_command: str = f"$key = [system.Text.Encoding]::UTF8.GetBytes('{password}'.PadRight(32,0));ConvertFrom-SecureString -Key $key (ConvertTo-SecureString '{payload}' -AsPlainText -Force)"
try:
output = (
subprocess . check_output ([ "pwsh" , "-c" , base_command]) . decode () . strip ( "\n" )
)
return output
except Exception as e :
print (f "[!] Error: {str(e)}" )
return None
def executor (encrypted : str ) -> str :
password = "(([System.Text.encoding]::UTF8.GetBytes(((-join($env:USERDNSDOMAIN,'\\',$env:USERNAME)).PadRight(32,0).ToLower()))))"
return f"(New-Object System.Net.NetworkCredential('', (ConvertTo-SecureString -key ${password} '{encrypted}'))).Password|Invoke-Expression"
def main () -> None :
password : str = "johto.local\\lance"
payload : str = "Get-Date"
encrypted : str = get_encrypted_payload (payload, password)
if not encrypted :
quit ()
cradle : str = executor (encrypted)
print (cradle)
if __name__ == "__main__" :
main ()
This script is a Python3.9+ utility which automates all of the previous steps discussed. Running the script will give:
Copy (New-Object System.Net.NetworkCredential('', (ConvertTo-SecureString -key $(([System.Text.encoding]::UTF8.GetBytes(((-join($env:USERDNSDOMAIN,'\',$env:USERNAME)).PadRight(32,0).ToLower())))) '76492d1116743f0423413b16050a5345MgB8ADEAMABOADgAUQBBADQATQBDAEoAZABpAE4AdwA2AFoAdQBiAE8AVgBDAFEAPQA9AHwAYwBmAGMAMwA5AGMAZgA2AGYANwA5ADQAOAA4ADkAZABlADcAMAAxADMAYQBhADgAMQA3ADAAOQA2ADcAMgAyADEANAA3ADIANABkADUAMgA1ADEAMQBiAGIANwAyAGMAMQAwADEANQBjADMAOAA5ADYAOQAzADkAMAA4AGUAYwA='))).Password|Invoke-Expression
Running on the incorrect target:
And on the correct host:
Voila.
Conclusion
This isn't new, nor is it particularly exciting. Its just something I ended up spending a few hours playing with. As PowerShell doesn't really have much usage offensively any more, it is also widely used in dotnet .
Go Up!