Created
January 13, 2021 16:47
-
-
Save khr0x40sh/a941c5d280a3eed594184352a1366c68 to your computer and use it in GitHub Desktop.
Generates the SUNBURST GUID and an example DGA prefix as would be seen on a compromised SolarWinds system
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function Invoke-Sunburst-GUID-creator | |
{ | |
<# | |
.Synopsis | |
SUNBURST GUID CREATOR | |
.Description | |
The script will generate the SUNBURST GUID and an example DGA prefix as would be seen on a compromised SolarWinds server. | |
The SUNBURST GUID is made up of the machine's primary non local loopback network adapter's MAC address, | |
the domain name of the system, and the (optional) GUID of the system in the registry. | |
These values are concatenated together and then an MD5 hash of the string is created. | |
Finally this MD5 hash is is condensed into 8 bytes with two rounds of XOR on the 16 bytes (upon themselves) of the MD5. | |
The SUNBURST GUID can then be used to track other packets the system may have sent out over the DGA DNS stage 1 C2 if those packets were decoded. | |
For more information on decoding the DGA, please visit: | |
https://www.netresec.com/?page=Blog&month=2020-12&post=Reassembling-Victim-Domain-Fragments-from-SUNBURST-DNS | |
After the malware GUID is created, an example DGA prefix will also be created to show what the first 15 bytes of DGA subdomain could look like. | |
It is important to note that the DGA prefix may not match any DNS logs as the prefix is based off a random number 1-127 and then XOR'd across the bytes. | |
As for the "Why did you do this?": | |
We (my team and I) were aiding a tiger team to hunt down hosts that had beaconed out Stage 1 C&C via DNS records. | |
After utilizing the amazing SUNBURST decoder assembly linked above, we were able to correlate hosts to their callouts. | |
Due to the nature of log ingestion, we could see the DNS requests and responses, but we sometimes did not see the originating system(s). | |
With the ability to take key information from the systems, or run this script on the systems in a sandbox, we were able to marry up the decoded DNS requests | |
and the GUIDs to the systems, so we could track what information left over DGA (don't worry, this wasn't the only thing that was examined). | |
Additionally, as far as emulating this attack, for educational purposes, we know have a quick way to show the first 15 bytes of a DGA may look like. | |
Eventually, I plan to include the ability to create the full sample DGAs from powershell, if others haven't already done so. And before someone says, | |
"well, couldn't you just do it from C# or do an Add-Type? ..." I have had some limited success doing this, but really want to have a nice clean port in psh! | |
If you have any issues or questions using this script, please direct them to @khr0x40sh on Twitter. | |
.Parameter machinename | |
External system's machinename. If this value is not provided, the script will assume you are running against the local system. | |
.Parameter mac | |
External system's MAC Address. If this value is not provided, the script will assume you are running against the local system. | |
.Parameter domain | |
External system's domain name. | |
.Parameter guid | |
External system's guid from the key HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\MachineGuid. | |
This is an "optional" parameter in the original malware | |
.Example | |
Import-Module SUNBURST-GUID-creator.ps1 | |
# Run on local system to generate SUNBURST GUID and DGA prefix from current system | |
Invoke-Sunburst-GUID-creator | |
# Run with parameters to calculate SUNBURST GUID and DGA prefix | |
Invoke-Sunburst-GUID-creator -machinename TESTDEV -domain dev.local -mac 11-22-33-44-55-66 -guid 12345678-1234-1234-1234-123456789ABC | |
# Run with parameters to calculate SUNBURST GUID and DGA prefix, output to CSV | |
Invoke-Sunburst-GUID-creator -machinename TESTDEV -domain dev.local -mac 11-22-33-44-55-66 -guid 12345678-1234-1234-1234-123456789ABC | Export-Csv -notypeinformation -path "C:\Users\Public\exported.csv" | |
# Run with parameters to calculate SUNBURST GUID and DGA prefix, append to CSV | |
Invoke-Sunburst-GUID-creator -machinename TESTDEV -domain dev.local -mac 11-22-33-44-55-66 -guid 12345678-1234-1234-1234-123456789ABC | Export-Csv -notypeinformation -path "C:\Users\Public\exported.csv" -Append | |
#> | |
Param($machinename = "", | |
$mac = "", | |
$domain = "", | |
$guid = ""); | |
function XOR{ | |
Param($byteA, $key); | |
$arr = @() | |
$arr += $key | |
for ($i=1; $i -lt $byteA.Length; $i++) | |
{ | |
$c = $byteA[$i] -bxor $key | |
$arr += $c | |
} | |
$arr | |
} | |
function MACaddr | |
{ | |
foreach( $nic in [System.Net.NetworkInformation.NetworkInterface]::GetAllNetworkInterfaces()) | |
{ | |
if ($nic.OperationalStatus -eq [System.Net.NetworkInformation.OperationalStatus]::Up -and $nic.NetworkInterfaceType -ne [System.Net.NetworkInformation.NetworkInterfaceType]::Loopback) | |
{ | |
return $nic.GetPhysicalAddress().ToString() | |
} | |
} | |
} | |
function CreateUserID | |
{ | |
Param($mac,$domain, $guid) | |
$text = $mac | |
$hash64 = @(0)*8 # new byte[8]; | |
#[Array]::Clear($hash64, 0, $hash64.Length); | |
if ($text -eq $null) | |
{ | |
return false; | |
} | |
$text += $domain | |
try | |
{ | |
if([System.String]::IsNullOrEmpty($guid)) | |
{ | |
# HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography | |
#MachineGuid | |
$text += (Get-ItemProperty -Path HKLM:\SOFTWARE\Microsoft\Cryptography\MachineGuid).ToString() | |
} | |
else | |
{ | |
$text += $guid | |
} | |
} | |
catch | |
{ | |
Write-Warning "[!] Could not retrieve Registry GUID. This is an optional piece of the malware GUID, but the results may differ from actual malware GUID." | |
} | |
$md = [System.Security.Cryptography.MD5]::Create() | |
[byte[]] $bytes = [System.Text.Encoding]::ASCII.GetBytes($text); | |
[byte[]] $array = $md.ComputeHash($bytes); | |
for ($i = 0; $i -lt $array.Length; $i++) | |
{ | |
$num = $i % $hash64.Length; | |
$hash64[$num] = $hash64[$num] -bxor $array[$i] | |
} | |
@($hash64, $array) | |
} | |
function CustomBase32{ | |
Param($bytes) | |
# This is based of the leaked C# source, hopefully I didn't mess up porting it over to psh lol :-) | |
[string] $text = "ph2eifo3n5utg1j8d94qrvbmk0sal76c" | |
$r = [Random]::new() | |
$xor = $r.Next(1,127) | |
$bytes = XOR $bytes $xor | |
$text2 = ""; | |
$num = 0 | |
$rt = $true | |
$i = 0 | |
foreach ($b in $bytes) | |
{ | |
$num = $num -bor $b -shr $i | |
for ($i += 8; $i -ge 5; $i -= 5) | |
{ | |
$text2 += $text[[int]($num -band 31)].ToString(); | |
$num = $num -shr 5 | |
} | |
} | |
if ($i -gt 0) | |
{ | |
if ($rt) | |
{ | |
$num = $num -bor ($r.Next() -shl $i) | |
} | |
$text2 += $text[[int]($num -band 31)].ToString(); | |
} | |
$text2 | |
} | |
function ConvertTo-Object($hashtable) | |
{ | |
$object = New-Object PSObject | |
$hashtable.GetEnumerator() | | |
ForEach-Object { Add-Member -inputObject $object ` | |
-memberType NoteProperty -name $_.Name -value $_.Value } | |
$object | |
} | |
<# MAIN #> | |
if ([System.String]::IsNullOrEmpty($mac) -and [System.String]::IsNullOrEmpty($machinename)) | |
{ | |
Write-Output "[-] Parameters are null, grabbing data from system..." | |
#Grab the primary NIC | |
$mac = MACAddr | |
#Grab the machinename | |
$machinename = [System.Environment]::MachineName; | |
#Grab the domain name | |
$domain = [System.Net.NetworkInformation.IPGlobalProperties]::GetIPGlobalProperties().DomainName; | |
} | |
else | |
{ | |
if ([System.String]::IsNullOrEmpty($domain)) | |
{ | |
Write-Error "[!] Domain not specified. Malware will not execute on non-domain joined system. Exiting..." | |
return | |
} | |
if ([System.String]::IsNullOrEmpty($guid)) | |
{ | |
Write-Warning "[!] No registry GUID specified. This is an optional parameter the malware may use, but if it is present the GUID output may be different!" | |
} | |
$mac = $mac.ToUpper().Replace("-","") | |
$mac = $mac.Replace(":", "") | |
$domain = $domain.ToLower() #Not 100% on this yet, but it appears that domain is always lowercase | |
} | |
$IDX = (CreateUserID $mac $domain $guid) | |
$prefix = CustomBase32 $IDX[0] | |
$machine = @{MachineName = $machinename; Mac= $mac; Domain=$domain; RegGUID = $guid; GUID = (($IDX[0]|ForEach-Object ToString X2) -join ''); MD5 = (($IDX[1]|ForEach-Object ToString X2) -join ''); Prefix = $prefix } | |
# I left the above hashtable in if you prefer hashtable output over the object output, you can just uncomment below | |
$machine = ConvertTo-Object $machine | |
$machine | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment