Techniques24-Jan-26|12 min read

Invisible Unicode Attacks: Detection Guide

Detection patterns, defensive implementations, and the taxonomy of invisible character attacks

Definition:

Zero-width Unicode obfuscation exploits invisible characters from the Unicode standard to encode malicious payloads within seemingly empty text. Unlike traditional encoding methods that produce visible output, zero-width obfuscation renders payloads completely invisible to human reviewers and most static analysis tools. First weaponised in targeted phishing campaigns in early 2025, the technique reached critical mass with the GlassWorm supply chain worm in October 2025, which compromised over 35,000 VSCode installations using invisible code injection.

You are staring at a blank line in a code review. The diff shows whitespace changes. You approve the merge. You just deployed a remote access trojan. Koi Security documented exactly this pattern in October 2025: seven compromised VSCode extensions on the OpenVSX marketplace, malicious code encoded in Unicode characters that render as empty space. Reviewers saw nothing because there was nothing to see. You already understand zero-width obfuscation conceptually. What you need are detection patterns you can deploy, queries you can run, and response procedures you can execute. That is what follows.

The Invisible Character Taxonomy

Unicode defines multiple character categories that occupy no visual space. Attackers have weaponised each differently. Knowing which characters map to which attack techniques determines what you search for.

Key Examples
Hangul Fillers
Code PointsU+3164, U+FFA0
Attack TechniqueBinary payload encoding
First ObservedOctober 2024
Detection PriorityCritical
Zero-Width Spaces
Code PointsU+200B, U+200C, U+200D
Attack TechniqueBinary encoding, URL obfuscation
First ObservedJanuary 2019 (Z-WASP)
Detection PriorityCritical
Variation Selectors
Code PointsU+FE00-FE0F
Attack TechniqueNibble encoding (4x denser)
First ObservedOctober 2025 (GlassWorm)
Detection PriorityCritical
BiDi Overrides
Code PointsU+202A-U+202E, U+2066-U+2069
Attack TechniqueSource code logic manipulation
First ObservedNovember 2021 (Trojan Source)
Detection PriorityHigh
Soft Hyphen
Code PointsU+00AD
Attack TechniqueEmail header obfuscation
First ObservedJanuary 2025 (Shy Z-WASP)
Detection PriorityMedium
Braille Blank
Code PointsU+2800
Attack TechniqueExtension masquerading
First ObservedAugust 2024 (Void Banshee)
Detection PriorityMedium
Tag Characters
Code PointsU+E0000-U+E007F
Attack TechniqueLLM prompt injection
First ObservedJanuary 2024
Detection PriorityEmerging

The Hangul filler technique dominates current attacks. The Halfwidth Hangul Filler (U+FFA0) represents binary zero; the Hangul Filler (U+3164) represents binary one. Eight invisible characters encode one ASCII byte. This is the encoding used in Tycoon 2FA phishing kits and the Juniper-documented PAC campaign.

GlassWorm introduced Variation Selector encoding, which achieves higher density by mapping each selector to a nibble (4 bits) rather than a single bit. This reduces payload size by 50% compared to binary encoding, making detection based on file size anomalies less reliable.

Detection Patterns You Can Deploy Today

The gap in existing guidance is not explanation. It is implementation. Here are patterns you can use immediately.

Command-Line Detection

Find files containing invisible characters in any codebase:

bash
# Detect Hangul fillers (most common attack vector)
grep -rP '[\x{3164}\x{FFA0}]' --include="*.js" --include="*.ts" --include="*.py" .

# Detect zero-width spaces and joiners
grep -rP '[\x{200B}-\x{200D}\x{2060}\x{FEFF}]' --include="*.js" --include="*.ts" .

# Detect variation selectors (GlassWorm technique)
grep -rP '[\x{FE00}-\x{FE0F}]' --include="*.js" --include="*.ts" .

# Detect BiDi overrides (Trojan Source)
grep -rP '[\x{202A}-\x{202E}\x{2066}-\x{2069}]' .

