Writing a static string decryptor for the CargoBay backdoor

While browsing Cindy Xiao's excellent Rust malware gallery, I stumbled across her entry for the CargoBay backdoor:

It's difficult to definitively identify CargoBay samples, as public information about it is limited.

Talk about a sales pitch, right? I wanted to take a look at this malware and try to improve my knowledge of Rust reverse-engineering in the process.

Taking an initial look at CargoBay, its string encryption techniques are quite clear:

CargoBay first decodes a large base64-encoded string and uses it as a decryption key. Anytime a static string is loaded, CargoBay decodes an encrypted base64-string and decrypts it using a simple XOR operation:

To aid in reverse-engineering this sample, let's write a static string decryptor for CargoBay. The script has to perform the following steps:

  1. Identify the decryption key
  2. Identify the decryption key's global memory address
  3. Identify the decryption function
  4. Trace every reference to the decryption function
  5. Find every reference's encrypted string
  6. Decrypt each string ourselves

To identify the decryption key, we can make use of the fact that it is passed as an argument to the base64-decoding function:

Notice that a pointer to the base64-encoded string is loaded into rdx using an lea instruction, whereas the length of the string is moved into r8d as an immediate. We can use capstone to disassemble the entire .text section and search for the following pattern of instructions:

encryption_key_patterns = [
	"lea rdx",
	"lea rsi",
	"mov r8d, (0x.*?)$",
	"mov",
	"call"
]

This pattern finds every reference to the base64-decoding function. A key characteristic of CargoBay's string encryption is that the decryption key is the longest base64-encoded string in the binary. Thus, we can sort all occurances by the immediate value moved into r8d and identify the instance that decodes the decryption key.

Having identified the decryption key, we're also able to identify the global memory address that will hold a pointer to the decoded key data:

The disassembly reveals that the address is loaded into the rsi register using an lea instruction:

By simply walking forward from the base64-decoding function call to the first lea rsi instruction, we're able to identify the key's location in memory.

This enables us to easily identify the string decryption function. A typical instance of CargoBay's string decryption looks like this:

Note that the decryption key is consistently passed as the fourth argument in the function call. In assembly, this manifests as a mov r9 instruction. With this knowledge, we can search for the following type of instruction to identify the decryption function:

decryption_function_patterns = [
	r"mov r9, qword ptr \[rip \+ 0x.*?\]"
]

If the pointer is equal to the decryption key's location in memory and there is a call instruction soon after, this is a potential reference to the string decryption function. We can iterate over all search results and choose the most common function that is referenced by the first nearby call instruction in order to definitively identify the right function address.

Once the string decryption function is identified, we're able to find every call instruction that references it, walk backwards to the most recent call instruction that references the base64-decoding function, extract the argument that points to the encrypted string and decrypt it ourselves:

Success! But a little more work remains to make good use of the decrypted strings. After a static string is decrypted, it is typically also moved to a global memory address:

After having walked backwards from the decryption call instruction, we can also walk forwards to extract this memory address for each decrypted string. By pairing each string to its memory address, we can output our data as a JSON blob:

At this point, I really wanted an easy way to import my results into IDA Pro so I could easily see which string is used in which location. This would allow me to quickly identify and circumvent CargoBay's evasive techniques, understand the actions it takes on objectives and analyze its C2 protocol.

So I wrote a plug-in for just this purpose! Better Annotator takes in a JSON blob that maps keys to decrypted strings and provides a couple of ways to annotate the IDB:

In my case, the virtual addresses are used as globals, so that's the option I selected. However, the plug-in also supports the creation of enums if you're mapping string indexes to their decrypted strings, as well as pseudocode comments if you're mapping decryption calls to their results.

After clicking the button, global variables are created and renamed within the IDB:

To further illustrate why this is helpful, let's take a look at this specific case:

