The Prestige of Malware: Unmasking ClickFix, Destructor Hijacking, and the “Dictionary Symphony”
好的,我现在需要帮用户总结一篇文章的内容,控制在100字以内。用户的要求是直接写文章描述,不需要特定的开头。首先,我得仔细阅读文章内容,抓住主要信息。 文章主要讲的是ClickFix攻击链如何通过伪装成普通的CAPTCHA或浏览器更新来诱骗用户点击,进而安装恶意软件。这种攻击利用了用户的信任和对常见弹窗的忽视。一旦点击,就会启动一个多阶段的感染过程。 攻击的第一步是通过MSI安装程序传播恶意软件,利用DLL搜索顺序劫持加载恶意DLL。然后,通过劫持C++对象的析构函数来执行恶意代码,避开传统的DLL入口点检测。接着,使用“字典交响乐”技术解密payload,结合自定义字母表和滚动XOR加密来混淆代码。 最后,Lumma Stealer窃取用户的浏览器会话cookie和token,绕过MFA保护,直接接管账户。文章还提到即使使用MFA也不能完全防止这种攻击,因为攻击者窃取的是已经认证的会话。 总结时需要涵盖攻击链的关键点:伪装、多阶段感染、DLL劫持、解密技术以及绕过MFA的方式。同时要控制在100字以内,所以要简洁明了。 现在组织语言:ClickFix攻击通过伪装成普通弹窗诱骗点击,启动多阶段感染链。利用DLL劫持加载恶意软件,并通过C++析构函数执行代码。结合自定义字母表和滚动XOR解密payload。最终窃取会话cookie和token,绕过MFA控制企业账户。 </think> ClickFix攻击通过伪装成普通弹窗诱骗点击,启动多阶段感染链,利用DLL劫持加载恶意软件,并结合自定义字母表和滚动XOR解密payload,最终窃取会话cookie和token,绕过MFA控制企业账户. 2026-3-14 04:49:15 Author: infosecwriteups.com(查看原文) 阅读量:22 收藏

MabLevi

How a single “I’m not a robot” click can hand attackers your corporate Slack, Azure AD, and AWS sessions, even if you use MFA.

Press enter or click to view image in full size

It’s Friday evening. You’re clearing tickets, browsing a site, or trying to open a document when a clean, professional overlay appears:

“Verify you are human to continue. Click to fix browser issues.”

Many people would click. It looks like a routine web annoyance.

This infection chain follows the three acts of a classic magic trick, much like Christopher Nolan’s The Prestige:

“🛡️Note on intent: This is a defensive analysis for awareness and detection. It intentionally omits operational details that would materially help deploy the chain.”

  1. The Pledge: The attacker shows you something ordinary — a routine CAPTCHA or a browser update. You trust it because it looks familiar.
  2. The Turn: The ordinary becomes extraordinary. The malware “disappears” into the memory space of a trusted, signed process through Sideloading and Destructor Hijacking.
  3. The Prestige: The final reveal. Out of the “Dictionary Symphony” emerges the Lumma Stealer, fully operational and exfiltrating your data before you even realize the trick was played.

That overlay is the ClickFix pattern (also called “FakeUpdates”): a social-engineering trick designed to move a target from “this is suspicious” to “I just want it to work.” One click, an MSI installer launches, and a multi-stage infection chain begins.

Further reading: Inside a real ClickFix attack (BleepingComputer).

Within minutes, the attacker is not “stealing a password.” They are stealing active web sessions: the cookies and tokens that prove you already logged in. That is why MFA alone often does not save you.

This article reverse-engineers one such chain delivering Lumma Stealer, focusing on three ideas that matter to both everyday users and defenders:

  • Weaponized trust: the malware hides behind signed Microsoft binaries.
  • Delayed execution: the loader runs from an unexpected place, inside a C++ object’s cleanup path.
  • A “Dictionary Symphony”: a two-layer decode routine designed to slow analysts and break simple emulation.

“🧠 If you only remember one thing: attackers do not need your password if they can steal the browser session that proves you already authenticated.”

What you can learn (even if you never reverse malware)