# Comprehensive scan - all suspicious invisible characters
grep -rP '[\x{200B}-\x{200D}\x{2060}\x{FEFF}\x{3164}\x{FFA0}\x{00AD}\x{2800}\x{202A}-\x{202E}\x{FE00}-\x{FE0F}]' .

For large repositories, ripgrep provides significantly better performance:

bash
rg '[\u200B-\u200D\u2060\uFEFF\u3164\uFFA0\u00AD\u2800\u202A-\u202E\uFE00-\uFE0F]' --type js --type ts --type py

Git Pre-Commit Hook

Block commits containing invisible characters before they enter your repository:

bash
#!/bin/bash
# .git/hooks/pre-commit

INVISIBLE_PATTERN='[\x{200B}-\x{200D}\x{2060}\x{FEFF}\x{3164}\x{FFA0}\x{00AD}\x{2800}\x{202A}-\x{202E}\x{FE00}-\x{FE0F}]'

if git diff --cached --name-only | xargs grep -lP "$INVISIBLE_PATTERN" 2>/dev/null; then
    echo "ERROR: Commit blocked. Invisible Unicode characters detected."
    echo "Files containing suspicious characters:"
    git diff --cached --name-only | xargs grep -lP "$INVISIBLE_PATTERN"
    echo ""
    echo "If these are intentional (e.g., internationalisation files), use:"
    echo "  git commit --no-verify"
    exit 1
fi

GitHub Actions Workflow

Scan pull requests automatically in CI/CD:

yaml
name: Invisible Character Scan
on: [pull_request]

jobs:
  scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Scan for invisible Unicode
        run: |
          FOUND=$(grep -rP '[\x{200B}-\x{200D}\x{2060}\x{FEFF}\x{3164}\x{FFA0}\x{00AD}\x{2800}\x{202A}-\x{202E}\x{FE00}-\x{FE0F}]' \
            --include="*.js" --include="*.ts" --include="*.py" --include="*.jsx" --include="*.tsx" \
            . 2>/dev/null || true)
          
          if [ -n "$FOUND" ]; then
            echo "::error::Invisible Unicode characters detected in source files"
            echo "$FOUND"
            exit 1
          fi
          echo "No invisible characters detected"

YARA Rule

For malware analysis pipelines and EDR integration:

yara
rule Invisible_Unicode_Payload {
    meta:
        description = "Detects invisible Unicode character encoding used in malware"
        author = "KlaroSkope"
        reference = "GlassWorm, Tycoon 2FA, Juniper PAC campaigns"
        severity = "high"
    
    strings:
        // Hangul filler binary encoding: U+3164 (\xe3\x85\xa4) and U+FFA0 (\xef\xbe\xa0)
        $hangul = /(\xe3\x85\xa4|\xef\xbe\xa0){8,}/ // 8+ invisible chars = 1+ encoded bytes
        
        // ZWS/ZWNJ binary pattern: U+200B-U+200D
        $zws = /[\xe2\x80\x8b-\xe2\x80\x8d]{16,}/ // UTF-8 encoded
        
        // Variation selectors (GlassWorm): U+FE00-U+FE0F
        $vs = /[\xef\xb8\x80-\xef\xb8\x8f]{8,}/ // UTF-8 encoded
        
        // BiDi override sequences (Trojan Source): U+202A-U+202E
        $bidi = /[\xe2\x80\xaa-\xe2\x80\xae]/ // Single instance is suspicious
        
    condition:
        any of them
}

Splunk SPL Query

For SIEM environments ingesting source code or log data:

spl
index=code_repos sourcetype=git_commits
| eval has_hangul=if(match(_raw, "[\x{3164}\x{FFA0}]+"), 1, 0)
| eval has_zws=if(match(_raw, "[\x{200B}-\x{200D}]+"), 1, 0)
| eval has_bidi=if(match(_raw, "[\x{202A}-\x{202E}]+"), 1, 0)
| where has_hangul=1 OR has_zws=1 OR has_bidi=1
| table _time, repo, author, commit_hash, file_path
| sort -_time

Real-World Attack Chains