Even though Rust malware is very difficult to read, I can understand at a glance that CargoBay is enumerating the victim's processes and checking each process name against the following hard-coded list of malware analysis tools:

  • vmsrvc.exe
  • tcpview.exe
  • wireshark.exe
  • fiddler.exe
  • vmware.exe
  • VirtualBox.exe
  • procexp.exe
  • autoit.exe
  • vboxtray.exe
  • vmtoolsd.exe
  • vmrawdsk.exe
  • vmusbmouse.exe
  • df5serv.exe
  • vboxservice.exe
  • OllyDbg.exe
  • x64dbg.exe
  • x32dbg.exe
  • WinDbg.exe
  • fakenet32.exe
  • fakenet64.exe
  • ProcessHacker.exe
  • autorunsc.exe
  • filemon.exe
  • procmon.exe
  • regmon.exe
  • idaq.exe
  • idaq64.exe
  • ImmunityDebugger.exe
  • dumpcap.exe
  • HookExplorer.exe
  • ImportREC.exe
  • PETools.exe
  • LordPE.exe
  • SysInspector.exe
  • proc_analyzer.exe
  • sysAnalyzer.exe
  • sniff_hit.exe
  • joeboxcontrol.exe
  • joeboxserver.exe
  • ResourceHacker.exe
  • Fidder.exe (❓)
  • httpdebugger.exe
  • PE-bear.exe
  • die.exe

As a side-note, these blacklists can be a great way to find out about cool analysis software as a junior analyst! I personally didn't know about iDaq Data Logger or ESET SysInspector before I analyzed CargoBay 😁.

Here's another example:

CargoBay appears to be capable of spawning new processes, or performing process injection on a common target like explorer.exe, upon being provided with a payload.

As a bonus, here's a run-down of CargoBay's language checks to avoid infecting victims located within the CIS:

My reverse-engineering skills for Rust malware aren't up to snuff yet, so I'll put off a more comprehensive analysis of CargoBay's functionality for another time.

Obfuscation


So far, my analysis is based on the following, largely unobfuscated samples of CargoBay:

SHA-256Submission Date
4a74f20641f31d3b0b63bb7baf4ff02fe8f11257fcf228aefc31d3e05cbf35d42022-11-08
a963a8a8e1583081daa43638744eef6c410d1a410c11eb9413da15a26e802de52023-02-17
5218ea3cfcc44010b18418240a2f2dd81cff0a52d5ba4a300b42563e346bf8442023-02-17

However, I've also encountered two additional CargoBay samples that are obfuscated and feature ChaCha20-based string decryption.

SHA-256Submission Date
b0620f36f136d0c8e4c036a67798de2902bbd45bd21bd026102d53285d56622c2022-11-09
9432024a253af6bbbd19d011547117edafff8a18b10c0ac739661999ad9ba2a22023-02-17

I also encountered another sample that is currently unlabelled but is almost certainly a newer, obfuscated instance of CargoBay based on the IOCs observed by this JoeSandbox report:

SHA-256Submission Date
daa78ec9ac5ba2efffe8ee414c348e2eafa787e341dea0ac83f602b56520fa752022-11-10

So far, the string decryptor only works for the unobfuscated samples. I have uploaded it to Github.

Indicators of Compromise


The following network indicators were observed during analysis:

http[:]//itfrontdiscovery[.]com
http[:]//itfrontdiscovery[.]com/XbnZ/XmznAcQ
http[:]//itfrontdiscovery[.]com/XbnZ/LiFeA/YteHhcI
http[:]//it-south-bridge[.]com
http[:]//it-south-bridge[.]com/XbnZ/XmznAcQ
http[:]//it-south-bridge[.]com/XbnZ/LiFeA/YteHhcI
http[:]//cloudstarsolution[.]com
http[:]//cloudstarsolution[.]com/XbnZ/XmznAcQ
http[:]//cloudstarsolution[.]com/XbnZ/LiFeA/YteHhcI
Windows-AzureAD-Authentication-Provider/2.0 (User-Agent)

The following host indicators were observed during analysis:

PathMode
C:\ProgramData\StndUpdateQuery
%APPDATA%\StndUpdateQuery
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProductNameRead
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\DisplayVersionRead
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\CurrentBuildNumberRead
SUpdate.batWrite
StdntsUpdate (Scheduled Task)Write