ClickFix campaigns succeed because they target normal behavior:

  • You trust overlays that look “browser-like.”
  • You assume an installer is harmless if it looks official.
  • You assume MFA prevents account takeover.

Specialists: the technical core here is a loader chain that combines MSI execution, DLL search order hijacking, vtable/destructor hijacking, in-memory RWX staging, and a custom decode routine.

Stage 1: The MSI Shell Game (evidence in the IDT)

The infection begins with a weaponized MSI installer. Instead of treating it as a black box, we extract MSI database tables (.idt) to see exactly what is planted and what is executed.

In File.idt, the main “actors” are:

  • Distributed_Pr.exe (Internal ID: I6yKanRU): a digitally signed, legitimate binary (~4.1MB)
  • mfc100.dll (Internal ID: i6WamKb0QZ9L): a core Microsoft library, but modified
  • 8nbzn8e_.flc (Internal ID: buqVwfBpEI): a high-entropy payload (~2.2MB)
  • o — hihq_.zxt (Internal ID: douFGdE7): a 28KB “dictionary” file used during decoding

Press enter or click to view image in full size

Deep dive into the MSI database: The File table links the legitimate carrier to the malicious components, while CustomAction dictates the silent launch.

CustomAction.idt reveals the trigger: LaunchFile (Type 210) executes I6yKanRU without command-line arguments.

That simplicity is deliberate. The MSI places files in a user-writable directory and launches a trusted binary from there. When that signed EXE resolves dependencies, it loads the attacker’s mfc100.dll from the same directory instead of the legitimate copy in System32.

This is DLL search order hijacking (MITRE T1574.002). Many defenders also refer to related tradecraft as DLL side-loading. Regardless of the name, the idea is the same:

  • a legitimate signed process starts
  • a malicious DLL is loaded because it is placed where Windows will look first

Trust is inherited.

“✅For everyday users: if a random webpage tells you to “fix” your browser by downloading an installer, treat it as hostile.”

Stage 2: The trick: hijacking a C++ destructor instead of DllMain

Many EDR products pay close attention to DllMain. To reduce attention, this loader avoids the typical DLL entry-point trigger and hides execution inside a less-observed path: C++ object cleanup.

While analyzing the hijacked mfc100.dll in Ghidra, the CAnimationBaseObject class looked abnormal. Tracing the virtual function table (vtable) revealed the payload: the Scalar Deleting Destructor had been redirected to Ordinal_13834.

Here is the mental model:

  • Normal code path: destructor → memory cleanup → return
  • Modified path: destructor pointer → Ordinal_13834 → loader trampoline → shellcode staging

The “Prestige” moment: the malware does not need to execute at program start. It can wait until the application finishes a task and attempts to clean up.

When the destructor runs, the process unknowingly jumps into the loader. But here is the critical detail: the destructor isn’t the brain; it’s the firing pin. In the debugger, you won’t find complex decryption logic inside the MFC class. Instead, you typically see a short transfer of control: a sharp JMP or CALL (often indirect) that pivots execution into a newly staged, dynamically allocated memory region.

As the application cleans up its GUI and prepares to exit, it inadvertently breathes life into the malware. The moment that trigger is pulled, the CPU stops “cleaning” and starts churning. If you are watching memory telemetry, you will see the cinematic version of an entropy explosion backed by very real signals: fresh private executable memory appears (often with RWX→RX transitions), buffers fill with high-entropy bytes, and then the final reveal begins as the shellcode “unrolls” the payload inside Distributed_Pr.exe.

Ordinal_13834 acts as a trampoline to Ordinal_14142, which:

  • resolves APIs dynamically
  • allocates PAGE_EXECUTE_READWRITE (RWX) memory
  • transitions into shellcode

Press enter or click to view image in full size

The malicious payload logic decompiled: Ordinal_14142 acts as the loader, changing memory permissions, copying the shellcode into executable memory, and finally passing execution control via a function pointer.

🛠️While dynamic analysis tools crashed against the anti-emulation barriers, manual reverse engineering revealed the mathematical heart of the “Dictionary Symphony” In DFIR, simply knowing a payload is encrypted isn’t enough; you have to understand the adversary’s logic.

Get MabLevi’s stories in your inbox

