Using SecureString to protect Malware
Malware and SecureString? Why not.
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...
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:
WMI Connection 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.As an example, here is how data would be encrypted:
- 1.Get a key: For now, an array of 0 -> 31 will do.
$key = (0..31)
- 2.Encrypt the data with ConvertTo-SecureString
ConvertFrom-SecureString -Key $key (ConvertTo-SecureString "Get-Date" -AsPlainText -Force)
This will produce something like:
76492d1116743f0423413b16050a5345MgB8AG8AVQBaAGwAUgBpAEkAZQAzAHYAOQBIAEEAdgBkADMAVABqAGkANwBvAEEAPQA9AHwAMwAwADAAMAAwADIANwBlADIANgBjAGUAOAA1ADgAZABiADMAMQBhAGMAMgA0ADAAMQA1AGUAZQA0ADkAYQA5ADEANgA4ADIANABjADMAYwAxADMAOABkADkAOABiADUAYgA3ADMAMwBlAGYAZgAzADcAMAAxAGEAOABjADgAYQA=
And here is a screenshot of that all executing:
To then decrypt it, the following command can be used:
(New-Object System.Net.NetworkCredential("", (ConvertTo-SecureString -key $key $encrypted))).Password
Which looks like this:
Converting Get-Date to SecureString
Quite simple.
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:$env:USERDNSDOMAIN\$env:USERNAME
In this case, it would be:
johto.local\lance
To join the string:
(-join($env:USERDNSDOMAIN,'\',$env:USERNAME)).ToLower()
To convert this to a byte array:
[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()
:[system.Text.Encoding]::UTF8.GetBytes(((-join($env:USERDNSDOMAIN,'\',$env:USERNAME)).PadRight(32,0).ToLower()))
Wrapping this up:
$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:
76492d1116743f0423413b16050a5345MgB8AFYAMQBhAEYARABsAGYAaQBjAEgAeABSAEYAQQAvAE8AQgBwADMAVABDAHcAPQA9AHwAMwA3ADgAOQA4ADYANgBmAGIANwBlADUAZgA0ADMAMQBjADUANQA4ADkAYQBjADQAMgBjAGMAMwBiAGQANABjADMAOQA=
Here is an example of it all running:
Decrypting Get-Date
Now that it works, lets automate 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:
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:
(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:
Incorrect target
And on the correct host:
Correct target
Voila.
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!