Invisible Unicode obfuscation hides a payload inside characters that render with no visible width: zero-width spaces and non-joiners, Hangul Filler glyphs, variation selectors, soft hyphens or bidirectional overrides. Two invisible characters standing for 0 and 1 are enough to carry a URL or a command through an email body, a chat message or a source file. KlaroSkope detects these code points in pasted text, reads the binary or nibble encoding they form, and returns the hidden string, so a message that looks like an ordinary sentence gives up the command it carried.

Worked examples
Synthetic samples. Each input decodes to the output shown, and the outputs are checked against the live engine before publication.
Example 1: Hangul Filler binary inside an invoice sentence
Your invoice is attached.\uffa0\u3164\u3164\uffa0\u3164\uffa0\uffa0\uffa0\uffa0\u3164\u3164\u3164\uffa0\u3164\uffa0\uffa0\uffa0\u3164\u3164\u3164\uffa0\u3164\uffa0\uffa0... (288 code points) Thank you.https://example.com/hidden-stage.txtU+FFA0 stands for 0 and U+3164 for 1; eight of them per byte spell the staging URL between two visible sentences.
Example 2: Zero-width space / non-joiner binary
Please review\u200b\u200c\u200c\u200c\u200b\u200b\u200b\u200b\u200b\u200c\u200c\u200b\u200c\u200c\u200c\u200c\u200b\u200c\u200c\u200c\u200b\u200c\u200c\u200c... (192 code points) the document.powershell -enc SQBFAFgAThe same idea with U+200B and U+200C; the recovered text is itself a PowerShell encoded command, a second layer to decode next.
Two ways invisible characters are abused
Invisible characters serve two different purposes in an obfuscated file, and telling them apart is the first decision an analyst makes. The first purpose is padding. A soft hyphen (U+00AD), a zero-width joiner (U+200D) or a word joiner (U+2060) is dropped into the middle of a keyword, so a reader still sees powershell while a rule matching that literal word does not fire. The characters carry no data of their own; they exist to break the match, and removing the listed code points restores the original text. That is the whole of the work for this variant. Phishing kits apply it to brand names and to the words a mail filter scores, and the same trick has turned up in package metadata and import names. The second purpose is carriage, and here the invisible run is the payload. Two glyphs are assigned to the bits 0 and 1, eight of them make a byte, and a few hundred of them carry a URL, a command or a key while the visible sentence around them is a decoy chosen to look ordinary. A sixteen-glyph variant does the same job with variation selectors, where each character stands for a nibble and two characters make a byte, which cuts the run to a quarter of the length the binary form needs for the same payload. The distinction matters because the two forms call for opposite handling. Padding is stripped and the surrounding text is the finding. A carrier is extracted and decoded, and the visible text is set aside, because it was chosen precisely to be unremarkable. One file can hold both at once. The two samples above are carriers: the sentences reading Your invoice is attached. and Please review the document. are there to make the message look like ordinary correspondence, and neither of them is the finding.
| Code point | Name | Seen in |
|---|---|---|
| U+200B | Zero width space | Binary carriers, as one of the two bit glyphs |
| U+200C | Zero width non-joiner | Binary carriers, usually paired with U+200B |
| U+3164 and U+FFA0 | Hangul Filler and its halfwidth form | Binary carrier, publicly disclosed in October 2024 |
| U+FE00 to U+FE0F | Variation selectors | Nibble carrier, reported in a 2025 developer tooling worm campaign |
| U+202E | Right-to-left override | Trojan Source (CVE-2021-42574) and file name disguise |
| U+2800 | Braille pattern blank | File extension padding (CVE-2024-43461) |
| U+00AD | Soft hyphen | Keyword padding in phishing bodies and in source code |
Decode it by hand
# 1. Padding. The invisible characters sit INSIDE a keyword, so the fix
# is to drop them and read what is left.
PADDING = "\u00ad\u200b\u200c\u200d\u2060\ufeff"
clean = "".join(ch for ch in text if ch not in PADDING)
# 2. Carrier, binary form. Two glyphs stand for the bits 0 and 1.
# Hangul Filler pair below; swap in \u200b and \u200c for the
# zero-width form used by the second sample.
BIT0, BIT1 = "\uffa0", "\u3164"
bits = "".join("0" if ch == BIT0 else "1"
for ch in text if ch in (BIT0, BIT1))
data = bytes(int(bits[i:i + 8], 2) for i in range(0, len(bits), 8))
print(data.decode("utf-8", "replace"))
# 3. Carrier, variation-selector form. Each character is one nibble,
# U+FE00 is 0x0 and U+FE0F is 0xF, so two characters make a byte.
nib = [ord(ch) - 0xFE00 for ch in text if 0xFE00 <= ord(ch) <= 0xFE0F]
data = bytes(hi * 16 + lo for hi, lo in zip(nib[::2], nib[1::2]))
# Sanity check before you trust the result: the recovered bytes should
# be printable text. A wall of high bytes usually means the two bit
# glyphs are the other way round, so swap BIT0 and BIT1 and rerun.Two questions settle most carriers before you write any code. The first is which glyph stands for 0 and which stands for 1. There is no convention to appeal to, and a builder is as likely to pick one assignment as the other, so try both: one of them yields printable text and the other yields bytes with the high bit set in improbable places. The second question is the length of the run. A binary carrier divides by 8, a nibble carrier by 2, and a run that divides by neither is more likely to be padding sprinkled through a keyword than a payload. Two further checks are worth the seconds they cost. Count the distinct invisible code points in the run: a carrier tends to use exactly two, or exactly sixteen in the variation-selector form, while padding tends to repeat one. Then look at where the run sits. Padding is interleaved with visible letters, one invisible character between two of them, whereas a carrier arrives as a solid block, usually between two words or at the end of a line, because interleaving several hundred characters through a sentence would stand out in any editor that renders them as dots. If both bit assignments produce mojibake, widen the search rather than concluding the run is decorative. A builder may offset each byte by a fixed amount, reverse the bit order within a byte, or prefix a short length field, and those variants are cheap to test once the run itself has been isolated.