Understanding documented campaigns helps calibrate detection priorities. Each represents confirmed compromise, not theoretical risk.

GlassWorm (October 2025-Present)The most sophisticated invisible Unicode attack documented to date. Koi Security identified the initial wave in seven VSCode extensions; subsequent waves expanded to npm packages and macOS variants. The attack chain: invisible Variation Selector characters decode to Base64, which decodes to AES-256-CBC encrypted data. The decryption key is fetched from HTTP response headers on a legitimate-looking domain. The final payload is ZOMBI RAT, which harvests GitHub tokens, npm credentials, SSH keys, and cryptocurrency wallet seeds.

What made GlassWorm unprecedented was its command-and-control infrastructure. Primary C2 instructions are encoded in Solana blockchain transaction memos. Because blockchain transactions are immutable, this infrastructure cannot be taken down. Backup C2 uses Google Calendar event titles. The combination creates what researchers termed "unkillable infrastructure."

Detection indicators: Variation Selector sequences in JavaScript files, HTTP requests with unusual response header patterns, Solana RPC API calls from developer workstations, unexpected Calendar API access.

Tycoon 2FA (January 2025-Present)A phishing-as-a-service kit that added invisible Unicode encoding to its evasion toolkit in early 2025. Trustwave SpiderLabs documented the technique in April 2025. The implementation stores malicious JavaScript as an object property with an invisible name (Hangul fillers). The object appears empty when inspected. A bootstrap script uses a JavaScript Proxy with a get() trap to intercept all property access, decode the invisible property name back to binary, reconstruct the code, and execute it via indirect eval.

This technique defeats static analysis because the malicious code does not exist in parseable form until runtime. The Proxy mechanism provides legitimate-seeming indirection.

Detection indicators: Hangul filler sequences in HTML/JavaScript, Proxy object instantiation patterns, indirect eval calls, anti-debugging checks (timing-based breakpoint detection).

Juniper PAC Campaign (February 2025)Juniper Threat Labs documented targeted phishing against political action committees using invisible JavaScript. The campaign demonstrated that the technique had moved from proof-of-concept to operational use by sophisticated threat actors. The attack used Hangul binary encoding with personalised lures containing non-public information about targets. Analysis revealed the same Proxy-based execution mechanism later seen in Tycoon 2FA, suggesting shared tooling or common ancestry.
Trojan Source (November 2021-Present)CVE-2021-42574 uses BiDi override characters (particularly Right-to-Left Override, U+202E) to manipulate how source code displays versus how it executes. Unlike payload encoding, this technique hides malicious logic within ostensibly visible code. A conditional statement can appear to check one value while actually checking another. Five years after disclosure, vulnerable code patterns remain in production systems.

Detection indicators: Any BiDi override character in source code outside of string literals or comments in explicitly RTL-supporting applications.

Multi-Layer Nesting Patterns

Zero-width encoding is never the final obfuscation layer. Analysis of documented campaigns reveals consistent nesting patterns that inform deobfuscation workflows.

The outermost layer is always zero-width encoding. This provides the stealth that bypasses visual inspection. Inner layers use traditional techniques that would be obvious if not wrapped in invisibility.

Base64 appears as the second layer in approximately 90% of samples. The decoded invisible content is almost always valid Base64. This provides character set normalisation for downstream processing.

Encryption or XOR typically appears as the third layer. Tycoon 2FA campaigns use CryptoJS for AES. GlassWorm uses AES-256-CBC with external key delivery. Simpler campaigns use single-byte XOR with predictable keys (0x17, 0x5A are common).

Compression occasionally appears within the chain, typically gzip. This reduces the size anomaly created by the 8:1 character expansion of binary encoding.

For analysis: decode invisible characters first, then check for Base64 signatures, then evaluate for XOR or additional encoding. Automated deobfuscation handles XOR with known or brute-forceable keys. Strong encryption (AES, RC4, ChaCha) represents a terminus. You can identify the encrypted blob and its likely algorithm, but decryption requires key recovery through memory forensics, traffic analysis, or reverse engineering the key derivation logic.

Response Procedures