Join Medium for free to get updates from this writer.

Remember me for faster sign in

The mechanism relies on a strict context dependency. Without the specific 28KB dictionary file dropped by the MSI, the payload is cryptographic noise. Here is the reconstructed logic demonstrating how the shellcode translates the custom alphabet and applies the rolling XOR to reveal the final Stealer:

import base64

def decrypt_Symphony(payload_data: bytes, dict_data: bytes) -> bytes:
"""
Reconstructs the Lumma payload by resolving the two-layer 'Symphony' logic.
Layer 1: Custom Alphabet Mapping
Layer 2: Rolling XOR with Dictionary
"""

# --- Layer 1: Custom Alphabet Reconstruction ---
# The loader builds a 64-byte alphabet from the unique bytes of the dictionary
alphabet = []
for b in dict_data:
if b not in alphabet:
alphabet.append(b)
if len(alphabet) == 64:
break

# Here, the shellcode would map the text-based payload back to 6-bit values
# using the 'alphabet' as a translation table (Legacy-style Base64)
# [Analyst Note: Exact bit-packing logic is omitted for brevity]

# --- Layer 2: Rolling XOR ---
# Every byte of the decoded payload is XORed with the dictionary file
out = bytearray(len(payload_data))
dict_len = len(dict_data)

for i, b in enumerate(payload_data):
# The rolling key dependency ensures analysis fails without the correct .zxt file
out[i] = b ^ dict_data[i % dict_len]

return bytes(out)
# Final output: The "Prestige" moment - resulting in a valid MZ header
# print(decrypt_Symphony(payload, dictionary)[:2].hex()) # Output: 4d5a

Stage 3: The “Dictionary Symphony” and the emulator trap

Once shellcode runs, it must decode the 2.2MB payload (.flc). The 28KB dictionary (.zxt) is not a decoy: it is required. This is the “Dictionary Symphony”

  1. The Custom Alphabet: The payload begins as obfuscated text. The shellcode reads the first unique bytes of the dictionary file to reconstruct a custom Base64 alphabet. This translates “text-looking” data (which can slip past basic filters) into raw binary.
  2. The Dictionary Symphony (Rolling XOR): This is where the magic happens. The malware doesn’t use a static key. It performs a cyclic XOR operation against the 28KB dictionary file (douFGdE7). It’s as if the malware is “reading a book” to recognize its own code.
# The "Symphony" logic (pseudo-code)
for i in range(payload_len):
payload[i] ^= dictionary[i % dict_len] # dict_len ≈ 28KB

Without that exact “secret book,” the 2.2MB payload remains a meaningless jumble of bytes — high entropy, hard to validate, and easy to misclassify during static triage.

Analyst’s toolbox: automating the unpacking

In DFIR, if you cannot reproduce the decode routine, you do not own the analysis.

Below is a skeleton that captures the key dependency (dictionary-driven XOR). It is intentionally incomplete for the alphabet-mapping step.

# "Dictionary Symphony" skeleton
# NOTE: This example demonstrates the dictionary-driven XOR dependency.
# TODO: Add the Base64-like remapping layer once the exact bit packing is confirmed.

def decrypt_Symphony(payload_data: bytes, dict_data: bytes) -> bytes:
# Layer 1 (TODO): Custom alphabet reconstruction + decode
# - build 64-byte alphabet from first unique bytes in dict_data
# - map text payload back to 6-bit values
# - repack into bytes
# Layer 2: Rolling XOR
out = bytearray(len(payload_data))
for i, b in enumerate(payload_data):
out[i] = b ^ dict_data[i % len(dict_data)]
return bytes(out)

The anti-emulation booby trap

When I tried to emulate the shellcode with scdbg, it crashed almost immediately. Early bytes included 0xD6, an opcode that decodes differently across modes and is invalid in x64 long mode.

In a real Windows environment, the loader expects the resulting fault to route execution through a Structured Exception Handler (SEH). In simpler emulators, it is a claymore mine.

Pivoting to Speakeasy (Mandiant) bypassed the trap and exposed the API sequence.

Press enter or click to view image in full size