Where this shows up
Invisible carriers turn up wherever text is copied from one place to another without being inspected. Phishing bodies are the most common habitat: the visible message is a short, plausible sentence and the invisible run holds the URL the recipient is meant to reach, sometimes alongside a proxy or PAC configuration lure that asks the reader to paste a line into a terminal. Chat and collaboration clients pass these code points through unchanged, which makes them a convenient channel for a link that a mail gateway would otherwise have rewritten. Source code is the second habitat. A README, a comment or a commit message can hold a run that a reviewer scrolls straight past, and a 2025 campaign against developer tooling used variation selectors in exactly that way. File names are the third, and they draw on a different member of the family: the right-to-left override at U+202E reverses the rendering of what follows it, so a file stored as invoice plus the override plus txt.exe is displayed as invoice followed by exe.txt, which reads as a text file to a hurried eye. The same class of confusion in source code is catalogued as Trojan Source, CVE-2021-42574, and a braille pattern blank at U+2800 was used to pad an extension in the activity behind CVE-2024-43461. In ATT&CK terms this sits under T1027 (Obfuscated Files or Information). For the detection half of the job, which is a question about scanning repositories and mail flow rather than decoding one message, see the Invisible Unicode Attacks: Detection Guide.
Paste the message, the file name or the source file as it arrived, with the invisible characters intact, because retyping the visible part discards the payload. Submit the whole file when you have one. When the recovered string is itself encoded, as in the second sample, the analysis continues into that layer as well, so a hidden -enc argument carries on into Decode PowerShell -EncodedCommand without a second paste.
Frequently Asked Questions
How can text that looks empty contain a payload?
Which invisible characters should I search for?
What is the Hangul Filler technique?
Does removing invisible characters break legitimate text?
Can KlaroSkope decode invisible text hidden in a file rather than a pasted message?
Continue Learning
Ready to decode?
Paste the script or upload the file. Multi-layer samples continue past this technique into whatever comes next.
Open the analysis console