If you detect invisible Unicode encoding in your environment, the presence of the technique indicates intentional obfuscation. Legitimate code does not accidentally contain Hangul filler sequences.

For source code findingsTreat the commit as potentially malicious. Identify the author and validate their identity. Review the full commit history for the affected files. Check whether the invisible content decodes to executable code. If it does, initiate incident response. The repository should be considered compromised.
For VSCode extension infections (GlassWorm specifically)The scope extends beyond the extension itself. The malware harvests credentials. Rotate: GitHub personal access tokens, npm publish tokens, SSH keys present on the workstation, any cryptocurrency wallet seeds accessible from the machine. Audit recent commits from the affected developer account for signs of propagation.
For email-based findings (Tycoon 2FA, phishing)The invisible encoding indicates an evasion-focused threat actor. Standard phishing response applies, but expect the payload to have anti-analysis features. Dynamic analysis in a sandboxed environment is preferable to static examination.

Why This Matters Now

The trajectory from Martin Kleppe's October 2024 proof-of-concept to GlassWorm's October 2025 supply chain compromise took twelve months. In that time, the technique moved from academic curiosity to active exploitation by multiple threat actors across phishing, supply chain, and targeted intrusion campaigns.

No CISA advisory addresses invisible Unicode attacks. No NIST guidance covers detection. The security community is building defences ad hoc, sharing techniques through blog posts and conference talks rather than standardised frameworks.

Organisations without explicit detection for invisible characters have a blind spot that sophisticated attackers are actively exploiting. Implementing detection now places you ahead of most defenders and ahead of eventual regulatory guidance.

KlaroSkope automatically detects and decodes invisible Unicode obfuscation across all documented encoding schemes, revealing hidden payloads through nested Base64, hex, URL encoding, and XOR layers. Try KlaroSkope Free →

Frequently Asked Questions

Q

What are the most common invisible characters used in malware?

Hangul Filler (U+3164) and Halfwidth Hangul Filler (U+FFA0) dominate current attacks, used for binary payload encoding in Tycoon 2FA and Juniper-documented campaigns. Zero-Width Space (U+200B), Zero-Width Non-Joiner (U+200C), and Zero-Width Joiner (U+200D) appear in older attacks. Variation Selectors (U+FE00-FE0F) were introduced by GlassWorm for higher-density encoding. BiDi overrides (U+202A-U+202E) enable Trojan Source logic manipulation attacks.
Q

How do I detect invisible Unicode characters in my codebase?

Use grep or ripgrep with Unicode-aware patterns. The command grep -rP '[\x{200B}-\x{200D}\x{3164}\x{FFA0}\x{FE00}-\x{FE0F}]' . will find files containing the most commonly weaponised invisible characters. Implement this as a pre-commit hook to prevent invisible characters from entering your repository, and as a CI/CD check to catch them in pull requests.
Q

What is GlassWorm and why is it significant?

GlassWorm is a self-propagating worm discovered in October 2025 that infected VSCode extensions on the OpenVSX marketplace. It represents the first supply chain attack using invisible Unicode encoding, compromising over 35,000 installations. The malware introduced Variation Selector encoding for higher payload density and used Solana blockchain transactions as command-and-control infrastructure, making takedown effectively impossible.
Q

Can static analysis tools detect invisible Unicode obfuscation?

Most static analysis tools fail to detect invisible Unicode payloads because they search for recognisable patterns in visible content. When the malicious code is encoded as invisible characters, there are no patterns to match. Detection requires explicit scanning for invisible character presence before the payload can be analysed.
Q

What should I do if I find invisible Unicode encoding in code I'm analysing?

First, decode the invisible characters to reveal the hidden content using binary-to-ASCII conversion. The decoded content will almost always require further deobfuscation, usually Base64 decoding followed by decryption. For incident response, treat any invisible encoding in source code as intentional and potentially malicious. Investigate the commit author, rotate credentials that may have been exposed, and audit for propagation.

Found this useful? Sharing is caring!

Ready to decode?

See KlaroSkope transform obfuscated scripts into actionable intelligence.

Try It Free