The ‘D6’ booby trap in action: Speakeasy emulator crashing immediately upon hitting the intentionally invalid opcode placed by the threat actors.

Stage 4: Why MFA often doesn’t save you

Lumma Stealer doesn’t care about your password. In the modern threat landscape, identity is defined by state, not credentials. By stealing the cookies and tokens your browser stores after you’ve already passed MFA, the attacker effectively becomes a “ghost” inside your existing session.

It does not need your password if it can steal what your browser stores to keep you logged in: cookies, tokens, and session artifacts. That is how an attacker can replay access to Azure AD, AWS, Slack, and other portals as if they were the legitimate user.

What this looks like in practice:

  • The attacker imports or replays the stolen session.
  • The service sees a valid, already-authenticated context.
  • MFA is not triggered because the attacker is not logging in the normal way.

The downstream risk: session theft feeds the Initial Access Broker (IAB) pipeline. A low-skill “click” on Friday can become a full ransomware intrusion over the weekend.

Stage 5: Detection and threat hunting (focus on TTPs, not fragile IOCs)

IOCs are useful, but they decay quickly. This chain is better hunted via behavior.

🎯If you can only deploy 3 detections:

  • A signed binary launched by msiexec.exe from a user-writable directory (Temp/AppData) shortly after an MSI install event.
  • A common DLL name (for example, mfc100.dll) loaded from the same user-writable directory instead of System32.
  • Private / unbacked executable memory (RWX or rapid RW→RX transitions) appearing inside that signed process soon after startup.

1. Memory hunting: suspicious RWX in a trusted process

Hunt for:

  • private (unbacked) memory regions
  • PAGE_EXECUTE_READWRITE permissions
  • inside a signed process that should not JIT or self-modify

2. Behavioral correlation: provenance of DLL loads

Do not just alert on mfc100.dll. Alert on where it was loaded from and what launched the process.

  • Logic idea: a common system DLL name loaded from a user-writable directory (Temp/AppData) shortly after msiexec.exe activity.

3. Mechanism-focused YARA (loader-oriented)

Instead of hardcoding a filename, target artifacts of the loader’s behavior.

rule Universal_Vtable_Hijack_Loader {
meta:
description = "Detects DLLs manipulated via vtable/destructor hijacking for shellcode injection"
author = "(SOC/CSIRT Analyst)"
strings:
// X64 shellcode trampoline pattern (Ordinal or Indirect Jump)
$trampoline = { 48 83 EC ?? 48 8B ?? ?? ?? ?? ?? FF D? }

// VirtualProtect setup for RWX memory allocation
$rwx_alloc = { 41 B8 00 10 00 00 [0-10] BA 40 00 00 00 }

// Custom alphabet start often used in ClickFix dictionaries
$alphabet_start = { 65 6f 73 61 6f 68 78 65 } // "eosaohxe"

condition:
uint16(0) == 0x5A4D and (2 of them) and filesize < 10MB
}

🧩Advanced Detection Signals

For SOC teams looking to move beyond basic alerts, these telemetry signals provide much higher fidelity:

Conclusion

ClickFix works because it looks ordinary and targets impatience, not ignorance. The malware chain then exploits the trust we place in signed binaries and hides execution in places defenders often check last.

The uncomfortable takeaway is also the useful one:

  • Users: if a site asks you to “fix” your browser by installing something, close it.
  • Defenders: if a signed process loads a common DLL from a user-writable directory and then allocates RWX memory, treat it as a high-signal event.

MITRE ATT&CK mapping (observed behavior)

Because the initial entry point is not confirmed, the mapping below focuses on what is directly supported by analysis of the chain:

  • Execution: T1204.002 (Malicious File)
  • Defense Evasion: T1574.002 (Hijack Execution Flow: DLL Search Order Hijacking), T1055 (Process Injection), T1140 (Deobfuscate/Decode)
  • Credential Access: T1539 (Steal Web Session Cookie)

文章来源: https://infosecwriteups.com/the-prestige-of-malware-unmasking-clickfix-destructor-hijacking-and-the-dictionary-symphony-1c980f5582e5?source=rss----7b722bfd1b8d---4
如有侵权请联系:admin#unsafe.sh