Skip to main content

Command Palette

Search for a command to run...

Extract config by CyberChef in advanced

Published
3 min read
Extract config by CyberChef in advanced

1. Introduction

After analyzing malware, the next common step is to extract its configuration information for future research. Most people choose to write Python scripts for this task. While Python is powerful, it can be a challenge for those who prefer not to code. For them, CyberChef is an excellent alternative.

In this post, I will use CyberChef to extract the C&C (Command & Control) server information from a Cobalt Strike malware sample. Note: I have modified this malware file slightly to fit the purpose of this guide.

Materials: https://github.com/n0pex3/blog/blob/main/cyberchef.zip

2. Methodology

2.1. Deobfuscating the PowerShell Code

We start with a PowerShell file that is nearly 800KB in size. The first thing that stands out is a very long Base64 string. To extract and decode this string, I used the Drop bytes and From Base64 operations.

As you can see, the output has NULL characters between the letters. This indicates that each character is represented by 16 bits, meaning the code is encoded in UTF-16. Therefore, we need to convert it to UTF16 using the Decode text operation with the UTF-16LE (1200) option.

Inside, we find another Base64 string. Looking at the end of the string, it appears this data was compressed with Gzip before being executed. To handle this, we use:

  1. Regular Expression to extract only the Base64 string and remove unnecessary data.

  2. From Base64 to decode it.

  3. Gunzip to decompress it.

At this point, we have successfully decoded part of the PowerShell script. However, there are still several layers of obfuscation. By scrolling through the code, we can see that the payload is decoded via Base64, decrypted using the AES algorithm with an embedded key, and finally XORed with the value 35 before being loaded into memory.

CyberChef has a very useful feature called Subsection. This allows you to transform a specific part of the data without affecting the rest. I will use Subsection to decode the Base64-encoded AES key and IV.

As shown above, the key and IV strings are decoded from Base64 while the rest of the bytes remain unchanged. Next, to make the process easier, we will create variables for the XOR key, AES key, and AES IV. This is helpful if you encounter similar samples with different keys later. We use the Register operation to store these values.

If your values match the image above, you have done it correctly. We now have:

  • R0: XOR key

  • R1: AES key

  • R2: AES IV

Just like before, we use Regex to grab the main Base64 payload, decode it, and use our stored variables to decrypt it. The final result is a PE (Portable Executable) file.

2.2. Extracting the Configuration

Now, we will extract the actual configuration. I am using the logic from SentinelOne’s CobaltStrikeParse tool as a reference.

This tool supports parsing Cobalt Strike versions v3 and v4. It finds the config using a regex pattern and decrypts it with a specific XOR key. We will do the same in CyberChef. To use "IF/ELSE" logic, we use the Conditional Jump and Label operations. If the condition in the Jump is met, CyberChef skips to the specified Label.

For example, the hex pattern \x69\x68\x69\x68\x69\x6b..\x69\x6b\x69\x68\x69\x6b..\x69\x6a is equivalent to the regex \ihihik.*\ikihik.*\ij, and the pattern \x2e\x2f\x2e\x2f\x2e...\x2e\x2c\x2e\x2f is equivalent to \.\/\.\/\..+\.,\.\/.

After successfully decrypting the configuration, we need to extract the server information. Since servers can be either domains or IP addresses, we use Conditional Jump and Label again to categorize and extract them separately.

3. Summary (The Recipe)

Below is the complete CyberChef recipe used in this process:

Drop_bytes(0,43,false)
From_Base64('A-Za-z0-9+/=',true,false)
Decode_text('UTF-16LE (1200)')
Regular_expression('User defined','[a-zA-Z0-9/\\+]{50,}',true,true,false,false,false,false,'List matches')
From_Base64('A-Za-z0-9+/=',true,false)
Gunzip()
Syntax_highlighter('powershell')
Subsection('[a-zA-Z0-9]{20,50}={1,3}?',true,true,false)
From_Base64('A-Za-z0-9+/=',true,false)
Merge(true)
Register('-bxor(\\s\\d{2})',true,false,false)
Register('\'([a-zA-Z0-9=]{20,50})\'',true,false,false)
Register('\\s\'([a-zA-Z0-9=]{10,20})\'',true,false,false)
Regular_expression('User defined','[a-zA-Z0-9/\\+]{50,}',true,true,false,false,false,false,'List matches')
From_Base64('A-Za-z0-9+/=',true,false)
AES_Decrypt({'option':'Latin1','string':'$R1'},{'option':'Latin1','string':'$R2'},'CBC','Raw','Raw',{'option':'Hex','string':''},{'option':'Hex','string':''})
XOR({'option':'Decimal','string':'$R0'},'Standard',false)
Conditional_Jump('\\ihihik.*\\ikihik.*\\ij',false,'V3',1)
Regular_expression('User defined','\\.\\/\\.\\/\\..+\\.,\\.\\/',true,true,false,false,false,false,'List matches')
XOR({'option':'Hex','string':'2e'},'Standard',false)
Jump('Extract',1)
Label('V3')
Regular_expression('User defined','\\ihihik.*\\ikihik.*\\ij',true,true,false,false,false,false,'List matches')
XOR({'option':'Hex','string':'69'},'Standard',false)
Label('Extract')
Conditional_Jump('\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}',false,'IP',1)
Extract_domains(true,false,true)
Jump('Exit',10)
Label('IP')
Extract_IP_addresses(true,false,false,true,false,true)
Label('Exit')